Author: phd
Date: 2005-11-02 10:46:14 +0000 (Wed, 02 Nov 2005)
New Revision: 1186
Added:
home/phd/SQLObject/paramstyles/sqlobject/include/pydispatch/
home/phd/SQLObject/paramstyles/sqlobject/tests/test_new_joins.py
Modified:
home/phd/SQLObject/paramstyles/sqlobject/declarative.py
home/phd/SQLObject/paramstyles/sqlobject/events.py
home/phd/SQLObject/paramstyles/sqlobject/joins.py
home/phd/SQLObject/paramstyles/sqlobject/main.py
home/phd/SQLObject/paramstyles/sqlobject/manager/command.py
Log:
Merged patches from the revisions 1181:1185 from the trunk: new joins
Modified: home/phd/SQLObject/paramstyles/sqlobject/declarative.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/declarative.py 2005-11-02 04:46:27 UTC (rev 1185)
+++ home/phd/SQLObject/paramstyles/sqlobject/declarative.py 2005-11-02 10:46:14 UTC (rev 1186)
@@ -33,6 +33,7 @@
"""
from __future__ import generators
+import events
__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative')
@@ -88,10 +89,16 @@
class DeclarativeMeta(type):
def __new__(meta, class_name, bases, new_attrs):
+ post_funcs = []
+ events.send(events.ClassCreateSignal,
+ bases[0], class_name, bases, new_attrs,
+ post_funcs)
cls = type.__new__(meta, class_name, bases, new_attrs)
if new_attrs.has_key('__classinit__'):
cls.__classinit__ = staticmethod(cls.__classinit__.im_func)
cls.__classinit__(cls, new_attrs)
+ for func in post_funcs:
+ func(cls)
return cls
class Declarative(object):
@@ -190,3 +197,8 @@
__repr__ = classinstancemethod(__repr__)
+def setup_attributes(cls, new_attrs):
+ for name, value in new_attrs.items():
+ if hasattr(value, '__addtoclass__'):
+ value.__addtoclass__(cls, name)
+
Modified: home/phd/SQLObject/paramstyles/sqlobject/events.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/events.py 2005-11-02 04:46:27 UTC (rev 1185)
+++ home/phd/SQLObject/paramstyles/sqlobject/events.py 2005-11-02 10:46:14 UTC (rev 1186)
@@ -1,15 +1,24 @@
-from dispatch import dispatcher
+from sqlobject.include.pydispatch import dispatcher
from weakref import ref
subclassClones = {}
def listen(receiver, soClass, signal, alsoSubclasses=True):
+ """
+ Listen for the given ``signal`` on the SQLObject subclass
+ ``soClass``, calling ``receiver()`` when ``send(soClass, signal,
+ ...)`` is called.
+
+ If ``alsoSubclasses`` is true, receiver will also be called when
+ an event is fired on any subclass.
+ """
dispatcher.connect(receiver, signal=signal, sender=soClass)
weakSOClass = ref(soClass)
weakReceiver = ref(receiver)
subclassClones.setdefault(weakSOClass, []).append((weakReceiver, signal))
+# We export this function:
send = dispatcher.send
class Signal(object):
Copied: home/phd/SQLObject/paramstyles/sqlobject/include/pydispatch (from rev 1185, SQLObject/trunk/sqlobject/include/pydispatch)
Modified: home/phd/SQLObject/paramstyles/sqlobject/joins.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/joins.py 2005-11-02 04:46:27 UTC (rev 1185)
+++ home/phd/SQLObject/paramstyles/sqlobject/joins.py 2005-11-02 10:46:14 UTC (rev 1186)
@@ -3,8 +3,10 @@
import styles
import classregistry
from col import popKey
+import events
-__all__ = ['MultipleJoin', 'SQLMultipleJoin', 'RelatedJoin', 'SQLRelatedJoin', 'SingleJoin']
+__all__ = ['MultipleJoin', 'SQLMultipleJoin', 'RelatedJoin', 'SQLRelatedJoin',
+ 'SingleJoin', 'ManyToMany', 'OneToMany']
def getID(obj):
try:
@@ -263,3 +265,209 @@
class SingleJoin(Join):
baseClass = SOSingleJoin
+
+
+
+
+class ManyToMany(object):
+
+ def __init__(self, otherClassName,
+ intermediateTable=None,
+ joinColumn=None,
+ otherColumn=None,
+ createJoinTable=True):
+ self.otherClassName = otherClassName
+ self.intermediateTable = intermediateTable
+ self.joinColumn = joinColumn
+ self.otherColumn = otherColumn
+ self.createJoinTable = createJoinTable
+
+ def __addtoclass__(self, soClass, name):
+ setattr(soClass, name,
+ SOManyToMany(soClass, name=name,
+ otherClassName=self.otherClassName,
+ intermediateTable=self.intermediateTable,
+ joinColumn=self.joinColumn,
+ otherColumn=self.otherColumn,
+ createJoinTable=self.createJoinTable))
+
+class SOManyToMany(object):
+
+ def __init__(self, soClass, name, otherClassName,
+ intermediateTable, joinColumn, otherColumn,
+ createJoinTable):
+ self.name = name
+ self.intermediateTable = intermediateTable
+ self.joinColumn = joinColumn
+ self.otherColumn = otherColumn
+ self.createJoinTable = createJoinTable
+ self.soClass = self.otherClass = None
+ classregistry.registry(
+ soClass.sqlmeta.registry).addClassCallback(
+ otherClassName, self._setOtherClass)
+ classregistry.registry(
+ soClass.sqlmeta.registry).addClassCallback(
+ soClass.__name__, self._setThisClass)
+
+ def _setThisClass(self, soClass):
+ self.soClass = soClass
+ if self.soClass and self.otherClass:
+ self._finishSet()
+
+ def _setOtherClass(self, otherClass):
+ self.otherClass = otherClass
+ if self.soClass and self.otherClass:
+ self._finishSet()
+
+ def _finishSet(self):
+ if self.intermediateTable is None:
+ names = [self.soClass.sqlmeta.table,
+ self.otherClass.sqlmeta.table]
+ names.sort()
+ self.intermediateTable = '%s_%s' % (names[0], names[1])
+ if not self.otherColumn:
+ self.otherColumn = self.soClass.sqlmeta.style.tableReference(
+ self.otherClass.sqlmeta.table)
+ if not self.joinColumn:
+ self.joinColumn = styles.getStyle(
+ self.soClass).tableReference(self.soClass.sqlmeta.table)
+ events.listen(self.event_CreateTableSignal,
+ self.soClass, events.CreateTableSignal)
+ events.listen(self.event_CreateTableSignal,
+ self.otherClass, events.CreateTableSignal)
+
+ def __get__(self, obj, type):
+ if obj is None:
+ return self
+ query = (
+ (self.otherClass.q.id ==
+ sqlbuilder.Field(self.intermediateTable, self.otherColumn))
+ & (sqlbuilder.Field(self.intermediateTable, self.joinColumn)
+ == obj.id))
+ select = self.otherClass.select(query)
+ return _ManyToManySelectWrapper(obj, self, select)
+
+ def __sqlrepr__(self, dbname):
+ return self.query.__sqlrepr__(self, dbname)
+
+ def event_CreateTableSignal(self, soClass, connection, extra_sql,
+ post_funcs):
+ if self.createJoinTable:
+ post_funcs.append(self.event_CreateTableSignalPost)
+
+ def event_CreateTableSignalPost(self, soClass, connection):
+ if connection.tableExists(self.intermediateTable):
+ return
+ connection._SO_createJoinTable(self)
+
+class _ManyToManySelectWrapper(object):
+
+ def __init__(self, forObject, join, select):
+ self.forObject = forObject
+ self.join = join
+ self.select = select
+
+ def __getattr__(self, attr):
+ # @@: This passes through private variable access too... should it?
+ # Also magic methods, like __str__
+ return getattr(self, select, attr)
+
+ def __repr__(self):
+ return '<%s for: %s>' % (self.__class__.__name__, repr(self.select))
+
+ def __str__(self):
+ return str(self.select)
+
+ def __iter__(self):
+ return iter(self.select)
+
+ def __getitem__(self, key):
+ return self.select[key]
+
+ def add(self, obj):
+ print "Add", obj, "to", self.forObject
+ obj._connection._SO_intermediateInsert(
+ self.join.intermediateTable,
+ self.join.joinColumn,
+ getID(self.forObject),
+ self.join.otherColumn,
+ getID(obj))
+
+ def remove(self, obj):
+ obj._connection._SO_intermediateDelete(
+ self.join.intermediateTable,
+ self.join.joinColumn,
+ getID(self.forObject),
+ self.join.otherColumn,
+ getID(obj))
+
+ def create(self, **kw):
+ obj = self.join.otherClass(**kw)
+ self.add(obj)
+ return obj
+
+class OneToMany(object):
+
+ def __init__(self, otherClassName, joinColumn=None):
+ self.otherClassName = otherClassName
+ self.joinColumn = joinColumn
+
+ def __addtoclass__(self, soClass, name):
+ setattr(soClass, name,
+ SOOneToMany(soClass, name=name,
+ otherClassName=self.otherClassName,
+ joinColumn=self.joinColumn))
+
+class SOOneToMany(object):
+
+ def __init__(self, soClass, name, otherClassName, joinColumn):
+ self.soClass = soClass
+ self.name = name
+ self.joinColumn = joinColumn
+ classregistry.registry(
+ soClass.sqlmeta.registry).addClassCallback(
+ otherClassName, self._setOtherClass)
+
+ def _setOtherClass(self, otherClass):
+ self.otherClass = otherClass
+ if not self.joinColumn:
+ self.joinColumn = styles.getStyle(
+ self.soClass).tableReference(self.soClass.sqlmeta.table)
+
+ def __get__(self, obj, type):
+ if obj is None:
+ return self
+ query = (
+ sqlbuilder.Field(self.otherClass.sqlmeta.table, self.joinColumn)
+ == obj.id)
+ select = self.otherClass.select(query)
+ return _OneToManySelectWrapper(obj, self, select)
+
+class _OneToManySelectWrapper(object):
+
+ def __init__(self, forObject, join, select):
+ self.forObject = forObject
+ self.join = join
+ self.select = select
+
+ def __getattr__(self, attr):
+ # @@: This passes through private variable access too... should it?
+ # Also magic methods, like __str__
+ return getattr(self, select, attr)
+
+ def __repr__(self):
+ return '<%s for: %s>' % (self.__class__.__name__, repr(self.select))
+
+ def __str__(self):
+ return str(self.select)
+
+ def __iter__(self):
+ return iter(self.select)
+
+ def __getitem__(self, key):
+ return self.select[key]
+
+ def create(self, **kw):
+ kw[self.join.joinColumn] = self.forObject.id
+ return self.join.otherClass(**kw)
+
Modified: home/phd/SQLObject/paramstyles/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/main.py 2005-11-02 04:46:27 UTC (rev 1185)
+++ home/phd/SQLObject/paramstyles/sqlobject/main.py 2005-11-02 10:46:14 UTC (rev 1186)
@@ -45,6 +45,7 @@
import index
import classregistry
import declarative
+import events
from sresults import SelectResults
from formencode import schema, compound
@@ -236,10 +237,16 @@
for attr in cls._unshared_attributes:
if not new_attrs.has_key(attr):
setattr(cls, attr, None)
+ declarative.setup_attributes(cls, new_attrs)
def __init__(self, instance):
self.instance = instance
+ def send(cls, signal, *args, **kw):
+ events.send(signal, cls.soClass, *args, **kw)
+
+ send = classmethod(send)
+
def setClass(cls, soClass):
cls.soClass = soClass
if not cls.style:
@@ -289,6 +296,9 @@
########################################
def addColumn(cls, columnDef, changeSchema=False, connection=None):
+ post_funcs = []
+ cls.send(events.AddColumnSignal, cls.soClass, connection,
+ columnDef.name, columnDef, changeSchema, post_funcs)
sqlmeta = cls
soClass = cls.soClass
del cls
@@ -396,9 +406,6 @@
setattr(soClass, setterName(name)[:-2], setter)
sqlmeta._plainForeignSetters[name[:-2]] = 1
- # We'll need to put in a real reference at
- # some point. See needSet at the top of the
- # file for more on this.
classregistry.registry(sqlmeta.registry).addClassCallback(
column.foreignKey,
lambda foreign, me, attr: setattr(me, attr, foreign),
@@ -415,6 +422,9 @@
if soClass._SO_finishedClassCreation:
makeProperties(soClass)
+ for func in post_funcs:
+ func(soClass, column)
+
addColumn = classmethod(addColumn)
def addColumnsFromDatabase(sqlmeta, connection=None):
@@ -438,6 +448,9 @@
else:
raise IndexError(
"Column with definition %r not found" % column)
+ post_funcs = []
+ cls.send(events.DeleteColumnSignal, connection, column.name, column,
+ post_funcs)
name = column.name
del sqlmeta.columns[name]
del sqlmeta.columnDefinitions[name]
@@ -465,6 +478,9 @@
if soClass._SO_finishedClassCreation:
unmakeProperties(soClass)
+ for func in post_funcs:
+ func(soClass, column)
+
delColumn = classmethod(delColumn)
########################################
@@ -666,6 +682,8 @@
def __classinit__(cls, new_attrs):
+ declarative.setup_attributes(cls, new_attrs)
+
# This is true if we're initializing the SQLObject class,
# instead of a subclass:
is_base = cls.__bases__ == (object,)
@@ -1039,6 +1057,11 @@
# in the database, and we can't insert it until all
# the parts are set. So we just keep them in a
# dictionary until later:
+ d = {name: value}
+ self.sqlmeta.send(events.RowUpdateSignal, self, d)
+ if len(d) != 1 or name not in d:
+ return self.set(**d)
+ value = d[name]
if from_python:
dbValue = from_python(value, self._SO_validatorState)
else:
@@ -1059,6 +1082,7 @@
setattr(self, instanceName(name), value)
def set(self, **kw):
+ self.sqlmeta.send(events.RowUpdateSignal, self, kw)
# set() is used to update multiple values at once,
# potentially with one SQL statement if possible.
@@ -1179,11 +1203,15 @@
if kw.has_key('_SO_fetch_no_create'):
return
+ post_funcs = []
+ self.sqlmeta.send(events.RowCreateSignal, kw, post_funcs)
+
# Pass the connection object along if we were given one.
if kw.has_key('connection'):
self._connection = kw['connection']
self.sqlmeta._perConnection = True
del kw['connection']
+
self._SO_writeLock = threading.Lock()
if kw.has_key('id'):
@@ -1193,6 +1221,8 @@
id = None
self._create(id, **kw)
+ for func in post_funcs:
+ func(self)
def _create(self, id, **kw):
@@ -1306,9 +1336,17 @@
conn = connection or cls._connection
if ifExists and not conn.tableExists(cls.sqlmeta.table):
return
+ extra_sql = []
+ post_funcs = []
+ cls.sqlmeta.send(events.DropTableSignal, cls, connection,
+ extra_sql, post_funcs)
conn.dropTable(cls.sqlmeta.table, cascade)
if dropJoinTables:
cls.dropJoinTables(ifExists=ifExists, connection=conn)
+ for sql in extra_sql:
+ connection.query(sql)
+ for func in post_funcs:
+ func(cls, conn)
dropTable = classmethod(dropTable)
def createTable(cls, ifNotExists=False, createJoinTables=True,
@@ -1317,14 +1355,21 @@
conn = connection or cls._connection
if ifNotExists and conn.tableExists(cls.sqlmeta.table):
return
+ extra_sql = []
+ post_funcs = []
+ cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
+ extra_sql, post_funcs)
constraints = conn.createTable(cls)
+ extra_sql.extend(constraints)
if createJoinTables:
cls.createJoinTables(ifNotExists=ifNotExists,
connection=conn)
if createIndexes:
cls.createIndexes(ifNotExists=ifNotExists,
connection=conn)
- return constraints
+ for func in post_funcs:
+ func(cls, conn)
+ return extra_sql
createTable = classmethod(createTable)
def createTableSQL(cls, createJoinTables=True, connection=None,
@@ -1419,6 +1464,7 @@
clearTable = classmethod(clearTable)
def destroySelf(self):
+ self.sqlmeta.send(events.RowDestroySignal, self)
# Kills this object. Kills it dead!
depends = []
klass = self.__class__
Modified: home/phd/SQLObject/paramstyles/sqlobject/manager/command.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/manager/command.py 2005-11-02 04:46:27 UTC (rev 1185)
+++ home/phd/SQLObject/paramstyles/sqlobject/manager/command.py 2005-11-02 10:46:14 UTC (rev 1186)
@@ -422,8 +422,7 @@
def config_from_egg(self, egg_spec, warn_no_sqlobject=True):
import pkg_resources
- pkg_resources.require(egg_spec)
- dist = pkg_resources.working_set.find(pkg_resources.Requirement(egg_spec))
+ dist = pkg_resources.get_distribution(egg_spec)
if not dist.has_metadata('sqlobject.txt'):
if warn_no_sqlobject:
print 'No sqlobject.txt in %s egg info' % egg_spec
Copied: home/phd/SQLObject/paramstyles/sqlobject/tests/test_new_joins.py (from rev 1185, SQLObject/trunk/sqlobject/tests/test_new_joins.py)
|