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

Source Code for Module orm2.dbobject

  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 COPYING 
 27   
 28  # Changelog 
 29  # --------- 
 30  # 
 31  # $Log: dbobject.py,v $ 
 32  # Revision 1.25  2007/06/07 10:58:14  diedrich 
 33  # Added __has_dbproperty__() function. 
 34  # 
 35  # Revision 1.24  2007/05/14 19:47:30  diedrich 
 36  # Fixed count(). 
 37  # 
 38  # Revision 1.23  2007/04/14 20:36:14  diedrich 
 39  # Added __update_from_dict__() method. 
 40  # 
 41  # Revision 1.22  2007/01/22 19:43:09  diedrich 
 42  # Added alias 'fetchone' to result.next() 
 43  # 
 44  # Revision 1.21  2006/09/19 18:05:03  diedrich 
 45  # Added __delete__() method. 
 46  # 
 47  # Revision 1.20  2006/09/19 14:24:36  diedrich 
 48  # Copy views and view_specs from a class to its child classes. Make sure 
 49  # everything is initialized correctly. 
 50  # 
 51  # Revision 1.19  2006/09/07 13:55:58  diedrich 
 52  # Changed order in which a dbobject's components are initialized 
 53  # (dbobject's metatype.__init__()). Also added widget_spec mechanism 
 54  # (again). 
 55  # 
 56  # Revision 1.18  2006/09/06 23:19:37  diedrich 
 57  # Added __schema__ cvar to dbobject 
 58  # 
 59  # Revision 1.17  2006/07/08 17:07:17  diedrich 
 60  # Renamed result's __len__() method to count(), because it caused extra 
 61  # SQL queries to be yielded. 
 62  # 
 63  # Revision 1.16  2006/07/04 22:44:16  diedrich 
 64  # Fixed widget_specs() 
 65  # 
 66  # Revision 1.15  2006/06/10 18:03:11  diedrich 
 67  # - Rewrote widget handling 
 68  # - Added handling of special __ds parameter for dbobjects, because that makes 
 69  #   handling ds in views much much easier 
 70  # 
 71  # Revision 1.14  2006/06/09 21:51:08  diedrich 
 72  # - relation names that are derived from the class's name will be overwritten 
 73  #   with the childclass's name when a class is subclassed 
 74  # - added zope_dbobject class 
 75  # 
 76  # Revision 1.13  2006/06/09 09:04:56  diedrich 
 77  # Wrote dbobject.result and used it as default for dbobject.__result__ 
 78  # 
 79  # Revision 1.12  2006/05/15 21:49:10  diedrich 
 80  # Working on ui basics 
 81  # 
 82  # Revision 1.11  2006/05/13 17:23:42  diedrich 
 83  # Massive docstring update. 
 84  # 
 85  # Revision 1.10  2006/05/13 14:54:59  diedrich 
 86  # Use the actual dbclass as first param to __init_dbclass__, not the 
 87  # metaclass anymore. 
 88  # 
 89  # Revision 1.9  2006/04/28 09:49:27  diedrich 
 90  # Docstring updates for epydoc 
 91  # 
 92  # Revision 1.8  2006/04/28 08:42:03  diedrich 
 93  # - __init__dbclass__ is called by dbobject.__init__ for those properties 
 94  #   that are not part of the class definition now 
 95  # - Changed widget handling 
 96  # - Added __eq__() and __ne__() based on keys.primary_key.__eq__() 
 97  # 
 98  # Revision 1.7  2006/04/21 18:58:58  diedrich 
 99  # Added __select_columns__ and widgets() methods to dbobject class. 
