On 08/21/2016 10:22 AM, Tom Kedem wrote:
It seems I confused "concrete" with "joined" inheritance.

What I want to achieve is /joined/ inheritance. I've modified the code
to reflect that (just removed all concrete references).
According to the documentation relationships on joined inheritance are
inherited, but I still get an error saying it doesn't recognize
super_user as a child of user.


This is the error:

sqlalchemy.orm.exc.FlushError: Attempting to flush an item of type <class '__main__.SuperUser'> as a member of collection "UserKeyword.user". Expected an object of type <class '__main__.User'> or a polymorphic subclass of this type. If <class '__main__.SuperUser'> is a subclass of <class '__main__.User'>, configure mapper "Mapper|User|user" to load this subtype polymorphically, or set enable_typechecks=False to allow any subtype to be accepted for flush.


this error is more of a warning to stop you from proceeding as though things are "normal" - it has no problem persisting a SuperUser here, however when you go later to load some_user_keyword.user, it will query the user table only, and return a User object, not a SuperUser. It's not possible for it to detect a SuperUser because you aren't using a discriminator.

Your options are:

1. make two separate user_keywords relationships, and get rid of UserKeyword.user totally, since it can't be relied upon to load a SuperUser.

2. same thing, but keep UserKeyword.user and set the above-mentioned enable_typechecks=False on it. Still risky to call upon it because it can't load a SuperUser. see attached.

Unfortunately, it does not seem to be possible to have a "user" and a separate "super_user" relationship on UserKeyword that share the same UserKeyword.user_id parameter, and they appear to conflict on flush.

3. Use polymorphic loading without a discriminator column, by using a CASE expression that checks for the super_user table being present in a row. This is a lot like what the concrete polymorphic loading does, though we don't have the declarative helpers for this pattern. Setting it up would be a little more manual and it means all queries for User, or at least the ones from UserKeyword.user if we made a special mapper just for this operation, would query an outer join against both tables. I can try to work this out if you are interested. But I don't think you even need to have UserKeyword.user here.







On Sunday, August 21, 2016 at 6:37:19 AM UTC+3, Mike Bayer wrote:



    On 08/20/2016 08:27 PM, Tom Kedem wrote:
    > I suppose I could have a discriminator value for "function type",
    but I
    > have no use for it now (so yeah, the base class is a single column
    one).
    >
    > It's a simplified model. The real use-case is as such - the base
    class
    > is "function" and the inheriting classes are all sorts of
    functions (all
    > concrete classes). They all have a collection of "arguments" (many to
    > many), which I define in the base class - since the "arguments"
    can only
    > point to a single table.
    >
    > Maybe it's not the best mapping, I'm open for suggestions. But is
    there
    > anything wrong in my configuration or understanding?

    Two things do not make sense, in terms of the use of ConcreteBase and
    "concrete=True".

    One is:


         id = Column(Integer, ForeignKey(User.id), primary_key=True)

    on SuperUser.

    The other is:

    class UserKeyword(Base):
         __tablename__ = 'user_keyword'
         user_id = Column(Integer, ForeignKey('user.id
    <http://user.id>'), primary_key=True)


    both of these imply that the fields of a SuperUser object are stored in
    the "super_user" table *and* the "user" table.

    Also, when you say "I suppose I could have a discriminator..." in
    response to the question, "did you mean for this to be joined
    inheritance?",  that suggests you might be under the impression that
    "don't have a discriminator" means you must use "concrete inheritance",
    which is not true.    A "discriminator" is not necessary for any style
    of inheritance, as long as you don't need to load objects
    polymorphically.

    Same question as before, more specifically.  When you load a row from
    "super_user" in order to get a SuperUser object, should the ORM also be
    loading a row from "user" that matches up to it?  Or can you get every
    possible field in a SuperUser from the "super_user" table alone without
    ever looking at "user"?  If the former, that would be joined
    inheritance.   that's what this looks like.









    >
    > On Saturday, August 20, 2016 at 10:44:24 PM UTC+3, Mike Bayer wrote:
    >
    >     This doesn't look like a concrete mapping, you have a foreign key
    >     from SuperUser to User.   Are you sure this isn't supposed to
    be an
    >     ordinary joined inheritance model ?
    >
    >     On Saturday, August 20, 2016, Tom Kedem <tomk...@gmail.com
    >     <javascript:>> wrote:
    >
    >         I have the following setup (attached python file).
    >         I'm using an inheritance hierarchy without a discriminator
    >         field, deriving from AbstractBase.
    >         I want to be able to use the "keywords" attribute in the
    >         "SuperUser" class, and from the documentation I understand I
    >         need to redefine it, however that doesn't seem to work.
    >         I assume I could manually use a primary join there (as the
    error
    >         indicates), but as I understand that's exactly what
    >         "AbstractBase" class should handle...
    >
    >         --
    >         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:>.
    >         To post to this group, send email to
    sqlal...@googlegroups.com <javascript:>.
    >         Visit this group at
    https://groups.google.com/group/sqlalchemy
    <https://groups.google.com/group/sqlalchemy>
    >         <https://groups.google.com/group/sqlalchemy
    <https://groups.google.com/group/sqlalchemy>>.
    >         For more options, visit https://groups.google.com/d/optout
    <https://groups.google.com/d/optout>
    >         <https://groups.google.com/d/optout
    <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+...@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
    <https://groups.google.com/group/sqlalchemy>.
    > For more options, visit https://groups.google.com/d/optout
    <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.
from sqlalchemy import ForeignKey

from sqlalchemy import Integer

from sqlalchemy import Column
from sqlalchemy import String

from sqlalchemy import create_engine
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


# Inheritance without discriminator
class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    keywords = association_proxy('user_keywords', 'keyword')
    user_keywords = relationship(
        "UserKeyword",
        back_populates="user",   # this parameter would be removed
        cascade="all, delete-orphan")


class UserKeyword(Base):
    __tablename__ = 'user_keyword'
    user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
    keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
    special_key = Column(String(50))

    # recommended: don't even use this relationship, because it can't
    # load a SuperUser object correctly
    user = relationship(
        User, back_populates="user_keywords", enable_typechecks=False)

    keyword = relationship("Keyword")

    def __init__(self, keyword=None, user=None, special_key=None):
        self.user = user
        self.keyword = keyword
        self.special_key = special_key


class Keyword(Base):
    __tablename__ = 'keyword'
    id = Column(Integer, primary_key=True)
    keyword = Column('keyword', String(64))

    def __init__(self, keyword):
        self.keyword = keyword

    def __repr__(self):
        return 'Keyword(%s)' % repr(self.keyword)


class SuperUser(User):
    __tablename__ = 'super_user'

    id = Column(Integer, ForeignKey(User.id), primary_key=True)
    role = Column(String(64))

    user_keywords = relationship(
        "UserKeyword",
        back_populates="user",   # this parameter would be removed
        cascade="all, delete-orphan")

engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

user = SuperUser()
for kw in (Keyword('new_from_blammo'), Keyword('its_big')):
    user.keywords.append(kw)

session.add(user)
session.commit()

session.close()

u1 = session.query(SuperUser).first()
print u1.keywords

Reply via email to