event listener not working for sessionmaker, or any Session subclass
import sqlalchemy as sa
import sqlalchemy.exc as saexc
import sqlalchemy.ext.declarative as sadec
import sqlalchemy.sql as sasql
import sqlalchemy.orm as saorm
@sa.event.listens_for(saorm.Session, 'before_flush')
def handler1(session, flush_context, instances):
print 'handler1'
engine = sa.create_engine('sqlite://')
meta = sa.MetaData()
Base = sadec.declarative_base(metadata=meta)
class MySession(saorm.Session):
pass
# works
#sess = saorm.Session(bind=engine, autoflush=False)
# fails
#sessmaker = saorm.sessionmaker(bind = engine, autoflush = False)
#sess = sessmaker()
# fails
sess = MySession(bind=engine, autoflush=False)
class Anything(Base):
__tablename__ = 'families'
id = sa.Column(sa.Integer, primary_key=True)
meta.create_all(bind=engine)
@sa.event.listens_for(saorm.Session, 'before_flush')
def handler2(session, flush_context, instances):
print 'handler2'
f = Anything()
sess.add(f)
sess.commit()
Comments (7)
-
reporter -
reporter this is not the fix but illustrates the issue, that new subclasses made after the fact don't get populated in the parent._clslevel mapping.
diff -r be71c73f614f9c316c0c02a98505a8c03d5ef788 lib/sqlalchemy/event.py --- a/lib/sqlalchemy/event.py Mon Mar 05 15:20:07 2012 -0500 +++ b/lib/sqlalchemy/event.py Wed Mar 07 11:28:21 2012 -0500 @@ -252,11 +252,20 @@ _exec_once = False def __init__(self, parent, target_cls): - self.parent_listeners = parent._clslevel[target_cls](target_cls) + #self.parent_listeners = parent._clslevel[target_cls](target_cls) + self.target_cls = target_cls + self.parent = parent self.name = parent.__name__ self.listeners = [ self.propagate = set() + @property + def parent_listeners(self): + lis = set() + for cls in self.target_cls.__mro__: + lis.update(self.parent._clslevel[cls](] )) + return list(lis) + def exec_once(self, *args, **kw): """Execute this event, but only if it has not been executed already for this collection."""
-
reporter here's a patch that fixes, though we're having a heisenbug in test.sql.test_metadata:CatchAllEventsTest with it. Running test_metadata by itself passes, running all of -w sql, the patch fails, so something with the "event cleanup" code might be getting in the way here.
diff -r be71c73f614f9c316c0c02a98505a8c03d5ef788 lib/sqlalchemy/event.py --- a/lib/sqlalchemy/event.py Mon Mar 05 15:20:07 2012 -0500 +++ b/lib/sqlalchemy/event.py Wed Mar 07 11:54:00 2012 -0500 @@ -206,21 +206,19 @@ assert isinstance(target, type), \ "Class-level Event targets must be classes." - stack = [target](target) - while stack: - cls = stack.pop(0) - stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).insert(0, obj) + self._clslevel[target](target).insert(0, obj) def append(self, obj, target, propagate): assert isinstance(target, type), \ "Class-level Event targets must be classes." - stack = [target](target) - while stack: - cls = stack.pop(0) - stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).append(obj) + self._clslevel[target](target).append(obj) + + def update_subclass(self, target): + clslevel = self._clslevel[target](target) + for cls in target.__mro__: + if cls in self._clslevel: + clslevel.extend(self._clslevel[cls](cls)) def remove(self, obj, target): stack = [target](target) @@ -252,6 +250,8 @@ _exec_once = False def __init__(self, parent, target_cls): + if target_cls not in parent._clslevel: + parent.update_subclass(target_cls) self.parent_listeners = parent._clslevel[target_cls](target_cls) self.name = parent.__name__ self.listeners = []
-
reporter yeah it's the "clear" - which needs to hold onto those lists that are already created. if we just make that check for parent._cls more liberal then it's OK:
diff -r be71c73f614f9c316c0c02a98505a8c03d5ef788 lib/sqlalchemy/event.py --- a/lib/sqlalchemy/event.py Mon Mar 05 15:20:07 2012 -0500 +++ b/lib/sqlalchemy/event.py Wed Mar 07 11:59:36 2012 -0500 @@ -206,21 +206,19 @@ assert isinstance(target, type), \ "Class-level Event targets must be classes." - stack = [target](target) - while stack: - cls = stack.pop(0) - stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).insert(0, obj) + self._clslevel[target](target).insert(0, obj) def append(self, obj, target, propagate): assert isinstance(target, type), \ "Class-level Event targets must be classes." - stack = [target](target) - while stack: - cls = stack.pop(0) - stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).append(obj) + self._clslevel[target](target).append(obj) + + def update_subclass(self, target): + clslevel = self._clslevel[target](target) + for cls in target.__mro__: + if cls in self._clslevel: + clslevel.extend(self._clslevel[cls](cls)) def remove(self, obj, target): stack = [target](target) @@ -252,6 +250,8 @@ _exec_once = False def __init__(self, parent, target_cls): + if not parent._clslevel[target_cls](target_cls): + parent.update_subclass(target_cls) self.parent_listeners = parent._clslevel[target_cls](target_cls) self.name = parent.__name__ self.listeners = []
this incurs a lot of unnecessary, instance-level calls to
update_subclass()
though, in the usual case that there's no events set up, so let's keep trying -
reporter this one should do it:
diff -r be71c73f614f9c316c0c02a98505a8c03d5ef788 lib/sqlalchemy/event.py --- a/lib/sqlalchemy/event.py Mon Mar 05 15:20:07 2012 -0500 +++ b/lib/sqlalchemy/event.py Wed Mar 07 12:13:36 2012 -0500 @@ -210,7 +210,11 @@ while stack: cls = stack.pop(0) stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).insert(0, obj) + if cls is not target and \ + cls not in self._clslevel: + self.update_subclass(cls) + else: + self._clslevel[cls](cls).insert(0, obj) def append(self, obj, target, propagate): assert isinstance(target, type), \ @@ -220,7 +224,18 @@ while stack: cls = stack.pop(0) stack.extend(cls.__subclasses__()) - self._clslevel[cls](cls).append(obj) + if cls is not target and \ + cls not in self._clslevel: + self.update_subclass(cls) + else: + self._clslevel[cls](cls).append(obj) + + + def update_subclass(self, target): + clslevel = self._clslevel[target](target) + for cls in target.__mro__: + if cls in self._clslevel: + clslevel.extend(self._clslevel[cls](cls)) def remove(self, obj, target): stack = [target](target) @@ -252,6 +267,8 @@ _exec_once = False def __init__(self, parent, target_cls): + if target_cls not in parent._clslevel: + parent.update_subclass(target_cls) self.parent_listeners = parent._clslevel[target_cls](target_cls) self.name = parent.__name__ self.listeners = []
-
reporter - changed status to resolved
-
reporter - removed milestone
Removing milestone: 0.7.6 (automated comment)
- Log in to comment
this is basically an issue with class generation in the event system.