100  # 
101  # Revision 1.6  2006/02/25 00:20:20  diedrich 
102  # - Added and tested the ability to use multiple column primary keys. 
103  # - Some small misc bugs. 
104  # 
105  # Revision 1.5  2006/01/01 20:41:11  diedrich 
106  # Added __is_stored__() and __dbproperty__() 
107  # 
108  # Revision 1.4  2005/12/31 18:27:22  diedrich 
109  # - Updated year in copyright header ;) 
110  # - The difference between __set__ and __set_from_result__ has be re-introduced 
111  #   to accomodate for the relationships and other situations 
112  # - __dbproperties__() and __paimary_key_column__() are classmethods now 
113  # 
114  # Revision 1.3  2005/12/18 22:35:46  diedrich 
115  # - Inheritance 
116  # - pgsql adapter 
117  # - first unit tests 
118  # - some more comments 
119  # 
120  # Revision 1.2  2005/11/21 19:59:11  diedrich 
121  # - renamed __columns__() to __dbproperties__() and have it return a list of 
122  #   properties 
123  # - added the __repr__() method from orm's old dbclass module 
124  # 
125  # Revision 1.1.1.1  2005/11/20 14:55:46  diedrich 
126  # Initial import 
127  # 
128  # 
129  # 
130   
131  __docformat__ = "epytext en" 
132   
133  """ 
134  This module defines one class, L{dbobject}, which is the base class 
135  for all objects orm retrieves from or stores in the database. 
136  """ 
137   
138  from string import * 
139  from types import * 
140  import copy 
141   
142  from orm2 import sql, keys 
143  from orm2.datasource import datasource_base 
144  from orm2.util import stupid_dict, module_property 
145  from orm2.exceptions import * 
146  from orm2.datatypes import datatype 
147  from orm2.ui import view, view_spec 
148   
149 -class result:
150 """ 151 This is the base class for all results. A result is a collection of one 152 kind of dbobjects that have been retrieved from the database. This class 153 will emulate a sequence but also has a next() method like a generator (so 154 I don't have to change all that code that assumes a result to be a 155 generator). The result class also has two methods to determine the 156 result's length by querying the database. If you need to traverse over a 157 result more than once, you must cast it into a list (and by that copying 158 all dbobjects to the client's memory). 159 160 The result class needs to deal with datasources that have an attribute 161 called no_fetchone set, that makes this class use the cursor.fetchall() 162 method (most notable for the gadfly adapter). 163 """ 164
165 - def __init__(self, ds, dbclass, select):
166 """ 167 @param ds: Datasource object 168 @param dbclass: dbclass object of whoes instances this result will be 169 @param select: orm2.sql.select instance of the query 170 """ 171 self.ds = ds 172 self.dbclass = dbclass 173 174 if not isinstance(select, sql.select): 175 raise TypeError("result can only work on sql.select instances!") 176 177 self.select = select 178 179 self.columns = dbclass.__select_columns__() 180 self.cursor = ds.execute(select) 181 182 if getattr(self.ds, "no_fetchone", False): 183 self.rows = self.cursor.fetchall() 184 self.rows.reverse() 185 186 for name, prop in self.__class__.__dict__.items(): 187 # Go through the views and view_specs of this result class 188 # and call (i.e. instantiate) it, and put the resulting 189 # view instance into the dbobj's __dict__ 190 if ( type(prop) == ClassType and issubclass(prop, view) ): 191 setattr(self, name, prop(result=self)) 192 193 if isinstance(prop, view_spec): 194 cls = prop.import_(name) 195 setattr(self, name, cls(result=self))
196 197
198 - def __iter__(self):
199 return self
200
201 - def next(self):
202 if hasattr(self, "rows"): 203 if len(self.rows) == 0: 204 tpl = None 205 else: 206 tpl = self.rows.pop() 207 else: 208 tpl = self.cursor.fetchone() 209 210 if tpl is None: 211 raise StopIteration 212 else: 213 info = stupid_dict(zip(self.columns, tpl)) 214 return self.dbclass.__from_result__(self.ds, info)
215 216 fetchone = next 217
218 - def count(self):
219 """ 220 This is a helper function that will perform a query as 221 222 SELECT COUNT(*) ... 223 224 appropriate to determine the number of rows in this result. 225 This will remove all clauses of the original select except the 226 WHERE clause. 227 228 This can't be called __len__(), because then it is used by 229 list() and yields a superflous SELECT query. 230 """ 231 count_select = copy.deepcopy(self.select) 232 count_select.clauses = \ 233 filter(lambda clause: isinstance(clause, sql.where), 234 count_select.clauses) 235 count_select.columns = sql.expression("COUNT(*)") 236 return int(self.ds.query_one(count_select))
237 238 count_all = count
239 240 241
242 -class dbobject(object):
243 """ 244 Base class for all database aware classes. 245 246 It contains a number of helper methods which are called like this: 247 __help__(). You may safely add db-aware properties, regular properties 248 and methods. 249 250 @cvar __primary_key__: The primary key must be either 251 - a keys.primary_key instance 252 - a tuple of strings indicating attribute (not column!) names of this 253 class that form a multi column primary key 254 - a simple string indicating the attribute that manages the primary 255 key column of this dbclass 256 - None if the class does not have a primary key (which makes it 257 impossible to update rows by updating an instance's attributes 258 through orm) 259 260 @cvar __result__: This attribute must be a class which inherits 261 from result. It is used to represent results, sets of 262 this dbclass retrieved from the database. It will returned for all 263 calls to the datasource.run_select() method (which takes care of 264 all methods'select' in their names, except where explicitly noted. 265 266 @cvar __relation__: Name of the relation this dbclass' values are 267 stored in. Defaults to the class' name. May be set to a string or an 268 sql.relation instance. 269 270 @cvar __schema__: String containing the name of the schema this dbclass' 271 relatin resides in. 272 """ 273 274 __primary_key__ = "id" 275 __result__ = result 276 __model__ = module_property() 277
278 - class __metaclass__(type):
279 - def __new__(cls, name, bases, dict):
280 ret = type.__new__(cls, name, bases, dict) 281 282 if name != "dbobject": 283 if not hasattr(ret, "__relation__") or \ 284 getattr(ret.__relation__, "__autocreated__", False): 285 # __relation__ which are set by this procedure 286 # are overwritten with one that uses the current class' 287 # name, considering the __schema__ class variable. 288 schema = getattr(ret, "__schema__", None) 289 ret.__relation__ = sql.relation(name, schema) 290 ret.__relation__.__autocreated__ = True 291 elif type(ret.__relation__) == StringType: 292 schema = getattr(ret, "__schema__", None) 293 ret.__relation__ = sql.relation(ret.__relation__, schema) 294 elif type(ret.__relation__) == UnicodeType: 295 raise TypeError("Unicode not allowed as SQL identifyer") 296 elif isinstance(ret.__relation__, sql.relation): 297 pass 298 else: 299 msg = "Relation name must be a string or an" + \ 300 "sql.relation() instance, not %s (%s)" 301 raise TypeError(msg % ( repr(type(ret.__relation__)), 302 repr(ret.__relation__),) ) 303 304 # Initialize the dbproperties 305 for attr_name, property in dict.items(): 306 if isinstance(property, datatype): 307 property.__init_dbclass__(ret, attr_name) 308 309 # Add (=inherit) db-properties from our parent classes 310 for base in bases: 311 for attr_name, property in base.__dict__.items(): 312 if isinstance(property, (datatype, view, view_spec,)): 313 property_cpy = copy.copy(property) 314 if hasattr(property_cpy, "__init_dbclass__"): 315 property_cpy.__init_dbclass__(ret, attr_name) 316 setattr(ret, attr_name, property_cpy) 317 318 return ret
319 320
321 - def __init__(self, **kw):
322 """ 323 Construct a dbobj from key word arguments. Example:: 324 325 me = person(firstname='Diedrich', lastname='Vorberg') 326 327 firstname and lastname are dbproperties. The reserved parameter 328 __ds allows you to pass a datasource to objects that are not 329 inserted yet and might need a ds to construct views and the like. 330 """ 331 if kw.has_key("__ds"): 332 __ds = kw["__ds"] 333 del kw["__ds"] 334 335 if not isinstance(__ds, datasource_base): 336 raise TypeError("__ds must be a subclass of "+\ 337 "orm2.datasource.datasource_base") 338 339 else: 340 __ds = None 341 342 self._ds = __ds 343 self._is_stored = False 344 345 for name, prop in self.__class__.__dict__.iteritems(): 346 if isinstance(prop, datatype) and not hasattr(prop, "dbclass"): 347 prop.__init_dbclass__(self.__class__, name) 348 349 # Go through the views and view_specs of this dbclass 350 # and call (i.e. instantiate) it, and put the resulting 351 # view instance into the dbobj's __dict__ 352 if ( type(prop) == ClassType and issubclass(prop, view) ): 353 setattr(self, name, prop(self)) 354 355 if isinstance(prop, view_spec): 356 cls = prop.import_(name) 357 setattr(self, name, cls(dbobj=self)) 358 359 self.__update_from_dict__(kw) 360 361 if self.__primary_key__ is not None: 362 self.__primary_key__ = keys.primary_key(self)
363 364
365 - def __from_result__(cls, ds, info):
366 """ 367 This constructor is called by L{datasource.datasource_base} 368 when an object is created using a row retreived from the RDBMS. 369 370 @param ds: datasource we are created by (see select() method) 371 @param info: dictionary as { 'column_name': <data> } 372 """ 373 self = cls(__ds=ds) 374 for property in cls.__dbproperties__(): 375 if info.has_key(property.column): 376 property.__set_from_result__(ds, self, info[property.column]) 377 378 self._ds = ds 379 self._is_stored = True 380 381 return self
382 383 __from_result__ = classmethod(__from_result__) 384
385 - def __insert__(self, ds):
386 """ 387 This method is called by datasource.insert() after the insert 388 query has been performed. It sets the dbobj's _ds attribute. 389 390 @param ds: datasource that just inserted us 391 """ 392 self._ds = ds 393 self._is_stored = True
394
395 - def __ds__(self):
396 """ 397 Return this dbobject's datasource (the one it is stored in). 398 """ 399 if not hasattr(self, "_ds"): 400 raise ObjectMustBeInserted("...before you use __ds__()") 401 402 return self._ds
403
404 - def __is_stored__(self):
405 """ 406 @returns: Wheather this dbobj has been stored in the database already 407 or retrieved from it 408 """ 409 return self._is_stored
410
411 - def __dbproperties__(cls):
412 """ 413 Return the datatype objects among this dbobjects attributes as a dict 414 like { name: property, ... } 415 """ 416 for prop in cls.__dict__.values(): 417 if isinstance(prop, datatype): 418 yield prop
419 420 __dbproperties__ = classmethod(__dbproperties__) 421 422
423 - def __dbproperty__(cls, name=None):
424 """ 425 Return a dbproperty by its name. Raise exceptions if 426 427 - there is no property by that name 428 - it's not a dbproperty 429 430 name defaults to the dbclass' primary key. 431 """ 432 if name is None: 433 if cls.__primary_key__ is None: 434 raise NoPrimaryKey() 435 else: 436 name = cls.__primary_key__ 437 438 try: 439 property = cls.__dict__[name] 440 except KeyError: 441 tpl = ( repr(name), cls.__name__, ) 442 raise AttributeError("No such attribute: %s (in class %s)" % tpl) 443 444 if not isinstance(property, datatype): 445 raise NoDbPropertyByThatName(name + " is not a orm2 datatype!") 446 447 return property
448 449 __dbproperty__ = classmethod(__dbproperty__) 450
451 - def __has_dbproperty__(cls, name):
452 """ 453 Return whether this dbclass has a property named `name`. 454 """ 455 try: 456 cls.__dbproperty__(name) 457 return True 458 except NoDbPropertyByThatName: 459 return False 460 except AttributeError: 461 return False
462 463 464 __has_dbproperty__ = classmethod(__has_dbproperty__) 465 466
467 - def __select_columns__(cls):
468 """ 469 A list of columns to select from the relation to construct one 470 of these. 471 """ 472 # The use of the stupid_dict class has become neccessary, because 473 # sql._part instances are not hashable. 474 columns = stupid_dict() 475 for property in cls.__dbproperties__(): 476 if property.__select_this_column__(): 477 columns[property.column] = 0 478 479 columns = list(columns.keys()) 480 481 return columns
482 483 __select_columns__ = classmethod(__select_columns__) 484 485
486 - def __repr__(self):
487 """ 488 Return a human readable (more or less) representation of this 489 dbobject. 490 """ 491 ret = [] 492 493 ret.append("pyid=" + str(id(self))) 494 495 #if self.oid(): 496 # ret.append("oid=%i" % self.oid()) 497 #else: 498 # ret.append("oid=NULL") 499 500 attribute_names = [] 501 for name, value in self.__dict__.items(): 502 if isinstance(value, datatype): 503 attribute_names.append(name) 504 505 for a in attribute_names: 506 b = a + "=" 507 508 try: 509 val = getattr(self, a) 510 511 #if not isinstance(val, relationships.relationshipColumn): 512 # b += repr(val.get()) 513 #else: 514 b += repr(val) 515 except AttributeError: 516 b += "<not set>" 517 518 ret.append(b) 519 520 return "<" + self.__class__.__name__ + " (" + \ 521 join(ret, " ") + ")>"
522 523
524 - def __eq__(self, other):
525 """ 526 Two dbobjects are considered equal, if they have the same dbclass 527 and the same primary key. B{This method does not check any 528 attributes!} 529 """ 530 if self.__primary_key__ is None or other.__primary_key__ is None: 531 raise ValueError("Can't check equality on dbclasses that don't have a primary key") 532 533 if not self.__primary_key__.isset() or \ 534 not other.__primary_key__.isset(): 535 raise ValueError("Can't check equality on a dbobj whoes primary key is not yet set") 536 537 return self.__primary_key__.__eq__(other.__primary_key__)
538
539 - def __ne__(self, other):
540 """ 541 Same as L{__eq__}, just the other way 'round ;-) 542 """ 543 return (not self == other)
544
545 - def __widget_specs__(self, module_name):
546 """ 547 Return a list of all widget_specs for the module named module_name. 548 """ 549 ret = [] 550 for property in self.__dbproperties__(): 551 for spec in property.widget_specs(): 552 if spec.belongs_to(module_name): 553 ret.append(spec) 554 555 ret.sort() 556 return ret
557
558 - def __delete__(self):
559 cmd = sql.delete(self.__relation__, 560 self.__primary_key__.where()) 561 self.__ds__().execute(cmd, modify=True)
562
563 - def __update_from_dict__(self, kw):
564 for name, value in kw.items(): 565 if self.__class__.__dict__.has_key(name): 566 self.__class__.__dict__[name].__set__(self, value) 567 else: 568 raise NoSuchAttributeOrColumn(name)
569
570 -class zope_dbobject(dbobject):
571 __allow_access_to_unprotected_subobjects__ = True 572
573 - def __getattr__(self, name):
574 context = self.__ds__().context 575 return getattr(context, name)
576