sqlobject-cvs Mailing List for SQLObject (Page 170)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <sub...@co...> - 2005-02-10 04:50:05
|
Author: ianb Date: 2005-02-10 04:50:00 +0000 (Thu, 10 Feb 2005) New Revision: 593 Modified: trunk/SQLObject/sqlobject/main.py Log: * Give some more explicit controls about how warnings and errors for deprecated features are given. * Export sqlmeta Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-10 04:49:16 UTC (rev 592) +++ trunk/SQLObject/sqlobject/main.py 2005-02-10 04:50:00 UTC (rev 593) @@ -189,11 +189,18 @@ # something. Turning it on gives earlier warning about things # that will be deprecated (having this off we won't flood people # with warnings right away). -strict_warnings = False +warnings_level = 1 +exception_level = None +# Current levels: +# 1) Actively deprecated in version after 0.6.1 (0.7?); removed after +# 2) Deprecated after 1 (0.8?) +# 3) Deprecated after 2 (0.9?) -def deprecated(message): - if strict_warnings: - warnings.warn(message, DeprecationWarning, stacklevel=1) +def deprecated(message, level=1): + if exception_level is not None and exception_level <= level: + raise NotImplementedError(message) + if warnings_level is not None and warnings_level <= level: + warnings.warn(message, DeprecationWarning, stacklevel=2) # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and @@ -294,7 +301,7 @@ if (new_attrs.has_key('_table') and not is_base): deprecated("'_table' is deprecated; please set the 'table' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.table = cls._table del cls._table @@ -349,7 +356,7 @@ if (new_attrs.has_key('_style') and not is_base): deprecated("'_style' is deprecated; please set the 'style' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.style = cls._style del cls._style @@ -372,7 +379,7 @@ if (new_attrs.has_key('_idName') and not is_base): deprecated("'_idName' is deprecated; please set the 'idName' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.idName = cls._idName del cls._idName @@ -1450,6 +1457,6 @@ else: return obj -__all__ = ['NoDefault', 'SQLObject', +__all__ = ['NoDefault', 'SQLObject', 'sqlmeta', 'getID', 'getObject', 'SQLObjectNotFound'] |
From: <sub...@co...> - 2005-02-10 04:49:24
|
Author: ianb Date: 2005-02-10 04:49:16 +0000 (Thu, 10 Feb 2005) New Revision: 592 Modified: trunk/SQLObject/sqlobject/classregistry.py Log: Be careful about sys.modules when generating an error (py.test doesn't put modules in sys.modules) Modified: trunk/SQLObject/sqlobject/classregistry.py =================================================================== --- trunk/SQLObject/sqlobject/classregistry.py 2005-02-09 13:16:59 UTC (rev 591) +++ trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 04:49:16 UTC (rev 592) @@ -74,9 +74,11 @@ "%r, from the module %s in %s)" % (cls.__name__, other, other.__module__, - sys.modules[other.__module__].__file__, + getattr(sys.modules.get(other.__module__), + '__file__', '(unknown)'), cls, cls.__module__, - sys.modules[cls.__module__].__file__)) + getattr(sys.modules.get(cls.__module__), + '__file__', '(unknown)'))) self.classes[cls.__name__] = cls if self.callbacks.has_key(cls.__name__): for callback, args, kw in self.callbacks[cls.__name__]: |
From: <sub...@co...> - 2005-02-09 13:17:02
|
Author: phd Date: 2005-02-09 13:16:59 +0000 (Wed, 09 Feb 2005) New Revision: 591 Added: home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/ home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/__init__.py home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py Log: Added inheritance tests. Added: home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/__init__.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/__init__.py 2005-02-09 13:15:16 UTC (rev 590) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/__init__.py 2005-02-09 13:16:59 UTC (rev 591) @@ -0,0 +1 @@ +# Added: home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py 2005-02-09 13:15:16 UTC (rev 590) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py 2005-02-09 13:16:59 UTC (rev 591) @@ -0,0 +1,34 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject.inheritance import InheritableSQLObject + +######################################## +## Inheritance +######################################## + + +class Person(InheritableSQLObject): + _inheritable = 1 # I want this class to be inherited + firstName = StringCol() + lastName = StringCol() + +class Employee(Person): + _inheritable = 0 # If I don't want this class to be inherited + position = StringCol() + + +def test_inheritance(): + setupClass(Person) + setupClass(Employee) + + Employee(firstName='Ian', lastName='Bicking', position='Project leader') + Person(firstName='Daniel', lastName='Savard') + + persons = Person.select() # all + for person in persons: + assert isinstance(person, Person) + if isinstance(person, Employee): + assert not hasattr(person, "childName") + else: + assert hasattr(person, "childName") + assert not person.childName |
From: <sub...@co...> - 2005-02-09 13:15:25
|
Author: phd Date: 2005-02-09 13:15:16 +0000 (Wed, 09 Feb 2005) New Revision: 590 Added: home/phd/SQLObject/inheritance/sqlobject/boundattributes.py home/phd/SQLObject/inheritance/sqlobject/declarative.py home/phd/SQLObject/inheritance/sqlobject/inheritance/iteration.py home/phd/SQLObject/inheritance/sqlobject/tests/test_boundattributes.py home/phd/SQLObject/inheritance/sqlobject/tests/test_declarative.py Modified: home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/sqlobject/dbconnection.py home/phd/SQLObject/inheritance/sqlobject/include/validators.py home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py home/phd/SQLObject/inheritance/sqlobject/sqlite/sqliteconnection.py home/phd/SQLObject/inheritance/sqlobject/styles.py home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py home/phd/SQLObject/inheritance/sqlobject/tests/test_auto.py home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py home/phd/SQLObject/inheritance/sqlobject/tests/test_blob.py home/phd/SQLObject/inheritance/sqlobject/tests/test_distinct.py home/phd/SQLObject/inheritance/sqlobject/tests/test_joins.py home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py Log: Merged patches from revisiosn 571:589 from the trunk. Moved InheritanceIteration from dbconnection.py to inheritance/iteration.py. Copied: home/phd/SQLObject/inheritance/sqlobject/boundattributes.py (from rev 585, trunk/SQLObject/sqlobject/boundattributes.py) Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-09 13:15:16 UTC (rev 590) @@ -888,6 +888,8 @@ """ def fromPython(self, value, state): + if value is None: + return None return state.soObject._connection.createBinary(value) class SOBLOBCol(SOStringCol): Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-09 13:15:16 UTC (rev 590) @@ -45,13 +45,14 @@ atexit.register(_closeConnection, weakref.ref(self)) def uri(self): - auth = self.user or '' + auth = getattr(self, 'user', None) or '' if auth: if self.password: auth = auth + '@' + self.password auth = auth + ':' else: - assert not self.password, 'URIs cannot express passwords without usernames' + assert not getattr(self, 'password', None), ( + 'URIs cannot express passwords without usernames') uri = '%s://%s' % (self.dbName, auth) if self.host: uri += self.host + '/' @@ -181,6 +182,8 @@ conn.close() def printDebug(self, conn, s, name, type='query'): + if name == 'Pool' and self.debug != 'Pool': + return if type == 'query': sep = ': ' else: @@ -541,80 +544,6 @@ def __del__(self): self._cleanup() -class InheritableIteration(Iteration): - #phd: default array size for cursor.fetchmany() - defaultArraySize = 10000 - - def __init__(self, dbconn, rawconn, select, keepConnection=False): - super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) - self.cursor.arraysize = self.defaultArraySize - self._results = [] - #phd: find the index of the childName column - childNameIdx = None - columns = select.sourceClass._SO_columns - for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2 - if columns[i].name == "childName": - childNameIdx = i - break - self._childNameIdx = childNameIdx - - def next(self): - lazyColumns = self.select.ops.get('lazyColumns', 0) - if not self._results: - self._results = list(self.cursor.fetchmany()) - if not lazyColumns: self.fetchChildren() - if not self._results: - self._cleanup() - raise StopIteration - result = self._results[0] - del self._results[0] - if lazyColumns: - obj = self.select.sourceClass.get(result[0], connection=self.dbconn) - return obj - else: - id = result[0] - if id in self._childrenResults: - childResults = self._childrenResults[id] - del self._childrenResults[id] - else: - childResults = None - obj = self.select.sourceClass.get(id, selectResults=result[1:], - childResults=childResults, connection=self.dbconn) - return obj - - def fetchChildren(self): - """Prefetch childrens' data - - Fetch childrens' data for every subclass in one big .select() - to avoid .get() fetching it one by one. - """ - self._childrenResults = {} - if self._childNameIdx is None: - return - childIdsNames = {} - childNameIdx = self._childNameIdx - for result in self._results: - childName = result[childNameIdx+1] - if childName: - ids = childIdsNames.get(childName) - if ids is None: - ids = childIdsNames[childName] = [] - ids.append(result[0]) - dbconn = self.dbconn - rawconn = self.rawconn - cursor = rawconn.cursor() - registry = self.select.sourceClass._registry - for childName, ids in childIdsNames.items(): - klass = findClass(childName, registry) - select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids)) - query = dbconn.queryForSelect(select) - if dbconn.debug: - dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName) - self.dbconn._executeRetry(rawconn, cursor, query) - for result in cursor.fetchall(): - self._childrenResults[result[0]] = result[1:] - - class Transaction(object): def __init__(self, dbConnection): Copied: home/phd/SQLObject/inheritance/sqlobject/declarative.py (from rev 585, trunk/SQLObject/sqlobject/declarative.py) Modified: home/phd/SQLObject/inheritance/sqlobject/include/validators.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/include/validators.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/include/validators.py 2005-02-09 13:15:16 UTC (rev 590) @@ -195,7 +195,7 @@ self._toPython, self.validatePython) - def fromPython(self, value, state): + def fromPython(self, value, state=None): return self.attemptConvert(value, state, self.validatePython, self._fromPython, @@ -227,7 +227,7 @@ def toPython(self, value, state=None): return self.attemptConvert(value, state, toPython) - def fromPython(self, value, state): + def fromPython(self, value, state=None): return self.attemptConvert(value, state, fromPython) def matchesProtocol(self, validator, state): Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py 2005-02-09 13:15:16 UTC (rev 590) @@ -1,11 +1,11 @@ from sqlobject import sqlbuilder -from sqlobject import dbconnection from sqlobject import classregistry from sqlobject.main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName +import iteration class InheritableSelectResults(SelectResults): - IterationClass = dbconnection.InheritableIteration + IterationClass = iteration.InheritableIteration def __init__(self, sourceClass, clause, clauseTables=None, **ops): @@ -177,7 +177,7 @@ ] ) self._parent = parentClass(kw=parent_kw) - self._parent.childName = self._className + self._parent.childName = self.__class__.__name__ id = self._parent.id super(InheritableSQLObject, self)._create(id, **kw) Added: home/phd/SQLObject/inheritance/sqlobject/inheritance/iteration.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance/iteration.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/iteration.py 2005-02-09 13:15:16 UTC (rev 590) @@ -0,0 +1,76 @@ +from sqlobject import sqlbuilder +from sqlobject.classregistry import findClass +from sqlobject.dbconnection import Iteration + +class InheritableIteration(Iteration): + #phd: default array size for cursor.fetchmany() + defaultArraySize = 10000 + + def __init__(self, dbconn, rawconn, select, keepConnection=False): + super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) + self.cursor.arraysize = self.defaultArraySize + self._results = [] + #phd: find the index of the childName column + childNameIdx = None + columns = select.sourceClass._SO_columns + for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2 + if columns[i].name == "childName": + childNameIdx = i + break + self._childNameIdx = childNameIdx + + def next(self): + lazyColumns = self.select.ops.get('lazyColumns', 0) + if not self._results: + self._results = list(self.cursor.fetchmany()) + if not lazyColumns: self.fetchChildren() + if not self._results: + self._cleanup() + raise StopIteration + result = self._results[0] + del self._results[0] + if lazyColumns: + obj = self.select.sourceClass.get(result[0], connection=self.dbconn) + return obj + else: + id = result[0] + if id in self._childrenResults: + childResults = self._childrenResults[id] + del self._childrenResults[id] + else: + childResults = None + obj = self.select.sourceClass.get(id, selectResults=result[1:], + childResults=childResults, connection=self.dbconn) + return obj + + def fetchChildren(self): + """Prefetch childrens' data + + Fetch childrens' data for every subclass in one big .select() + to avoid .get() fetching it one by one. + """ + self._childrenResults = {} + if self._childNameIdx is None: + return + childIdsNames = {} + childNameIdx = self._childNameIdx + for result in self._results: + childName = result[childNameIdx+1] + if childName: + ids = childIdsNames.get(childName) + if ids is None: + ids = childIdsNames[childName] = [] + ids.append(result[0]) + dbconn = self.dbconn + rawconn = self.rawconn + cursor = rawconn.cursor() + registry = self.select.sourceClass._registry + for childName, ids in childIdsNames.items(): + klass = findClass(childName, registry) + select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids)) + query = dbconn.queryForSelect(select) + if dbconn.debug: + dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName) + self.dbconn._executeRetry(rawconn, cursor, query) + for result in cursor.fetchall(): + self._childrenResults[result[0]] = result[1:] Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-09 13:15:16 UTC (rev 590) @@ -4,14 +4,6 @@ SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. -Modified by - Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 - - Added support for simple table inheritance. - - Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 - - Split inheritance support into a number of separate classes - - InheritableSQLObject at al. - This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -39,6 +31,7 @@ import index import classregistry findClass = classregistry.findClass # for those who imported findClass from sqlobject.main +import declarative import sys if sys.version_info[:3] < (2, 2, 0): @@ -51,209 +44,6 @@ True, False = 1==1, 0==1 -# This is the metaclass. It essentially takes a dictionary -# of all the attributes (and thus methods) defined in the -# class definition. It futzes with them, and spits out the -# new class definition. -class MetaSQLObject(type): - - def __new__(cls, className, bases, d): - - # We fix up the columns here -- replacing any strings with - # simply-contructed Col objects, and searching the class - # variables for instances of Col objects (which get put into - # the _columns instance variable and deleted). - columns = [] - for column in d.get('_columns', []): - if isinstance(column, str): - column = col.Col(column) - columns.append(column) - if columns: - d['_columns'] = columns - - implicitColumns = [] - implicitJoins = [] - implicitIndexes = [] - for attr, value in d.items(): - if isinstance(value, col.Col): - value.name = attr - implicitColumns.append(value) - del d[attr] - continue - if isinstance(value, joins.Join): - value.joinMethodName = attr - implicitJoins.append(value) - del d[attr] - continue - if isinstance(value, index.DatabaseIndex): - value.setName(attr) - implicitIndexes.append(value) - del d[attr] - continue - - # We *don't* want to inherit _table, so we make sure it - # is defined in this class (not a superclass) - if not d.has_key('_table'): - d['_table'] = None - - - if d.has_key('_connection'): - connection = d['_connection'] - del d['_connection'] - assert not d.has_key('connection') - elif d.has_key('connection'): - connection = d['connection'] - del d['connection'] - else: - connection = None - - # We actually create the class. - newClass = type.__new__(cls, className, bases, d) - newClass._SO_finishedClassCreation = False - - #DSM: Need to keep the name of the class for easy access later - newClass._className = className - newClass._childClasses = {} - #DSM: Need to know very soon if the class is a child of an - #DSM: inheritable class. If so, we keep a link to our parent class. - for cls in bases: - if hasattr(cls, '_inheritable') and cls._inheritable: - newClass._parentClass = cls - newClass._parent = None - cls._childClasses[className] = newClass - - # We append to _columns, but we don't want to change the - # superclass's _columns list, so we make a copy if necessary - if not d.has_key('_columns'): - newClass._columns = newClass._columns[:] - #DSM: If this class is a child of a parent class, we need to do some - #DSM: attribute check and a a foreign key to the parent. - if newClass._parentClass: - #DSM: First, look for invalid column name: - #DSM: reserved ones or same as a parent - parentCols = [column.name for column in newClass._columns] - for column in implicitColumns: - cname = column.name - if cname in ['childName']: - raise AttributeError, \ - "The column name '%s' is reserved" % cname - if cname in parentCols: - raise AttributeError, "The column '%s' is already " \ - "defined in an inheritable parent" % cname - #DSM: Remove columns if inherited from an inheritable class - #DSM: as we don't want them. All we want is a foreign key - #DSM: that will link to our parent - newClass._columns = [] - #DSM: If this is inheritable, add some default columns - # to be able to link to children - if hasattr(newClass, '_inheritable') and newClass._inheritable: - newClass._columns.append( - col.StringCol(name='childName',default=None)) - newClass._columns.extend(implicitColumns) - if not d.has_key('_joins'): - newClass._joins = newClass._joins[:] - newClass._joins.extend(implicitJoins) - if not d.has_key('_indexes'): - newClass._indexes = newClass._indexes[:] - newClass._indexes.extend(implicitIndexes) - - ###################################################### - # Set some attributes to their defaults, if necessary. - # First we get the connection: - if not connection and not getattr(newClass, '_connection', None): - mod = sys.modules[newClass.__module__] - # See if there's a __connection__ global in - # the module, use it if there is. - if hasattr(mod, '__connection__'): - connection = mod.__connection__ - - if connection or not hasattr(newClass, '_connection'): - newClass.setConnection(connection) - - # The style object tells how to map between Python - # identifiers and Database identifiers: - if not newClass._style: - if newClass._connection and newClass._connection.style: - newClass._style = newClass._connection.style - else: - newClass._style = styles.defaultStyle - - # plainSetters are columns that haven't been overridden by the - # user, so we can contact the database directly to set them. - # Note that these can't set these in the SQLObject class - # itself, because they specific to this subclass of SQLObject, - # and cannot be shared among classes. - newClass._SO_plainSetters = {} - newClass._SO_plainGetters = {} - newClass._SO_plainForeignSetters = {} - newClass._SO_plainForeignGetters = {} - newClass._SO_plainJoinGetters = {} - newClass._SO_plainJoinAdders = {} - newClass._SO_plainJoinRemovers = {} - - # This is a dictionary of columnName: columnObject - newClass._SO_columnDict = {} - newClass._SO_columns = [] - - # If _table isn't given, use style default - if not newClass._table: - newClass._table = newClass._style.pythonClassToDBTable(className) - - # If _idName isn't given, use style default - if not d.has_key('_idName'): - newClass._idName = newClass._style.idForTable(newClass._table) - - # We use the magic "q" attribute for accessing lazy - # SQL where-clause generation. See the sql module for - # more. - newClass.q = sqlbuilder.SQLObjectTable(newClass) - #DSM: If we are a child, get the q magic from the parent - currentClass = newClass - while currentClass._parentClass: - currentClass = currentClass._parentClass - for column in currentClass._columns: - if type(column) == col.ForeignKey: continue - setattr(newClass.q, column.name, - getattr(currentClass.q, column.name)) - - # We have to check if there are columns in the inherited - # _columns where the attribute has been set to None in this - # class. If so, then we need to remove that column from - # _columns. - for column in newClass._columns[:]: - if d.has_key(column.name) and d[column.name] is None: - newClass._columns.remove(column) - - for column in newClass._columns: - newClass.addColumn(column) - if newClass._fromDatabase: - newClass.addColumnsFromDatabase() - - ######################################## - # Now we do the joins: - - # We keep track of the different joins by index, - # putting them in this list. - newClass._SO_joinList = [] - newClass._SO_joinDict = {} - - for join in newClass._joins: - newClass.addJoin(join) - - # We don't setup the properties until we're finished with the - # batch adding of all the columns... - newClass._SO_finishedClassCreation = True - makeProperties(newClass) - - newClass._SO_indexList = [] - for idx in newClass._indexes: - newClass.addIndex(idx) - - classregistry.registry(newClass._registry).addClass(newClass) - - # And return the class - return newClass - def makeProperties(obj): """ This function takes a dictionary of methods and finds @@ -343,12 +133,67 @@ """ pass +class sqlmeta(object): -try: - basestring -except NameError: # Python 2.2 - basestring = (types.StringType, types.UnicodeType) + """ + This object is the object we use to keep track of all sorts of + information. Subclasses are made for each SQLObject subclass + (dynamically if necessary), and instances are created to go + alongside every SQLObject instance. + """ + table = None + idName = None + style = None + + __metaclass__ = declarative.DeclarativeMeta + + # These attributes shouldn't be shared with superclasses: + _unshared_attributes = ['table'] + + def __classinit__(cls, new_attrs): + for attr in cls._unshared_attributes: + if not new_attrs.has_key(attr): + setattr(cls, attr, None) + + def __init__(self, instance): + self.instance = instance + + def setClass(cls, soClass): + cls.soClass = soClass + setClass = classmethod(setClass) + + def finishClass(cls): + if not cls.style: + if cls.soClass._connection and cls.soClass._connection.style: + cls.style = cls.soClass._connection.style + else: + cls.style = styles.defaultStyle + if cls.table is None: + cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__) + if cls.idName is None: + cls.idName = cls.style.idForTable(cls.table) + finishClass = classmethod(finishClass) + +class _sqlmeta_attr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, type=None): + return getattr((type or obj).sqlmeta, self.name) + + +# @@: This should become a public interface or documented or +# something. Turning it on gives earlier warning about things +# that will be deprecated (having this off we won't flood people +# with warnings right away). +strict_warnings = False + +def deprecated(message): + if strict_warnings: + warnings.warn(message, DeprecationWarning, stacklevel=1) + class SelectResults(object): IterationClass = dbconnection.Iteration @@ -360,10 +205,6 @@ self.clause = clause tablesDict = sqlbuilder.tablesUsedDict(self.clause) tablesDict[sourceClass._table] = 1 - orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): - tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) - if clauseTables: for table in clauseTables: tablesDict[table] = 1 @@ -487,12 +328,12 @@ Return the accumulate result """ conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self,expression) + return conn.accumulateSelect(self, expression) def count(self): """ Counting elements of current select results """ assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" - + count = self.accumulate('COUNT(*)') if self.ops.get('start'): count -= self.ops['start'] @@ -511,7 +352,6 @@ expression = sqlbuilder.func.SUM(attribute) return self.accumulate(expression) - # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -520,7 +360,7 @@ # MetaSQLObject. class SQLObject(object): - __metaclass__ = MetaSQLObject + __metaclass__ = declarative.DeclarativeMeta # When an object is being created, it has an instance # variable _SO_creating, which is true. This way all the @@ -542,14 +382,6 @@ # it's set (default 1). _cacheValues = True - #DSM: The _inheritable attribute controls wheter the class can by - #DSM: inherited 'logically' with a foreignKey and back reference. - _inheritable = False # Does this class is inheritable - _parentClass = None # A reference to the parent class - _parent = None # A reference to the parent instance - _childClasses = {} # Reference to child classes - childName = None # Children name (to be able to get a subclass) - # The _defaultOrder is used by SelectResults _defaultOrder = None @@ -578,13 +410,233 @@ # aren't using integer IDs _idType = int + sqlmeta = sqlmeta + + #DSM: The _inheritable attribute controls wheter the class can by + #DSM: inherited 'logically' with a foreignKey and back reference. + _inheritable = False # Does this class is inheritable + _parentClass = None # A reference to the parent class + _parent = None # A reference to the parent instance + _childClasses = {} # Reference to child classes + childName = None # Children name (to be able to get a subclass) + # The law of Demeter: the class should not call another classes by name SelectResultsClass = SelectResults + def __classinit__(cls, new_attrs): + + # This is true if we're initializing the SQLObject class, + # instead of a subclass: + is_base = cls.__bases__ == (object,) + + if (not new_attrs.has_key('sqlmeta') + and not is_base): + # We have to create our own subclass, usually. + # type(className, bases_tuple, attr_dict) creates a new + # subclass: + #cls.sqlmeta = cls.sqlmeta.clone() + cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) + cls.sqlmeta.setClass(cls) + cls.sqlmeta.finishClass() + + + implicitColumns = [] + implicitJoins = [] + implicitIndexes = [] + for attr, value in new_attrs.items(): + if isinstance(value, col.Col): + value.name = attr + implicitColumns.append(value) + delattr(cls, attr) + continue + if isinstance(value, joins.Join): + value.joinMethodName = attr + implicitJoins.append(value) + delattr(cls, attr) + continue + if isinstance(value, index.DatabaseIndex): + value.setName(attr) + implicitIndexes.append(value) + delattr(cls, attr) + continue + + if (new_attrs.has_key('_table') and not is_base): + deprecated("'_table' is deprecated; please set the 'table' " + "attribute in sqlmeta instead") + cls.sqlmeta.table = cls._table + del cls._table + + if new_attrs.has_key('_connection'): + connection = new_attrs['_connection'] + del cls._connection + assert not new_attrs.has_key('connection') + elif new_attrs.has_key('connection'): + connection = new_attrs['connection'] + del cls.connection + else: + connection = None + + cls._SO_finishedClassCreation = False + + # We fix up the columns here -- replacing any strings with + # simply-contructed Col objects, and searching the class + # variables for instances of Col objects (which get put into + # the _columns instance variable and deleted). + columns = [] + for column in new_attrs.get('_columns', []): + if isinstance(column, str): + column = col.Col(column) + columns.append(column) + if columns: + cls._columns = columns + + # We append to _columns, but we don't want to change the + # superclass's _columns list, so we make a copy if necessary + if not new_attrs.has_key('_columns'): + cls._columns = cls._columns[:] + + #DSM: Need to know very soon if the class is a child of an + #DSM: inheritable class. If so, we keep a link to our parent class. + cls._childClasses = {} + for _cls in cls.__bases__: + if hasattr(_cls, '_inheritable') and _cls._inheritable: + cls._parentClass = _cls + cls._parent = None + _cls._childClasses[cls.__name__] = cls + + #DSM: If this class is a child of a parent class, we need to do some + #DSM: attribute check and a a foreign key to the parent. + if cls._parentClass: + #DSM: First, look for invalid column name: + #DSM: reserved ones or same as a parent + parentCols = [column.name for column in cls._columns] + for column in implicitColumns: + cname = column.name + if cname in ['childName']: + raise AttributeError, \ + "The column name '%s' is reserved" % cname + if cname in parentCols: + raise AttributeError, "The column '%s' is already " \ + "defined in an inheritable parent" % cname + #DSM: Remove columns if inherited from an inheritable class + #DSM: as we don't want them. All we want is a foreign key + #DSM: that will link to our parent + cls._columns = [] + #DSM: If this is inheritable, add some default columns + # to be able to link to children + if hasattr(cls, '_inheritable') and cls._inheritable: + cls._columns.append( + col.StringCol(name='childName',default=None)) + + cls._columns.extend(implicitColumns) + if not new_attrs.has_key('_joins'): + cls._joins = cls._joins[:] + cls._joins.extend(implicitJoins) + if not new_attrs.has_key('_indexes'): + cls._indexes = cls._indexes[:] + cls._indexes.extend(implicitIndexes) + + ###################################################### + # Set some attributes to their defaults, if necessary. + # First we get the connection: + if not connection and not getattr(cls, '_connection', None): + mod = sys.modules[cls.__module__] + # See if there's a __connection__ global in + # the module, use it if there is. + if hasattr(mod, '__connection__'): + connection = mod.__connection__ + + if connection or not hasattr(cls, '_connection'): + cls.setConnection(connection) + + if (new_attrs.has_key('_style') and not is_base): + deprecated("'_style' is deprecated; please set the 'style' " + "attribute in sqlmeta instead") + cls.sqlmeta.style = cls._style + del cls._style + + # plainSetters are columns that haven't been overridden by the + # user, so we can contact the database directly to set them. + # Note that these can't set these in the SQLObject class + # itself, because they specific to this subclass of SQLObject, + # and cannot be shared among classes. + cls._SO_plainSetters = {} + cls._SO_plainGetters = {} + cls._SO_plainForeignSetters = {} + cls._SO_plainForeignGetters = {} + cls._SO_plainJoinGetters = {} + cls._SO_plainJoinAdders = {} + cls._SO_plainJoinRemovers = {} + + # This is a dictionary of columnName: columnObject + cls._SO_columnDict = {} + cls._SO_columns = [] + + if (new_attrs.has_key('_idName') and not is_base): + deprecated("'_idName' is deprecated; please set the 'idName' " + "attribute in sqlmeta instead") + cls.sqlmeta.idName = cls._idName + del cls._idName + + #DSM: If we are a child, get the q magic from the parent + currentClass = cls + while currentClass._parentClass: + currentClass = currentClass._parentClass + for column in currentClass._columns: + if type(column) == col.ForeignKey: continue + setattr(cls.q, column.name, + getattr(currentClass.q, column.name)) + + # We have to check if there are columns in the inherited + # _columns where the attribute has been set to None in this + # class. If so, then we need to remove that column from + # _columns. + for column in cls._columns[:]: + if (new_attrs.has_key(column.name) + and new_attrs[column.name] is None): + cls._columns.remove(column) + + for column in cls._columns: + cls.addColumn(column) + if cls._fromDatabase: + cls.addColumnsFromDatabase() + + ######################################## + # Now we do the joins: + + # We keep track of the different joins by index, + # putting them in this list. + cls._SO_joinList = [] + cls._SO_joinDict = {} + + for join in cls._joins: + cls.addJoin(join) + + # We don't setup the properties until we're finished with the + # batch adding of all the columns... + cls._SO_finishedClassCreation = True + makeProperties(cls) + + cls._SO_indexList = [] + for idx in cls._indexes: + cls.addIndex(idx) + + # We use the magic "q" attribute for accessing lazy + # SQL where-clause generation. See the sql module for + # more. + if not is_base: + cls.q = sqlbuilder.SQLObjectTable(cls) + + classregistry.registry(cls._registry).addClass(cls) + + _style = _sqlmeta_attr('style') + _table = _sqlmeta_attr('table') + _idName = _sqlmeta_attr('idName') + def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name - + id = cls._idType(id) if connection is None: @@ -662,7 +714,7 @@ setattr(cls, '_SO_toPython_%s' % name, column.toPython) setattr(cls, rawSetterName(name), setter) # Then do the aliasing - if not hasattr(cls, setterName(name)) or name == 'childName': + if not hasattr(cls, setterName(name)) or (name == 'childName'): setattr(cls, setterName(name), setter) # We keep track of setters that haven't been # overridden, because we can combine these @@ -775,7 +827,7 @@ conn.delColumn(cls._table, column) if cls._SO_finishedClassCreation: - delattr(cls, name) + unmakeProperties(cls) delColumn = classmethod(delColumn) @@ -853,7 +905,7 @@ delattr(cls, 'add' + join.addRemovePrefix) if cls._SO_finishedClassCreation: - delattr(cls, meth) + unmakeProperties(cls) delJoin = classmethod(delJoin) @@ -863,6 +915,10 @@ # created, unlike __init__ which would be called # anytime the object was returned from cache. self.id = id + # We shadow the sqlmeta class with an instance of sqlmeta + # that points to us (our sqlmeta buddy object; where the + # sqlmeta class is our class's buddy class) + self.sqlmeta = self.__class__.sqlmeta(self) self._SO_writeLock = threading.Lock() # If no connection was given, we'll inherit the class @@ -1119,6 +1175,7 @@ self._create(id, **kw) def _create(self, id, **kw): + self._SO_creating = True self._SO_createValues = {} self._SO_validatorState = SQLObjectState(self) @@ -1393,7 +1450,6 @@ cls._connection = value setConnection = classmethod(setConnection) - def capitalize(name): return name[0].capitalize() + name[1:] Modified: home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-09 13:15:16 UTC (rev 590) @@ -329,6 +329,9 @@ def __init__(self, soClass): self.soClass = soClass + assert soClass._table, ( + "Bad table name in class %r: %r" + % (soClass, soClass._table)) Table.__init__(self, soClass._table) def __getattr__(self, attr): Modified: home/phd/SQLObject/inheritance/sqlobject/sqlite/sqliteconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sqlite/sqliteconnection.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/sqlite/sqliteconnection.py 2005-02-09 13:15:16 UTC (rev 590) @@ -23,11 +23,18 @@ def connectionFromURI(cls, uri): user, password, host, path, args = cls._parseURI(uri) - assert host is None, "SQLite can only be used locally (with a URI like sqlite:///file or sql:/file, not %r)" % uri - assert user is None and password is None, "You may not provide usernames or passwords for SQLite databases" + assert host is None, ( + "SQLite can only be used locally (with a URI like " + "sqlite:///file or sqlite:/file, not %r)" % uri) + assert user is None and password is None, ( + "You may not provide usernames or passwords for SQLite " + "databases") return cls(filename=path, **args) connectionFromURI = classmethod(connectionFromURI) + def uri(self): + return 'sqlite:///%s' % self.filename + def _setAutoCommit(self, conn, auto): conn.autocommit = auto Modified: home/phd/SQLObject/inheritance/sqlobject/styles.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/styles.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/styles.py 2005-02-09 13:15:16 UTC (rev 590) @@ -58,6 +58,9 @@ # keys, you can't really change this style. return attr + "ID" + def tableReference(self, table): + return table + "_id" + class MixedCaseUnderscoreStyle(Style): """ Modified: home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py 2005-02-09 13:15:16 UTC (rev 590) @@ -43,17 +43,26 @@ } -def setupClass(soClass): +def setupClass(soClasses): """ - Makes sure the class has a corresponding and correct table. - This won't recreate the table if it already exists. It will - check that the table is properly defined (in case you change - your table definition). + Makes sure the classes have a corresponding and correct table. + This won't recreate the table if it already exists. It will check + that the table is properly defined (in case you change your table + definition). + + You can provide a single class or a list of classes; if a list + then classes will be created in the order you provide, and + destroyed in the opposite order. So if class A depends on class + B, then do setupClass([B, A]) and B won't be destroyed or cleared + until after A is destroyed or cleared. """ + if not isinstance(soClasses, (list, tuple)): + soClasses = [soClasses] connection = getConnection() - soClass._connection = connection - installOrClear(soClass) - return soClass + for soClass in soClasses: + soClass._connection = connection + installOrClear(soClasses) + return soClasses installedDBFilename = os.path.join(os.getcwd(), 'dbs_data.tmp') @@ -82,27 +91,33 @@ createSQL = sqlobject.StringCol(notNull=True) connectionURI = sqlobject.StringCol(notNull=True) - def installOrClear(cls, soClass): + def installOrClear(cls, soClasses): cls.setup() - table = soClass._table - if not soClass._connection.tableExists(table): - cls.install(soClass) - items = list(cls.selectBy( - tableName=table, - connectionURI=soClass._connection.uri())) - if items: - instance = items[0] - sql = instance.createSQL - else: - sql = None - newSQL = soClass.createTableSQL() - if sql != newSQL: - if sql is not None: - instance.destroySelf() - cls.drop(soClass) - cls.install(soClass) - else: - cls.clear(soClass) + reversed = list(soClasses)[:] + reversed.reverse() + for soClass in reversed: + table = soClass._table + if not soClass._connection.tableExists(table): + continue + items = list(cls.selectBy( + tableName=table, + connectionURI=soClass._connection.uri())) + if items: + instance = items[0] + sql = instance.createSQL + else: + sql = None + newSQL = soClass.createTableSQL() + if sql != newSQL: + if sql is not None: + instance.destroySelf() + cls.drop(soClass) + else: + cls.clear(soClass) + for soClass in soClasses: + table = soClass._table + if not soClass._connection.tableExists(table): + cls.install(soClass) installOrClear = classmethod(installOrClear) def install(cls, soClass): Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_auto.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_auto.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_auto.py 2005-02-09 13:15:16 UTC (rev 590) @@ -73,6 +73,7 @@ age INT DEFAULT NULL, created DATETIME NOT NULL, happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT, wannahavefun TINYINT DEFAULT 0 NOT NULL ) """ @@ -85,6 +86,7 @@ age INT DEFAULT 0, created VARCHAR(40) NOT NULL, happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT, wannahavefun BOOL DEFAULT FALSE NOT NULL ) """ @@ -96,7 +98,8 @@ last_name VARCHAR(200) NOT NULL, age INT DEFAULT 0, created VARCHAR(40) NOT NULL, - happy char(1) DEFAULT 'Y' NOT NULL + happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT ) """ @@ -125,12 +128,16 @@ lastName='doe', age=10, created=now(), - wannahavefun=False) + wannahavefun=False, + long_field='x'*1000) jane = AutoTest(firstName='jane', lastName='doe', happy='N', created=now(), - wannahavefun=True) + wannahavefun=True, + long_field='x'*1000) assert not john.wannahavefun assert jane.wannahavefun + assert john.long_field == 'x'*1000 + assert jane.long_field == 'x'*1000 del classregistry.registry(AutoTest._registry).classes['AutoTest'] Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py 2005-02-09 13:15:16 UTC (rev 590) @@ -77,8 +77,7 @@ me = StringCol(length=10) def test_foreignKey(): - setupClass(TestSO3) - setupClass(TestSO4) + setupClass([TestSO4, TestSO3]) tc3 = TestSO3(name='a') assert tc3.other is None assert tc3.other2 is None Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_blob.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_blob.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_blob.py 2005-02-09 13:15:16 UTC (rev 590) @@ -19,4 +19,7 @@ ImageData._connection.cache.clear() prof2 = ImageData.get(iid) + # @@: This seems to fail in SQLite, which trims off the + # first and last character (\x00 and \xff). We should write + # a test for the upstream driver, as the error might be there. assert prof2.image == data Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_boundattributes.py (from rev 585, trunk/SQLObject/sqlobject/tests/test_boundattributes.py) Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_declarative.py (from rev 585, trunk/SQLObject/sqlobject/tests/test_declarative.py) Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_distinct.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_distinct.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_distinct.py 2005-02-09 13:15:16 UTC (rev 590) @@ -18,8 +18,7 @@ return result def test_distinct(): - setupClass(Distinct1) - setupClass(Distinct2) + setupClass([Distinct1, Distinct2]) obs = [Distinct1(n=i) for i in range(3)] Distinct2(other=obs[0]) Distinct2(other=obs[0]) Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_joins.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_joins.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_joins.py 2005-02-09 13:15:16 UTC (rev 590) @@ -76,8 +76,7 @@ class TestJoin2: def setup_method(self, meth): - setupClass(PersonJoiner2) - setupClass(AddressJoiner2) + setupClass([PersonJoiner2, AddressJoiner2]) p1 = PersonJoiner2(name='bob') p2 = PersonJoiner2(name='sally') for z in ['11111', '22222', '33333']: Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py 2005-02-09 08:37:37 UTC (rev 589) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py 2005-02-09 13:15:16 UTC (rev 590) @@ -19,8 +19,7 @@ _style = AnotherStyle() def test_style(): - setupClass(SOStyleTest2) - setupClass(SOStyleTest1) + setupClass([SOStyleTest2, SOStyleTest1]) st1 = SOStyleTest1(a='something', st2=None) st2 = SOStyleTest2(b='whatever') st1.st2 = st2 |
From: <sub...@co...> - 2005-02-09 08:37:40
|
Author: ianb Date: 2005-02-09 08:37:37 +0000 (Wed, 09 Feb 2005) New Revision: 589 Modified: trunk/SQLObject/sqlobject/main.py Log: Started putting information into the sqlmeta class: * the table name * the ID name * the style Maintaining backward compatibility. Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-09 08:35:21 UTC (rev 588) +++ trunk/SQLObject/sqlobject/main.py 2005-02-09 08:37:37 UTC (rev 589) @@ -143,9 +143,58 @@ alongside every SQLObject instance. """ + table = None + idName = None + style = None + + __metaclass__ = declarative.DeclarativeMeta + + # These attributes shouldn't be shared with superclasses: + _unshared_attributes = ['table'] + + def __classinit__(cls, new_attrs): + for attr in cls._unshared_attributes: + if not new_attrs.has_key(attr): + setattr(cls, attr, None) + def __init__(self, instance): self.instance = instance + def setClass(cls, soClass): + cls.soClass = soClass + setClass = classmethod(setClass) + + def finishClass(cls): + if not cls.style: + if cls.soClass._connection and cls.soClass._connection.style: + cls.style = cls.soClass._connection.style + else: + cls.style = styles.defaultStyle + if cls.table is None: + cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__) + if cls.idName is None: + cls.idName = cls.style.idForTable(cls.table) + finishClass = classmethod(finishClass) + +class _sqlmeta_attr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, type=None): + return getattr((type or obj).sqlmeta, self.name) + + +# @@: This should become a public interface or documented or +# something. Turning it on gives earlier warning about things +# that will be deprecated (having this off we won't flood people +# with warnings right away). +strict_warnings = False + +def deprecated(message): + if strict_warnings: + warnings.warn(message, DeprecationWarning, stacklevel=1) + # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -208,13 +257,20 @@ def __classinit__(cls, new_attrs): + # This is true if we're initializing the SQLObject class, + # instead of a subclass: + is_base = cls.__bases__ == (object,) + if (not new_attrs.has_key('sqlmeta') - and not cls.__bases__ == (object,)): + and not is_base): # We have to create our own subclass, usually. # type(className, bases_tuple, attr_dict) creates a new # subclass: + #cls.sqlmeta = cls.sqlmeta.clone() cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) - cls.sqlmeta.soClass = cls + cls.sqlmeta.setClass(cls) + cls.sqlmeta.finishClass() + implicitColumns = [] implicitJoins = [] @@ -236,10 +292,11 @@ delattr(cls, attr) continue - # We *don't* want to inherit _table, so we make sure it - # is defined in this class (not a superclass) - if not new_attrs.has_key('_table'): - cls._table = None + if (new_attrs.has_key('_table') and not is_base): + deprecated("'_table' is deprecated; please set the 'table' " + "attribute in sqlmeta instead") + cls.sqlmeta.table = cls._table + del cls._table if new_attrs.has_key('_connection'): connection = new_attrs['_connection'] @@ -290,13 +347,11 @@ if connection or not hasattr(cls, '_connection'): cls.setConnection(connection) - # The style object tells how to map between Python - # identifiers and Database identifiers: - if not cls._style: - if cls._connection and cls._connection.style: - cls._style = cls._connection.style - else: - cls._style = styles.defaultStyle + if (new_attrs.has_key('_style') and not is_base): + deprecated("'_style' is deprecated; please set the 'style' " + "attribute in sqlmeta instead") + cls.sqlmeta.style = cls._style + del cls._style # plainSetters are columns that haven't been overridden by the # user, so we can contact the database directly to set them. @@ -315,19 +370,12 @@ cls._SO_columnDict = {} cls._SO_columns = [] - # If _table isn't given, use style default - if not cls._table: - cls._table = cls._style.pythonClassToDBTable(cls.__name__) + if (new_attrs.has_key('_idName') and not is_base): + deprecated("'_idName' is deprecated; please set the 'idName' " + "attribute in sqlmeta instead") + cls.sqlmeta.idName = cls._idName + del cls._idName - # If _idName isn't given, use style default - if not new_attrs.has_key('_idName'): - cls._idName = cls._style.idForTable(cls._table) - - # We use the magic "q" attribute for accessing lazy - # SQL where-clause generation. See the sql module for - # more. - cls.q = sqlbuilder.SQLObjectTable(cls) - # We have to check if there are columns in the inherited # _columns where the attribute has been set to None in this # class. If so, then we need to remove that column from @@ -362,8 +410,18 @@ for idx in cls._indexes: cls.addIndex(idx) + # We use the magic "q" attribute for accessing lazy + # SQL where-clause generation. See the sql module for + # more. + if not is_base: + cls.q = sqlbuilder.SQLObjectTable(cls) + classregistry.registry(cls._registry).addClass(cls) + _style = _sqlmeta_attr('style') + _table = _sqlmeta_attr('table') + _idName = _sqlmeta_attr('idName') + def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name @@ -1329,7 +1387,7 @@ Return the accumulate result """ conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self,expression) + return conn.accumulateSelect(self, expression) def count(self): """ Counting elements of current select results """ |
From: <sub...@co...> - 2005-02-09 08:35:25
|
Author: ianb Date: 2005-02-09 08:35:21 +0000 (Wed, 09 Feb 2005) New Revision: 588 Modified: trunk/SQLObject/sqlobject/tests/dbtest.py Log: typo/bug Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-09 08:35:01 UTC (rev 587) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-09 08:35:21 UTC (rev 588) @@ -115,6 +115,7 @@ else: cls.clear(soClass) for soClass in soClasses: + table = soClass._table if not soClass._connection.tableExists(table): cls.install(soClass) installOrClear = classmethod(installOrClear) |
From: <sub...@co...> - 2005-02-09 08:35:07
|
Author: ianb Date: 2005-02-09 08:35:01 +0000 (Wed, 09 Feb 2005) New Revision: 587 Modified: trunk/SQLObject/sqlobject/sqlbuilder.py Log: Avoid tables with empty names Modified: trunk/SQLObject/sqlobject/sqlbuilder.py =================================================================== --- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-09 08:34:41 UTC (rev 586) +++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-09 08:35:01 UTC (rev 587) @@ -329,6 +329,9 @@ def __init__(self, soClass): self.soClass = soClass + assert soClass._table, ( + "Bad table name in class %r: %r" + % (soClass, soClass._table)) Table.__init__(self, soClass._table) def __getattr__(self, attr): |
From: <sub...@co...> - 2005-02-09 08:34:43
|
Author: ianb Date: 2005-02-09 08:34:41 +0000 (Wed, 09 Feb 2005) New Revision: 586 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: Don't print out as much debugging about the pool acquisition/release Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-09 08:27:56 UTC (rev 585) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-09 08:34:41 UTC (rev 586) @@ -181,6 +181,8 @@ conn.close() def printDebug(self, conn, s, name, type='query'): + if name == 'Pool' and self.debug != 'Pool': + return if type == 'query': sep = ': ' else: |
From: <sub...@co...> - 2005-02-09 08:28:00
|
Author: phd Date: 2005-02-09 08:27:56 +0000 (Wed, 09 Feb 2005) New Revision: 585 Modified: trunk/SQLObject/sqlobject/col.py Log: Do not convert None to Binary. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-02-09 05:52:00 UTC (rev 584) +++ trunk/SQLObject/sqlobject/col.py 2005-02-09 08:27:56 UTC (rev 585) @@ -890,6 +890,8 @@ """ def fromPython(self, value, state): + if value is None: + return None return state.soObject._connection.createBinary(value) class SOBLOBCol(SOStringCol): |
From: <sub...@co...> - 2005-02-09 05:52:04
|
Author: ianb Date: 2005-02-09 05:52:00 +0000 (Wed, 09 Feb 2005) New Revision: 584 Modified: trunk/SQLObject/sqlobject/main.py Log: Added a sqlmeta buddy class/object, which will hold data in the very near future. Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-09 05:40:19 UTC (rev 583) +++ trunk/SQLObject/sqlobject/main.py 2005-02-09 05:52:00 UTC (rev 584) @@ -134,6 +134,18 @@ """ pass +class sqlmeta(object): + + """ + This object is the object we use to keep track of all sorts of + information. Subclasses are made for each SQLObject subclass + (dynamically if necessary), and instances are created to go + alongside every SQLObject instance. + """ + + def __init__(self, instance): + self.instance = instance + # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -192,7 +204,18 @@ # aren't using integer IDs _idType = int + sqlmeta = sqlmeta + def __classinit__(cls, new_attrs): + + if (not new_attrs.has_key('sqlmeta') + and not cls.__bases__ == (object,)): + # We have to create our own subclass, usually. + # type(className, bases_tuple, attr_dict) creates a new + # subclass: + cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) + cls.sqlmeta.soClass = cls + implicitColumns = [] implicitJoins = [] implicitIndexes = [] @@ -623,6 +646,10 @@ # created, unlike __init__ which would be called # anytime the object was returned from cache. self.id = id + # We shadow the sqlmeta class with an instance of sqlmeta + # that points to us (our sqlmeta buddy object; where the + # sqlmeta class is our class's buddy class) + self.sqlmeta = self.__class__.sqlmeta(self) self._SO_writeLock = threading.Lock() # If no connection was given, we'll inherit the class |
From: <sub...@co...> - 2005-02-09 05:40:25
|
Author: ianb Date: 2005-02-09 05:40:19 +0000 (Wed, 09 Feb 2005) New Revision: 583 Modified: trunk/SQLObject/sqlobject/main.py Log: Removed SQLObjectMeta and use DeclarativeMeta, with all the logic in __classinit__. __classinit__ looks almost exactly like DeclarativeMeta.__new__, except newClass renamed to cls and d to new_attrs Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-09 05:27:03 UTC (rev 582) +++ trunk/SQLObject/sqlobject/main.py 2005-02-09 05:40:19 UTC (rev 583) @@ -30,6 +30,7 @@ import joins import index import classregistry +import declarative import sys if sys.version_info[:3] < (2, 2, 0): @@ -42,167 +43,6 @@ True, False = 1==1, 0==1 -# This is the metaclass. It essentially takes a dictionary -# of all the attributes (and thus methods) defined in the -# class definition. It futzes with them, and spits out the -# new class definition. -class MetaSQLObject(type): - - def __new__(cls, className, bases, d): - - # We fix up the columns here -- replacing any strings with - # simply-contructed Col objects, and searching the class - # variables for instances of Col objects (which get put into - # the _columns instance variable and deleted). - columns = [] - for column in d.get('_columns', []): - if isinstance(column, str): - column = col.Col(column) - columns.append(column) - if columns: - d['_columns'] = columns - - implicitColumns = [] - implicitJoins = [] - implicitIndexes = [] - for attr, value in d.items(): - if isinstance(value, col.Col): - value.name = attr - implicitColumns.append(value) - del d[attr] - continue - if isinstance(value, joins.Join): - value.joinMethodName = attr - implicitJoins.append(value) - del d[attr] - continue - if isinstance(value, index.DatabaseIndex): - value.setName(attr) - implicitIndexes.append(value) - del d[attr] - continue - - # We *don't* want to inherit _table, so we make sure it - # is defined in this class (not a superclass) - if not d.has_key('_table'): - d['_table'] = None - - - if d.has_key('_connection'): - connection = d['_connection'] - del d['_connection'] - assert not d.has_key('connection') - elif d.has_key('connection'): - connection = d['connection'] - del d['connection'] - else: - connection = None - - # We actually create the class. - newClass = type.__new__(cls, className, bases, d) - newClass._SO_finishedClassCreation = False - - # We append to _columns, but we don't want to change the - # superclass's _columns list, so we make a copy if necessary - if not d.has_key('_columns'): - newClass._columns = newClass._columns[:] - newClass._columns.extend(implicitColumns) - if not d.has_key('_joins'): - newClass._joins = newClass._joins[:] - newClass._joins.extend(implicitJoins) - if not d.has_key('_indexes'): - newClass._indexes = newClass._indexes[:] - newClass._indexes.extend(implicitIndexes) - - ###################################################### - # Set some attributes to their defaults, if necessary. - # First we get the connection: - if not connection and not getattr(newClass, '_connection', None): - mod = sys.modules[newClass.__module__] - # See if there's a __connection__ global in - # the module, use it if there is. - if hasattr(mod, '__connection__'): - connection = mod.__connection__ - - if connection or not hasattr(newClass, '_connection'): - newClass.setConnection(connection) - - # The style object tells how to map between Python - # identifiers and Database identifiers: - if not newClass._style: - if newClass._connection and newClass._connection.style: - newClass._style = newClass._connection.style - else: - newClass._style = styles.defaultStyle - - # plainSetters are columns that haven't been overridden by the - # user, so we can contact the database directly to set them. - # Note that these can't set these in the SQLObject class - # itself, because they specific to this subclass of SQLObject, - # and cannot be shared among classes. - newClass._SO_plainSetters = {} - newClass._SO_plainGetters = {} - newClass._SO_plainForeignSetters = {} - newClass._SO_plainForeignGetters = {} - newClass._SO_plainJoinGetters = {} - newClass._SO_plainJoinAdders = {} - newClass._SO_plainJoinRemovers = {} - - # This is a dictionary of columnName: columnObject - newClass._SO_columnDict = {} - newClass._SO_columns = [] - - # If _table isn't given, use style default - if not newClass._table: - newClass._table = newClass._style.pythonClassToDBTable(className) - - # If _idName isn't given, use style default - if not d.has_key('_idName'): - newClass._idName = newClass._style.idForTable(newClass._table) - - # We use the magic "q" attribute for accessing lazy - # SQL where-clause generation. See the sql module for - # more. - newClass.q = sqlbuilder.SQLObjectTable(newClass) - - # We have to check if there are columns in the inherited - # _columns where the attribute has been set to None in this - # class. If so, then we need to remove that column from - # _columns. - for column in newClass._columns[:]: - if d.has_key(column.name) and d[column.name] is None: - newClass._columns.remove(column) - - for column in newClass._columns: - newClass.addColumn(column) - if newClass._fromDatabase: - newClass.addColumnsFromDatabase() - - ######################################## - # Now we do the joins: - - # We keep track of the different joins by index, - # putting them in this list. - newClass._SO_joinList = [] - newClass._SO_joinDict = {} - - for join in newClass._joins: - newClass.addJoin(join) - - # We don't setup the properties until we're finished with the - # batch adding of all the columns... - newClass._SO_finishedClassCreation = True - makeProperties(newClass) - - newClass._SO_indexList = [] - for idx in newClass._indexes: - newClass.addIndex(idx) - - classregistry.registry(newClass._registry).addClass(newClass) - - # And return the class - return newClass - def makeProperties(obj): """ This function takes a dictionary of methods and finds @@ -302,7 +142,7 @@ # MetaSQLObject. class SQLObject(object): - __metaclass__ = MetaSQLObject + __metaclass__ = declarative.DeclarativeMeta # When an object is being created, it has an instance # variable _SO_creating, which is true. This way all the @@ -352,6 +192,155 @@ # aren't using integer IDs _idType = int + def __classinit__(cls, new_attrs): + implicitColumns = [] + implicitJoins = [] + implicitIndexes = [] + for attr, value in new_attrs.items(): + if isinstance(value, col.Col): + value.name = attr + implicitColumns.append(value) + delattr(cls, attr) + continue + if isinstance(value, joins.Join): + value.joinMethodName = attr + implicitJoins.append(value) + delattr(cls, attr) + continue + if isinstance(value, index.DatabaseIndex): + value.setName(attr) + implicitIndexes.append(value) + delattr(cls, attr) + continue + + # We *don't* want to inherit _table, so we make sure it + # is defined in this class (not a superclass) + if not new_attrs.has_key('_table'): + cls._table = None + + if new_attrs.has_key('_connection'): + connection = new_attrs['_connection'] + del cls._connection + assert not new_attrs.has_key('connection') + elif new_attrs.has_key('connection'): + connection = new_attrs['connection'] + del cls.connection + else: + connection = None + + cls._SO_finishedClassCreation = False + + # We fix up the columns here -- replacing any strings with + # simply-contructed Col objects, and searching the class + # variables for instances of Col objects (which get put into + # the _columns instance variable and deleted). + columns = [] + for column in new_attrs.get('_columns', []): + if isinstance(column, str): + column = col.Col(column) + columns.append(column) + if columns: + cls._columns = columns + + # We append to _columns, but we don't want to change the + # superclass's _columns list, so we make a copy if necessary + if not new_attrs.has_key('_columns'): + cls._columns = cls._columns[:] + cls._columns.extend(implicitColumns) + if not new_attrs.has_key('_joins'): + cls._joins = cls._joins[:] + cls._joins.extend(implicitJoins) + if not new_attrs.has_key('_indexes'): + cls._indexes = cls._indexes[:] + cls._indexes.extend(implicitIndexes) + + ###################################################### + # Set some attributes to their defaults, if necessary. + # First we get the connection: + if not connection and not getattr(cls, '_connection', None): + mod = sys.modules[cls.__module__] + # See if there's a __connection__ global in + # the module, use it if there is. + if hasattr(mod, '__connection__'): + connection = mod.__connection__ + + if connection or not hasattr(cls, '_connection'): + cls.setConnection(connection) + + # The style object tells how to map between Python + # identifiers and Database identifiers: + if not cls._style: + if cls._connection and cls._connection.style: + cls._style = cls._connection.style + else: + cls._style = styles.defaultStyle + + # plainSetters are columns that haven't been overridden by the + # user, so we can contact the database directly to set them. + # Note that these can't set these in the SQLObject class + # itself, because they specific to this subclass of SQLObject, + # and cannot be shared among classes. + cls._SO_plainSetters = {} + cls._SO_plainGetters = {} + cls._SO_plainForeignSetters = {} + cls._SO_plainForeignGetters = {} + cls._SO_plainJoinGetters = {} + cls._SO_plainJoinAdders = {} + cls._SO_plainJoinRemovers = {} + + # This is a dictionary of columnName: columnObject + cls._SO_columnDict = {} + cls._SO_columns = [] + + # If _table isn't given, use style default + if not cls._table: + cls._table = cls._style.pythonClassToDBTable(cls.__name__) + + # If _idName isn't given, use style default + if not new_attrs.has_key('_idName'): + cls._idName = cls._style.idForTable(cls._table) + + # We use the magic "q" attribute for accessing lazy + # SQL where-clause generation. See the sql module for + # more. + cls.q = sqlbuilder.SQLObjectTable(cls) + + # We have to check if there are columns in the inherited + # _columns where the attribute has been set to None in this + # class. If so, then we need to remove that column from + # _columns. + for column in cls._columns[:]: + if (new_attrs.has_key(column.name) + and new_attrs[column.name] is None): + cls._columns.remove(column) + + for column in cls._columns: + cls.addColumn(column) + if cls._fromDatabase: + cls.addColumnsFromDatabase() + + ######################################## + # Now we do the joins: + + # We keep track of the different joins by index, + # putting them in this list. + cls._SO_joinList = [] + cls._SO_joinDict = {} + + for join in cls._joins: + cls.addJoin(join) + + # We don't setup the properties until we're finished with the + # batch adding of all the columns... + cls._SO_finishedClassCreation = True + makeProperties(cls) + + cls._SO_indexList = [] + for idx in cls._indexes: + cls.addIndex(idx) + + classregistry.registry(cls._registry).addClass(cls) + def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name |
From: <sub...@co...> - 2005-02-09 05:27:07
|
Author: ianb Date: 2005-02-09 05:27:03 +0000 (Wed, 09 Feb 2005) New Revision: 582 Modified: trunk/SQLObject/sqlobject/declarative.py Log: Move the counting part into the class and out of the metaclass Modified: trunk/SQLObject/sqlobject/declarative.py =================================================================== --- trunk/SQLObject/sqlobject/declarative.py 2005-02-09 05:18:31 UTC (rev 581) +++ trunk/SQLObject/sqlobject/declarative.py 2005-02-09 05:27:03 UTC (rev 582) @@ -91,7 +91,6 @@ cls = type.__new__(meta, class_name, bases, new_attrs) if new_attrs.has_key('__classinit__'): cls.__classinit__ = staticmethod(cls.__classinit__.im_func) - cls.declarative_count = counter.next() cls.__classinit__(cls, new_attrs) return cls @@ -104,6 +103,7 @@ __metaclass__ = DeclarativeMeta def __classinit__(cls, new_attrs): + cls.declarative_count = counter.next() for name in cls.__mutableattributes__: if not new_attrs.has_key(name): setattr(cls, copy.copy(getattr(cls, name))) |
From: <sub...@co...> - 2005-02-09 05:18:38
|
Author: ianb Date: 2005-02-09 05:18:31 +0000 (Wed, 09 Feb 2005) New Revision: 581 Modified: trunk/SQLObject/sqlobject/tests/test_distinct.py trunk/SQLObject/sqlobject/tests/test_joins.py trunk/SQLObject/sqlobject/tests/test_style.py Log: More fixes to the tests to create/destroy classes in the right order. Modified: trunk/SQLObject/sqlobject/tests/test_distinct.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-09 04:50:46 UTC (rev 580) +++ trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-09 05:18:31 UTC (rev 581) @@ -18,8 +18,7 @@ return result def test_distinct(): - setupClass(Distinct1) - setupClass(Distinct2) + setupClass([Distinct1, Distinct2]) obs = [Distinct1(n=i) for i in range(3)] Distinct2(other=obs[0]) Distinct2(other=obs[0]) Modified: trunk/SQLObject/sqlobject/tests/test_joins.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-09 04:50:46 UTC (rev 580) +++ trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-09 05:18:31 UTC (rev 581) @@ -76,8 +76,7 @@ class TestJoin2: def setup_method(self, meth): - setupClass(PersonJoiner2) - setupClass(AddressJoiner2) + setupClass([PersonJoiner2, AddressJoiner2]) p1 = PersonJoiner2(name='bob') p2 = PersonJoiner2(name='sally') for z in ['11111', '22222', '33333']: Modified: trunk/SQLObject/sqlobject/tests/test_style.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-09 04:50:46 UTC (rev 580) +++ trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-09 05:18:31 UTC (rev 581) @@ -19,8 +19,7 @@ _style = AnotherStyle() def test_style(): - setupClass(SOStyleTest2) - setupClass(SOStyleTest1) + setupClass([SOStyleTest2, SOStyleTest1]) st1 = SOStyleTest1(a='something', st2=None) st2 = SOStyleTest2(b='whatever') st1.st2 = st2 |
From: <sub...@co...> - 2005-02-09 04:50:52
|
Author: ianb Date: 2005-02-09 04:50:46 +0000 (Wed, 09 Feb 2005) New Revision: 580 Modified: trunk/SQLObject/sqlobject/tests/dbtest.py trunk/SQLObject/sqlobject/tests/test_basic.py Log: Allow for a set of classes that need to be created; they are destroyed in reverse order. This is needed with constraints where one table depends on another. Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-09 04:34:12 UTC (rev 579) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-09 04:50:46 UTC (rev 580) @@ -43,17 +43,26 @@ } -def setupClass(soClass): +def setupClass(soClasses): """ - Makes sure the class has a corresponding and correct table. - This won't recreate the table if it already exists. It will - check that the table is properly defined (in case you change - your table definition). + Makes sure the classes have a corresponding and correct table. + This won't recreate the table if it already exists. It will check + that the table is properly defined (in case you change your table + definition). + + You can provide a single class or a list of classes; if a list + then classes will be created in the order you provide, and + destroyed in the opposite order. So if class A depends on class + B, then do setupClass([B, A]) and B won't be destroyed or cleared + until after A is destroyed or cleared. """ + if not isinstance(soClasses, (list, tuple)): + soClasses = [soClasses] connection = getConnection() - soClass._connection = connection - installOrClear(soClass) - return soClass + for soClass in soClasses: + soClass._connection = connection + installOrClear(soClasses) + return soClasses installedDBFilename = os.path.join(os.getcwd(), 'dbs_data.tmp') @@ -82,27 +91,32 @@ createSQL = sqlobject.StringCol(notNull=True) connectionURI = sqlobject.StringCol(notNull=True) - def installOrClear(cls, soClass): + def installOrClear(cls, soClasses): cls.setup() - table = soClass._table - if not soClass._connection.tableExists(table): - cls.install(soClass) - items = list(cls.selectBy( - tableName=table, - connectionURI=soClass._connection.uri())) - if items: - instance = items[0] - sql = instance.createSQL - else: - sql = None - newSQL = soClass.createTableSQL() - if sql != newSQL: - if sql is not None: - instance.destroySelf() - cls.drop(soClass) - cls.install(soClass) - else: - cls.clear(soClass) + reversed = list(soClasses)[:] + reversed.reverse() + for soClass in reversed: + table = soClass._table + if not soClass._connection.tableExists(table): + continue + items = list(cls.selectBy( + tableName=table, + connectionURI=soClass._connection.uri())) + if items: + instance = items[0] + sql = instance.createSQL + else: + sql = None + newSQL = soClass.createTableSQL() + if sql != newSQL: + if sql is not None: + instance.destroySelf() + cls.drop(soClass) + else: + cls.clear(soClass) + for soClass in soClasses: + if not soClass._connection.tableExists(table): + cls.install(soClass) installOrClear = classmethod(installOrClear) def install(cls, soClass): Modified: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-09 04:34:12 UTC (rev 579) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-09 04:50:46 UTC (rev 580) @@ -77,8 +77,7 @@ me = StringCol(length=10) def test_foreignKey(): - setupClass(TestSO4) - setupClass(TestSO3) + setupClass([TestSO4, TestSO3]) tc3 = TestSO3(name='a') assert tc3.other is None assert tc3.other2 is None |
From: <sub...@co...> - 2005-02-09 04:34:16
|
Author: ianb Date: 2005-02-09 04:34:12 +0000 (Wed, 09 Feb 2005) New Revision: 579 Modified: trunk/SQLObject/sqlobject/tests/test_basic.py Log: Create tables in correct order (for constraints) Modified: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-08 16:48:34 UTC (rev 578) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-09 04:34:12 UTC (rev 579) @@ -77,8 +77,8 @@ me = StringCol(length=10) def test_foreignKey(): + setupClass(TestSO4) setupClass(TestSO3) - setupClass(TestSO4) tc3 = TestSO3(name='a') assert tc3.other is None assert tc3.other2 is None |
From: <sub...@co...> - 2005-02-08 16:48:37
|
Author: ianb Date: 2005-02-08 16:48:34 +0000 (Tue, 08 Feb 2005) New Revision: 578 Modified: trunk/SQLObject/sqlobject/tests/test_auto.py Log: Added an unlimited-length text field for testing. Modified: trunk/SQLObject/sqlobject/tests/test_auto.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-08 16:47:54 UTC (rev 577) +++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-08 16:48:34 UTC (rev 578) @@ -73,6 +73,7 @@ age INT DEFAULT NULL, created DATETIME NOT NULL, happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT, wannahavefun TINYINT DEFAULT 0 NOT NULL ) """ @@ -85,6 +86,7 @@ age INT DEFAULT 0, created VARCHAR(40) NOT NULL, happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT, wannahavefun BOOL DEFAULT FALSE NOT NULL ) """ @@ -96,7 +98,8 @@ last_name VARCHAR(200) NOT NULL, age INT DEFAULT 0, created VARCHAR(40) NOT NULL, - happy char(1) DEFAULT 'Y' NOT NULL + happy char(1) DEFAULT 'Y' NOT NULL, + long_field TEXT ) """ @@ -125,12 +128,16 @@ lastName='doe', age=10, created=now(), - wannahavefun=False) + wannahavefun=False, + long_field='x'*1000) jane = AutoTest(firstName='jane', lastName='doe', happy='N', created=now(), - wannahavefun=True) + wannahavefun=True, + long_field='x'*1000) assert not john.wannahavefun assert jane.wannahavefun + assert john.long_field == 'x'*1000 + assert jane.long_field == 'x'*1000 del classregistry.registry(AutoTest._registry).classes['AutoTest'] |
From: <sub...@co...> - 2005-02-08 16:48:06
|
Author: ianb Date: 2005-02-08 16:47:54 +0000 (Tue, 08 Feb 2005) New Revision: 577 Modified: trunk/SQLObject/sqlobject/include/validators.py Log: Minor change in method signature to make them more consistent Modified: trunk/SQLObject/sqlobject/include/validators.py =================================================================== --- trunk/SQLObject/sqlobject/include/validators.py 2005-02-08 16:47:12 UTC (rev 576) +++ trunk/SQLObject/sqlobject/include/validators.py 2005-02-08 16:47:54 UTC (rev 577) @@ -195,7 +195,7 @@ self._toPython, self.validatePython) - def fromPython(self, value, state): + def fromPython(self, value, state=None): return self.attemptConvert(value, state, self.validatePython, self._fromPython, @@ -227,7 +227,7 @@ def toPython(self, value, state=None): return self.attemptConvert(value, state, toPython) - def fromPython(self, value, state): + def fromPython(self, value, state=None): return self.attemptConvert(value, state, fromPython) def matchesProtocol(self, validator, state): |
From: <sub...@co...> - 2005-02-08 16:47:18
|
Author: ianb Date: 2005-02-08 16:47:12 +0000 (Tue, 08 Feb 2005) New Revision: 576 Modified: trunk/SQLObject/sqlobject/styles.py Log: Make the base style functional by adding a missing method Modified: trunk/SQLObject/sqlobject/styles.py =================================================================== --- trunk/SQLObject/sqlobject/styles.py 2005-02-08 11:08:52 UTC (rev 575) +++ trunk/SQLObject/sqlobject/styles.py 2005-02-08 16:47:12 UTC (rev 576) @@ -58,6 +58,9 @@ # keys, you can't really change this style. return attr + "ID" + def tableReference(self, table): + return table + "_id" + class MixedCaseUnderscoreStyle(Style): """ |
From: <sub...@co...> - 2005-02-08 11:09:02
|
Author: phd Date: 2005-02-08 11:08:52 +0000 (Tue, 08 Feb 2005) New Revision: 575 Added: home/phd/SQLObject/inheritance/sqlobject/inheritance/ home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py Removed: home/phd/SQLObject/inheritance/sqlobject/inheritance.py Modified: home/phd/SQLObject/inheritance/setup.py Log: Moved sqlobject/inheritance.py to sqlobject/inheritance/__init__.py. Modified: home/phd/SQLObject/inheritance/setup.py =================================================================== --- home/phd/SQLObject/inheritance/setup.py 2005-02-08 08:00:25 UTC (rev 574) +++ home/phd/SQLObject/inheritance/setup.py 2005-02-08 11:08:52 UTC (rev 575) @@ -2,7 +2,7 @@ import warnings warnings.filterwarnings("ignore", "Unknown distribution option") -subpackages = ['firebird', 'include', 'mysql', 'postgres', +subpackages = ['firebird', 'include', 'inheritance', 'mysql', 'postgres', 'sqlite', 'sybase', 'maxdb'] import sys Copied: home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py (from rev 571, home/phd/SQLObject/inheritance/sqlobject/inheritance.py) =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 11:02:13 UTC (rev 571) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/__init__.py 2005-02-08 11:08:52 UTC (rev 575) @@ -0,0 +1,192 @@ +from sqlobject import sqlbuilder +from sqlobject import dbconnection +from sqlobject import classregistry +from sqlobject.main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName + + +class InheritableSelectResults(SelectResults): + IterationClass = dbconnection.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults + + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) + + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val + + get = classmethod(get) + + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + + addColumn = classmethod(addColumn) + + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) + + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) + + delColumn = classmethod(delColumn) + + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addJoin(joinDef) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + + addJoin = classmethod(addJoin) + + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) + + delJoin = classmethod(delJoin) + + def _create(self, id, **kw): + + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self._className + id = self._parent.id + + super(InheritableSQLObject, self)._create(id, **kw) + + def destroySelf(self): + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() + + +__all__ = ['InheritableSQLObject'] Deleted: home/phd/SQLObject/inheritance/sqlobject/inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-08 08:00:25 UTC (rev 574) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-08 11:08:52 UTC (rev 575) @@ -1,192 +0,0 @@ -import sqlbuilder -import dbconnection -import classregistry -from main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName - - -class InheritableSelectResults(SelectResults): - IterationClass = dbconnection.InheritableIteration - - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - tablesDict = sqlbuilder.tablesUsedDict(clause) - tablesDict[sourceClass._table] = 1 - orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): - tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) - #DSM: if this class has a parent, we need to link it - #DSM: and be sure the parent is in the table list. - #DSM: The following code is before clauseTables - #DSM: because if the user uses clauseTables - #DSM: (and normal string SELECT), he must know what he wants - #DSM: and will do himself the relationship between classes. - if type(clause) is not str: - tableRegistry = {} - allClasses = classregistry.registry(sourceClass._registry).allClasses() - for registryClass in allClasses: - if registryClass._table in tablesDict: - #DSM: By default, no parents are needed for the clauses - tableRegistry[registryClass] = registryClass - for registryClass in allClasses: - if registryClass._table in tablesDict: - currentClass = registryClass - while currentClass._parentClass: - currentClass = currentClass._parentClass - if tableRegistry.has_key(currentClass): - #DSM: Must keep the last parent needed - #DSM: (to limit the number of join needed) - tableRegistry[registryClass] = currentClass - #DSM: Remove this class as it is a parent one - #DSM: of a needed children - del tableRegistry[currentClass] - #DSM: Table registry contains only the last children - #DSM: or standalone classes - parentClause = [] - for (currentClass, minParentClass) in tableRegistry.items(): - while currentClass != minParentClass and currentClass._parentClass: - parentClass = currentClass._parentClass - parentClause.append(currentClass.q.id == parentClass.q.id) - currentClass = parentClass - tablesDict[currentClass._table] = 1 - clause = reduce(sqlbuilder.AND, parentClause, clause) - - super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, - **ops) - - -class InheritableSQLObject(SQLObject): - SelectResultsClass = InheritableSelectResults - - def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): - - val = super(InheritableSQLObject, cls).get(id, connection, selectResults) - - #DSM: If we are updating a child, we should never return a child... - if childUpdate: return val - #DSM: If this class has a child, return the child - if hasattr(val, 'childName'): - childName = val.childName - if childName is not None: - return val._childClasses[childName].get(id, selectResults=childResults) - #DSM: Now, we know we are alone or the last child in a family... - #DSM: It's time to find our parents - inst = val - while inst._parentClass and not inst._parent: - inst._parent = inst._parentClass.get(id, childUpdate=True) - inst = inst._parent - #DSM: We can now return ourself - return val - - get = classmethod(get) - - def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic column to refresh the current class - if childUpdate or cls._parentClass: - for col in cls._parentClass._columns: - cname = col.name - if cname == 'childName': continue - setattr(cls, getterName(cname), eval( - 'lambda self: self._parent.%s' % cname)) - if not col.kw.has_key('immutable') or not col.kw['immutable']: - setattr(cls, setterName(cname), eval( - 'lambda self, val: setattr(self._parent, %s, val)' - % repr(cname))) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic column as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addColumn(columnDef, childUpdate=True) - - addColumn = classmethod(addColumn) - - def delColumn(cls, column, changeSchema=False, connection=None): - super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) - - #DSM: Update each child class if needed - #DSM: and delete properties for this column - for c in cls._childClasses.values(): - delattr(c, name) - - delColumn = classmethod(delColumn) - - def addJoin(cls, joinDef, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic join to refresh the current class - if childUpdate or cls._parentClass: - for jdef in cls._parentClass._joins: - join = jdef.withClass(cls) - jname = join.joinMethodName - jarn = join.addRemoveName - setattr(cls, getterName(jname), - eval('lambda self: self._parent.%s' % jname)) - if hasattr(join, 'remove'): - setattr(cls, 'remove' + jarn, - eval('lambda self,o: self._parent.remove%s(o)' % jarn)) - if hasattr(join, 'add'): - setattr(cls, 'add' + jarn, - eval('lambda self,o: self._parent.add%s(o)' % jarn)) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addJoin(joinDef) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic join as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addJoin(joinDef, childUpdate=True) - - addJoin = classmethod(addJoin) - - def delJoin(cls, joinDef): - super(InheritableSQLObject, cls).delJoin(joinDef) - - #DSM: Update each child class if needed - #DSM: and delete properties for this join - for c in cls._childClasses.values(): - delattr(c, meth) - - delJoin = classmethod(delJoin) - - def _create(self, id, **kw): - - #DSM: If we were called by a children class, - #DSM: we must retreive the properties dictionary. - #DSM: Note: we can't use the ** call paremeter directly - #DSM: as we must be able to delete items from the dictionary - #DSM: (and our children must know that the items were removed!) - if kw.has_key('kw'): - kw = kw['kw'] - #DSM: If we are the children of an inheritable class, - #DSM: we must first create our parent - if self._parentClass: - parentClass = self._parentClass - parent_kw = dict( - [(name, value) for (name, value) in kw.items() - if hasattr(parentClass, name) - ] - ) - self._parent = parentClass(kw=parent_kw) - self._parent.childName = self._className - id = self._parent.id - - super(InheritableSQLObject, self)._create(id, **kw) - - def destroySelf(self): - #DSM: If this object has parents, recursivly kill them - if hasattr(self, '_parent') and self._parent: - self._parent.destroySelf() - super(InheritableSQLObject, self).destroySelf() - - -__all__ = ['InheritableSQLObject'] |
From: <sub...@co...> - 2005-02-08 08:00:30
|
Author: ianb Date: 2005-02-08 08:00:25 +0000 (Tue, 08 Feb 2005) New Revision: 574 Modified: trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py Log: * Made dbconnection.DBConnection.uri() a bit more tollerant of missing attributes * Gave sqlite.SQLiteConnection a uri method, since it's not like the other databases. Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-08 07:59:38 UTC (rev 573) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-08 08:00:25 UTC (rev 574) @@ -44,13 +44,14 @@ atexit.register(_closeConnection, weakref.ref(self)) def uri(self): - auth = self.user or '' + auth = getattr(self, 'user', None) or '' if auth: if self.password: auth = auth + '@' + self.password auth = auth + ':' else: - assert not self.password, 'URIs cannot express passwords without usernames' + assert not getattr(self, 'password', None), ( + 'URIs cannot express passwords without usernames') uri = '%s://%s' % (self.dbName, auth) if self.host: uri += self.host + '/' Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py =================================================================== --- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-08 07:59:38 UTC (rev 573) +++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-08 08:00:25 UTC (rev 574) @@ -23,11 +23,18 @@ def connectionFromURI(cls, uri): user, password, host, path, args = cls._parseURI(uri) - assert host is None, "SQLite can only be used locally (with a URI like sqlite:///file or sql:/file, not %r)" % uri - assert user is None and password is None, "You may not provide usernames or passwords for SQLite databases" + assert host is None, ( + "SQLite can only be used locally (with a URI like " + "sqlite:///file or sqlite:/file, not %r)" % uri) + assert user is None and password is None, ( + "You may not provide usernames or passwords for SQLite " + "databases") return cls(filename=path, **args) connectionFromURI = classmethod(connectionFromURI) + def uri(self): + return 'sqlite:///%s' % self.filename + def _setAutoCommit(self, conn, auto): conn.autocommit = auto |
From: <sub...@co...> - 2005-02-08 07:59:41
|
Author: ianb Date: 2005-02-08 07:59:38 +0000 (Tue, 08 Feb 2005) New Revision: 573 Modified: trunk/SQLObject/sqlobject/tests/test_blob.py Log: Added note about SQLite Modified: trunk/SQLObject/sqlobject/tests/test_blob.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-08 07:45:24 UTC (rev 572) +++ trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-08 07:59:38 UTC (rev 573) @@ -19,4 +19,7 @@ ImageData._connection.cache.clear() prof2 = ImageData.get(iid) + # @@: This seems to fail in SQLite, which trims off the + # first and last character (\x00 and \xff). We should write + # a test for the upstream driver, as the error might be there. assert prof2.image == data |
From: <sub...@co...> - 2005-02-08 07:45:29
|
Author: ianb Date: 2005-02-08 07:45:24 +0000 (Tue, 08 Feb 2005) New Revision: 572 Added: trunk/SQLObject/sqlobject/boundattributes.py trunk/SQLObject/sqlobject/declarative.py trunk/SQLObject/sqlobject/tests/test_boundattributes.py trunk/SQLObject/sqlobject/tests/test_declarative.py Log: Added declarative and boundattributes, which will be the basis for the new SQLObject metaclass Added: trunk/SQLObject/sqlobject/boundattributes.py =================================================================== --- trunk/SQLObject/sqlobject/boundattributes.py 2005-02-07 11:02:13 UTC (rev 571) +++ trunk/SQLObject/sqlobject/boundattributes.py 2005-02-08 07:45:24 UTC (rev 572) @@ -0,0 +1,124 @@ +""" +Bound attributes are attributes that are bound to a specific class and +a specific name. In SQLObject a typical example is a column object, +which knows its name and class. + +A bound attribute should define a method ``__addtoclass__(added_class, +name)`` (attributes without this method will simply be treated as +normal). The return value is ignored; if the attribute wishes to +change the value in the class, it must call ``setattr(added_class, +name, new_value)``. + +BoundAttribute is a class that facilitates lazy attribute creation. + +``bind_attributes(cls, new_attrs)`` is a function that looks for +attributes with this special method. ``new_attrs`` is a dictionary, +as typically passed into ``__classinit__`` with declarative (calling +``bind_attributes`` in ``__classinit__`` would be typical). + +Note if you do this that attributes defined in a superclass will not +be rebound in subclasses. If you want to rebind attributes in +subclasses, use ``bind_attributes_local``, which adds a +``__bound_attributes__`` variable to your class to track these active +attributes. +""" + +__all__ = ['BoundAttribute', 'BoundFactory', 'bind_attributes', + 'bind_attributes_local'] + +import declarative + +class BoundAttribute(declarative.Declarative): + + """ + This is a declarative class that passes all the values given to it + to another object. So you can pass it arguments (via + __init__/__call__) or give it the equivalent of keyword arguments + through subclassing. Then a bound object will be added in its + place. + + To hook this other object in, override ``make_object(added_class, + name, **attrs)`` and maybe ``set_object(added_class, name, + **attrs)`` (the default implementation of ``set_object`` + just resets the attribute to whatever ``make_object`` returned). + """ + + _private_variables = ( + '_private_variables', + '_all_attributes', + '__classinit__', + '__addtoclass__', + '_add_attrs', + 'set_object', + 'make_object', + ) + + _all_attrs = () + + def __classinit__(cls, new_attrs): + declarative.Declarative.__classinit__(cls, new_attrs) + cls._all_attrs = cls._add_attrs(cls, new_attrs) + + def __instanceinit__(self, new_attrs): + declarative.Declarative.__instanceinit__(self, new_attrs) + self._all_attrs = self._add_attrs(self, new_attrs) + + def _add_attrs(this_object, new_attrs): + private = this_object._private_variables + all_attrs = list(this_object._all_attrs) + for key in new_attrs.keys(): + if key.startswith('_') or key in private: + continue + if key not in all_attrs: + all_attrs.append(key) + return tuple(all_attrs) + _add_attrs = staticmethod(_add_attrs) + + def __addtoclass__(self, cls, added_class, attr_name): + me = self or cls + attrs = {} + for name in me._all_attrs: + attrs[name] = getattr(me, name) + attrs['added_class'] = added_class + attrs['attr_name'] = attr_name + obj = me.make_object(**attrs) + me.set_object(added_class, attr_name, obj) + + __addtoclass__ = declarative.classinstancemethod(__addtoclass__) + + def set_object(cls, added_class, attr_name, obj): + setattr(added_class, attr_name, obj) + + set_object = classmethod(set_object) + + def make_object(cls, added_class, attr_name, *args, **attrs): + raise NotImplementedError + + make_object = classmethod(make_object) + +class BoundFactory(BoundAttribute): + + factory_class = None + + def make_object(cls, *args, **kw): + return cls.factory_class(*args, **kw) + +def bind_attributes(cls, new_attrs): + for name, value in new_attrs.items(): + if hasattr(value, '__addtoclass__'): + value.__addtoclass__(cls, name) + +def bind_attributes_local(cls, new_attrs): + new_bound_attributes = {} + for name, value in getattr(cls, '__bound_attributes__', {}).items(): + if new_attrs.has_key(name): + # The attribute is being REbound, so don't try to bind it + # again. + continue + value.__addtoclass__(cls, name) + new_bound_attributes[name] = value + for name, value in new_attrs.items(): + if hasattr(value, '__addtoclass__'): + value.__addtoclass__(cls, name) + new_bound_attributes[name] = value + cls.__bound_attributes__ = new_bound_attributes Copied: trunk/SQLObject/sqlobject/declarative.py (from rev 552, trunk/Validator/validator/declarative.py) =================================================================== --- trunk/Validator/validator/declarative.py 2005-01-24 12:10:39 UTC (rev 552) +++ trunk/SQLObject/sqlobject/declarative.py 2005-02-08 07:45:24 UTC (rev 572) @@ -0,0 +1,192 @@ +""" +Declarative objects. + +Declarative objects have a simple protocol: you can use classes in +lieu of instances and they are equivalent, and any keyword arguments +you give to the constructor will override those instance variables. +(So if a class is received, we'll simply instantiate an instance with +no arguments). + +You can provide a variable __unpackargs__ (a list of strings), and if +the constructor is called with non-keyword arguments they will be +interpreted as the given keyword arguments. + +If __unpackargs__ is ('*', name), then all the arguments will be put +in a variable by that name. + +You can define a __classinit__(cls, new_attrs) method, which will be +called when the class is created (including subclasses). Note: you +can't use super() in __classinit__ because the class isn't bound to a +name. As an analog to __classinit__, Declarative adds +__instanceinit__ which is called with the same argument (new_attrs). +This is like __init__, but after __unpackargs__ and other factors have +been taken into account. + +If __mutableattributes__ is defined as a sequence of strings, these +attributes will not be shared between superclasses and their +subclasses. E.g., if you have a class variable that contains a list +and you append to that list, changes to subclasses will effect +superclasses unless you add the attribute here. + +Also defines classinstancemethod, which acts as either a class method +or an instance method depending on where it is called. +""" + +from __future__ import generators + +__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative') + +import copy + +try: + import itertools + counter = itertools.count() +except ImportError: + def _counter(): + i = 0 + while 1: + i += 1 + yield i + counter = _counter() + +class classinstancemethod(object): + """ + Acts like a class method when called from a class, like an + instance method when called by an instance. The method should + take two arguments, 'self' and 'cls'; one of these will be None + depending on how the method was called. + """ + + def __init__(self, func): + self.func = func + + def __get__(self, obj, type=None): + return _methodwrapper(self.func, obj=obj, type=type) + +class _methodwrapper(object): + + def __init__(self, func, obj, type): + self.func = func + self.obj = obj + self.type = type + + def __call__(self, *args, **kw): + assert not kw.has_key('self') and not kw.has_key('cls'), ( + "You cannot use 'self' or 'cls' arguments to a " + "classinstancemethod") + return self.func(*((self.obj, self.type) + args), **kw) + + def __repr__(self): + if self.obj is None: + return ('<bound class method %s.%s>' + % (self.type.__name__, self.func.func_name)) + else: + return ('<bound method %s.%s of %r>' + % (self.type.__name__, self.func.func_name, self.obj)) + + +class DeclarativeMeta(type): + + def __new__(meta, class_name, bases, new_attrs): + cls = type.__new__(meta, class_name, bases, new_attrs) + if new_attrs.has_key('__classinit__'): + cls.__classinit__ = staticmethod(cls.__classinit__.im_func) + cls.declarative_count = counter.next() + cls.__classinit__(cls, new_attrs) + return cls + +class Declarative(object): + + __unpackargs__ = () + + __mutableattributes__ = () + + __metaclass__ = DeclarativeMeta + + def __classinit__(cls, new_attrs): + for name in cls.__mutableattributes__: + if not new_attrs.has_key(name): + setattr(cls, copy.copy(getattr(cls, name))) + + def __instanceinit__(self, new_attrs): + for name, value in new_attrs.items(): + setattr(self, name, value) + if not new_attrs.has_key('declarative_count'): + self.declarative_count = counter.next() + + def __init__(self, *args, **kw): + if self.__unpackargs__ and self.__unpackargs__[0] == '*': + assert len(self.__unpackargs__) == 2, \ + "When using __unpackargs__ = ('*', varname), you must only provide a single variable name (you gave %r)" % self.__unpackargs__ + name = self.__unpackargs__[1] + if kw.has_key(name): + raise TypeError( + "keyword parameter '%s' was given by position and name" + % name) + kw[name] = args + else: + if len(args) > len(self.__unpackargs__): + raise TypeError( + '%s() takes at most %i arguments (%i given)' + % (self.__class__.__name__, + len(self.__unpackargs__), + len(args))) + for name, arg in zip(self.__unpackargs__, args): + if kw.has_key(name): + raise TypeError( + "keyword parameter '%s' was given by position and name" + % name) + kw[name] = arg + if kw.has_key('__alsocopy'): + for name, value in kw['__alsocopy'].items(): + if not kw.has_key(name): + if name in self.__mutableattributes__: + value = copy.copy(value) + kw[name] = value + del kw['__alsocopy'] + self.__instanceinit__(kw) + + def __call__(self, *args, **kw): + kw['__alsocopy'] = self.__dict__ + return self.__class__(*args, **kw) + + def singleton(self, cls): + if self: + return self + name = '_%s__singleton' % cls.__name__ + if not hasattr(cls, name): + setattr(cls, name, cls(declarative_count=cls.declarative_count)) + return getattr(cls, name) + singleton = classinstancemethod(singleton) + + def __repr__(self, cls): + if self: + name = '%s object' % self.__class__.__name__ + v = self.__dict__.copy() + else: + name = '%s class' % cls.__name__ + v = cls.__dict__.copy() + if v.has_key('declarative_count'): + name = '%s %i' % (name, v['declarative_count']) + del v['declarative_count'] + # @@: simplifying repr: + #v = {} + names = v.keys() + args = [] + for n in self._repr_vars(names): + args.append('%s=%r' % (n, v[n])) + if not args: + return '<%s>' % name + else: + return '<%s %s>' % (name, ' '.join(args)) + + def _repr_vars(dictNames): + names = [n for n in dictNames + if not n.startswith('_') + and n != 'declarative_count'] + names.sort() + return names + _repr_vars = staticmethod(_repr_vars) + + __repr__ = classinstancemethod(__repr__) + Added: trunk/SQLObject/sqlobject/tests/test_boundattributes.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_boundattributes.py 2005-02-07 11:02:13 UTC (rev 571) +++ trunk/SQLObject/sqlobject/tests/test_boundattributes.py 2005-02-08 07:45:24 UTC (rev 572) @@ -0,0 +1,57 @@ +from sqlobject import declarative +from sqlobject import boundattributes + +class TestMe(object): + + __metaclass__ = declarative.DeclarativeMeta + __classinit__ = boundattributes.bind_attributes_local + +class AttrReplace(boundattributes.BoundAttribute): + + __unpackargs__ = ('replace',) + + replace = None + + def make_object(self, cls, added_class, attr_name, **attrs): + if not self: + return cls.singleton().make_object( + added_class, attr_name, **attrs) + self.replace.added_class = added_class + self.replace.name = attr_name + assert attrs['replace'] is self.replace + del attrs['replace'] + self.replace.attrs = attrs + return self.replace + + make_object = declarative.classinstancemethod(make_object) + +class Holder: + def __init__(self, name): + self.holder_name = name + def __repr__(self): + return '<Holder %s>' % self.holder_name + +def test_1(): + v1 = Holder('v1') + v2 = Holder('v2') + v3 = Holder('v3') + class V2Class(AttrReplace): + arg1 = 'nothing' + arg2 = ['something'] + class A1(TestMe): + a = AttrReplace(v1) + v = V2Class(v2) + class inline(AttrReplace): + replace = v3 + arg3 = 'again' + arg4 = 'so there' + for n in ('a', 'v', 'inline'): + assert getattr(A1, n).name == n + assert getattr(A1, n).added_class is A1 + assert A1.a is v1 + assert A1.a.attrs == {} + assert A1.v is v2 + assert A1.v.attrs == {'arg1': 'nothing', 'arg2': ['something']} + assert A1.inline is v3 + assert A1.inline.attrs == {'arg3': 'again', 'arg4': 'so there'} + Added: trunk/SQLObject/sqlobject/tests/test_declarative.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_declarative.py 2005-02-07 11:02:13 UTC (rev 571) +++ trunk/SQLObject/sqlobject/tests/test_declarative.py 2005-02-08 07:45:24 UTC (rev 572) @@ -0,0 +1,78 @@ +from sqlobject.declarative import * + +class A1(Declarative): + + a = 1 + b = [] + +class A2(A1): + + a = 5 + +A3 = A2(b=5) + +def test_a_classes(): + assert A1.a == 1 + assert A1.singleton().a == 1 + assert A1.b is A2.b + assert A3.b == 5 + assert A1.declarative_count == A1.singleton().declarative_count + assert A1.declarative_count < A2.declarative_count + assert A2.singleton() is not A1.singleton() + assert A3.singleton().b == A3.b + +class B1(Declarative): + + attrs = [] + + def __classinit__(cls, new_attrs): + Declarative.__classinit__(cls, new_attrs) + cls.attrs = cls.add_attrs(cls.attrs, new_attrs) + + def __instanceinit__(self, new_attrs): + Declarative.__instanceinit__(self, new_attrs) + self.attrs = self.add_attrs(self.attrs, new_attrs) + + def add_attrs(old_attrs, new_attrs): + old_attrs = old_attrs[:] + for name in new_attrs.keys(): + if (name in old_attrs + or name.startswith('_') + or name in ('add_attrs', 'declarative_count', 'attrs')): + continue + old_attrs.append(name) + old_attrs.sort() + return old_attrs + add_attrs = staticmethod(add_attrs) + + c = 1 + +class B2(B1): + + g = 3 + + def __classinit__(cls, new_attrs): + new_attrs['test'] = 'whatever' + B1.__classinit__(cls, new_attrs) + +B3 = B2(c=5, d=3) +B4 = B3(d=5) +B5 = B1(a=1) + +def test_b_classes(): + assert B1.attrs == ['c'] + assert B1.c == 1 + assert B2.attrs == ['c', 'g', 'test'] + assert B3.d == 3 + assert B4.d == 5 + assert B5.a == 1 + assert B5.attrs == ['a', 'c'] + assert B3.attrs == ['c', 'd', 'g', 'test'] + assert B4.attrs == ['c', 'd', 'g', 'test'] + order = [B1, B1.singleton(), B2, B2.singleton(), + B3, B3.singleton(), B4, B4.singleton(), + B5, B5.singleton()] + last = 0 + for obj in order: + assert obj.declarative_count >= last + last = obj.declarative_count |
From: <sub...@co...> - 2005-02-07 11:02:19
|
Author: phd Date: 2005-02-07 11:02:13 +0000 (Mon, 07 Feb 2005) New Revision: 571 Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance.py home/phd/SQLObject/inheritance/sqlobject/main.py Log: Fixed destroySelf() - removed inheritance-related code from SQLObject. Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 10:43:44 UTC (rev 570) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 11:02:13 UTC (rev 571) @@ -183,7 +183,6 @@ super(InheritableSQLObject, self)._create(id, **kw) def destroySelf(self): - # Kills this object. Kills it dead! #DSM: If this object has parents, recursivly kill them if hasattr(self, '_parent') and self._parent: self._parent.destroySelf() Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 10:43:44 UTC (rev 570) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 11:02:13 UTC (rev 571) @@ -1325,9 +1325,6 @@ def destroySelf(self): # Kills this object. Kills it dead! - #DSM: If this object has parents, recursivly kill them - if hasattr(self, '_parent') and self._parent: - self._parent.destroySelf() depends = [] klass = self.__class__ depends = self._SO_depends() |
From: <sub...@co...> - 2005-02-07 10:44:01
|
Author: phd Date: 2005-02-07 10:43:44 +0000 (Mon, 07 Feb 2005) New Revision: 570 Added: home/phd/SQLObject/inheritance/sqlobject/inheritance.py home/phd/SQLObject/inheritance/sqlobject/tests/ Removed: home/phd/SQLObject/inheritance/test home/phd/SQLObject/inheritance/tests/ Modified: home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt home/phd/SQLObject/inheritance/docs/FAQ.txt home/phd/SQLObject/inheritance/sqlobject/dbconnection.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py Log: Split sqlobject/main.py and moved inheritance-related classes into sqlobject/inheritance.py. Merged patches from the revisions 559:569 from the trunk. Modified: home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt 2005-02-07 10:43:44 UTC (rev 570) @@ -26,16 +26,19 @@ * I don't stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you - can). + can). Do asserts like:: + assert some_condition(a, b), ( + "Some condition failed, %r isn't right!" % a) + * But if you are having problems with line length, maybe you should just break the expression up into multiple statements. * Blank lines between methods, unless they are very small and closely bound to each other. -* Never use the form ``condition and trueValue or falseValue``. Break - it out and use a variable. +* *Never* use the form ``condition and trueValue or falseValue``. + Break it out and use a variable. * Careful of namespace pollution. SQLObject does allow for ``from sqlobject import *`` so names should be fairly distinct, or they @@ -122,27 +125,42 @@ Tests are important. Tests keep everything from falling apart. All new additions should have tests. -Right now all the tests are in one big file ``tests.py``. Which is -unfortunate, but that's the way it is. They may seem complex, but -they aren't so bad really. They all subclass from ``SQLObjectTest``. +Testing uses `py.test`__, an alternative to ``unittest``. It is +available via subversion at http://codespeak.net/svn/py/dist. Read +its `getting started`_ document for more. -The ``classes`` attribute is special in a test class. This is a list -of `SQLObject` subclasses that this test uses. `SQLObjectTest` will -create the tables before the tests are run, and destroy them after. +.. __: http://codespeak.net/py/current/doc/test.html +.. _getting started: http://codespeak.net/py/current/doc/getting_started.html -You may also define an ``.inserts()`` method. This method sets up the -basic data. When doing verbose input (``-vv``) you won't see these -inserts, since they may be overwhelming. Use the command-line options -``--inserts`` to show them (as well as the create statement. +To actually run the test, you have to give it a database to connect +to. You do this with the ``TESTDB`` environmental variable (right now +py.test doesn't have a better way to add custom options). You can +give the complete URI to access your test database, or you can give it +a shortcut like ``mysql`` (these shortcuts are defined in the top of +``tests/dbtest.py``. -When running tests, use ``-ddbname`` to test with ``dbname`` (e.g., -``-dmysql``, ``-dpostgres``, etc), or ``-dall`` to use Postgres, -MySQL, Firebird, and SQLite (all the core supported databases; -everything I have installed on my computer). Please try to test as -many databases as you can. At least SQLite and one other should be -easy, though if you can test both Postgres and MySQL that would be -much better. +All the tests are modules in ``sqlobject/tests``. Each module tests +one kind of feature, more or less. If you are testing a module, call +the test module ``tests/test_modulename.py`` -- only modules that +start with ``test_`` will be picked up by py.test. +The "framework" for testing is in ``tests/dbtest``. There's a couple +important functions: + +``setupClass(soClass)`` creates the tables for the class. It tries to +avoid recreating tables if not necessary. + +``supports(featureName)`` checks if the database backend supports the +named feature. What backends support what is defined at the top of +``dbtest``. + +If you ``import *`` you'll also get py.test's version of raises_, an +``inserts`` function that can create instances for you, and a couple +miscellaneous functions. + +.. _raises: http://codespeak.net/py/current/doc/test.html#id4 + + If you submit a patch or implement a feature without a test, I'll be forced to write the test. That's no fun for me, to just be writing tests. So please, write tests; everything at least needs to be Modified: home/phd/SQLObject/inheritance/docs/FAQ.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/FAQ.txt 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/docs/FAQ.txt 2005-02-07 10:43:44 UTC (rev 570) @@ -182,3 +182,12 @@ For this reason and several others, reloading modules is highly error-prone and difficult to support. + +Python Keywords +--------------- + +If you have a table column that is a Python keyword, you should know +that the Python attribute doesn't have to match the name of the +column. See `Irregular Naming`_ in the documentation. + +.. _Irregular Naming: SQLObject.html#irregular-naming Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-07 10:43:44 UTC (rev 570) @@ -51,7 +51,7 @@ auth = auth + '@' + self.password auth = auth + ':' else: - assert not password, 'URIs cannot express passwords without usernames' + assert not self.password, 'URIs cannot express passwords without usernames' uri = '%s://%s' % (self.dbName, auth) if self.host: uri += self.host + '/' @@ -481,6 +481,10 @@ self.close() def close(self): + if not hasattr(self, '_pool'): + # Probably there was an exception while creating this + # instance, so it is incomplete. + return if not self._pool: return self._poolLock.acquire() Added: home/phd/SQLObject/inheritance/sqlobject/inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 10:43:44 UTC (rev 570) @@ -0,0 +1,193 @@ +import sqlbuilder +import dbconnection +import classregistry +from main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName + + +class InheritableSelectResults(SelectResults): + IterationClass = dbconnection.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults + + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) + + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val + + get = classmethod(get) + + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + + addColumn = classmethod(addColumn) + + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) + + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) + + delColumn = classmethod(delColumn) + + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addJoin(joinDef) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + + addJoin = classmethod(addJoin) + + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) + + delJoin = classmethod(delJoin) + + def _create(self, id, **kw): + + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self._className + id = self._parent.id + + super(InheritableSQLObject, self)._create(id, **kw) + + def destroySelf(self): + # Kills this object. Kills it dead! + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() + + +__all__ = ['InheritableSQLObject'] Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 10:43:44 UTC (rev 570) @@ -511,58 +511,7 @@ expression = sqlbuilder.func.SUM(attribute) return self.accumulate(expression) -class InheritableSelectResults(SelectResults): - IterationClass = dbconnection.InheritableIteration - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - tablesDict = sqlbuilder.tablesUsedDict(clause) - tablesDict[sourceClass._table] = 1 - orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): - tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) - #DSM: if this class has a parent, we need to link it - #DSM: and be sure the parent is in the table list. - #DSM: The following code is before clauseTables - #DSM: because if the user uses clauseTables - #DSM: (and normal string SELECT), he must know what he wants - #DSM: and will do himself the relationship between classes. - if type(clause) is not str: - tableRegistry = {} - allClasses = classregistry.registry(sourceClass._registry).allClasses() - for registryClass in allClasses: - if registryClass._table in tablesDict: - #DSM: By default, no parents are needed for the clauses - tableRegistry[registryClass] = registryClass - for registryClass in allClasses: - if registryClass._table in tablesDict: - currentClass = registryClass - while currentClass._parentClass: - currentClass = currentClass._parentClass - if tableRegistry.has_key(currentClass): - #DSM: Must keep the last parent needed - #DSM: (to limit the number of join needed) - tableRegistry[registryClass] = currentClass - #DSM: Remove this class as it is a parent one - #DSM: of a needed children - del tableRegistry[currentClass] - #DSM: Table registry contains only the last children - #DSM: or standalone classes - parentClause = [] - for (currentClass, minParentClass) in tableRegistry.items(): - while currentClass != minParentClass and currentClass._parentClass: - parentClass = currentClass._parentClass - parentClause.append(currentClass.q.id == parentClass.q.id) - currentClass = parentClass - tablesDict[currentClass._table] = 1 - clause = reduce(sqlbuilder.AND, parentClause, clause) - - super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, - **ops) - - # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -1447,139 +1396,7 @@ cls._connection = value setConnection = classmethod(setConnection) -class InheritableSQLObject(SQLObject): - SelectResultsClass = InheritableSelectResults - def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): - - val = super(InheritableSQLObject, cls).get(id, connection, selectResults) - - #DSM: If we are updating a child, we should never return a child... - if childUpdate: return val - #DSM: If this class has a child, return the child - if hasattr(val, 'childName'): - childName = val.childName - if childName is not None: - return val._childClasses[childName].get(id, selectResults=childResults) - #DSM: Now, we know we are alone or the last child in a family... - #DSM: It's time to find our parents - inst = val - while inst._parentClass and not inst._parent: - inst._parent = inst._parentClass.get(id, childUpdate=True) - inst = inst._parent - #DSM: We can now return ourself - return val - - get = classmethod(get) - - def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic column to refresh the current class - if childUpdate or cls._parentClass: - for col in cls._parentClass._columns: - cname = col.name - if cname == 'childName': continue - setattr(cls, getterName(cname), eval( - 'lambda self: self._parent.%s' % cname)) - if not col.kw.has_key('immutable') or not col.kw['immutable']: - setattr(cls, setterName(cname), eval( - 'lambda self, val: setattr(self._parent, %s, val)' - % repr(cname))) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic column as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addColumn(columnDef, childUpdate=True) - - addColumn = classmethod(addColumn) - - def delColumn(cls, column, changeSchema=False, connection=None): - super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) - - #DSM: Update each child class if needed - #DSM: and delete properties for this column - for c in cls._childClasses.values(): - delattr(c, name) - - delColumn = classmethod(delColumn) - - def addJoin(cls, joinDef, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic join to refresh the current class - if childUpdate or cls._parentClass: - for jdef in cls._parentClass._joins: - join = jdef.withClass(cls) - jname = join.joinMethodName - jarn = join.addRemoveName - setattr(cls, getterName(jname), - eval('lambda self: self._parent.%s' % jname)) - if hasattr(join, 'remove'): - setattr(cls, 'remove' + jarn, - eval('lambda self,o: self._parent.remove%s(o)' % jarn)) - if hasattr(join, 'add'): - setattr(cls, 'add' + jarn, - eval('lambda self,o: self._parent.add%s(o)' % jarn)) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addJoin(joinDef) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic join as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addJoin(joinDef, childUpdate=True) - - addJoin = classmethod(addJoin) - - def delJoin(cls, joinDef): - super(InheritableSQLObject, cls).delJoin(joinDef) - - #DSM: Update each child class if needed - #DSM: and delete properties for this join - for c in cls._childClasses.values(): - delattr(c, meth) - - delJoin = classmethod(delJoin) - - def _create(self, id, **kw): - - #DSM: If we were called by a children class, - #DSM: we must retreive the properties dictionary. - #DSM: Note: we can't use the ** call paremeter directly - #DSM: as we must be able to delete items from the dictionary - #DSM: (and our children must know that the items were removed!) - if kw.has_key('kw'): - kw = kw['kw'] - #DSM: If we are the children of an inheritable class, - #DSM: we must first create our parent - if self._parentClass: - parentClass = self._parentClass - parent_kw = dict( - [(name, value) for (name, value) in kw.items() - if hasattr(parentClass, name) - ] - ) - self._parent = parentClass(kw=parent_kw) - self._parent.childName = self._className - id = self._parent.id - - super(InheritableSQLObject, self)._create(id, **kw) - - def destroySelf(self): - # Kills this object. Kills it dead! - #DSM: If this object has parents, recursivly kill them - if hasattr(self, '_parent') and self._parent: - self._parent.destroySelf() - super(InheritableSQLObject, self).destroySelf() - def capitalize(name): return name[0].capitalize() + name[1:] @@ -1634,6 +1451,6 @@ else: return obj -__all__ = ['SQLObject', 'InheritableSQLObject', - 'NoDefault', 'getID', 'getObject', +__all__ = ['NoDefault', 'SQLObject', + 'getID', 'getObject', 'SQLObjectNotFound'] Modified: home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-07 10:43:44 UTC (rev 570) @@ -152,9 +152,14 @@ return SQLCall(self, args) def __repr__(self): - return self.__sqlrepr__(None) + try: + return self.__sqlrepr__(None) + except AssertionError: + return '<%s %s>' % ( + self.__class__.__name__, hex(id(self))[2:]) + def __str__(self): - return self.__sqlrepr__(None) + return repr(self) def __cmp__(self, other): raise VersionError, "Python 2.1+ required" Copied: home/phd/SQLObject/inheritance/sqlobject/tests (from rev 569, trunk/SQLObject/sqlobject/tests) Deleted: home/phd/SQLObject/inheritance/test =================================================================== --- home/phd/SQLObject/inheritance/test 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/test 2005-02-07 10:43:44 UTC (rev 570) @@ -1,18 +0,0 @@ -#!/bin/sh -VERSION="" -if [ "$1" = "2.3" ] ; then - VERSION=2.3 - shift -elif [ "$1" = "2.2" ] ; then - VERSION=2.2 - shift -fi - -if [ "$1" = "cover" ] ; then - shift - sudo python$VERSION setup.py -q install - python$VERSION ./tests/coverage.py -x tests/test_sqlobject.py $* - ./tests/coverage.py -a `find tests SQLObject -name '*.py'` -else - sudo python$VERSION setup.py -q install && python$VERSION tests/test_sqlobject.py $* -fi |
From: <sub...@co...> - 2005-02-03 12:14:35
|
Author: phd Date: 2005-02-03 12:14:28 +0000 (Thu, 03 Feb 2005) New Revision: 569 Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/tests/test_inheritance.py Log: Split inheritance support into a number of separate classes - InheritableSQLObject at al. Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:13:26 UTC (rev 568) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:14:28 UTC (rev 569) @@ -244,7 +244,7 @@ return self._runWithConnection(self._queryInsertID, soInstance, id, names, values) def iterSelect(self, select): - return Iteration(self, self.getConnection(), + return select.IterationClass(self, self.getConnection(), select, keepConnection=False) def accumulateSelect(self, select, expression): @@ -498,20 +498,52 @@ self._poolLock.release() class Iteration(object): - #phd: default array size for cursor.fetchmany() - defaultArraySize = 10000 def __init__(self, dbconn, rawconn, select, keepConnection=False): self.dbconn = dbconn self.rawconn = rawconn self.select = select self.keepConnection = keepConnection - self.cursor = cursor = rawconn.cursor() + self.cursor = rawconn.cursor() self.query = self.dbconn.queryForSelect(select) if dbconn.debug: dbconn.printDebug(rawconn, self.query, 'Select') - self.dbconn._executeRetry(self.rawconn, cursor, self.query) - cursor.arraysize = self.defaultArraySize + self.dbconn._executeRetry(self.rawconn, self.cursor, self.query) + + def __iter__(self): + return self + + def next(self): + result = self.cursor.fetchone() + if result is None: + self._cleanup() + raise StopIteration + if self.select.ops.get('lazyColumns', 0): + obj = self.select.sourceClass.get(result[0], connection=self.dbconn) + return obj + else: + obj = self.select.sourceClass.get(result[0], selectResults=result[1:], connection=self.dbconn) + return obj + + def _cleanup(self): + if getattr(self, 'query', None) is None: + # already cleaned up + return + self.query = None + if not self.keepConnection: + self.dbconn.releaseConnection(self.rawconn) + self.dbconn = self.rawconn = self.select = self.cursor = None + + def __del__(self): + self._cleanup() + +class InheritableIteration(Iteration): + #phd: default array size for cursor.fetchmany() + defaultArraySize = 10000 + + def __init__(self, dbconn, rawconn, select, keepConnection=False): + super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) + self.cursor.arraysize = self.defaultArraySize self._results = [] #phd: find the index of the childName column childNameIdx = None @@ -522,9 +554,6 @@ break self._childNameIdx = childNameIdx - def __iter__(self): - return self - def next(self): lazyColumns = self.select.ops.get('lazyColumns', 0) if not self._results: @@ -581,20 +610,7 @@ for result in cursor.fetchall(): self._childrenResults[result[0]] = result[1:] - def _cleanup(self): - if getattr(self, 'query', None) is None: - # already cleaned up - return - self.query = None - if not self.keepConnection: - self.dbconn.releaseConnection(self.rawconn) - self.dbconn = self.rawconn = self.select = self.cursor = None - def __del__(self): - self._cleanup() - - - class Transaction(object): def __init__(self, dbConnection): @@ -631,7 +647,7 @@ # still iterating through the results. # @@: But would it be okay for psycopg, with threadsafety # level 2? - return iter(list(Iteration(self, self._connection, + return iter(list(select.IterationClass(self, self._connection, select, keepConnection=True))) def commit(self): Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:13:26 UTC (rev 568) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:14:28 UTC (rev 569) @@ -8,6 +8,10 @@ Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 - Added support for simple table inheritance. + Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 + - Split inheritance support into a number of separate classes - + InheritableSQLObject at al. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -339,6 +343,226 @@ """ pass + +try: + basestring +except NameError: # Python 2.2 + basestring = (types.StringType, types.UnicodeType) + +class SelectResults(object): + IterationClass = dbconnection.Iteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + self.sourceClass = sourceClass + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + self.clause = clause + tablesDict = sqlbuilder.tablesUsedDict(self.clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + + if clauseTables: + for table in clauseTables: + tablesDict[table] = 1 + self.clauseTables = clauseTables + self.tables = tablesDict.keys() + self.ops = ops + if self.ops.get('orderBy', NoDefault) is NoDefault: + self.ops['orderBy'] = sourceClass._defaultOrder + orderBy = self.ops['orderBy'] + if isinstance(orderBy, list) or isinstance(orderBy, tuple): + orderBy = map(self._mungeOrderBy, orderBy) + else: + orderBy = self._mungeOrderBy(orderBy) + #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy'])) + self.ops['dbOrderBy'] = orderBy + if ops.has_key('connection') and ops['connection'] is None: + del ops['connection'] + + def _mungeOrderBy(self, orderBy): + if isinstance(orderBy, str) and orderBy.startswith('-'): + orderBy = orderBy[1:] + desc = True + else: + desc = False + if isinstance(orderBy, (str, unicode)): + if self.sourceClass._SO_columnDict.has_key(orderBy): + val = self.sourceClass._SO_columnDict[orderBy].dbName + if desc: + return '-' + val + else: + return val + else: + if desc: + return '-' + orderBy + else: + return orderBy + else: + return orderBy + + def clone(self, **newOps): + ops = self.ops.copy() + ops.update(newOps) + return self.__class__(self.sourceClass, self.clause, + self.clauseTables, **ops) + + def orderBy(self, orderBy): + return self.clone(orderBy=orderBy) + + def connection(self, conn): + return self.clone(connection=conn) + + def limit(self, limit): + return self[:limit] + + def lazyColumns(self, value): + return self.clone(lazyColumns=value) + + def reversed(self): + return self.clone(reversed=not self.ops.get('reversed', False)) + + def distinct(self): + return self.clone(distinct=True) + + def __getitem__(self, value): + if type(value) is type(slice(1)): + assert not value.step, "Slices do not support steps" + if not value.start and not value.stop: + # No need to copy, I'm immutable + return self + + # Negative indexes aren't handled (and everything we + # don't handle ourselves we just create a list to + # handle) + if (value.start and value.start < 0) \ + or (value.stop and value.stop < 0): + if value.start: + if value.stop: + return list(self)[value.start:value.stop] + return list(self)[value.start:] + return list(self)[:value.stop] + + + if value.start: + assert value.start >= 0 + start = self.ops.get('start', 0) + value.start + if value.stop is not None: + assert value.stop >= 0 + if value.stop < value.start: + # an empty result: + end = start + else: + end = value.stop + self.ops.get('start', 0) + if self.ops.get('end', None) is not None \ + and value['end'] < end: + # truncated by previous slice: + end = self.ops['end'] + else: + end = self.ops.get('end', None) + else: + start = self.ops.get('start', 0) + end = value.stop + start + if self.ops.get('end', None) is not None \ + and self.ops['end'] < end: + end = self.ops['end'] + return self.clone(start=start, end=end) + else: + if value < 0: + return list(iter(self))[value] + else: + start = self.ops.get('start', 0) + value + return list(self.clone(start=start, end=start+1))[0] + + def __iter__(self): + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.iterSelect(self) + + def accumulate(self, expression): + """ Use an accumulate expression to select result + using another SQL select through current + connection. + Return the accumulate result + """ + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.accumulateSelect(self,expression) + + def count(self): + """ Counting elements of current select results """ + assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" + + count = self.accumulate('COUNT(*)') + if self.ops.get('start'): + count -= self.ops['start'] + if self.ops.get('end'): + count = min(self.ops['end'] - self.ops.get('start', 0), count) + return count + + def sum(self, attribute): + """ Making the sum of a given select result attribute. + `attribute` can be a column name (like 'a_column') + or a dot-q attribute (like Table.q.aColumn) + """ + if type(attribute) == type(''): + expression = 'SUM(%s)' % attribute + else: + expression = sqlbuilder.func.SUM(attribute) + return self.accumulate(expression) + +class InheritableSelectResults(SelectResults): + IterationClass = dbconnection.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -405,8 +629,11 @@ # aren't using integer IDs _idType = int - def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + # The law of Demeter: the class should not call another classes by name + SelectResultsClass = SelectResults + def get(cls, id, connection=None, selectResults=None): + assert id is not None, 'None is not a possible id for %s' % cls.__name id = cls._idType(id) @@ -427,20 +654,6 @@ cache.put(id, cls, val) finally: cache.finishPut(cls) - #DSM: If we are updating a child, we should never return a child... - if childUpdate: return val - #DSM: If this class has a child, return the child - if hasattr(val, 'childName'): - childName = val.childName - if childName is not None: - return val._childClasses[childName].get(id, selectResults=childResults) - #DSM: Now, we know we are alone or the last child in a family... - #DSM: It's time to find our parents - inst = val - while inst._parentClass and not inst._parent: - inst._parent = inst._parentClass.get(id, childUpdate=True) - inst = inst._parent - #DSM: We can now return ourself return val get = classmethod(get) @@ -450,24 +663,7 @@ cls._SO_indexList.append(index) addIndex = classmethod(addIndex) - def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic column to refresh the current class - if childUpdate or cls._parentClass: - for col in cls._parentClass._columns: - cname = col.name - if cname == 'childName': continue - setattr(cls, getterName(cname), eval( - 'lambda self: self._parent.%s' % cname)) - if not col.kw.has_key('immutable') or not col.kw['immutable']: - setattr(cls, setterName(cname), eval( - 'lambda self, val: setattr(self._parent, %s, val)' - % repr(cname))) - if childUpdate: - makeProperties(cls) - return - + def addColumn(cls, columnDef, changeSchema=False, connection=None): column = columnDef.withClass(cls) name = column.name assert name != 'id', "The 'id' column is implicit, and should not be defined as a column" @@ -582,11 +778,6 @@ if cls._SO_finishedClassCreation: makeProperties(cls) - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic column as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addColumn(columnDef, childUpdate=True) - addColumn = classmethod(addColumn) def addColumnsFromDatabase(cls, connection=None): @@ -637,34 +828,9 @@ if cls._SO_finishedClassCreation: delattr(cls, name) - #DSM: Update each child class if needed - #DSM: and delete properties for this column - for c in cls._childClasses.values(): - delattr(c, name) - delColumn = classmethod(delColumn) - def addJoin(cls, joinDef, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic join to refresh the current class - if childUpdate or cls._parentClass: - for jdef in cls._parentClass._joins: - join = jdef.withClass(cls) - jname = join.joinMethodName - jarn = join.addRemoveName - setattr(cls, getterName(jname), - eval('lambda self: self._parent.%s' % jname)) - if hasattr(join, 'remove'): - setattr(cls, 'remove' + jarn, - eval('lambda self,o: self._parent.remove%s(o)' % jarn)) - if hasattr(join, 'add'): - setattr(cls, 'add' + jarn, - eval('lambda self,o: self._parent.add%s(o)' % jarn)) - if childUpdate: - makeProperties(cls) - return - + def addJoin(cls, joinDef): # The name of the method we'll create. If it's # automatically generated, it's generated by the # join class. @@ -713,11 +879,6 @@ if cls._SO_finishedClassCreation: makeProperties(cls) - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic join as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addJoin(joinDef, childUpdate=True) - addJoin = classmethod(addJoin) def delJoin(cls, joinDef): @@ -745,11 +906,6 @@ if cls._SO_finishedClassCreation: delattr(cls, meth) - #DSM: Update each child class if needed - #DSM: and delete properties for this join - for c in cls._childClasses.values(): - delattr(c, meth) - delJoin = classmethod(delJoin) def _init(self, id, connection=None, selectResults=None): @@ -1014,29 +1170,6 @@ self._create(id, **kw) def _create(self, id, **kw): - - #DSM: If we were called by a children class, - #DSM: we must retreive the properties dictionary. - #DSM: Note: we can't use the ** call paremeter directly - #DSM: as we must be able to delete items from the dictionary - #DSM: (and our children must know that the items were removed!) - fromChild = False - if kw.has_key('kw'): - kw = kw['kw'] - fromChild = True - #DSM: If we are the children of an inheritable class, - #DSM: we must first create our parent - if self._parentClass: - parentClass = self._parentClass - parent_kw = dict( - [(name, value) for (name, value) in kw.items() - if hasattr(parentClass, name) - ] - ) - self._parent = parentClass(kw=parent_kw) - self._parent.childName = self._className - id = self._parent.id - self._SO_creating = True self._SO_createValues = {} self._SO_validatorState = SQLObjectState(self) @@ -1129,7 +1262,7 @@ lazyColumns=False, reversed=False, distinct=False, connection=None): - return SelectResults(cls, clause, + return cls.SelectResultsClass(cls, clause, clauseTables=clauseTables, orderBy=orderBy, limit=limit, @@ -1141,7 +1274,7 @@ def selectBy(cls, connection=None, **kw): conn = connection or cls._connection - return SelectResults(cls, + return cls.SelectResultsClass(cls, conn._SO_columnClause(cls, kw), connection=conn) @@ -1314,222 +1447,154 @@ cls._connection = value setConnection = classmethod(setConnection) -def capitalize(name): - return name[0].capitalize() + name[1:] +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults -def setterName(name): - return '_set_%s' % name -def rawSetterName(name): - return '_SO_set_%s' % name -def getterName(name): - return '_get_%s' % name -def rawGetterName(name): - return '_SO_get_%s' % name -def instanceName(name): - return '_SO_val_%s' % name + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) -try: - basestring -except NameError: # Python 2.2 - basestring = (types.StringType, types.UnicodeType) + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val -class SelectResults(object): + get = classmethod(get) - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - self.sourceClass = sourceClass - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - self.clause = clause - tablesDict = sqlbuilder.tablesUsedDict(self.clause) - tablesDict[sourceClass._table] = 1 - orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): - tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) - #DSM: if this class has a parent, we need to link it - #DSM: and be sure the parent is in the table list. - #DSM: The following code is before clauseTables - #DSM: because if the user uses clauseTables - #DSM: (and normal string SELECT), he must know what he wants - #DSM: and will do himself the relationship between classes. - if type(self.clause) is not str: - tableRegistry = {} - allClasses = classregistry.registry(sourceClass._registry).allClasses() - for registryClass in allClasses: - if registryClass._table in tablesDict: - #DSM: By default, no parents are needed for the clauses - tableRegistry[registryClass] = registryClass - for registryClass in allClasses: - if registryClass._table in tablesDict: - currentClass = registryClass - while currentClass._parentClass: - currentClass = currentClass._parentClass - if tableRegistry.has_key(currentClass): - #DSM: Must keep the last parent needed - #DSM: (to limit the number of join needed) - tableRegistry[registryClass] = currentClass - #DSM: Remove this class as it is a parent one - #DSM: of a needed children - del tableRegistry[currentClass] - #DSM: Table registry contains only the last children - #DSM: or standalone classes - parentClause = [] - for (currentClass, minParentClass) in tableRegistry.items(): - while currentClass != minParentClass and currentClass._parentClass: - parentClass = currentClass._parentClass - parentClause.append(currentClass.q.id == parentClass.q.id) - currentClass = parentClass - tablesDict[currentClass._table] = 1 - self.clause = reduce(sqlbuilder.AND, parentClause, clause) + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return - if clauseTables: - for table in clauseTables: - tablesDict[table] = 1 - self.clauseTables = clauseTables - self.tables = tablesDict.keys() - self.ops = ops - if self.ops.get('orderBy', NoDefault) is NoDefault: - self.ops['orderBy'] = sourceClass._defaultOrder - orderBy = self.ops['orderBy'] - if isinstance(orderBy, list) or isinstance(orderBy, tuple): - orderBy = map(self._mungeOrderBy, orderBy) - else: - orderBy = self._mungeOrderBy(orderBy) - #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy'])) - self.ops['dbOrderBy'] = orderBy - if ops.has_key('connection') and ops['connection'] is None: - del ops['connection'] + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) - def _mungeOrderBy(self, orderBy): - if isinstance(orderBy, str) and orderBy.startswith('-'): - orderBy = orderBy[1:] - desc = True - else: - desc = False - if isinstance(orderBy, (str, unicode)): - if self.sourceClass._SO_columnDict.has_key(orderBy): - val = self.sourceClass._SO_columnDict[orderBy].dbName - if desc: - return '-' + val - else: - return val - else: - if desc: - return '-' + orderBy - else: - return orderBy - else: - return orderBy + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) - def clone(self, **newOps): - ops = self.ops.copy() - ops.update(newOps) - return self.__class__(self.sourceClass, self.clause, - self.clauseTables, **ops) + addColumn = classmethod(addColumn) - def orderBy(self, orderBy): - return self.clone(orderBy=orderBy) + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) - def connection(self, conn): - return self.clone(connection=conn) + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) - def limit(self, limit): - return self[:limit] + delColumn = classmethod(delColumn) - def lazyColumns(self, value): - return self.clone(lazyColumns=value) + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return - def reversed(self): - return self.clone(reversed=not self.ops.get('reversed', False)) + super(InheritableSQLObject, cls).addJoin(joinDef) - def distinct(self): - return self.clone(distinct=True) + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) - def __getitem__(self, value): - if type(value) is type(slice(1)): - assert not value.step, "Slices do not support steps" - if not value.start and not value.stop: - # No need to copy, I'm immutable - return self + addJoin = classmethod(addJoin) - # Negative indexes aren't handled (and everything we - # don't handle ourselves we just create a list to - # handle) - if (value.start and value.start < 0) \ - or (value.stop and value.stop < 0): - if value.start: - if value.stop: - return list(self)[value.start:value.stop] - return list(self)[value.start:] - return list(self)[:value.stop] + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) - if value.start: - assert value.start >= 0 - start = self.ops.get('start', 0) + value.start - if value.stop is not None: - assert value.stop >= 0 - if value.stop < value.start: - # an empty result: - end = start - else: - end = value.stop + self.ops.get('start', 0) - if self.ops.get('end', None) is not None \ - and value['end'] < end: - # truncated by previous slice: - end = self.ops['end'] - else: - end = self.ops.get('end', None) - else: - start = self.ops.get('start', 0) - end = value.stop + start - if self.ops.get('end', None) is not None \ - and self.ops['end'] < end: - end = self.ops['end'] - return self.clone(start=start, end=end) - else: - if value < 0: - return list(iter(self))[value] - else: - start = self.ops.get('start', 0) + value - return list(self.clone(start=start, end=start+1))[0] + delJoin = classmethod(delJoin) - def __iter__(self): - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.iterSelect(self) + def _create(self, id, **kw): - def accumulate(self, expression): - """ Use an accumulate expression to select result - using another SQL select through current - connection. - Return the accumulate result - """ - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self,expression) + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self._className + id = self._parent.id - def count(self): - """ Counting elements of current select results """ - assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" + super(InheritableSQLObject, self)._create(id, **kw) - count = self.accumulate('COUNT(*)') - if self.ops.get('start'): - count -= self.ops['start'] - if self.ops.get('end'): - count = min(self.ops['end'] - self.ops.get('start', 0), count) - return count + def destroySelf(self): + # Kills this object. Kills it dead! + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() - def sum(self, attribute): - """ Making the sum of a given select result attribute. - `attribute` can be a column name (like 'a_column') - or a dot-q attribute (like Table.q.aColumn) - """ - if type(attribute) == type(''): - expression = 'SUM(%s)' % attribute - else: - expression = sqlbuilder.func.SUM(attribute) - return self.accumulate(expression) +def capitalize(name): + return name[0].capitalize() + name[1:] +def setterName(name): + return '_set_%s' % name +def rawSetterName(name): + return '_SO_set_%s' % name +def getterName(name): + return '_get_%s' % name +def rawGetterName(name): + return '_SO_get_%s' % name +def instanceName(name): + return '_SO_val_%s' % name + + class SQLObjectState(object): def __init__(self, soObject): @@ -1569,6 +1634,6 @@ else: return obj -__all__ = ['NoDefault', 'SQLObject', - 'getID', 'getObject', +__all__ = ['SQLObject', 'InheritableSQLObject', + 'NoDefault', 'getID', 'getObject', 'SQLObjectNotFound'] Modified: home/phd/SQLObject/inheritance/tests/test_inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/tests/test_inheritance.py 2005-02-03 12:13:26 UTC (rev 568) +++ home/phd/SQLObject/inheritance/tests/test_inheritance.py 2005-02-03 12:14:28 UTC (rev 569) @@ -1,9 +1,9 @@ -from sqlobject import SQLObject, StringCol +from sqlobject import InheritableSQLObject, StringCol from test_sqlobject import SQLObjectTest, main # 'i' prepended to distinguish it from test_sqlobject.Person -class iPerson(SQLObject): +class iPerson(InheritableSQLObject): _inheritable = 1 # I want this class to be inherited firstName = StringCol() lastName = StringCol() |