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

Source Code for Module orm2.datatypes

   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: datatypes.py,v $ 
  32  # Revision 1.26  2007/01/22 19:42:43  diedrich 
  33  # Fixed bug raising NotImplementedError() 
  34  # 
  35  # Revision 1.25  2006/10/10 15:49:14  diedrich 
  36  # - Opt in procedure parameters. (3) Oink! 
  37  # - Fixed datetime handling. OinkOink! 
  38  # 
  39  # Revision 1.24  2006/10/07 22:05:25  diedrich 
  40  # - Rather than checking for mx.DateTime classes, date and datetime will 
  41  #   check for the presence of day, month, year etc. attributes. It will 
  42  #   even check if these are methods and call them. This avoids a host of 
  43  #   trouble with different psycopg versions who say they use Python's 
  44  #   builtin datetime types but don't. 
  45  # - Wrapper's __getattribtue__ will check the wrapper class for methods. 
  46  #   This let's you write methods in the wrapper class as I did for 
  47  #   widget_spec(). 
  48  # - Wrote wrapper.widget_spec() which let's wrappered datatypes work 
  49  #   with widgets. 
  50  # - Added a default value to expression that is used for a newly created 
  51  #   object that's not SELECTed from the database. Needs boolean 
  52  #   has_default and default attribtue to the delayed class. 
  53  # 
  54  # Revision 1.23  2006/09/19 14:23:55  diedrich 
  55  # Removed debugging print 
  56  # 
  57  # Revision 1.22  2006/09/07 13:54:56  diedrich 
  58  # Added place holder mechanism to expression datatype. 
  59  # 
  60  # Revision 1.21  2006/09/06 23:17:23  diedrich 
  61  # - Fixed wrapper class 
  62  # - Added expression wrapper 
  63  # 
  64  # Revision 1.20  2006/09/04 15:51:17  diedrich 
  65  # Make unicode_type handle unicode strings that are returned by the 
  66  # database correctly. 
  67  # 
  68  # Revision 1.19  2006/07/04 22:43:24  diedrich 
  69  # - fixed widget_specs() 
  70  # - fixed __set_from_result__ for Unicode and datetime related types 
  71  # - added Boolean 
  72  # 
  73  # Revision 1.18  2006/07/03 17:08:58  diedrich 
  74  # Added date, time and datetime. 
  75  # 
  76  # Revision 1.17  2006/06/10 18:02:05  diedrich 
  77  # Rewrote widget handling 
  78  # 
  79  # Revision 1.16  2006/05/15 21:49:10  diedrich 
  80  # Working on ui basics 
  81  # 
  82  # Revision 1.15  2006/05/13 17:23:42  diedrich 
  83  # Massive docstring update. 
  84  # 
  85  # Revision 1.14  2006/05/13 14:54:20  diedrich 
  86  # - Added Unicode.__convert__(). This is ment to create a more 
  87  #   meningfull errormessage on a problem I keep running into often 
  88  # - Added wrapper datatype (which is only ment as a base for others) 
  89  # - Added delayed (which uses wrapper) 
  90  # - Added property_group (which is kind of a wrapper, too, but can't inherit 
  91  #   from wrapper, because it's responsible for more than one dbproperty) 
  92  # 
  93  # Revision 1.13  2006/05/10 21:54:46  diedrich 
  94  # Docstring update for common_serial class 
  95  # 
  96  # Revision 1.12  2006/04/28 09:49:27  diedrich 
  97  # Docstring updates for epydoc 
  98  # 
  99  # Revision 1.11  2006/04/28 08:40:31  diedrich 
 100  # - Changed validator handling 
 101  # - __set__ and __get__ check if they fit the dbobj passed 
 102  # - Added widget handling 
 103  # - Varchar now has a length_validator, use text for unlimited-length strings 
 104  # 
 105  # Revision 1.10  2006/04/21 18:53:45  diedrich 
 106  # Added validator exceptions 
 107  # 
 108  # Revision 1.9  2006/04/15 23:17:46  diedrich 
 109  # Changed widget_spec handling 
 110  # 
 111  # Revision 1.8  2006/02/25 17:59:55  diedrich 
 112  # Made the many2one work with multi column keys. 
 113  # 
 114  # Revision 1.7  2006/02/25 00:20:20  diedrich 
 115  # - Added and tested the ability to use multiple column primary keys. 
 116  # - Some small misc bugs. 
 117  # 
 118  # Revision 1.6  2006/01/01 20:39:55  diedrich 
 119  # - data_attribute_name() has been made a function 
 120  # - Actually update the database on property access 
 121  # 
 122  # Revision 1.5  2005/12/31 18:24:58  diedrich 
 123  # - Updated year in copyright header ;) 
 124  # - The difference between __set__ and __set_from_result__ has be re-introduced 
 125  #   to accomodate for the relationships and other situations 
 126  # - took care of situationsin which two properties refer to the same column 
 127  #   (the data_attribute_name is based on the column name instead of the 
 128  #    attribute name) 
 129  # - added Long datatype 
 130  # - Enforce the id name convention for the common_serial type. 
 131  # 
 132  # Revision 1.4  2005/12/31 09:56:11  diedrich 
 133  # - comments 
 134  # - added the common_serial datatype 
 135  # 
 136  # Revision 1.3  2005/12/18 22:35:46  diedrich 
 137  # - Inheritance 
 138  # - pgsql adapter 
 139  # - first unit tests 
 140  # - some more comments 
 141  # 
 142  # Revision 1.2  2005/11/21 19:57:51  diedrich 
 143  # - The hidden attributes of the dbobj start with a space character now, 
 144  #   not with a _ to avoid name clashes 
 145  # - added the has_default and __select_after_insert__() mechanism 
 146  # - added thorogh error checking in __get__() 
 147  # - added isset() method to be able to tell the difference between <not-set>, 
 148  #   None and a value 
 149  # - Wrote a more meaningfull error message when __delete__() is called 
 150  # 
 151  # Revision 1.1.1.1  2005/11/20 14:55:46  diedrich 
 152  # Initial import 
 153  # 
 154  # 
 155  # 
 156   
 157  __docformat__ = "epytext en" 
 158   
 159  """ 
 160  Datatype classes for the default SQL datatypes. 
 161  =============================================== 
 162   
 163    Each of the classes in this module models an SQL datatype. Their 
 164    instances will be responsible for managing an attribute in a 
 165    dbclass. The classes accept a number of arguments for you to 
 166    influence what exactly they do. Refer to L{datatype.__init__} for these. 
 167     
 168  """ 
 169   
 170  import sys, copy 
 171  from types import * 
 172  from string import * 
 173  from datetime import date as py_date 
 174  from datetime import datetime as py_datetime 
 175  from datetime import timedelta as py_timedelta 
 176   
 177  try: 
 178      from mx.DateTime import DateTimeType 
 179      from mx.DateTime import DateTimeDeltaType 
 180  except ImportError: 
 181      DateTimeType = None 
 182      DateTimeDeltaType = None 
 183       
 184  from orm2 import sql 
 185  from orm2.validators import validator, length_validator 
 186  from orm2.exceptions import * 
 187  from orm2.ui import widget 
 188   
