Skip to content

✨ Add support for SQLAlchemy polymorphic models #1226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d93a46
support sqlalchemy polymorphic
Nov 26, 2024
589237b
improve docs
Nov 26, 2024
4071b0f
fix polymorphic_on check
Nov 26, 2024
48f2a88
fix polymorphic_on check
Nov 26, 2024
e6ad74d
fix lint
Nov 26, 2024
277953a
fix pydantic v1 support
Nov 26, 2024
4aade03
fix type hint for <3.10
Nov 26, 2024
a3044bb
add needs_pydanticv2 mark to test
Nov 26, 2024
015601c
improve code structure
Dec 3, 2024
66c1d93
lint
Dec 3, 2024
0efd1bf
remove effort of pydantic v1
Dec 3, 2024
7173b44
Merge branch 'main' into sqlalchemy_polymorphic_support
PaleNeutron Dec 10, 2024
84d739e
Update sqlmodel/_compat.py
PaleNeutron Dec 12, 2024
dbd0101
fix default value is InstrumentedAttribute in inherit
Feb 5, 2025
88670a5
Merge branch 'sqlalchemy_polymorphic_support' of https://github.com/P…
Feb 5, 2025
b1ed8c3
fix inherit order
Feb 5, 2025
5d1bf5c
support python < 3.9
Feb 5, 2025
ccbb92a
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Feb 5, 2025
d0d0288
skip polymorphic in pydantic v1
Feb 5, 2025
525373d
Merge branch 'sqlalchemy_polymorphic_support' of https://github.com/P…
Feb 5, 2025
ab965f0
Merge branch 'main' into sqlalchemy_polymorphic_support
PaleNeutron Feb 5, 2025
68963e7
Merge branch 'main' into sqlalchemy_polymorphic_support
svlandeg Feb 20, 2025
95c6a1e
Merge branch 'main' into sqlalchemy_polymorphic_support
svlandeg Feb 24, 2025
c1dff79
disable pydantic warning during polymorphic
Mar 12, 2025
7d333fe
fix relationship problem of parent class during polymorphic inherit
May 15, 2025
32ebd6f
avoid add ClassVar multiple times
May 15, 2025
64f774f
Add support for polymorphic_abstract
May 27, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add support for polymorphic_abstract
  • Loading branch information
John Lyu committed May 27, 2025
commit 64f774fb3b5b66ef8d55aab0b26a7733146e60a8
8 changes: 5 additions & 3 deletions sqlmodel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,12 @@ def __init__(
# trying to create a new SQLAlchemy, for a new table, with the same name, that
# triggers an error
base_is_table = any(is_table_model_class(base) for base in bases)
polymorphic_identity = dict_.get("__mapper_args__", {}).get(
"polymorphic_identity"
_mapper_args = dict_.get("__mapper_args__", {})
polymorphic_identity = _mapper_args.get("polymorphic_identity")
polymorphic_abstract = _mapper_args.get("polymorphic_abstract")
has_polymorphic = (
polymorphic_identity is not None or polymorphic_abstract is not None
)
has_polymorphic = polymorphic_identity is not None

# allow polymorphic models inherit from table models
if is_table_model_class(cls) and (not base_is_table or has_polymorphic):
Expand Down
107 changes: 107 additions & 0 deletions tests/test_polymorphic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,110 @@ class Worker(Person):
result = db.exec(statement).all()
assert len(result) == 1
assert isinstance(result[0].tool, Tool)


@needs_pydanticv2
def test_polymorphic_deeper(clear_sqlmodel) -> None:
class Employee(SQLModel, table=True):
__tablename__ = "employee"

id: Optional[int] = Field(default=None, primary_key=True)
name: str
type: str = Field(default="employee")

__mapper_args__ = {
"polymorphic_identity": "employee",
"polymorphic_on": "type",
}

class Executive(Employee):
"""An executive of the company"""

executive_background: Optional[str] = Field(
sa_column=mapped_column(nullable=True), default=None
)

__mapper_args__ = {"polymorphic_abstract": True}

class Technologist(Employee):
"""An employee who works with technology"""

competencies: Optional[str] = Field(
sa_column=mapped_column(nullable=True), default=None
)

__mapper_args__ = {"polymorphic_abstract": True}

class Manager(Executive):
"""A manager"""

__mapper_args__ = {"polymorphic_identity": "manager"}

class Principal(Executive):
"""A principal of the company"""

__mapper_args__ = {"polymorphic_identity": "principal"}

class Engineer(Technologist):
"""An engineer"""

__mapper_args__ = {"polymorphic_identity": "engineer"}

class SysAdmin(Technologist):
"""A systems administrator"""

__mapper_args__ = {"polymorphic_identity": "sysadmin"}

# Create database and session
engine = create_engine("sqlite:///:memory:", echo=True)
SQLModel.metadata.create_all(engine)

with Session(engine) as db:
# Add different employee types
manager = Manager(name="Alice", executive_background="MBA")
principal = Principal(name="Bob", executive_background="Founder")
engineer = Engineer(name="Charlie", competencies="Python, SQL")
sysadmin = SysAdmin(name="Diana", competencies="Linux, Networking")

db.add(manager)
db.add(principal)
db.add(engineer)
db.add(sysadmin)
db.commit()

# Query each type to verify they persist correctly
managers = db.exec(select(Manager)).all()
principals = db.exec(select(Principal)).all()
engineers = db.exec(select(Engineer)).all()
sysadmins = db.exec(select(SysAdmin)).all()

# Query abstract classes to verify they return appropriate concrete classes
executives = db.exec(select(Executive)).all()
technologists = db.exec(select(Technologist)).all()

# All employees
all_employees = db.exec(select(Employee)).all()

# Assert individual type counts
assert len(managers) == 1
assert len(principals) == 1
assert len(engineers) == 1
assert len(sysadmins) == 1

# Check that abstract classes can't be instantiated directly
# but their subclasses are correctly returned when querying
assert len(executives) == 2
assert len(technologists) == 2
assert len(all_employees) == 4

# Check that properties of abstract classes are accessible from concrete instances
assert managers[0].executive_background == "MBA"
assert principals[0].executive_background == "Founder"
assert engineers[0].competencies == "Python, SQL"
assert sysadmins[0].competencies == "Linux, Networking"

# Check polymorphic identities
assert managers[0].type == "manager"
assert principals[0].type == "principal"
assert engineers[0].type == "engineer"
assert sysadmins[0].type == "sysadmin"