Author: phd
Date: 2008-05-20 07:52:57 -0600 (Tue, 20 May 2008)
New Revision: 3443
Modified:
SQLObject/trunk/docs/DeveloperGuide.txt
SQLObject/trunk/docs/News.txt
SQLObject/trunk/sqlobject/events.py
SQLObject/trunk/sqlobject/main.py
SQLObject/trunk/sqlobject/tests/test_events.py
Log:
Merged patches by Diez B. Roggisch from revisions 3439-3440:
A bug fixed for RowCreatedSignal together with InheritableSQLObject:
run post_funcs after the entire hierarchy has been created;
run post_funcs after RowDestroySignal.
Modified: SQLObject/trunk/docs/DeveloperGuide.txt
===================================================================
--- SQLObject/trunk/docs/DeveloperGuide.txt 2008-05-20 13:51:18 UTC (rev 3442)
+++ SQLObject/trunk/docs/DeveloperGuide.txt 2008-05-20 13:52:57 UTC (rev 3443)
@@ -185,7 +185,8 @@
tests. So please, write tests; everything at least needs to be
exercised, even if the tests are absolutely complete.
-Documentation =======
+Documentation
+=============
Please write documentation. Documentation should live in the docs/
directory. Pudge converts documentation from Restructured Text to
Modified: SQLObject/trunk/docs/News.txt
===================================================================
--- SQLObject/trunk/docs/News.txt 2008-05-20 13:51:18 UTC (rev 3442)
+++ SQLObject/trunk/docs/News.txt 2008-05-20 13:52:57 UTC (rev 3443)
@@ -117,6 +117,11 @@
SQLObject 0.9.7
===============
+* A bug fixed for RowCreatedSignal together with InheritableSQLObject:
+ run post_funcs after the entire hierarchy has been created.
+
+* Run post_funcs after RowDestroySignal.
+
* Aggregate functions now honors 'distinct'.
SQLObject 0.9.6
Modified: SQLObject/trunk/sqlobject/events.py
===================================================================
--- SQLObject/trunk/sqlobject/events.py 2008-05-20 13:51:18 UTC (rev 3442)
+++ SQLObject/trunk/sqlobject/events.py 2008-05-20 13:52:57 UTC (rev 3443)
@@ -97,16 +97,16 @@
class RowDestroySignal(Signal):
"""
Called before an instance is deleted. Sender is the instance's
- class. Arguments are ``(instance, post_funcs)``.
+ 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
+ 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).
+ 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).
Note: this is not called when an instance is destroyed through
garbage collection.
Modified: SQLObject/trunk/sqlobject/main.py
===================================================================
--- SQLObject/trunk/sqlobject/main.py 2008-05-20 13:51:18 UTC (rev 3442)
+++ SQLObject/trunk/sqlobject/main.py 2008-05-20 13:52:57 UTC (rev 3443)
@@ -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
@@ -1154,37 +1162,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
@@ -1248,9 +1279,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)
Modified: SQLObject/trunk/sqlobject/tests/test_events.py
===================================================================
--- SQLObject/trunk/sqlobject/tests/test_events.py 2008-05-20 13:51:18 UTC (rev 3442)
+++ SQLObject/trunk/sqlobject/tests/test_events.py 2008-05-20 13:52:57 UTC (rev 3443)
@@ -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)
@@ -48,7 +49,7 @@
assert not watcher.log
f.destroySelf()
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)
|