Thanks the for quick reply. Trying to mush it to a single file I accidentally found the source (not the solution unfortunately) of the problem. It only happens when I use FlaskSqlalchemy as the base class... so maybe it's something on their part? I attached a file (python 3). You must use flask-sqlalchemy master to run it, as it uses a custom base class which I think isn't released yet. I suppose their base class generates those events. I commented out the declarative_base usage that works (see attached file).
On Monday, August 15, 2016 at 4:57:19 PM UTC+3, Mike Bayer wrote: > > can you attach a quick single-file .py script that I can run, and will > show how you're getting this error? you might be in an "impossible" > block there, in that you are trying to add an event listener within that > same event, but I need to see if what you're doing is really something > that should be made to work. That is, perhaps declarative should use a > different system internally for the __declare_first__ in order to get > around this. > > > > On 08/15/2016 09:38 AM, Tom Kedem wrote: > > I'm trying to create a dynamic many_to_many relationship, inspired by: > > http://techspot.zzzeek.org/2011/05/17/magic-a-new-orm/ > > > https://bitbucket.org/zzzeek/pycon2014_atmcraft/src/f50cbe745a19/atmcraft/model/meta/?at=master > > > (orm.py and schema.py) > > > > I've got it working with just creating a secondary table in the > > __declare_first__ hook. > > > > Now I want it to be a "secondary" class and not just a table - and I'm > > having trouble. > > I've created a declared class in the __declare_first__ hook. Creating > > that class causes (Full stacktrace at the bottom): > > RuntimeError: deque mutated during iteration > > > > I suppose new events are being added when I do so. Searching google I > > came upon these: > > > https://bitbucket.org/zzzeek/sqlalchemy/issues/3163/use-deque-for-event-lists-so-that-remove > > > > https://bitbucket.org/zzzeek/sqlalchemy/commits/4a4cccfee5a2#Llib/sqlalchemy/event/api.pyT61 > > > > > But since I'm not creating those events explicitly (I suspect the > > declared class creates them), I'm not sure about how to proceed. > > > > Full stacktrace if it's helpful: > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\orm\instrumentation.py", > > > > line 347, in _new_state_if_none > > state = self._state_constructor(instance, self) > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\util\langhelpers.py", > > > > line 754, in __get__ > > obj.__dict__[self.__name__] = result = self.fget(obj) > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\orm\instrumentation.py", > > > > line 177, in _state_constructor > > self.dispatch.first_init(self, self.class_) > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\event\attr.py", > line > > 256, in __call__ > > fn(*args, **kw) > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\orm\mapper.py", > line > > 2943, in _event_on_first_init > > configure_mappers() > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\orm\mapper.py", > line > > 2822, in configure_mappers > > Mapper.dispatch._for_class(Mapper).before_configured() > > File > > > "C:\Users\vToMy\PycharmProjects\sqlalchemy\lib\sqlalchemy\event\attr.py", > line > > 217, in __call__ > > for fn in self.parent_listeners: > > RuntimeError: deque mutated during iteration > > > > > > -- > > You received this message because you are subscribed to the Google > > Groups "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send > > an email to sqlalchemy+...@googlegroups.com <javascript:> > > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>. > > To post to this group, send email to sqlal...@googlegroups.com > <javascript:> > > <mailto:sqlal...@googlegroups.com <javascript:>>. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.
from flask_sqlalchemy import SQLAlchemy, Model from sqlalchemy import Column, ForeignKeyConstraint from sqlalchemy import Integer from sqlalchemy_utils import get_declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declared_attr import inspect def _get_fk_column(ref_cls, refcol, nullable=None, primary_key=False): if nullable is None: nullable = not primary_key return Column(name="%s_%s" % (ref_cls.__tablename__, refcol.name), nullable=nullable, primary_key=primary_key) class References(object): """A mixin which creates foreign key references to related classes.""" _to_ref = set() _references = _to_ref.add _to_biref = set() _bireferences = _to_biref.add _association_tables = {} _foreign_keys_columns = {} already_called = False # TODO this is a patch @classmethod def __declare_first__(cls): """declarative hook called within the 'before_configure' mapper event.""" if cls.already_called: return for lcl, rmt, delete_orphan in cls._to_biref: lcl._reference_m2m(rmt, delete_orphan) cls._to_biref.clear() for lcl, rmt, fk_nullable in cls._to_ref: cls._decl_class_registry[lcl]._reference_table(cls._decl_class_registry[rmt], fk_nullable) cls._to_ref.clear() cls.add_constraints() cls.already_called = True @classmethod def _reference_table(cls, ref_cls, fk_nullable): """Create a foreign key reference from the local class to the given remote table. Adds column references to the declarative class and adds a ForeignKeyConstraint. """ ref_table = ref_cls.__table__ # create pairs of (Foreign key column, primary key column) cols = [(_get_fk_column(ref_cls, refcol, fk_nullable), refcol) for refcol in ref_table.primary_key] # set "tablename_colname = Foreign key Column" on the local class for col, refcol in cols: setattr(cls, col.name, col) # add a ForeignKeyConstraint([local columns], [remote columns]) cls.__table__.append_constraint(ForeignKeyConstraint(*zip(*cols))) cls._foreign_keys_columns[(cls, ref_cls)] = [i[0] for i in cols] @classmethod def _reference_m2m(cls, clsname, delete_orphan): tablename = '%s_%s' % (cls.__tablename__, clsname.__tablename__) local_pks = [(_get_fk_column(cls, refcol, primary_key=True), refcol) for refcol in cls.__table__.primary_key] remote_pks = [(_get_fk_column(clsname, refcol, primary_key=True), refcol) for refcol in clsname.__table__.primary_key] association_class_attributes = { '__tablename__': tablename, } for pk, refcol in local_pks + remote_pks: association_class_attributes[pk.name] = pk # TODO cascades? association_class_attributes[cls.__tablename__] = relationship(cls.__name__, foreign_keys=[i[0] for i in local_pks], backref=tablename) association_class_attributes[clsname.__tablename__] = relationship(clsname.__name__, foreign_keys=[i[0] for i in remote_pks], backref=tablename) association_class_name = cls.__name__ + clsname.__name__ base_class = get_declarative_base(cls) association_class = type(association_class_name, (base_class,), association_class_attributes) association_class.__table__.append_constraint(ForeignKeyConstraint(*zip(*local_pks))) association_class.__table__.append_constraint(ForeignKeyConstraint(*zip(*remote_pks))) cls._association_tables[(cls, clsname)] = association_class # TODO figure this out #if delete_orphan: # auto_delete_orphans(getattr(cls, rel_name)) @classmethod def add_constraints(cls): if hasattr(cls, 'constraints'): constraints = getattr(cls, 'constraints') if isinstance(constraints, tuple): constraint_class, mixed = constraints columns = [] for mix in mixed: if isinstance(mix, Column): columns.append(mix) elif isinstance(mix, str) and hasattr(cls, mix): attribute = getattr(cls, mix) columns += attribute.property.columns elif isinstance(mix, str) or inspect.isclass(mix): mix_class = cls._decl_class_registry[mix] if isinstance(mix, str) else mix foreign_key_columns = cls._foreign_keys_columns[(cls, mix_class)] columns += foreign_key_columns constraint = constraint_class(*columns) cls.__table__.append_constraint(constraint) def many_to_one(clsname, **kw): """Use an event to build a many-to-one relationship on a class. This makes use of the :meth:`.References._reference_table` method to generate a full foreign key relationship to the remote table. """ name = clsname.__name__ if inspect.isclass(clsname) else clsname fk_nullable = kw.pop('nullable', True) @declared_attr def m2o(cls): rel = relationship(name, foreign_keys=lambda: cls._foreign_keys_columns[(cls, clsname)], **kw) cls._references((cls.__name__, name, fk_nullable)) return rel return m2o def one_to_many(clsname, **kw): """Use an event to build a one-to-many relationship on a class. This makes use of the :meth:`.References._reference_table` method to generate a full foreign key relationship from the remote table. """ name = clsname.__name__ if inspect.isclass(clsname) else clsname fk_nullable = kw.pop('nullable', True) @declared_attr def o2m(cls): rel = relationship(name, foreign_keys=lambda: cls._foreign_keys_columns[(clsname, cls)], **kw) cls._references((name, cls.__name__, fk_nullable)) return rel return o2m def many_to_many(clsname, **kw): """Create an association table between parent/target as well as a relationship().""" name = clsname.__name__ if inspect.isclass(clsname) else clsname @declared_attr def m2m(cls): """Create an association table between parent/target as well as a relationship().""" delete_orphan = kw.pop('delete_orphan', False) rel_name = '%s_%s' % (cls.__tablename__, clsname.__tablename__) remote = clsname.__tablename__ association = association_proxy(rel_name, remote) cls._bireferences((cls, clsname, delete_orphan)) return association return m2m def add_constraint(constraint_class, columns, **kwargs): @declared_attr def foo(cls): cls._constraints.append((constraint_class, columns, kwargs)) class Base(Model, References): pass # This doesn't work! session_options = dict(autoflush=False) db = SQLAlchemy(model_class=Base, session_options=session_options) Base = db.Model # This works! # Base = declarative_base() class Tag(Base, References): __tablename__ = 'tag' id = Column(Integer, primary_key=True) class User(Base, References): __tablename__ = 'user' id = Column(Integer, primary_key=True) tags = many_to_many(Tag) user = User()