1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 """
29 This module defines helper classes that handle SQL primary and foreign
30 keys.
31 """
32
33 from types import *
34 from string import *
35
36 import sql
37 from exceptions import *
38
40 """
41 This class manages keys of dbobjects (not dbclasses! The object must be
42 initialized for this to work). It is instantiated by dbobject.__init__(),
43 among others, using the __primary_key__ attribute to set it up.
44
45 There are a number of methods in this class which come in pairs: a
46 singular one and a plural one. attribute() for instance returns returns
47 the name of the dbobj's key attribute, if, and only if, the key is a
48 single column key. If the key has multiple column, it will raise an
49 exception. This is true also for column() and value(). The plural form
50 of these functions is more generic: it will return a generator(!) yielding
51 the requested objects, a generator with only one element for single column
52 keys, a generator with several element for multiple column keys.
53
54 I know the way this is imlemented below duplicates a lot of code
55 and could be optimized. But since this would make the code much
56 harder to understand and these functions are not likely to change
57 in principle, I've decided to write it this way.
58 """
59
60 - def __init__(self, dbobj, *key_attributes):
61 """
62 @param dbobj: Dbobj that this key belongs to. (If you pass a dbclass
63 instead of a dbobj, all functionality will work that doesn't
64 depend on actual data).
65 @param key_attributes: Those attribute(s) the key consists of
66 """
67 self.dbobj = dbobj
68
69 if len(key_attributes) == 0:
70 raise ValueError("The primary key must have at least one " + \
71 "column (attribtue)!")
72
73
74 for attribute in key_attributes:
75
76
77
78 dbobj.__dbproperty__(attribute)
79
80 self.key_attributes = key_attributes
81
83 """
84 @returns: True, if all attributes that are needed for this key are
85 set within the dbobj.
86 """
87 for attribute in self.key_attributes:
88 dbproperty = self.dbobj.__dbproperty__(attribute)
89 if not dbproperty.isset(self.dbobj):
90 return False
91
92 return True
93
95 """
96 @returns: An string containing the name of the attribute managing
97 the key column.
98 @raises SimplePrimaryKeyNeeded: if the key is a multiple attribute key
99 """
100 if len(self.key_attributes) != 1:
101 msg = "%s not a single attribute key" % repr(self.key_attributes)
102 raise SimplePrimaryKeyNeeded(msg)
103 else:
104 return self.key_attributes[0]
105
107 """
108 @returns: A tuple of strings naming the db attributes managing the
109 key columns.
110 """
111 return tuple(self.key_attributes)
112
114 """
115 @returns: An datatype instance managing the key attribute.
116 @raises SimplePrimaryKeyNeeded: if the key is a multiple attribute key
117 """
118 if len(self.key_attributes) != 1:
119 msg = "%s not a single attribute key" % repr(self.key_attributes)
120 raise SimplePrimaryKeyNeeded(msg)
121 else:
122 return self.attributes().next()
123
125 """
126 @returns: A generator yielding the datatype instances that comprise
127 the key
128 """
129 for attribute in self.key_attributes:
130 yield self.dbobj.__dbproperty__(attribute)
131
133 """
134 @returns: An sql.column instance indicating the key's column.
135 @raises SimplePrimaryKeyNeeded: if the key is a multiple column key
136 """
137 if len(self.key_attributes) != 1:
138 msg = "%s not a single column key" % repr(self.key_columns)
139 raise SimplePrimaryKeyNeeded(msg)
140 else:
141 return self.columns().next()
142
144 """
145 @returns: A tuple of sql.column instances that comprise the key
146 """
147 for attribute in self.key_attributes:
148 dbproperty = self.dbobj.__dbproperty__(attribute)
149 yield dbproperty.column
150
152 """
153 @returns: The value of the key as a Python data object
154 @raises SimplePrimaryKeyNeeded: if the key is a multiple column key
155 """
156 if len(self.key_attributes) != 1:
157 msg = "%s not a single column key" % repr(self.key_columns)
158 raise SimplePrimaryKeyNeeded(msg)
159 else:
160 return self.values().next()
161
163 """
164 @returns: The value of the key as Python tuple
165 """
166 if not self.isset():
167 raise KeyNotSet()
168
169 ret = []
170 for attribute in self.key_attributes:
171 ret.append(getattr(self.dbobj, attribute))
172
173 return tuple(ret)
174
176 """
177 @returns: The sql_literal of the key as a Python data object
178 @raises SimplePrimaryKeyNeeded: if the key is a multiple column key
179 """
180 if len(self.key_attributes) != 1:
181 msg = "%s not a single column key" % repr(self.key_columns)
182 raise SimplePrimaryKeyNeeded(msg)
183 else:
184 return self.sql_literals().next()
185
187 """
188 @returns: A generator yielding the SQL literals of this key as
189 strings.
190 """
191 if not self.isset():
192 raise KeyNotSet()
193
194 for attribute in self.key_attributes:
195 dbproperty = self.dbobj.__dbproperty__(attribute)
196 yield dbproperty.sql_literal(self.dbobj)
197
198
215
217 """
218 @returns: sql.where() instance representing a where clause that
219 refers to this key
220 """
221 return self._where(self.columns())
222
224 """
225 @returns: True - if the other refers to the same key (maybe
226 different tables!) as this key, otherwise, you guessed it,
227 False. Order of attributes (i.e. columns) does matter, as it
228 does in SQL!
229 """
230 me = tuple(self.values())
231 other = tuple(other.values())
232
233 return me == other
234
236 error = False
237
238 if type(t) == StringType:
239 return (t,)
240
241 elif type(t) == TupleType:
242
243 for a in t:
244 if type(a) != StringType:
245 error = True
246
247 else:
248 error = True
249
250 if error:
251 raise TypeError("A key must be either a single string indicating"+\
252 " an attribute or a tuple of simple strings" + \
253 ", not %s" % repr(t))
254
255 return t
256
257
262
263
265 """
266 The foreign key class knows about two objects: 'me' and
267 'other'. 'Me' is the dbobj it belongs to (the relataionship's
268 parent object) and 'other' the one that the key refers to (the
269 relationship's child object(s)). The key class's methods
270 attribute[s](), column[s]() and where() refer to the parent object
271 and are also aliased by the my_attribute[s] (etc..) methods. The
272 other_attribute[s] (etc..) methods refer to the child
273 object. Thode methods that yield actual data values need not to be
274 duplicated, because those values are the same in parent and child
275 objects, of course.
276 """
277 - def __init__(self, my_dbobj, other_dbclass,
278 my_key_attributes, other_key_attributes):
279 """
280 Each of the attributes sets must point to the same datatypes
281 in the same order to function properly as a foreign key!
282
283 @param my_dbobj: The parent dbobj
284 @param child_dbclass: The child's dbclass (not object...!)
285 @param my_key_attributes: A tuple of string(s) refering to those
286 of the parent's properties that manage the key column(s).
287 @param other_key_attributes: A tuple of string(s) refering to those
288 of the child's properties that manage the key column(s).
289 """
290 my_key_attributes = self.make_tuple(my_key_attributes)
291 other_key_attributes = self.make_tuple(other_key_attributes)
292
293 if len(my_key_attributes) != len(other_key_attributes):
294 msg = "The number of key attributes (columns) " + \
295 "does not match (%s, %s)"
296 raise IllegalForeignKey(msg % ( repr(my_key_attributes),
297 repr(other_key_attributes), ))
298
299 for my, other in zip(my_key_attributes, other_key_attributes):
300 my_prp = my_dbobj.__dbproperty__(my)
301 other_prp = other_dbclass.__dbproperty__(other)
302
303 if my_prp.__class__ != other_prp.__class__:
304 msg = "Property datatypes do not match for foreign key " + \
305 "%s (%s) != %s (%s)" % ( my, repr(my_prp),
306 other, repr(other_prp), )
307 IllegalForeignKey(msg)
308
309 key.__init__(self, my_dbobj, *my_key_attributes)
310 self.other_key_attributes = other_key_attributes
311 self.other_dbclass = other_dbclass
312
313 my_attribute_name = key.attribute_name
314 my_attribute_names = key.attribute_names
315 my_attribute = key.attribute
316 my_attributes = key.attributes
317 my_column = key.column
318 my_columns = key.columns
319 my_where = key.where
320
322 """
323 @returns: An string containing the name of the attribute managing
324 the key column.
325 @raises SimplePrimaryKeyNeeded: if the key is a multiple attribute key
326 """
327 if len(self.other_key_attributes) != 1:
328 msg = "%s not a single attribute key" % repr(self.key_attributes)
329 raise SimplePrimaryKeyNeeded(msg)
330 else:
331 return self.other_key_attributes[0]
332
334 """
335 @returns: A tuple of strings naming the db attributes managing the
336 key columns.
337 """
338 return tuple(self.other_key_attributes)
339
341 """
342 @returns: An datatype instance managing the key attribute.
343 @raises SimplePrimaryKeyNeeded: if the key is a multiple attribute key
344 """
345 if len(self.other_key_attributes) != 1:
346 msg = "%s not a single attribute key" % repr(self.key_attributes)
347 raise SimplePrimaryKeyNeeded(msg)
348 else:
349 return self.other_attributes.next()
350
352 """
353 @returns: A generator yielding the datatype instances that comprise
354 the key
355 """
356 for attribute in self.other_key_attributes:
357 yield self.other_dbclass.__dbproperty__(attribute)
358
360 """
361 @returns: An sql.column instance indicating the key's column.
362 @raises SimplePrimaryKeyNeeded: if the key is a multiple column key
363 """
364 if len(self.other_key_attributes) != 1:
365 msg = "%s not a single column key" % repr(self.key_columns)
366 raise SimplePrimaryKeyNeeded(msg)
367 else:
368 return self.other_columns().next()
369
371 """
372 @returns: A tuple of sql.column instances that comprise the key
373 """
374 for attribute in self.other_key_attributes:
375 dbproperty = self.other_dbclass.__dbproperty__(attribute)
376 yield dbproperty.column
377
379 """
380 @returns: sql.where() instance representing a where clause that
381 refers to this key in the 'other' (child) relation
382 """
383 return self._where(self.other_columns())
384
385
386
387
388
389