[SQL-CVS] r569 - in home/phd/SQLObject/inheritance: sqlobject tests
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
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() |