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
|