Mike, Thank you for the response! I do have control over the external library. I like your idea having both the abstract classes and concrete classes available in the library because I have use cases for both in my applications. However, I'm not quite sure how to configure the inheritance and declarative base. What am I doing wrong here?
class AbstractFoo(object): __abstract__ = True __tablename__ = "foo" id = Column(Integer, primary_key=True) type = Column(String) foo_bar_id = Column(Integer, ForeignKey("foo_bar.id")) foo_bar = relationship("FooBar", backref=backref("foo")) __mapper_args__ = {"polymorphic_on": type} class AbstractBar(object): __abstract__ = True __mapper_args__ = {"polymorphic_identity": "bar"} class AbstractFooBar(object): __abstract__ = True __tablename__ = "foo_bar" id = Column(Integer, primary_key=True) Base = declarative_base() class Foo(AbstractFoo, Base): pass class Bar(AbstractBar, Foo): pass class FooBar(AbstractFooBar, Base): pass Does AbstractBar need to inherit from AbstractFoo? Do the abstract classes need to inherit their own declarative base? Do I need to change the inheritance order? The classes Foo, Bar, and FooBar are not getting mapped. Thanks, Angie On Fri, Jun 3, 2016 at 12:38 PM, Mike Bayer <mike...@zzzcomputing.com> wrote: > > > On 06/03/2016 02:44 PM, Angie E wrote: > >> Rather than creating mixin classes that models inherit from, I have a >> use case that requires me to configure classes the other way around. The >> classes that would normally be mixin classes need to be the classes that >> inherit from the models as well as the class that model objects are >> created from. This is because the models and the mapper configurations >> are in an external library from the main repository. I need to pass in >> the host for the engine from the main repository to the models library >> before any of the models are loaded so they can load with the >> declarative base already configured. >> > > I hope you are using reflection for these models, otherwise there's no > reason to have a depedency on the Engine for the model declaration. > > > After the engine information is > >> passed in, the session, Base class, and everything is created within a >> sort of base class that the models inherit from. Here is a simplified >> example: >> >> >> class SQLAlchemyBase(object): >> metadata = None >> Session = None >> Base = object >> sessionfactory = sessionmaker() >> >> def initialize(self, host): >> engine = create_engine(host) >> self.metadata = MetaData(bind=engine) >> self.Session = scoped_session(self.sessionfactory) >> self.Base = declarative_base(metadata=self.metadata) >> >> models = SQLAlchemyBase() >> >> (The models inherit from models.Base) >> >> >> So the SQLAlchemyBase will be imported into the main repository, the >> initialize method will be called, passing in the host for the engine, >> and the models can then be imported. >> > > Looks like no reflection taking place. I'd do away with the > MetaData(bind=engine), and just have the engine as part of the > sessionmaker(), and the sessionmaker() here also doesn't need to have > anything to do with the "library", if the "library" is just defining model > classes. > > The "bound metadata" pattern is highly discouraged in modern SQLAlchemy > because it leads to this kind of confusion, e.g. that one needs an Engine > in order to declare models. You don't. > > > > > > > The main repository has its own > >> classes with the same names as the models and have additional methods >> that a normal mixin class would have to extend functionality. However, I >> am unable to create model objects using the classes in the main >> repository because I can't get the mappers to play nice with this >> unusual inheritance that extends from the external models library. >> Additionally, in the models library, there are models that have multiple >> levels of inherited polymorphic relationships. Here is an example that >> is similar one of the more basic inherited polymorphic relationships: >> >> >> **Models Library** >> >> class Foo(models.Base): >> >> __tablename__ = "foo" >> id = Column(Integer, primary_key=True) >> type = Column(String) >> foo_bar_id = Column(Integer, ForeignKey("foo_bar.id")) >> foo_bar = relationship(Foo, backref=backref("foos")) >> >> __mapper_args__ = {"polymorphic_on": type} >> >> >> class Bar(Foo): >> >> __mapper_args__ = {"polymorphic_identity": "bar"} >> >> >> class FooBar(models.Base): >> >> __tablename__ = "foo_bar" >> id = Column(Integer, primary_key=True) >> >> >> **Main Repository** >> >> from separate_library.models import models, Foo as BaseFoo, Bar as >> BaseBar, FooBar as BaseFooBar >> >> >> class Foo(BaseFoo): >> >> @classmethod >> def custom_create_method(cls, **kw): >> foo_obj = cls(**kw) >> models.session.add(foo_obj) >> models.session.flush() >> >> >> class Bar(BaseBar): >> pass >> >> >> class FooBar(BaseFooBar): >> pass >> >> >> The original error I was getting was something like this: >> >> "InvalidRequestError: One or more mappers failed to initialize - can't >> proceed with initialization of other mappers. Original exception was: >> Multiple classes found for path "Foo" in the registry of this >> declarative base. Please use a fully module-qualified path." >> > > that only happens if someone is using a string name inside of > relationship(), like this: > > foobars = relationship("Foo") > > If the "library" is doing that, and you can't change it, easy solution, > use a different name for your "Foo" that's also in the 3rd party library. > Or declare your classes against a different base: > > Base = declarative_base() > > class Foo(other_package.Foo, Base): > # ... > > > class Bar(other_package.Bar, Base): > # ... > > I'm not 100% sure that will work but there's probably a way to make that > work. > > > > >> So I tried putting the full path in the relationships. Then it started >> giving me an error like this: >> > > oh, OK, then you're fine there. > > > >> "FlushError: Attempting to flush an item of type <class >> 'main_module.models.Foo'> as a member of collection "FooBar.foos". >> Expected an object of type <class 'separate_library.models.Foo'> or a >> polymorphic subclass of this type. If <class 'main_module.models.Foo'> >> is a subclass of <class 'separate_library.models.Foo'>, configure mapper >> "Mapper|Foo|foos" to load this subtype polymorphically, or set >> enable_typechecks=False to allow any subtype to be accepted for flush. >> > > Well, when this relationship loads, you'd like it to return a list of > main_module.models.Foo objects and not separate_library.models.Foo. Your > model here needs to have a polymorphic identity assigned so that it can do > this. I don't see those set up in your mappings, you need to put those in > explcitly. If you're trying to re-use the same names that the superclass > has, you might need to manipulate Foo.__mapper__.polymorphic_map directly, > not sure. > > > > >> Essentially, the main problem is getting the classes in the main module >> to point to and act like the model classes. For example, when I try to >> create relationships, it says it expected an object of type >> 'separate_library.models.Foo' instead of 'main_module.models.Foo'. >> > > other than using a different name, there are ways to manipulate the > registry that is doing this but they aren't publicly supported patterns, > unless the easy "use a different Base" trick works out here. > > Additionally, in the polymorphic relationships, I can't get the >> polymorphic_identity to populate for the polymorphic_on column. For >> example, Bar in the main repository will have the 'type' column empty >> when the object is initially created. >> > > when you make a subclass like you are doing, you need to give it its own > identity, so it at least needs to have "polymorphic_identity" set up > locally. > > Overall there would be a question here, which is: do you have control over > this external library? If so, I would design it to be used for subclassing > by another declarative system. Assign names like "AbstractFoo" to the > classes and additionally use the __abstract__ = True declarative flag to > mark them as such, then use @declared_attr around __mapper_args__ to assign > mapper arguments like polymorphic identity to subclasses properly. > > If you do *not* have control over this external library, then it is not > intended to be used in this way; the most preferable way to go about this > would be to get the maintainers of it to change it in order to allow it to > be used in this way. An easy way for the external library to work like > this would be that it supplies both the __abstract__ classes with nice > names like AbstractFoo, then for its own purposes it supplies its own Foo / > Bar concrete classes on top of a separate declarative Base, and you'd skip > using that part. > > > > >> One idea I tried was to add a metaclass to the declarative base in the >> models library and modify the mappers in the __init__ method during >> their initialization. I made progress this way, but haven't gotten it to >> work completely. >> > > There's proabably ways to make this work with more tricks and metaclasses > but they aren't supported on the SQLAlchemy end and they will be brittle, > they will break when declarative changes how it does things internally. > > > >> Sorry for the complex explanation, but this is a complex problem. I am >> not able to change anything about the models or the use case, >> unfortunately. I have to work within these constraints. >> > > If these are two different systems within your company, it would be a > better investment to get the upstream system to work more flexibly, rather > than invest efforts working around the system's limitations downstream. > But, if the overlapping names and the polymorphic identities are the only > problem you should be able to work around those. > > > > If anyone can > >> offer ideas on how to configure the mappers for the classes in the main >> repository to act like the models in the model library, I would be very >> grateful. >> >> -- >> 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 >> <mailto:sqlalchemy+unsubscr...@googlegroups.com>. >> To post to this group, send email to sqlalchemy@googlegroups.com >> <mailto:sqlalchemy@googlegroups.com>. >> 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 a topic in the > Google Groups "sqlalchemy" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/sqlalchemy/u_CiTJIK9Gs/unsubscribe. > To unsubscribe from this group and all its topics, 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. > -- 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.