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.
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+...@googlegroups.com
> <mailto:
sqlalchemy+...@googlegroups.com>.
> To post to this group, send email to
sqlal...@googlegroups.com
> <mailto:
sqlal...@googlegroups.com>.
> Visit this group at
https://groups.google.com/group/sqlalchemy.
> For more options, visit
https://groups.google.com/d/optout.