Closed
Description
I'm trying to implement concrete table inheritance with AbstractConcreteBase as described in the docs. I'm unable to get it to work without first instantiating the subclass of the class that implements AbstractConcreteBase otherwise I'm ending up with an unmapped entity.
Is this the expected behaviour? How do I get around calling CompanyListItem()
to get it registered as a mapped entity?
Code:
from sqlalchemy import (
Column,
Integer,
Text,
func,
String,
Boolean,
DateTime,
Date,
TIMESTAMP,
ForeignKey,
or_,
and_,
UniqueConstraint,
)
from sqlalchemy.orm import relationship, backref
from sqlalchemy.sql import expression
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import sessionmaker
engine = create_engine('postgresql://localhost/flsqla')
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class List(Base):
__tablename__ = "lists"
id = Column(Integer, primary_key=True)
label = Column(String(50), nullable=False)
items = relationship("ListItem", back_populates="llist")
class Company(Base):
__tablename__ = "companies"
id = Column(Integer, primary_key=True)
label = Column(String(50), nullable=False)
tracked = Column(Boolean, server_default=expression.true(), nullable=False)
class ListItem(AbstractConcreteBase, Base):
id = Column(Integer, primary_key=True)
@declared_attr
def list_id(cls):
return Column(ForeignKey('lists.id'))
@declared_attr
def llist(cls):
return relationship("List")
class CompanyListItem(ListItem):
__tablename__ = "company_list_item"
company_id = Column(ForeignKey("companies.id", deferrable=True, initially="DEFERRED"), nullable=False)
company = relationship("Company", primaryjoin="CompanyListItem.company_id == Company.id")
__mapper_args__ = {"polymorphic_identity": "company_item", "concrete": True}
Base.metadata.create_all(engine)
Error:
>>> from app import ListItem, session, CompanyListItem, Company, List
>>> session.query(ListItem)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/halfdan/flsqla-schema/.venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 1538, in query
return self._query_cls(entities, self, **kwargs)
File "/Users/halfdan/flsqla-schema/.venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 167, in __init__
self._set_entities(entities)
File "/Users/halfdan/flsqla-schema/.venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 197, in _set_entities
entity_wrapper(self, ent)
File "/Users/halfdan/flsqla-schema/.venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 4209, in __init__
"expected - got '%r'" % (column,)
sqlalchemy.exc.InvalidRequestError: SQL expression, column, or mapped entity expected - got '<class 'app.ListItem'>'
>>> CompanyListItem()
<app.CompanyListItem object at 0x105c9ac88>
>>> session.query(ListItem)
<sqlalchemy.orm.query.Query object at 0x106e38fd0>
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
zzzeek commentedon Jan 27, 2019
can you call configure_mappers() ? that should set things up
zzzeek commentedon Jan 27, 2019
works:
seems like docs should mention this
halfdan commentedon Jan 27, 2019
That does seem to work. I'm wondering if sqlalchemy should call this automatically however when a class that extends AbstractConcreteBase is used in a
session.query
.https://docs.sqlalchemy.org/en/latest/orm/mapping_api.html#sqlalchemy.orm.configure_mappers
It seems like that is not entirely true currently. At least from this I would expect
session.query(ListItem)
to work without having to callconfigure_mappers
manually.zzzeek commentedon Jan 27, 2019
technically, it is "entirely true" that it is "in most cases invoked automatically" :)
unfortunately the current implementation of AbstractConcreteBase cannot be automatically configured, the configure_mappers() step has to occur from somewhere else.
zzzeek commentedon Jan 27, 2019
note all the classes that inherit from your ABC do invoke the configure step automatically. it's just the actual base that doesn't. it is not mapped until all the subclasses are set up.
halfdan commentedon Jan 27, 2019
Haha fair point.
Would calling
session.query
with the ABC not suggest that I expect all its subclasses to have loaded?If that's not possible I think session.query should at least emit a warning when it encounters a class that implements ABC
zzzeek commentedon Jan 30, 2019
it should, but at the moment, it does not, because the class
ListItem
here has no mapper at all until configure_mappers() is called. this is again an issue w/ the implementation of abstractconcretebase.that would be the same as just having it call configure_mappers() for us, e.g., to fix the issue. there would need to be some new flag or something that gives it an extra hint.
the better solution would be that AbstractConcreteBase can be mapped up front and that it can alter the internals of this mapping as new subclasses are created (and thus change the base "polymorphic selectable" to which it is mapped). but the "extra hint" solution might have to suffice for the near term.
[-]AbstractConcreteBase requires subclasses to be instantiated before first use[/-][+]support special class of error messages generated for inspect() of decl _DeferredMappedConfig[/+]zzzeek commentedon Jan 30, 2019
e.g. AbstractConcreteBase, DeferredReflection, automap.
this is a patch which would do it but adds a chunk of latency to inspect(), so something would have to be worked out here to improve this:
sqla-tester commentedon Jan 30, 2019
Mike Bayer has proposed a fix for this issue in the master branch:
Add informative failure modes to _DeferredMapperConfig https://gerrit.sqlalchemy.org/1120
4 remaining items