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()
|