Package orm2 :: Module keys
[hide private]
[frames] | no frames]

Source Code for Module orm2.keys

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-1 -*- 
  3   
  4  ##  This file is part of orm, The Object Relational Membrane Version 2. 
  5  ## 
  6  ##  Copyright 2002-2006 by Diedrich Vorberg <diedrich@tux4web.de> 
  7  ## 
  8  ##  All Rights Reserved 
  9  ## 
 10  ##  For more Information on orm see the README file. 
 11  ## 
 12  ##  This program is free software; you can redistribute it and/or modify 
 13  ##  it under the terms of the GNU General Public License as published by 
 14  ##  the Free Software Foundation; either version 2 of the License, or 
 15  ##  (at your option) any later version. 
 16  ## 
 17  ##  This program is distributed in the hope that it will be useful, 
 18  ##  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  ##  GNU General Public License for more details. 
 21  ## 
 22  ##  You should have received a copy of the GNU General Public License 
 23  ##  along with this program; if not, write to the Free Software 
 24  ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 25  ## 
 26  ##  I have added a copy of the GPL in the file gpl.txt. 
 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   
39 -class key:
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 # check if all the attributes are in the dbobj... 74 for attribute in key_attributes: 75 # If there is an unknown attribute in the key_attributes list 76 # (a typo maybe), this will raise an NoDbPropertyByThatName 77 # exception. 78 dbobj.__dbproperty__(attribute) 79 80 self.key_attributes = key_attributes
81
82 - def isset(self):
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
94 - def attribute_name(self):
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
106 - def attribute_names(self):
107 """ 108 @returns: A tuple of strings naming the db attributes managing the 109 key columns. 110 """ 111 return tuple(self.key_attributes)
112
113 - def attribute(self):
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
124 - def attributes(self):
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
132 - def column(self):
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
143 - def columns(self):
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
151 - def value(self):
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
162 - def values(self):
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
175 - def sql_literal(self):
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
186 - def sql_literals(self):
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
199 - def _where(self, columns):
200 if not self.isset(): 201 raise KeyNotSet() 202 203 literals = self.sql_literals() 204 205 where = [] 206 for column, literal in zip(columns, literals): 207 where.append(column) 208 where.append("=") 209 where.append(literal) 210 where.append("AND") 211 212 del where[-1] # remove the straneous AND 213 214 return sql.where(*where)
215
216 - def where(self):
217 """ 218 @returns: sql.where() instance representing a where clause that 219 refers to this key 220 """ 221 return self._where(self.columns())
222
223 - def __eq__(self, other):
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
235 - def make_tuple(self, t):
236 error = False 237 238 if type(t) == StringType: 239 return (t,) 240 241 elif type(t) == TupleType: 242 # check if all members of the tuple are strings 243 for a in t: 244 if type(a) != StringType: 245 error = True 246 247 else: # all other types raise a TypeError exception 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
258 -class primary_key(key):
259 - def __init__(self, dbobj):
260 pkey = self.make_tuple(dbobj.__primary_key__) 261 key.__init__(self, dbobj, *pkey)
262 263
264 -class foreign_key(key):
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
321 - def other_attribute_name(self):
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
333 - def other_attribute_names(self):
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
340 - def other_attribute(self):
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
351 - def other_attributes(self):
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
359 - def other_column(self):
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
370 - def other_columns(self):
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
378 - def other_where(self):
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 # Local variables: 386 # mode: python 387 # ispell-local-dictionary: "english" 388 # End: 389