Author: phd
Date: 2008-05-20 07:43:34 -0600 (Tue, 20 May 2008)
New Revision: 3439
Modified:
SQLObject/branches/0.9/sqlobject/events.py
SQLObject/branches/0.9/sqlobject/main.py
SQLObject/branches/0.9/sqlobject/tests/test_events.py
Log:
Two patches by Diez B. Roggisch:
one for RowCreatedSignal together with InheritableSQLObject,
the other to run post_funcs after RowDestroySignal.
Modified: SQLObject/branches/0.9/sqlobject/events.py
===================================================================
--- SQLObject/branches/0.9/sqlobject/events.py 2008-05-20 13:22:12 UTC (rev 3438)
+++ SQLObject/branches/0.9/sqlobject/events.py 2008-05-20 13:43:34 UTC (rev 3439)
@@ -97,7 +97,14 @@
class RowDestroySignal(Signal):
"""
Called before an instance is deleted. Sender is the instance's
- class. Arguments are ``(instance)``. You cannot cancel the delete,
+ class. Arguments are ``(instance, post_funcs)``.
+
+ ``post_funcs`` is a list of callbacks, intended to have
+ functions appended to it, and are called without arguments. If
+ any of the post_funcs raises an exception, the deletion is only
+ affected if this will prevent a commit.
+
+ You cannot cancel the delete,
but you can raise an exception (which will probably cancel the
delete, but also cause an uncaught exception if not expected).
Modified: SQLObject/branches/0.9/sqlobject/main.py
===================================================================
--- SQLObject/branches/0.9/sqlobject/main.py 2008-05-20 13:22:12 UTC (rev 3438)
+++ SQLObject/branches/0.9/sqlobject/main.py 2008-05-20 13:43:34 UTC (rev 3439)
@@ -41,11 +41,19 @@
import events
from sresults import SelectResults
from formencode import schema, compound
+from util.threadinglocal import local
import sys
if sys.version_info[:3] < (2, 2, 0):
raise ImportError, "SQLObject requires Python 2.2.0 or later"
+"""
+This thread-local storage is needed for RowCreatedSignals. It gathers
+code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
+is created. See SQLObject._create
+"""
+_postponed_local = local()
+
NoDefault = sqlbuilder.NoDefault
class SQLObjectNotFound(LookupError): pass
@@ -1182,37 +1190,60 @@
return joinClass.get(id)
def __init__(self, **kw):
- # We shadow the sqlmeta class with an instance of sqlmeta
- # that points to us (our sqlmeta buddy object; where the
- # sqlmeta class is our class's buddy class)
- self.sqlmeta = self.__class__.sqlmeta(self)
- # The get() classmethod/constructor uses a magic keyword
- # argument when it wants an empty object, fetched from the
- # database. So we have nothing more to do in that case:
- if kw.has_key('_SO_fetch_no_create'):
- return
+ # If we are the outmost constructor of a hiearchy of
+ # InheritableSQLObjects (or simlpy _the_ constructor of a "normal"
+ # SQLObject), we create a threadlocal list that collects the
+ # RowCreatedSignals, and executes them if this very constructor is left
+ try:
+ _postponed_local.postponed_calls
+ postponed_created = False
+ except AttributeError:
+ _postponed_local.postponed_calls = []
+ postponed_created = True
- post_funcs = []
- self.sqlmeta.send(events.RowCreateSignal, kw, post_funcs)
+ try:
+ # We shadow the sqlmeta class with an instance of sqlmeta
+ # that points to us (our sqlmeta buddy object; where the
+ # sqlmeta class is our class's buddy class)
+ self.sqlmeta = self.__class__.sqlmeta(self)
+ # The get() classmethod/constructor uses a magic keyword
+ # argument when it wants an empty object, fetched from the
+ # database. So we have nothing more to do in that case:
+ if kw.has_key('_SO_fetch_no_create'):
+ return
- # 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']
+ post_funcs = []
+ self.sqlmeta.send(events.RowCreateSignal, kw, post_funcs)
- self._SO_writeLock = threading.Lock()
+ # 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']
- if kw.has_key('id'):
- id = self.sqlmeta.idType(kw['id'])
- del kw['id']
- else:
- id = None
+ self._SO_writeLock = threading.Lock()
- self._create(id, **kw)
- for func in post_funcs:
- func(self)
+ if kw.has_key('id'):
+ id = self.sqlmeta.idType(kw['id'])
+ del kw['id']
+ else:
+ id = None
+ self._create(id, **kw)
+
+ for func in post_funcs:
+ func(self)
+ finally:
+ # if we are the creator of the tl-storage, we
+ # have to exectute and under all circumstances
+ # remove the tl-storage
+ if postponed_created:
+ try:
+ for func in _postponed_local.postponed_calls:
+ func()
+ finally:
+ del _postponed_local.postponed_calls
+
def _create(self, id, **kw):
self.sqlmeta._creating = True
@@ -1276,9 +1307,11 @@
self._init(id)
post_funcs = []
kw = dict([('class', self.__class__), ('id', id)])
- self.sqlmeta.send(events.RowCreatedSignal, kw, post_funcs)
- for func in post_funcs:
- func(self)
+ def _send_RowCreatedSignal():
+ self.sqlmeta.send(events.RowCreatedSignal, kw, post_funcs)
+ for func in post_funcs:
+ func(self)
+ _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
def _SO_getID(self, obj):
return getID(obj)
@@ -1490,7 +1523,8 @@
clearTable = classmethod(clearTable)
def destroySelf(self):
- self.sqlmeta.send(events.RowDestroySignal, self)
+ post_funcs = []
+ self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
# Kills this object. Kills it dead!
klass = self.__class__
@@ -1552,6 +1586,9 @@
self._connection._SO_delete(self)
self._connection.cache.expire(self.id, self.__class__)
+ for func in post_funcs:
+ func()
+
def delete(cls, id, connection=None):
obj = cls.get(id, connection=connection)
obj.destroySelf()
Modified: SQLObject/branches/0.9/sqlobject/tests/test_events.py
===================================================================
--- SQLObject/branches/0.9/sqlobject/tests/test_events.py 2008-05-20 13:22:12 UTC (rev 3438)
+++ SQLObject/branches/0.9/sqlobject/tests/test_events.py 2008-05-20 13:43:34 UTC (rev 3439)
@@ -1,6 +1,7 @@
from sqlobject import *
from sqlobject.tests.dbtest import *
from sqlobject import events
+from sqlobject.inheritance import InheritableSQLObject
import sys
class EventTester(SQLObject):
@@ -32,7 +33,7 @@
assert watcher.log[0][1] == (EventTester,)
assert isinstance(watcher.log[0][2], dict)
assert isinstance(watcher.log[0][3], list)
-
+
def test_row_create():
setupClass(EventTester)
watcher = make_listen(events.RowCreateSignal)
@@ -41,14 +42,14 @@
assert len(watcher.log) == 2
assert watcher.log[0] == ({'name': 'foo'}, [])
-def test_row_destrow():
+def test_row_destroy():
setupClass(EventTester)
watcher = make_listen(events.RowDestroySignal)
f = EventTester(name='foo')
assert not watcher.log
f.destroySelf()
- assert watcher.log == [(f,)]
-
+ assert watcher.log == [(f, [])]
+
def test_row_update():
setupClass(EventTester)
watcher = make_listen(events.RowUpdateSignal)
@@ -59,7 +60,7 @@
assert watcher.log == [
(f, {'name': 'bar2'}),
(f, {'name': 'bar3'})]
-
+
def test_add_column():
setupClass(EventTester)
watcher = make_listen(events.AddColumnSignal)
@@ -72,3 +73,30 @@
False, [])
print zip(watcher.log[1], expect)
assert watcher.log[1] == expect
+
+
+class A(InheritableSQLObject):
+ a = IntCol()
+
+class B(A):
+ b = IntCol()
+
+class C(B):
+ c = IntCol()
+
+
+def test_inheritance_row_created():
+ setupClass(A)
+ setupClass(B)
+ setupClass(C)
+
+ def test_query(instance):
+ id = instance.id
+ A.get(id)
+
+ def signal(kwargs, postfuncs):
+ postfuncs.append(test_query)
+
+ events.listen(signal, A, events.RowCreatedSignal)
+
+ C(a=1, b=2, c=3)
|