On 06/22/2016 04:53 PM, Angie Ellis wrote:
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
<http://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.

So the way you have it is that those are mixins, which is fine, and I guess that's how we need it because you want to keep multiple Base's in play at the same time. You don't need the __abstract__ keyword in that case. So to map that looks like the following POC:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import declared_attr


class AbstractFoo(object):
    __tablename__ = "foo"

    id = Column(Integer, primary_key=True)
    type = Column(String)

    @declared_attr
    def foo_bar_id(cls):
        return Column(Integer, ForeignKey("foo_bar.id"))

    @declared_attr
    def foo_bar(cls):
        return relationship("FooBar", backref=backref("foo"))

    __mapper_args__ = {"polymorphic_on": type}


class AbstractBar(AbstractFoo):
    __mapper_args__ = {"polymorphic_identity": "bar"}


class AbstractFooBar(object):
    __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

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)

s.add(Bar(foo_bar=FooBar()))

s.commit()
s.close()

b1 = s.query(Foo).first()
print b1
print b1.foo_bar






Thanks,
Angie

On Fri, Jun 3, 2016 at 12:38 PM, Mike Bayer <mike...@zzzcomputing.com
<mailto: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
        <http://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%2bunsubscr...@googlegroups.com>
        <mailto:sqlalchemy+unsubscr...@googlegroups.com
        <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>>.
        To post to this group, send email to sqlalchemy@googlegroups.com
        <mailto:sqlalchemy@googlegroups.com>
        <mailto: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
    <mailto:sqlalchemy%2bunsubscr...@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 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 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.

Reply via email to