I think it's OK to add __abstract__ to those mixins, in this case it seems like the (pretty complicated) class graph is adding up to something that causes declarative to see AbstractCar as mapped class thus a "car" table is added. Adding __abstract__ to AbstractCoupe seems to resolve.



On 06/27/2016 05:10 PM, Angie Ellis wrote:
I implemented the changes you suggested, but I am running into errors
with some polymorphic relations. Using a different theme, here is an
example:


class AbstractVehicle(object):
    __tablename__ = "vehicles"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    type = Column(String)

    __mapper_args__ = {"polymorphic_on": type}


class AbstractCar(AbstractVehicle):
    __tablename__ = "cars"

    id = Column("id", Integer, primary_key=True)

    __mapper_args__ = {"polymorphic_identity": "car"}
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["vehicles.id <http://vehicles.id>"]),
    )


class AbstractCoupe(AbstractCar):
    __mapper_args__ = {"polymorphic_identity": "coupe"}


Base = declarative_base()


class Vehicle(AbstractVehicle, Base):
    pass


class Car(AbstractCar, Vehicle):
    pass


class Coupe(AbstractCoupe, Car):
    pass


This resulted in the following error:


  File ".../model/vehicle.py", line 255, in <module>
    class Coupe(AbstractCoupe, Car):
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py",
line 55, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
88, in _as_declarative
    _MapperConfig.setup_mapping(cls, classname, dict_)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
103, in setup_mapping
    cfg_cls(cls_, classname, dict_)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
131, in __init__
    self._setup_table()
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
395, in _setup_table
    **table_kw)
  File ".../env/lib/python2.7/site-packages/sqlalchemy/sql/schema.py",
line 398, in __new__
    "existing Table object." % key)
InvalidRequestError: Table 'cars' is already defined for this MetaData
instance.  Specify 'extend_existing=True' to redefine options and
columns on an existing Table object.


After adding "extend_existing=True" to the AbstractCoupe model, I
received this error:


  File ".../model/vehicle.py", line 255, in <module>
    class Coupe(AbstractCoupe, Car):
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py",
line 55, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
88, in _as_declarative
    _MapperConfig.setup_mapping(cls, classname, dict_)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
103, in setup_mapping
    cfg_cls(cls_, classname, dict_)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
135, in __init__
    self._early_mapping()
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
138, in _early_mapping
    self.map()
  File
".../env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line
530, in map
    **self.mapper_args
  File "<string>", line 2, in mapper
  File ".../env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py",
line 627, in __init__
    self._configure_properties()
  File ".../env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py",
line 1292, in _configure_properties
    self._adapt_inherited_property(key, prop, False)
  File ".../env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py",
line 1514, in _adapt_inherited_property
    self._configure_property(key, prop, init=False, setparent=False)
  File ".../env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py",
line 1541, in _configure_property
    prop.columns[0])
  File
".../env/lib/python2.7/site-packages/sqlalchemy/sql/selectable.py", line
476, in corresponding_column
    if self.c.contains_column(column):
  File
".../env/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py",
line 754, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File
".../env/lib/python2.7/site-packages/sqlalchemy/sql/selectable.py", line
553, in columns
    return self._columns.as_immutable()
AttributeError: 'Table' object has no attribute '_columns'


Is there a way to fix or work around this?

Thanks,
Angie

On Wed, Jun 22, 2016 at 7:27 PM, Mike Bayer <mike...@zzzcomputing.com
<mailto:mike...@zzzcomputing.com>> wrote:



    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>
        <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
    <http://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>
        <mailto: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>
                <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%2bunsubscr...@googlegroups.com
        <mailto:sqlalchemy%252bunsubscr...@googlegroups.com>>
                <mailto:sqlalchemy+unsubscr...@googlegroups.com
        <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>
                <mailto:sqlalchemy%2bunsubscr...@googlegroups.com
        <mailto:sqlalchemy%252bunsubscr...@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>>
                <mailto: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>
            <mailto:sqlalchemy%2bunsubscr...@googlegroups.com
        <mailto:sqlalchemy%252bunsubscr...@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 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