The example about joined inheritance in the docs uses the declarative mapping. I'm trying to adapt it to use "classic mappings", but is not working as it should.
I've read and used the docs from https://docs.sqlalchemy.org/en/14/orm/inheritance.html as a guide.
I have some simple classes using attrs
:
class Person:
pass
@attr.s(auto_attribs=True)
class Manager(Person):
name: str
data: str
@attr.s(auto_attribs=True)
class Engineer(Person):
name: str
info: int
@attr.s(auto_attribs=True)
class Company:
people: list[Person]
And I'm declaring the mappings and tables as follows:
persons_table = Table(
"person",
metadata,
Column("id", Integer, primary_key=True),
)
managers_table = Table(
"manager",
metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("data", String(50)),
)
engineers_table = Table(
"engineer",
metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("info", Integer),
)
company_table = Table(
"company",
metadata,
Column("id", Integer, primary_key=True),
)
pjoin = polymorphic_union(
{"person": persons_table, "manager": managers_table, "engineer": engineers_table},
"type",
"pjoin",
)
company_2_people_table = Table(
"company_2_people",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("company_id", ForeignKey("company.id")),
Column("person_id", ForeignKey("person.id")),
)
person_mapper = mapper(
Person,
pjoin,
with_polymorphic=("*", pjoin),
polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper(
Manager,
managers_table,
inherits=person_mapper,
concrete=True,
polymorphic_identity="manager",
)
engineer_mapper = mapper(
Engineer,
engineers_table,
inherits=person_mapper,
concrete=True,
polymorphic_identity="engineer",
)
company_mapper = mapper(
Company,
company_table,
properties={
"people": relationship(
person_mapper,
secondary=company_2_people_table,
collection_class=list,
),
},
)
A simple test:
fn = Path(__file__).with_suffix(".db")
fn.unlink(missing_ok=True)
engine = create_engine(f"sqlite:///{fn}", echo=True)
metadata.create_all(engine)
Session = sessionmaker(bind=engine)
with Session() as session:
m1 = Manager(name="Manager 1", data="Manager Data")
m2 = Manager(name="Manager 2", data="Manager Data")
e1 = Engineer(name="Eng", info=10)
company = Company([m1, e1, m2])
session.add(company)
session.commit()
with Session() as session:
print(session.query(Company).get(1))
This runs, however I get this output:
Company(people=[Engineer(name='Eng', info=10), Manager(name='Manager 1', data='Manager Data'), Manager(name='Manager 2', data='Manager Data')])
Notice that although the instances are correct, the order is not: it should be Manager
, Engineer
, Manager
.
Comparing my database file with the one generated from the example from the docs:
person
table contains all people, and a type
column with the type of the person.person
table is empty, and contains only an id
column (no type
).I have debugged the runtime classes generated by the example and tried to mimic the structures there (for example explicitly passing the internal _polymorphic_map
, but to no avail).
I've also changed the primary key definition for Manager
and Engineer
to Column('id', ForeignKey("person.id"), primary_key=True)
, however I get an exception:
sqlalchemy.orm.exc.FlushError: Instance <Engineer at 0x198e43cd280> has a NULL identity key. If this is an auto-generated value, check that the database table allows generation of new primary key values, and that the mapped Column object is configured to expect these generated values. Ensure also that this flush() is not occurring at an inappropriate time, such as within a load() event.
Any other suggestions or hints that might point me in the right direction?
Thanks.
I've posted the full source code at https://gist.github.com/nicoddemus/26de7bbcdfa9ed4b14fcfdde72b1d63f.
After reading the examples more carefully I found what I was doing wrong: I was mixing concepts from joined inheritance with concrete inheritance.
I want joined inheritance, so:
engineers_table = Table(
"engineer",
metadata,
Column('id', ForeignKey("person.id"), primary_key=True),
Column("name", String(50)),
Column("info", Integer),
)
person_mapper = mapper_registry.map_imperatively(
Person,
persons_table,
polymorphic_identity="person",
polymorphic_on=persons_table.c.type,
)
manager_mapper = mapper_registry.map_imperatively(
Manager,
managers_table,
inherits=person_mapper,
polymorphic_identity="manager",
)
And that's it, SQLA takes care of the rest. I've updated the Gist link with the full and now working code, in case it might help others.
https://gist.github.com/nicoddemus/26de7bbcdfa9ed4b14fcfdde72b1d63f
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With