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:
- In the table from the docs, the
persontable contains all people, and atypecolumn with the type of the person. - In mine, the
persontable is empty, and contains only anidcolumn (notype).
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.