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

Source Code for Module orm2.containers

  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-2008 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  # Changelog 
 30  # --------- 
 31  #  
 32  # $Log: containers.py,v $ 
 33  # Revision 1.1  2008/02/20 22:51:48  diedrich 
 34  # Initial commit. 
 35  # 
 36  # 
 37  # 
 38   
 39  """ 
 40  These classes are very simmilar to primitives in that they manage two 
 41  relations. The child relation however is not represented by a dbclass, 
 42  but only by a primitive type.  
 43  """ 
 44   
 45  import sys 
 46  from types import * 
 47   
 48  from orm2.dbobject import dbobject 
 49  from orm2.datatypes import datatype 
 50  from orm2.exceptions import * 
 51  from orm2 import sql, keys 
 52   
53 -class _container(datatype):
54 - def __init__(self, child_relation, child_column, 55 child_key=None, title=None):
56 """ 57 @param child_relation: sql.relation object or string, indicating 58 the name of the dependent relation. 59 @param child_column: It's datatype (datatype object). The datatype's 60 column parameter may be used as well as teh validators. Title and 61 has_default will be ignored. The column name defaults to the 62 container's attribute_name in the parent dbclass. 63 @param child_key: A string indicating the column in the child 64 relation that is used in the foreign key to point to the parent. 65 66 The the docstrings of the actual implementations for examples, 67 that's going to make it clearer. 68 69 As a sidenote: This only works for a single-column reference 70 key from the parent class to the child class. It would be 71 possible implementing this for multiple column keys using an 72 anonymous dbclass, but it's just soooo darn complicated! So I 73 thought to myself that orm2 is complecated enough... 74 """ 75 datatype.__init__(self, None, title) 76 self.child_relation = child_relation 77 self.child_column = child_column 78 self.child_key = child_key
79 80
81 - def __init_dbclass__(self, dbclass, attribute_name):
82 datatype.__init_dbclass__(self, dbclass, attribute_name) 83 if self.child_column is not None: 84 self.child_column.__init_dbclass__(dbobject, attribute_name) 85 86 if self.child_key is None: 87 pkey_column = dbclass.__primary_key__.column() 88 self.child_key = "%s_%s" % ( dbclass.__relation__.name, 89 pkey_column.name, )
90
91 - def __set_from_result__(self, ds, dbobj, value):
92 """ 93 A container is not selected from a result. 94 """ 95 raise NotImplementedError("Not implemented by this container datatype.")
96
97 - def __convert__(self, value):
98 "Containers do not need a convert method or can't use it anyway." 99 raise NotImplementedError(__doc__)
100
101 - def sql_literal(self, dbobj):
102 "This container cannot be represented as an SQL literal." 103 return None
104
105 - def __select_this_column__(self):
106 """ 107 @returns: False. Containers do not need to select anything. 108 """ 109 return False
110
111 - def __select_after_insert__(self, dbobj):
112 """ 113 @returns: False. Containers to not need to select anything, 114 even after the insert. 115 """ 116 return False
117
118 - def child_where(self, dbobj):
119 """ 120 A where clause that leads to all the rows in the child table 121 associated with this parent table. 122 """ 123 return sql.where( self.child_key, " = ", 124 dbobj.__primary_key__.sql_literal() )
125 126
127 -class sqltuple(_container):
128 """ 129 This datatype represents a relationship between relations in which 130 the child relation provides a number of values for a parent 131 relation indexed by the parent's primary key. In the parent dbobject 132 these values appear as a tuple. 133 134 Like this:: 135 136 CREATE parent ( 137 id INTEGER, 138 ... some more fields ... 139 ); 140 141 CREATE child ( 142 parent_id INTEGER NOT NULL REFERENCES parent(id), 143 info TEXT 144 ); 145 146 An appropriate dbclass would look like this:: 147 148 class parent(dbobject): 149 id = integer() 150 info = sqltuple('child', text(), 'id', 'parent_id') 151 152 result = ds.select(parent) 153 p = result.next() 154 p.info = ( "One", "Two", "Three", ) 155 156 An sqltuple is not mutable (i.e. a tuple and not a list), so you 157 can't set any member of the tupe as in t[3] = 'Hallo'. To append a 158 value you must say dbobj.tpl += ( "Hallo", ). The program will do 159 the right thing and only create as many INSERT statements as 160 strings added. For all other operations all rows referenced by the 161 parent's key will be DELETEd and INSERTed again. 162 163 The sqltuple dbattribute may be set to any iterable that yields 164 values of the appropriate type. It will always return a Python 165 tuple. 166 167 The orderby parameter allows you to specify an SQL clause that 168 will determine the order of the child table's rows that are 169 returned. It may be None. Orderby must be an instance of 170 sql.orderby. 171 172 An sqltuple cannot be None, just an empty tuple. 173 """
174 - def __init__(self, child_relation, child_column, orderby=None, 175 child_key=None, title=None):
176 _container.__init__(self, child_relation, child_column, 177 child_key, title) 178 assert orderby is None or isinstance(orderby, sql.orderby), \ 179 "Orderby must be an instance of sql.orderby" 180 self.orderby = orderby
181 182
183 - def __get__(self, dbobj, owner="What??"):
184 if dbobj is None: return self 185 self.check_dbobj(dbobj) 186 187 if self.isset(dbobj): 188 return getattr(dbobj, self.data_attribute_name()) 189 else: 190 # Consturct the SQL query 191 query = sql.select( self.child_column.column, 192 self.child_relation, 193 self.child_where(dbobj), 194 self.orderby ) 195 cursor = dbobj.__ds__().execute(query) 196 ret = map(lambda tpl: self.child_column.__convert__(tpl[0]), 197 cursor.fetchall()) 198 ret = tuple(ret) 199 setattr(dbobj, self.data_attribute_name(), ret) 200 return ret
201
202 - def __set__(self, dbobj, new_values):
203 self.check_dbobj(dbobj) 204 205 if new_values is None: 206 raise ValueError("You cannot set a sqltuple to None, sorry.") 207 208 # Convert each of the values in the new tuple to the 209 # child_column's type and run its validators on each of them. 210 211 new = [] 212 for value in new_values: 213 value = self.child_column.__convert__(value) 214 for validator in self.child_column.validators: 215 validator.check(dbobj, self, value) 216 217 new.append(value) 218 219 # Let's see if it's just an addition of values. In that case 220 # we'll not delete anything, just create a couple of INSERT 221 # statements. 222 223 if self.isset(dbobj): 224 dont_delete = True 225 old = getattr(dbobj, self.data_attribute_name()) 226 227 if len(old) <= len(new): 228 for o, n in zip(old, new): 229 if o != n: 230 dont_delete = False 231 break 232 else: 233 old = () 234 dont_delete = False 235 236 if dont_delete: 237 to_insert = new[len(old):] 238 else: 239 to_insert = new 240 241 dbobj.__ds__().execute(sql.delete(self.child_relation, 242 self.child_where(dbobj))) 243 244 for value in to_insert: 245 if value is None: 246 literal = sql.NULL 247 else: 248 literal = self.child_column.sql_literal_class(value) 249 250 dbobj.__ds__().execute( 251 sql.insert(self.child_relation, 252 ( self.child_key, 253 self.child_column.column, ), 254 ( dbobj.__primary_key__.sql_literal(), 255 literal, ) )) 256 257 setattr(dbobj, self.data_attribute_name(), tuple(new))
258 259
260 -class sqldict(_container):
261 """ 262 This datatype represents a relationship between relations in which 263 the child relation proveies a number of values for a parent 264 relation indexed by the parent's primary key and a unique key for 265 each of the entries. In the parent dbobject these key/value pairs 266 appear as a dict. 267 268 Like this: 269 270 CREATE user ( 271 id INTEGER, 272 ... some more fields ... 273 ); 274 275 CREATE user_info ( 276 user_id INTEGER REFERENCES user(id), 277 key VARCHAR(100), 278 value TEXT, 279 280 PRIMARY KEY(user_id, key) 281 ); 282 283 The PRIMARY KEY clause is not strictly necessary, but it will make 284 sure every user_id/key pair only appears once. You may also want to 285 create an index over the user_id column, because queries will 286 usually call for all the child rows associated with the parent. 287 288 An appropriate dbclass would look like this:: 289 290 class user(dbobject): 291 id = integer() 292 info = sqldict('user_info', varchar(column=key), 293 Unicode(column=value)) 294 295 result = ds.select(user) 296 me = result.next() 297 me.info['firstname'] = 'Diedrich' 298 me.info['lastname'] = 'Vorberg' 299 300 if me.info['age'] > 30: 301 print 'Consider suicide!' 302 303 As for the sqltuple the whole thing only works on single-column 304 primary keys. 305 """
306 - def __init__(self, child_relation, child_key_column, child_value_column, 307 child_key=None, title=None):
308 _container.__init__(self, child_relation, child_column=None, 309 child_key=child_key, title=title) 310 self.child_key_column = child_key_column 311 self.child_value_column = child_value_column
312
313 - def __init_dbclass__(self, dbclass, attribute_name):
314 _container.__init_dbclass__(self, dbclass, attribute_name) 315 316 self.child_key_column.__init_dbclass__(dbobject, "key") 317 self.child_value_column.__init_dbclass__(dbobject, "value")
318
319 - def __get__(self, dbobj, owner="Who??"):
320 if dbobj is None: return self 321 self.check_dbobj(dbobj) 322 323 if self.isset(dbobj): 324 return getattr(dbobj, self.data_attribute_name()) 325 else: 326 # Consturct the SQL query 327 query = sql.select( ( self.child_key_column.column, 328 self.child_value_column.column, ), 329 self.child_relation, 330 self.child_where(dbobj) ) 331 cursor = dbobj.__ds__().execute(query) 332 ret = map(lambda tpl: 333 ( self.child_key_column.__convert__(tpl[0]), 334 self.child_value_column.__convert__(tpl[1]), ), 335 cursor.fetchall()) 336 ret = self.sqldict_dict(self, dbobj, dict(ret)) 337 setattr(dbobj, self.data_attribute_name(), ret) 338 return ret
339 340
341 - def __set__(self, dbobj, new_dict):
342 self.check_dbobj(dbobj) 343 344 if new_values is None: 345 raise ValueError("You cannot set a sqldict to None, sorry.") 346 347 # Convert each of the keys and values in the new dict to the 348 # appropriate types and run the validators on each of the values. 349 350 new = {} 351 for key, value in new_dict.items(): 352 key = self.child_key_column.__convert__(key) 353 value = self.child_value_column.__convert__(value) 354 355 for validator in self.child_key_column.validators: 356 validator.check(dbobj, self, key) 357 358 for validator in self.child_value_column.validators: 359 validator.check(dbobj, self, value) 360 361 new[key] = value 362 363 # Delete the rows currently in the database 364 dbobj.__ds__().execute(sql.delete(self.child_relation, 365 self.child_where(dbobj))) 366 367 # And insert new ones 368 for key, value in new.items(): 369 key_literal = self.child_key_column.sql_literal_class(key) 370 371 if value is None: 372 value_literal = sql.NULL 373 else: 374 value_literal = self.child_value_column.sql_literal_class(value) 375 376 query = sql.insert( self.child_relation, 377 ( self.child_key, 378 self.child_key_columnd.column, 379 self.child_value_column.column, ), 380 ( dbobj.__primary_key__.sql_literal(), 381 key_literal, value_literal, ) ) 382 dbobj.__ds__().execute(query) 383 384 setattr(dbobj, self.data_attribute_name(), 385 self.sqldict_dict(self, dbobj, new))
386 387
388 - class sqldict_dict(dict):
389 - def __init__(self, sqldict, dbobj, data={}):
390 self.update(data) 391 self._sqldict = sqldict 392 self._dbobj = dbobj
393
394 - def __setitem__(self, key, value):
395 key_column = self._sqldict.child_key_column.column 396 key_literal = self._sqldict.child_key_column.sql_literal_class(key) 397 398 value_column = self._sqldict.child_value_column.column 399 value_literal = self._sqldict.child_value_column.sql_literal_class( 400 value) 401 402 if self.has_key(key): 403 where = self._sqldict.child_where(self._dbobj) + sql.where( 404 key_column, " = ", key_literal) 405 406 command = sql.update(self._sqldict.child_relation, where, 407 { str(value_column): value_literal }) 408 else: 409 command = sql.insert( self._sqldict.child_relation, 410 ( self._sqldict.child_key, 411 key_column, 412 value_column, ), 413 ( self._dbobj.__primary_key__.sql_literal(), 414 key_literal, value_literal, ) ) 415 416 self._dbobj.__ds__().execute(command) 417 418 dict.__setitem__(self, key, value)
419
420 - def __delitem__(self, key):
421 key_column = self._sqldict.child_key_column.column 422 key_literal = self._sqldict.child_key_column.sql_literal_class(key) 423 424 where = self.child_where(dbobj) + sql.where( 425 key_column, " = ", key_literal) 426 427 dbobj.__ds__().execute(sql.delete(self.child_relation, where)) 428 429 dict.__delitem__(self, key)
430