Skip to content

Commit 64f774f

Browse files
author
John Lyu
committed
Add support for polymorphic_abstract
1 parent 32ebd6f commit 64f774f

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

sqlmodel/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -644,10 +644,12 @@ def __init__(
644644
# trying to create a new SQLAlchemy, for a new table, with the same name, that
645645
# triggers an error
646646
base_is_table = any(is_table_model_class(base) for base in bases)
647-
polymorphic_identity = dict_.get("__mapper_args__", {}).get(
648-
"polymorphic_identity"
647+
_mapper_args = dict_.get("__mapper_args__", {})
648+
polymorphic_identity = _mapper_args.get("polymorphic_identity")
649+
polymorphic_abstract = _mapper_args.get("polymorphic_abstract")
650+
has_polymorphic = (
651+
polymorphic_identity is not None or polymorphic_abstract is not None
649652
)
650-
has_polymorphic = polymorphic_identity is not None
651653

652654
# allow polymorphic models inherit from table models
653655
if is_table_model_class(cls) and (not base_is_table or has_polymorphic):

tests/test_polymorphic_model.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,110 @@ class Worker(Person):
175175
result = db.exec(statement).all()
176176
assert len(result) == 1
177177
assert isinstance(result[0].tool, Tool)
178+
179+
180+
@needs_pydanticv2
181+
def test_polymorphic_deeper(clear_sqlmodel) -> None:
182+
class Employee(SQLModel, table=True):
183+
__tablename__ = "employee"
184+
185+
id: Optional[int] = Field(default=None, primary_key=True)
186+
name: str
187+
type: str = Field(default="employee")
188+
189+
__mapper_args__ = {
190+
"polymorphic_identity": "employee",
191+
"polymorphic_on": "type",
192+
}
193+
194+
class Executive(Employee):
195+
"""An executive of the company"""
196+
197+
executive_background: Optional[str] = Field(
198+
sa_column=mapped_column(nullable=True), default=None
199+
)
200+
201+
__mapper_args__ = {"polymorphic_abstract": True}
202+
203+
class Technologist(Employee):
204+
"""An employee who works with technology"""
205+
206+
competencies: Optional[str] = Field(
207+
sa_column=mapped_column(nullable=True), default=None
208+
)
209+
210+
__mapper_args__ = {"polymorphic_abstract": True}
211+
212+
class Manager(Executive):
213+
"""A manager"""
214+
215+
__mapper_args__ = {"polymorphic_identity": "manager"}
216+
217+
class Principal(Executive):
218+
"""A principal of the company"""
219+
220+
__mapper_args__ = {"polymorphic_identity": "principal"}
221+
222+
class Engineer(Technologist):
223+
"""An engineer"""
224+
225+
__mapper_args__ = {"polymorphic_identity": "engineer"}
226+
227+
class SysAdmin(Technologist):
228+
"""A systems administrator"""
229+
230+
__mapper_args__ = {"polymorphic_identity": "sysadmin"}
231+
232+
# Create database and session
233+
engine = create_engine("sqlite:///:memory:", echo=True)
234+
SQLModel.metadata.create_all(engine)
235+
236+
with Session(engine) as db:
237+
# Add different employee types
238+
manager = Manager(name="Alice", executive_background="MBA")
239+
principal = Principal(name="Bob", executive_background="Founder")
240+
engineer = Engineer(name="Charlie", competencies="Python, SQL")
241+
sysadmin = SysAdmin(name="Diana", competencies="Linux, Networking")
242+
243+
db.add(manager)
244+
db.add(principal)
245+
db.add(engineer)
246+
db.add(sysadmin)
247+
db.commit()
248+
249+
# Query each type to verify they persist correctly
250+
managers = db.exec(select(Manager)).all()
251+
principals = db.exec(select(Principal)).all()
252+
engineers = db.exec(select(Engineer)).all()
253+
sysadmins = db.exec(select(SysAdmin)).all()
254+
255+
# Query abstract classes to verify they return appropriate concrete classes
256+
executives = db.exec(select(Executive)).all()
257+
technologists = db.exec(select(Technologist)).all()
258+
259+
# All employees
260+
all_employees = db.exec(select(Employee)).all()
261+
262+
# Assert individual type counts
263+
assert len(managers) == 1
264+
assert len(principals) == 1
265+
assert len(engineers) == 1
266+
assert len(sysadmins) == 1
267+
268+
# Check that abstract classes can't be instantiated directly
269+
# but their subclasses are correctly returned when querying
270+
assert len(executives) == 2
271+
assert len(technologists) == 2
272+
assert len(all_employees) == 4
273+
274+
# Check that properties of abstract classes are accessible from concrete instances
275+
assert managers[0].executive_background == "MBA"
276+
assert principals[0].executive_background == "Founder"
277+
assert engineers[0].competencies == "Python, SQL"
278+
assert sysadmins[0].competencies == "Linux, Networking"
279+
280+
# Check polymorphic identities
281+
assert managers[0].type == "manager"
282+
assert principals[0].type == "principal"
283+
assert engineers[0].type == "engineer"
284+
assert sysadmins[0].type == "sysadmin"

0 commit comments

Comments
 (0)