Author: phd
Date: 2005-02-10 19:46:14 +0000 (Thu, 10 Feb 2005)
New Revision: 600
Added:
trunk/SQLObject/docs/Inheritance.txt
trunk/SQLObject/sqlobject/inheritance/
trunk/SQLObject/sqlobject/inheritance/__init__.py
trunk/SQLObject/sqlobject/inheritance/iteration.py
trunk/SQLObject/sqlobject/inheritance/tests/
trunk/SQLObject/sqlobject/inheritance/tests/__init__.py
trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py
Modified:
trunk/SQLObject/docs/Authors.txt
trunk/SQLObject/docs/FAQ.txt
trunk/SQLObject/sqlobject/classregistry.py
trunk/SQLObject/sqlobject/col.py
trunk/SQLObject/sqlobject/dbconnection.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/sresults.py
Log:
Inheritance patch!
Modified: trunk/SQLObject/docs/Authors.txt
===================================================================
--- trunk/SQLObject/docs/Authors.txt 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/docs/Authors.txt 2005-02-10 19:46:14 UTC (rev 600)
@@ -13,4 +13,7 @@
* James Ralston <jralston at hotmail.com>
* Sidnei da Silva <sidnei at awkly.org>
* Brad Bollenbach <brad at bbnet.ca>
+* Daniel Savard, Xsoli Inc <sqlobject at xsoli.com>
+* alexander smishlajev <alex at ank-sia.com>
+* Yaroslav Samchuk <yarcat at ank-sia.com>
* Oleg Broytmann <phd at phd.pp.ru>
Modified: trunk/SQLObject/docs/FAQ.txt
===================================================================
--- trunk/SQLObject/docs/FAQ.txt 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/docs/FAQ.txt 2005-02-10 19:46:14 UTC (rev 600)
@@ -107,6 +107,11 @@
anyway).
+There is also another kind of inheritance. See Inheritance.txt_
+
+.. _Inheritance.txt: Inheritance.txt
+
+
Composite/Compound Attributes
-----------------------------
Added: trunk/SQLObject/docs/Inheritance.txt
===================================================================
--- trunk/SQLObject/docs/Inheritance.txt 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/docs/Inheritance.txt 2005-02-10 19:46:14 UTC (rev 600)
@@ -0,0 +1,134 @@
+:Author: Daniel Savard, XSOLI Inc.
+
+Inheritance
+-----------
+
+* As suggested by Ian Bicking, each child class now has the same
+ ID than the parent class. No more need for childID column and
+ parent foreignKey (with a small speed boost).
+* No more need to call getSubClass as the 'latest' child will always
+ be returned when an instance of a class is created.
+* This version now seems to works correctly with addColumn, delColumn,
+ addJoin and delJoin.
+
+
+The following code::
+
+ class Person(SQLObject):
+ _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()
+
+will generate the following tables::
+
+ CREATE TABLE person (
+ id INT PRIMARY KEY,
+ child_name TEXT,
+ first_name TEXT,
+ last_name TEXT
+ );
+
+ CREATE TABLE employee (
+ id INT PRIMARY KEY,
+ position TEXT
+ )
+
+
+A new class attribute ``_inheritable`` is added. When this new
+attribute is set to 1, the class is marked 'inheritable' and a new
+columns will automatically be added: childName (TEXT).
+
+Each class that inherits from a parent class will get the same ID as
+the parent class. So, there is no need to keep track of parent ID and
+child ID as they are the same.
+
+The column childName will contain the name of the child class (for
+exemple 'Employee'). This will permit to a class to always return its
+child class if available (a person that is also an employee will always
+return an instance of the employee class).
+
+For exemple, the following code::
+
+ p = Person(firstName='John', lastName='Doe')
+ e = Employee(firstName='Jane', lastName='Doe', position='Chief')
+ p2 = Person.get(1)
+
+Will create the following data in the database::
+
+ *Person*
+ id
+ child_name
+ first_name
+ last_name
+ 0
+ Null
+ John
+ Doe
+ 1
+ Employee
+ Jane
+ Doe
+
+
+ *Employee*
+ id
+ position
+ 1
+ Chief
+
+
+You will still be able to ask for the attribute normally:
+e.firstName will return Jane and setting it will write the new value in
+the person table.
+
+If you use p2, as p2 is a person object, you will get an employee
+object.
+person(0) will return a Person instance and will have the following
+attributes: firstName and lastName
+person(1) or employee(1) will each return the same Employee instance and
+will have the following attributes: firstName, lastName and position
+
+Also, deleting a person or an employee that are linked will destroy
+both entries as one would expect.
+
+The SQLObject q magic also work. Using this select are valid::
+
+ Employee.select(AND(Employee.q.firstName == 'Jane' Employee.q.position == 'Chief')) will return Jane Doe
+ Employee.select(AND(Person.q.firstName == 'Jane', Employee.q.position == 'Chief')) will return Jane Doe
+ Employee.select(Employee.q.lastName == 'Doe') will only return Jane Doe (as Joe isn't an employee)
+ Person.select(Person.q.lastName == 'Doe') will return both entries.
+
+The SQL 'where' clause will contain additional clauses when used with
+'inherited' classes. These clauses are the link between the id and the
+parent id. This will look like the following request::
+
+ SELECT employee.id, person.first_name, person.last_name
+ FROM person, employee WHERE person.first_name = 'Jane'
+ AND employee.position = 'Chief' AND person.id = employee.id
+
+Some limitation or notice about this version:
+
+* Only simple inheritance will work. It is not possible to inherit
+ from multiple SQLObject classes.
+* It is possible to inherit from an inherited class and this will
+ work well. In the above exemple, you can have a Chief class that
+ inherits from Employee and all parents attributes will be
+ available through the Chief class.
+* You may not redefine columns in an inherited class (this
+ will raise an exception).
+* If you don't want 'childName' columns in your last class (one that
+ will never be inherited), you must set '_inheritable' to 0 in this
+ class.
+* I made it because I needed to be able to have automatic
+ inheritance with linked table.
+* This version works for me, it may not works for you. I tried to do
+ my best but it is possible that I broke some things... So, there
+ is no warranty that this version will work.
+* Thanks to Ian Bicking for SQLObject, this is a wonderful python
+ module.
+* If you have suggestion, bugs, or patch to this patch, you can
+ contact me at <sqlobject xsoli.com>
Modified: trunk/SQLObject/sqlobject/classregistry.py
===================================================================
--- trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -110,3 +110,6 @@
MasterRegistry = _MasterRegistry()
registry = MasterRegistry.registry
+
+def findClass(name, class_registry=None):
+ return registry(class_registry).getClass(name)
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/col.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -28,6 +28,7 @@
# arguments in this module, so we rename it:
import constraints as consts
from include import validators
+from classregistry import findClass
NoDefault = sqlbuilder.NoDefault
True, False = 1==1, 0==1
@@ -616,7 +617,6 @@
SOKeyCol.__init__(self, **kw)
def postgresCreateSQL(self):
- from main import findClass
sql = SOKeyCol.postgresCreateSQL(self)
other = findClass(self.foreignKey)
tName = other._table
@@ -642,7 +642,6 @@
return sql
def sybaseCreateSQL(self):
- from sqlobject.main import findClass
sql = SOKeyCol.sybaseCreateSQL(self)
other = findClass(self.foreignKey)
tName = other._table
@@ -654,7 +653,6 @@
return sql
def maxdbCreateSQL(self):
- from main import findClass
other = findClass(self.foreignKey)
fidName = self.dbName
#I assume that foreign key name is identical to the id of the reference table
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -15,6 +15,7 @@
from converters import sqlrepr
import urllib
import weakref
+from classregistry import findClass
warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used")
@@ -246,7 +247,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):
@@ -542,9 +543,7 @@
def __del__(self):
self._cleanup()
-
-
class Transaction(object):
def __init__(self, dbConnection):
@@ -581,7 +580,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):
Added: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -0,0 +1,192 @@
+from sqlobject import sqlbuilder
+from sqlobject import classregistry
+from sqlobject.main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName
+import iteration
+
+
+class InheritableSelectResults(SelectResults):
+ IterationClass = iteration.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.__class__.__name__
+ 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']
Added: trunk/SQLObject/sqlobject/inheritance/iteration.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -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:]
Added: trunk/SQLObject/sqlobject/inheritance/tests/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -0,0 +1 @@
+#
Added: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -0,0 +1,50 @@
+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 setup():
+ setupClass(Person)
+ setupClass(Employee)
+
+ Employee(firstName='Ian', lastName='Bicking', position='Project leader')
+ Person(firstName='Daniel', lastName='Savard')
+
+
+def test_inheritance():
+ setup()
+
+ 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
+
+
+def test_inheritance_select():
+ setup()
+
+ persons = Person.select(Person.q.firstName <> None)
+ assert persons.count() == 2
+
+ employees = Employee.select(Employee.q.firstName <> None)
+ assert employees.count() == 1
+
+ employees = Employee.select(Employee.q.position <> None)
+ assert employees.count() == 1
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -4,6 +4,14 @@
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 modules and 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
@@ -110,10 +118,6 @@
delFunc(obj, var)
break
-def findClass(name, registry=None):
- #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys())))
- return classregistry.registry(registry).getClass(name)
-
def findDependencies(name, registry=None):
depends = []
for klass in classregistry.registry(registry).allClasses():
@@ -181,8 +185,8 @@
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
@@ -260,6 +264,17 @@
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,
@@ -329,6 +344,40 @@
# 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[:]
@@ -419,6 +468,15 @@
if not is_base:
cls.q = sqlbuilder.SQLObjectTable(cls)
+ #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))
+
classregistry.registry(cls._registry).addClass(cls)
_style = _sqlmeta_attr('style')
@@ -488,7 +546,7 @@
# Here if the _get_columnName method isn't in the
# definition, we add it with the default
# _SO_get_columnName definition.
- if not hasattr(cls, getterName(name)):
+ if not hasattr(cls, getterName(name)) or (name == 'childName'):
setattr(cls, getterName(name), getter)
cls._SO_plainGetters[name] = 1
@@ -506,7 +564,7 @@
setattr(cls, '_SO_toPython_%s' % name, column.toPython)
setattr(cls, rawSetterName(name), setter)
# Then do the aliasing
- if not hasattr(cls, setterName(name)):
+ 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
@@ -1060,7 +1118,7 @@
lazyColumns=False, reversed=False,
distinct=False,
connection=None):
- return SelectResults(cls, clause,
+ return cls.SelectResultsClass(cls, clause,
clauseTables=clauseTables,
orderBy=orderBy,
limit=limit,
@@ -1072,7 +1130,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)
Modified: trunk/SQLObject/sqlobject/sresults.py
===================================================================
--- trunk/SQLObject/sqlobject/sresults.py 2005-02-10 18:46:08 UTC (rev 599)
+++ trunk/SQLObject/sqlobject/sresults.py 2005-02-10 19:46:14 UTC (rev 600)
@@ -1,6 +1,8 @@
import sqlbuilder
+import dbconnection
class SelectResults(object):
+ IterationClass = dbconnection.Iteration
def __init__(self, sourceClass, clause, clauseTables=None,
**ops):
|