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