189 -class datatype(property):
190 """ 191 This class encapsulates a dbclass' property (=attribute). It takes 192 care of the SQL column name and the information 193 actually stored in the database/the dbobject. 194 """ 195 python_class = None 196 sql_literal_class = None 197
198 - def __init__(self, column=None, title=None, 199 validators=(), has_default=False):
200 """ 201 @param column: A orm2.sql column instance or a string containing a SQL 202 column name pointing to the column this property is 203 responsible for. Defaults to the column with the same name 204 as the attribute. 205 @param title: The title of this column used in dialogs, tables and 206 validator error messages (among other things). This must be a 207 unicode object or None. 208 @param validators: A sequence of objects of validators.validator 209 children. (A single validator is ok, too) 210 @param has_default: Boolean property that determines whether this 211 dbproperty is retrieved from the database after the dbobject has 212 been INSERTed. (So has_default referrs to the SQL column really). 213 """ 214 if type(column) == StringType: 215 self.column = sql.column(column) 216 elif isinstance(column, sql.column): 217 self.column = column 218 elif column is None: 219 self.column = None 220 else: 221 raise TypeError("Column must either be a string or an sql.column"+\ 222 " instance, not %s (%s)" % ( repr(type(column)), 223 repr(column),) ) 224 self.title = title 225 226 if isinstance(validators, validator): 227 self.validators = ( validators, ) 228 else: 229 self.validators = tuple(validators) 230 231 self.has_default = has_default
232
233 - def __init_dbclass__(self, dbclass, attribute_name):
234 """ 235 This methods gets called by dbobject's metaclass. It supplies the 236 db property with info about the class it belongs to and its attribute 237 name. 238 """ 239 self.dbclass = dbclass 240 self.attribute_name = attribute_name 241 # The actual data attribute's names start with a space character 242 # to avoid name clashes. 243 244 if self.column is None: 245 self.column = sql.column(attribute_name) 246 247 self._data_attribute_name = " %s" % str(self.column) 248 249 if self.title is None: 250 self.title = unicode(self.attribute_name, "ascii")
251 # It's save to use ascii, because Python does not allow non-ascii 252 # identifyers and the attribute_name is an identifyer, of course. 253
254 - def data_attribute_name(self):
255 try: 256 return self._data_attribute_name 257 except AttributeError: 258 raise DatatypeMustBeUsedInClassDefinition(self.__class__.__name__)
259
260 - def __get__(self, dbobj, owner="owner? Like owner of what??"):
261 """ 262 See the Python Language Reference, chapter 3.3.2.2 for details on 263 how this works. Be sure to be in a relaxed, ready-for-hard-figuring 264 mood. 265 """ 266 # The error checking in this method may seem overblown. But 267 # working with orm1 showed me that informative error messages, 268 # that precisely say what's going on make development a lot 269 # more fun. 270 271 if dbobj is None: return self 272 self.check_dbobj(dbobj) 273 274 if self.isset(dbobj): 275 return getattr(dbobj, self.data_attribute_name()) 276 else: 277 primary_key_property = repr(tuple(dbobj.__primary_key__.\ 278 attribute_names())) 279 if not dbobj.__primary_key__.isset(): 280 pk_literal = "<unset>" 281 else: 282 pk_literal = repr(tuple(dbobj.__primary_key__.values())) 283 284 tpl = ( self.attribute_name, 285 dbobj.__class__.__name__, 286 primary_key_property, 287 pk_literal, ) 288 289 msg = "Attribute '%s' of '%s' [ %s=%s ] has not yet been set" % tpl 290 291 raise AttributeError( msg )
292
293 - def __set__(self, dbobj, value):
294 """ 295 Set the attribute managed by this datatype class on instance 296 to value. This will be called by Python on attribute 297 assignment. The __set_from_result__ method does the same thing 298 for data retrieved from the RDBMS. See below. 299 """ 300 self.check_dbobj(dbobj) 301 if value is not None: value = self.__convert__(value) 302 303 for validator in self.validators: 304 validator.check(dbobj, self, value) 305 306 setattr(dbobj, self.data_attribute_name(), value) 307 308 if dbobj.__is_stored__(): 309 dbobj.__ds__().update( dbobj.__relation__, 310 self.column, 311 self.sql_literal(dbobj), 312 dbobj.__primary_key__.where() )
313 314
315 - def __set_from_result__(self, ds, dbobj, value):
316 setattr(dbobj, self.data_attribute_name(), value)
317
318 - def check_dbobj(self, dbobj):
319 if self.attribute_name is not None and \ 320 not self in dbobj.__dbproperties__(): 321 msg = "dbclass '%s' does not have attribute '%s' (wrong " + \ 322 "dbclass for this dbproperty!)" 323 msg = msg % ( dbobj.__class__.__name__, self.attribute_name, ) 324 raise AttributeError(msg)
325
326 - def isset(self, dbobj):
327 """ 328 @returns: True, if this property is set, otherwise... well.. False. 329 """ 330 return hasattr(dbobj, self.data_attribute_name())
331
332 - def __convert__(self, value):
333 """ 334 Return value converted as a Python object of the class assigned to 335 this datatype. 336 """ 337 if not isinstance(value, self.python_class): 338 return self.python_class(value) 339 else: 340 return value
341
342 - def sql_literal(self, dbobj):
343 """ 344 Return an SQL literal representing the data managed by this property 345 in dbobj. 346 347 @returns: SQL literal as a string. 348 """ 349 350 if not self.isset(dbobj): 351 msg = "This attribute has not been retrieved from the database." 352 raise AttributeError(msg) 353 else: 354 value = getattr(dbobj, self.data_attribute_name()) 355 356 if value is None: 357 return sql.NULL 358 else: 359 return self.sql_literal_class(value)
360
361 - def __select_this_column__(self):
362 """ 363 Indicate whether this column shall be included in SELECT statements. 364 True by default, it will return False for most relationships. 365 """ 366 return True
367
368 - def __select_after_insert__(self, dbobj):
369 """ 370 Indicate whether this column needs to be SELECTed after the dbobj has 371 been inserted to pick up information supplied by backend as by SQL 372 default values and auto increment columns. 373 """ 374 return self.has_default and not self.isset(dbobj)
375
376 - def __delete__(self, dbobj):
377 raise NotImplementedError( 378 "Can't delete a database property from a dbobj.")
379
380 - def add_widget(self, attribute_name, widget):
381 setattr(self, attribute_name, widget)
382
383 - def widget_specs(self):
384 for prop in self.__dict__.values(): 385 if isinstance(prop, widget): 386 yield prop
387
388 - def __setattr__(self, name, value):
389 object.__setattr__(self, name, value) 390 if isinstance(value, widget): 391 value.__init_dbproperty__(self)
392
393 -class integer(datatype):
394 """ 395 dbclass property for INTEGER SQL columns. 396 """ 397 python_class = int 398 sql_literal_class = sql.integer_literal
399
400 -class Long(datatype):
401 """ 402 dbclass property for INTEGER SQL columns. 403 """ 404 python_class = long 405 sql_literal_class = sql.long_literal
406
407 -class Float(datatype):
408 """ 409 dbclass property for FLOAT and DOUBLE (etc) SQL columns. 410 """ 411 python_class = float 412 sql_literal_class = sql.float_literal
413 414 real = Float 415
416 -class string(datatype):
417 """ 418 dbclass property for TEXT etc. SQL columns. 419 """ 420 python_class = str 421 sql_literal_class = sql.string_literal
422 423 text = string 424
425 -class varchar(string):
426 """ 427 dbclass property for string values with a fixed (maximum-)length. 428 This is the string class above with a length_validator added. 429 """
430 - def __init__(self, max_length, column=None, title=None, 431 validators=(), has_default=False):
432 validators = list(validators) 433 validators.append(length_validator(max_length)) 434 datatype.__init__(self, column, title, 435 validators, has_default)
436 437 char = varchar 438
439 -class Unicode(datatype):
440 """ 441 dbclass property for TEXT, VARCHAR, CHAR() etc. SQL columns that 442 are to be converted from SQL literals (i.e. encoded strings) to 443 Python Unicode objectes and vice versa. 444 445 When setting a Unicode property of a dbobj, you might want to convert 446 the value to Unicode yourself. This class uses Python's default encoding 447 (see documentation for sys.getdefaultencoding()) to convert things *to* 448 Unicode, which may or may not be what you want. 449 """ 450 python_class = unicode 451 sql_literal_class = sql.unicode_literal 452
453 - def __set_from_result__(self, ds, dbobj, value):
454 if value is not None and type(value) != UnicodeType: 455 value = unicode(value, ds.backend_encoding()) 456 457 setattr(dbobj, self.data_attribute_name(), value)
458
459 - def __convert__(self, value):
460 if type(value) != UnicodeType: 461 try: 462 return unicode(value) 463 except UnicodeDecodeError: 464 raise ValueError("This string can't be converted "+\ 465 "to unicode using sys.defaultencoding. You must use the "+\ 466 "unicode() function with a specific encoding to convert it"+\ 467 " or set the default encoding. (%s)" % repr(value)) 468 else: 469 return value
470 471
472 -class datetime_base(datatype):
473 """ 474 This is the baseclass for datetime, date, and time. Writing a 475 datetime datatype is more intricate matter than one would expect 476 on first sight. The challenges that come with it are, among other 477 things: 478 479 - that there are different calendars, Gregorian, Julian, Arabic and 480 others, which were/are used historically and locally 481 - that time is pretty much linear, but not entirely: There have been 482 corrections in history, large and small, as well as leap years and 483 seconds 484 - There are numerous different conventions on how to write date and 485 time, which are even ambiguous. 486 487 SQL backends approach these problems in diverse ways, which must 488 be handled by specific classes in the backend's datatype 489 module. Fortunately they all share a common denominator which I'm 490 trying to work with here. There are three classes: date, time and 491 datetime. On the Python side they use the datetime module (the 492 timedelta, date and datetime classes for the time, data and 493 datetime datatype respectively). Values provided as mx.DateTime 494 instances by the database will be converted. Towards the database 495 it will convert these into ISO compliant date representations, 496 quoted like SQL string literals. This is going to just work in 497 most situations. 498 499 If anyone feels like writing custom datetime datatypes for 500 specific backends, mapping Python's new datatypes to PostgreSQL's 501 timezone or DateTimeDelta capabilities or MySQL's ability to store 502 illegal dates (with 0s in it), they are very welcome! 503 """
504 - def sql_literal(self, dbobj):
505 if not self.isset(dbobj): 506 msg = "This attribute has not been retrieved from the database." 507 raise AttributeError(msg) 508 else: 509 value = getattr(dbobj, self.data_attribute_name()) 510 511 if value is None: 512 return sql.NULL 513 else: 514 return sql.string_literal(self.datetime_as_string(value))
515
516 - def __set_from_result__(self, ds, dbobj, value):
517 if value is not None: value = self.__convert__(value) 518 datatype.__set_from_result__(self, ds, dbobj, value)
519
520 - def __convert__(self, value):
521 raise NotImplementedError()
522
523 - def datetime_as_string(self, value):
524 raise NotImplementedError()
525 526
527 -class datetime(datetime_base):
528 """ 529 Date and time without a timezone. 530 531 See L{datetime_base} for details. 532 """
533 - def datetime_as_string(self, value):
534 return str(value)
535
536 - def __convert__(self, value):
537 if isinstance(value, py_datetime): 538 return value 539 elif hasattr(value, "year") and hasattr(value, "month") \ 540 and hasattr(value, "day") and hasattr(value, "hour") \ 541 and hasattr(value, "minute") and hasattr(value, "second"): 542 543 if type(value.year) == MethodType: 544 return py_datetime(value.year(), 545 value.month(), 546 value.day(), 547 value.hour(), 548 value.minute(), 549 value.second()) 550 else: 551 second = int(value.second) 552 fraction = value.second - float(second) 553 milisecond = int(fraction * 1000) 554 555 return py_datetime(value.year, 556 value.month, 557 value.day, 558 value.hour, 559 value.minute, 560 second, milisecond) 561 562 else: 563 raise ValueError("This dbattribute may only be set to "+\ 564 "datetime.datetime instances, not %s!" % \ 565 repr(type(value)))
566 567
568 -class date(datetime):
569 """ 570 date (resolution of one day) 571 572 See L{datetime_base} for details. 573 """
574 - def datetime_as_string(self, value):
575 return value.strftime("%Y-%m-%d")
576
577 - def __convert__(self, value):
578 if isinstance(value, py_date): 579 return value 580 elif isinstance(value, py_datetime): 581 return py_date(value.year, value.month, value.day) 582 elif hasattr(value, "year") and hasattr(value, "month") \ 583 and hasattr(value, "day"): 584 if type(value.year) == MethodType: 585 return py_date(value.year(), 586 value.month(), 587 value.day()) 588 else: 589 return py_date(value.year, 590 value.month, 591 value.day) 592 else: 593 raise ValueError("This dbattribute may only be set to "+\ 594 "datetime.date instances, not %s!" % \ 595 repr(value))
596
597 -class time(datetime_base):
598 """ 599 Time of day, 00:00:00 to 23:59:59 600 The resolution of the time class is limited to one second. 601 602 See L{datetime_base} for details. 603 """
604 - def datetime_as_string(self, value):
605 return value
606
607 - def __convert__(self, value):
608 if isinstance(value, py_timedelta): 609 return value 610 elif DateTimeDeltaType is not None and \ 611 type(value) == DateTimeDeltaType: 612 ret = py_timedelta(0, 613 hours=value.hour, 614 minutes=value.minute, 615 seconds=value.second) 616 return ret 617 else: 618 raise ValueError("This dbattribute may only be set to "+\ 619 "datetime.datetime instances, not %s!" % \ 620 repr(type(value)))
621
622 -class boolean(datatype):
623 python_class = bool 624 sql_literal_class = sql.bool_literal
625 626 Boolean = boolean 627
628 -class common_serial(integer):
629 """ 630 The common_serial datatype is an primary key integer column whoes 631 value is supplied by the backend using its default mechanism. The 632 default mechanism for each backend is defined by the adapter's 633 datatype module (see there). The name of the common_serial column is 634 alway 'id'. 635 636 This class used by some of the test cases to define data models that 637 work on every backend. 638 639 - For gadfly this works on a regular INTEGER column and is not suitable 640 for multi user operations (see L{orm2.adapters.gadfly.datasource} 641 for details) 642 - For postgresql this works on a SERIAL column named id. 643 - For mysql this works on an INTEGER column with AUTO_INCREMENT set. 644 - For firebird this works with an INTEGER column which is 645 combined with a sequence as described U{here 646 <http://firebird.sourceforge.net/index.php?op=faq#q0011.dat>}. The 647 sequence must be named GEN_PK_<tablename>. This is basically 648 the same what L{orm2.adapter.firebird.datatypes.serial} does, 649 except for the naming scheme for the generator. 650 """ 651
652 - def __init__(self):
653 datatype.__init__(self, column="id", 654 has_default=True)
655
656 - def __init_dbclass__(self, dbclass, attribute_name):
657 if attribute_name != "id": 658 raise ORMException("All common_serial columns must be called 'id'") 659 integer.__init_dbclass__(self, dbclass, "id")
660
661 - def __set__(self, dbobj, value):
662 raise NotImplementedError("common_serial values are always " + \ 663 "retrieved from the database backend")
664
665 - def __set_from_result__(self, ds, dbobj, value):
666 integer.__set_from_result__(self, ds, dbobj, value)
667 668
669 -class _inside_method:
670 - def __init__(self, instance, method):
671 self.instance = instance 672 self.method = method
673
674 - def __call__(self, *args, **kw):
675 return self.method(self.instance, *args, **kw)
676
677 -class wrapper(datatype):
678 """ 679 This is the base class for those datatype that are 'wrappers' for regular 680 datatypes. This class will forward all attribute access to the inner 681 datatype, including method calls. Except for those methods and attributes 682 it contains itself. 683 684 All classes derived from wrapper must overload the __copy__ method for 685 dbclass inheritance to work properly. 686 """ 687
688 - def __init__(self, inside_datatype):
689 self.inside_datatype = inside_datatype
690
691 - def __getattribute__(self, name):
692 my_dict = object.__getattribute__(self, "__dict__") 693 my_cls = object.__getattribute__(self, "__class__") 694 my_cls_dict = my_cls.__dict__ 695 inside_datatype = object.__getattribute__(self, "inside_datatype") 696 697 if my_dict.has_key(name): 698 return my_dict[name] 699 700 elif my_cls_dict.has_key(name): 701 ret = my_cls_dict[name] 702 if type(ret) == FunctionType: 703 return _inside_method(self, ret) 704 else: 705 return ret 706 707 elif wrapper.__dict__.has_key(name): 708 return _inside_method(self, wrapper.__dict__[name]) 709 710 elif hasattr(inside_datatype, name): 711 return getattr(self.inside_datatype, name) 712 713 else: 714 raise AttributeError(name)
715
716 - def __eq__(self, other):
717 """ 718 This will let the in in L{datatype.check_dbobj} yield True. 719 """ 720 return other == self.inside_datatype
721
722 - def __copy__(self):
723 raise NotImplementedError( "All classes derived from wrapper must " 724 "overload the __copy__ method for " 725 "dbclass inheritance to work properly." )
726
727 - def widget_specs(self):
728 __dict__ = object.__getattribute__(self, "__dict__") 729 for prop in __dict__.values(): 730 if isinstance(prop, widget): 731 yield prop
732 733
734 -class delayed(wrapper):
735 """ 736 This is a pseudy-datatype that takes an actual datatype as argument. 737 Values from that datatypes's column will not be SELECTed from the database 738 regularly, but only on attribute access. This way you can treat a dbclass 739 that contains large amount of data just like all the others and only 740 load the data at the point in time when it's needed. 741 """
742 - def __init__(self, inside_datatype, cache=False):
743 """ 744 @param inside_datatype: The datatype <b>instance</b> this wrapper is 745 responsible for 746 @param cache: Parameter that determines whether the data is kept in 747 memory once it is loaded from the database 748 """ 749 wrapper.__init__(self, inside_datatype) 750 self.cache = cache
751
752 - def __get__(self, dbobj, owner="I don't know what this is for"):
753 if dbobj is None: return self 754 755 self.check_dbobj(dbobj) 756 757 if self.isset(dbobj): 758 return getattr(dbobj, self.data_attribute_name()) 759 else: 760 query = sql.select(( self.column, ), 761 dbobj.__relation__, 762 dbobj.__primary_key__.where()) 763 cursor = dbobj.__ds__().execute(query) 764 row = cursor.fetchone() 765 766 if row is None: raise IllegelPrimaryKey() # This shouldn't happen 767 768 value = row[0] 769 770 # The way this is handled is a little strange. Let me explain! 771 # The point is, __set_from_result__() may convert the 772 # data retreived from the RDBMS into some other Python 773 # representation (orm2.util.pickle works that way for instance). 774 # So we use the function to do its job and, if we're not supposed 775 # to cache the value, *undo* the changes it made on the dbobj. 776 # This presumes that the data_attribute_name() mechanism is used 777 # by __set_from_result__(), which is relatively save, I guess. 778 779 self.inside_datatype.__set_from_result__(dbobj.__ds__(), 780 dbobj, value) 781 782 ret = getattr(dbobj, self.data_attribute_name()) 783 784 if not self.cache and hasattr(dbobj, self.data_attribute_name()): 785 delattr(dbobj, self.data_attribute_name()) 786 787 return ret
788
789 - def __select_this_column__(self):
790 return False
791
792 - def __select_after_insert__(self, *args):
793 return False
794
795 - def __copy__(self):
796 return delayed(copy(self.inside_datatype), self.cache)
797
798 -class expression(wrapper):
799 """ 800 Insert an arbitrary SQL expression into the SQL query. 801 802 The expression string may contain placeholders $relation, $table 803 (which are the same thing) and $attribute_name which will be replaced 804 prior to execution by appropriate strings. This is especially usefull 805 when using inheritance. 806 807 The has_default and default parameters may be used to supply a 808 default value which the expression dbproperty will contain it the 809 dbobject has not been SELECTed from the database. Has_default must 810 be set to True to use this feature. Default defaults to None ;-) 811 """
812 - def __init__(self, inside_datatype, expression, 813 has_default=False, default=None):
814 wrapper.__init__(self, inside_datatype) 815 816 if type(expression) == StringType: 817 expression = sql.expression(expression) 818 819 if not isinstance(expression, sql.expression): 820 msg = ( "You must initialize an expression datatype width an " 821 "sql.expression instance, or a string, " 822 "not %s") % repr(expression) 823 raise TypeError(msg) 824 825 self.expression = expression 826 827 self.has_default = has_default 828 self.default = default
829
830 - def __get__(self, dbobj, owner="I don't know about this"):
831 if self.has_default and not self.isset(dbobj): 832 return self.default 833 else: 834 return wrapper.__get__(self, dbobj, owner)
835
836 - def __init_dbclass__(self, dbclass, attribute_name):
837 self.inside_datatype.__init_dbclass__(dbclass, attribute_name) 838 exp = self.expression.parts[:] 839 exp.insert(0, "(") 840 exp.append(") AS ") 841 exp.append(self.column) 842 843 # Perform template substitution 844 info = { "$relation": str(dbclass.__relation__), 845 "$table": str(dbclass.__relation__), 846 "$attribute": attribute_name } 847 848 for key, value in info.items(): 849 n = [] 850 for a in exp: 851 if type(a) == StringType: 852 parts = a.split(key) 853 parts.reverse() 854 855 s = [] 856 while parts: 857 s.append(parts.pop()) 858 if parts: s.append(value) 859 n.append(join(s, "")) 860 861 else: 862 n.append(a) 863 864 exp = n 865 866 self.column = sql.expression(*exp)
867
868 - def __copy__(self):
869 return expression(self.inside_datatype, self.expression)
870
871 -class property_group(datatype):
872 """ 873 This datatype will manage several columns of the same datatype 874 that follow a naming convention. It is maily indended for database 875 tables that store several versions of a string in several columns, 876 as for example for applications where you have more than one language. 877 878 Example:: 879 880 CREATE TABLE item_category 881 ( 882 id SERIAL, 883 name_en TEXT, 884 name_de TEXT, 885 886 PRIMARY KEY(id) 887 ) 888 889 And in Python:: 890 891 class item_category: 892 id = serial() 893 name = datatype_group(Unicode, ('en', 'de',), 'en') 894 895 The datatype_group instance will add one dbproperty to the dbclass for 896 each postfix you supply. These are not accessible directly, but the 897 datatype_group dbproperty will behave like a Python dictionary:: 898 899 lang = 'de' 900 category_name = item_category.name[lang] 901 902 The naming convention for the database column goes 903 <attribute name>_<postfix>. If you want to use your own column names, 904 you may pass a dictionary as the postfixes parameter like:: 905 906 { 'p1': sql.column(name1), 'p2': sql.column(name2) } 907 908 In this case the implementation will accept any datatype for the 909 postfixes. 910 """ 911
912 - def __init__(self, inside_datatype, postfixes, default_postfix=None, 913 title=None, validators=(), 914 has_default=False):
915 """ 916 The rest of the params just like L{datatype}. 917 918 @param inside_datatype: This is the datatype <b>class</b> for the 919 managed attributes. 920 @param postfixes: This may either be a tuple of strings, which will be 921 used in the column names as described above or a dictionary 922 mapping arbitrary keys to column names. 923 """ 924 datatype.__init__(self, None, title, validators, 925 has_default) 926 927 self.inside_datatype = inside_datatype 928 self.inside_dbproperty_names = {} 929 930 self.postfixes = postfixes 931 self.default_postfix = default_postfix 932 933 for prop in self.__dict__.values(): 934 if isinstance(prop, widget): 935 prop.__init_dbproperty__(self)
936 937
938 - def __init_dbclass__(self, dbclass, attribute_name):
939 datatype.__init_dbclass__(self, dbclass, attribute_name) 940 941 if type(self.postfixes) != DictType: 942 p = {} 943 for postfix in self.postfixes: 944 if type(postfix) != StringType: 945 raise TypeError("All postfixes must be strings!") 946 947 if p.has_key(postfix): 948 raise ValueError("The members of the postfixes " 949 "parameter must be unique.") 950 951 p[postfix] = sql.column("%s_%s" % ( attribute_name, 952 postfix, )) 953 954 self.postfixes = p 955 956 else: 957 for postfix, column in self.postfixes: 958 if not isinstance(column, sql.column): 959 self.postfixes[postfix] = sql.column(column) 960 961 962 for postfix, column in self.postfixes.items(): 963 attr_name = " %s_%s" % ( attribute_name, repr(postfix), ) 964 dt = self.inside_datatype(column=column, 965 title=self.title, 966 validators=self.validators, 967 has_default=self.has_default) 968 setattr(dbclass, attr_name, dt) 969 dt.__init_dbclass__(dbclass, attr_name) 970 self.inside_dbproperty_names[postfix] = attr_name
971 972
973 - def inside_dbproperties(self):
974 """ 975 Return a dict as { postfix: <datatype instance> } 976 """ 977 ret = {} 978 for postfix, attr_name in self.inside_dbproperty_names.items(): 979 ret[postfix] = self.dbclass.__dict__[attr_name] 980 981 return ret
982
983 - def __get__(self, dbobj, owner=""):
984 return self.result(self, dbobj)
985
986 - def __set__(self, dbobj, value):
987 if type(value) != DictType: 988 raise TypeError("%s.%s dbattribute can only be set to a dict!" % \ 989 ( self.dbclass.__name__, self.attribute_name, )) 990 991 for postfix, v in value.items(): 992 if postfix not in self.postfixes: 993 raise KeyError("%s not a valid postfix" % repr(postfix)) 994 995 property = self.inside_dbproperties()[postfix] 996 property.__set__(dbobj, v)
997
998 - def __set_from_result__(self, ds, dbobj, value):
999 raise NotImplementedError()
1000
1001 - def isset(self, dbobj):
1002 for property in self.inside_dbproperties().values(): 1003 if property.isset(dbobj): 1004 return True 1005 1006 return False
1007
1008 - def sql_literal(self, dbobj):
1009 return None
1010
1011 - def __select_this_column__(self):
1012 return False
1013
1014 - def __select_after_insert__(self, dbobj):
1015 return False
1016 1017
1018 - class result:
1019 - def __init__(self, parent_dbproperty, dbobj):
1020 self.parent = parent_dbproperty 1021 self.dbobj = dbobj
1022
1023 - def __getitem__(self, key):
1024 if not self.parent.postfixes.has_key(key): 1025 raise KeyError("Illegal postfix: %s" % repr(key)) 1026 1027 property = self.parent.inside_dbproperties()[key] 1028 if property.isset(self.dbobj): 1029 return property.__get__(self.dbobj) 1030 else: 1031 if self.parent.default_postfix is not None: 1032 pf = self.parent.default_postfix 1033 property = self.parent.inside_dbproperties()[pf] 1034 if property.isset(self.dbobj): 1035 return property.__get__(self.dbobj) 1036 raise KeyError("No value for %s, default not set" % repr(pf)) 1037 1038 raise KeyError(key)
1039
1040 - def __setitem__(self, key, value):
1041 if not self.parent.postfixes.has_key(key): 1042 raise KeyError("Illegal postfix: %s" % repr(key)) 1043 1044 property = self.parent.inside_dbproperties()[key] 1045 property.__set__(self.dbobj, value)
1046