<excluded for brevity>
2021-08-26 21:21:45,820 INFO sqlalchemy.engine.Engine
CREATE TABLE users (
id CHAR(32) NOT NULL,
is_active BOOLEAN,
type VARCHAR,
PRIMARY KEY (id)
)
<excluded for brevity>
Sit back and wonder why the inherited objects fields are not included (notice the create table statement not including the fields)
Operating System
Windows
Operating System Details
No response
SQLModel Version
0.0.4
Python Version
3.8.10
Additional Context
I think I can fall back to sqlalchemy in this case without any problems, but maybe I am at a loss and it should be done in another way. Removing the "table=True" from the inherited classes makes no difference. Maybe this is also an edge case that should not be supported, but anyway it would be nice to see how this should be handled by people smarter than me. I am currently evaluating rewriting a backend to sqlmodel as it is already implemented in FastApi (which is amazing), and although I know it's early days for this project, I like what it tries to achieve :)
The CustomMetaclass basically disables this check and thus allows the inheritance.
It might break some FastAPI functionality as the comment in the code of the check suggests.
Because this is a hack it would be great if we could add an officially supported way of disabling this check to support (at least) single table inheritance. It seems to work well outside of FastAPI (I did not test it with FastAPI).
I see two ways in doing this:
Explicit: Adding a keyword argument to disable the check (e.g. class Employee(SQLModel, table=True, allow_derived_tables=True):)
Implicit: Disabling the check if __mapper_args__.["polymorphic_identity"] is set in all bases which are also tables and only if also the __tablename__ is the same across the bases (to only allow single table inheritance).
@tiangolo, would you accept a PR with one of the two ideas? If yes, which one would you prefer? Then I would prepare a PR. :)
Reacted by lovetoburnswhen, Kristoffer, Federico, Eric Song, pomodoren, scd75, Santiago Bassani, Ilja Gubins, Igor Kucher, Huy Le and 2 more
I found a workaround/hack on how to allow Single Table Inheritance as mentioned by you (Joined Table Inheritance does not work):
I tried this but it didn't seem to work for me on sqlmodel 0.0.6.
Anyway, my project aims to use FastAPI so I am more interested in a proper solution rather than a workaround.
Is this by any chance on the roadmap @tiangolo?
Thanks a bunch!
Reacted by pomodoren, Artem ILIN, Szymon Bartnik, MrsMarple, wrabit, André Casimiro, kdonaldginkgo and Dylan Mendonca
Dear all, is there any workaround available for having polymorphic models? The code from movabo doesn’t seem to work within the latest version of SQLModel. Any ideas or plans to have it in future releases?
It's not super generic, but it could be useful to other people here as well, so here's the copypasta:
Hi all! i'd like to POST/GET a doubly-nested model (market_result -> result_curve (1:n) -> curve_point (1:n)) but I am struggling with the creation of the ORM object (object has no attribute '_sa_instance_state') so I'm probably doing something wrong. I should add that I'm also trying to use single-table-inheritance as i have subclassed my result_curve model into supply_result_curve and demand_result_curve, polymorphic on attribute "side". What's the easiest way one could achieve this?
I did manage to achieve what I wanted (albeit with more struggle than I would've expected), so I'll testify here in case someone has a similar problem.
I ended up using Wouterkoorn/sqlalchemy-pydantic-orm and this is the code I wrote for models/schemas:
Hello
This is still not working. From my own end, I noticed the problem is not really the polymorphic inheritance.
It is that SQLModel does not support model inheritance that is more than one layer deep, regardless of whether it is polymorphic or not.
I really need this to work for a project I'm currently working on as I want to use it with FastAPI. Thank you
I have a very ugly but working workaround for 0.0.8 for single table and joined inheritance.
I started from @movabo's answer, sadly that no longer works.
@DrOncogene was also correct, the problem is SQLModel doesn't allow inheritance of tables.
The things the workaround does:
Declares a new metaclass for SQLModel that allows inheritance
When inheriting from a table, craft a specific used_dict that doesn't include the properties of the part models (to avoid the duplicate field errors)
Declares a modified DeclarativeMeta that repects the fields we sent since by default it automatically overrides that
All in all, this is the workaround
"""Workaround to make single class / joined table inheritance work with SQLModel.https://github.com/tiangolo/sqlmodel/issues/36"""fromtypingimportAnyfromsqlalchemyimportexcfromsqlalchemy.ormimportregistryfromsqlalchemy.orm.decl_apiimport_as_declarative# type: ignorefromsqlmodel.mainimport (
BaseConfig, # type: ignoreDeclarativeMeta, # type: ignoreForwardRef, # type: ignoreModelField, # type: ignoreModelMetaclass, # type: ignoreRelationshipProperty, # type: ignoreSQLModelMetaclass,
get_column_from_field,
inspect, # type: ignorerelationship, # type: ignore
)
classSQLModelPolymorphicAwareMetaClass(SQLModelMetaclass):
"""Workaround to make single table inheritance work with SQLModel."""def__init__( # noqa: C901, PLR0912cls, classname: str, bases: tuple[type, ...], dict_: dict[str, Any], **kw: Any# noqa: ANN401, N805, ANN101
) ->None:
# Only one of the base classes (or the current one) should be a table model# this allows FastAPI cloning a SQLModel for the response_model without# trying to create a new SQLAlchemy, for a new table, with the same name, that# triggers an errorbase_table: type|None=Noneis_polymorphic=Falseforbaseinbases:
config=getattr(base, "__config__") # noqa: B009ifconfigandgetattr(config, "table", False):
base_table=baseis_polymorphic=bool(getattr(base, "__mapper_args__", {}).get("polymorphic_on"))
breakis_polymorphic&=bool(getattr(cls, "__mapper_args__", {}).get("polymorphic_identity"))
ifgetattr(cls.__config__, "table", False) and (notbase_tableoris_polymorphic):
dict_used=dict_.copy()
forfield_name, field_valueincls.__fields__.items():
# Do not include fields from the parent table if we are using inheritanceifbase_tableandfield_nameingetattr(base_table, "__fields__", {}):
continuedict_used[field_name] =get_column_from_field(field_value)
forrel_name, rel_infoincls.__sqlmodel_relationships__.items():
# Do not include fields from the parent table if we are using inheritanceifbase_tableandrel_nameingetattr(base_table, "__sqlmodel_relationships__", {}):
continueifrel_info.sa_relationship:
# There's a SQLAlchemy relationship declared, that takes precedence# over anything else, use that and continue with the next attributedict_used[rel_name] =rel_info.sa_relationshipcontinueann=cls.__annotations__[rel_name]
temp_field=ModelField.infer(
name=rel_name,
value=rel_info,
annotation=ann,
class_validators=None,
config=BaseConfig,
)
relationship_to=temp_field.type_ifisinstance(temp_field.type_, ForwardRef):
relationship_to=temp_field.type_.__forward_arg__rel_kwargs: dict[str, Any] = {}
ifrel_info.back_populates:
rel_kwargs["back_populates"] =rel_info.back_populatesifrel_info.link_model:
ins=inspect(rel_info.link_model)
local_table=getattr(ins, "local_table") # noqa: B009iflocal_tableisNone:
msg=f"Couldn't find the secondary table for model {rel_info.link_model}"raiseRuntimeError(msg)
rel_kwargs["secondary"] =local_tablerel_args: list[Any] = []
ifrel_info.sa_relationship_args:
rel_args.extend(rel_info.sa_relationship_args)
ifrel_info.sa_relationship_kwargs:
rel_kwargs.update(rel_info.sa_relationship_kwargs)
rel_value: RelationshipProperty=relationship( # type: ignorerelationship_to, *rel_args, **rel_kwargs
)
dict_used[rel_name] =rel_valuesetattr(cls, rel_name, rel_value) # Fix #315PatchedDeclarativeMeta.__init__(cls, classname, bases, dict_used, **kw) # type: ignoreelse:
ModelMetaclass.__init__(cls, classname, bases, dict_, **kw)
classPatchedDeclarativeMeta(DeclarativeMeta): # noqa: D101def__init__(
cls, classname: str, bases: tuple[type, ...], dict_, **kw# noqa: N805, ANN001, ARG002, ANN101, ANN003
) ->None:
# early-consume registry from the initial declarative base,# assign privately to not conflict with subclass attributes named# "registry"reg=getattr(cls, "_sa_registry", None)
ifregisNone:
reg=dict_.get("registry", None)
ifnotisinstance(reg, registry):
msg="Declarative base class has no 'registry' attribute, or registry is not a sqlalchemy.orm.registry() object"raiseexc.InvalidRequestError(msg)
cls._sa_registry=regifnotcls.__dict__.get("__abstract__", False):
_as_declarative(reg, cls, dict_)
type.__init__(cls, classname, bases, dict_)
It looks like a lot of code, but it's mostly because I needed to copy the original metaclasses and just do a few modifications here and there. The actual changes are very little (<10 lines).
PS. This is a workaround and code WILL MOST LIKELY BREAK when either SQLModel or SQLAlchemy update.
I can't really use metaclasses for my app, so here's an ugly workaround for joined inheritance. I prefer the code from @andruli .
v0.0.18.
fromenumimportEnumfromdbimportenginefromsqlalchemyimportForeignKeyfromsqlmodelimportField, SQLModelfromsqlmodel.mainimportdefault_registryPKey=Field(primary_key=True)
FKey=ForeignKey("barcodes.barcode")
classBarcodedType(str, Enum):
Tool="Tool"User="User"classBarcoded(SQLModel, table=True):
__tablename__="barcodes"# type: ignorebarcode: str=Field(primary_key=True, max_length=10)
type: BarcodedType__mapper_args__= {
"polymorphic_identity": "Barcoded",
"polymorphic_abstract": True,
"polymorphic_on": "type",
}
# @default_registry.mapped # does not work when there are multiple child classesclassTool(Barcoded, table=True):
__tablename__="tools"# type: ignorebarcode: str=Field(FKey, primary_key=True)
type: BarcodedType=BarcodedType.Toolname: str=Field()
__mapper_args__= {
"polymorphic_identity": BarcodedType.Tool,
# can't set this here, or future queries will raise# _mysql_connector.MySQLInterfaceError: Python type FieldInfo cannot be converted# this gets memoised in sqlalchemy, you can't let it get resolved early# "inherit_condition": barcode == Barcoded.barcode,"inherit_condition": None, # masks type error
}
# updates Tool.barcode to replace FieldInfo with InstrumentedAttributeSQLModel.metadata.create_all(bind=engine)
# set the join condition _after_ converting the Tool.barcode attribute to sqlalchemy native# otherwise, you get _mysql_connector.MySQLInterfaceError: Python type FieldInfo cannot be convertedTool.__mapper_args__["inherit_condition"] =Barcoded.barcode# raises: dictionary changed size during iteration# Tool = default_registry.mapped(Tool)# @default_registry.mapped # does not work when there are multiple child classesclassUser(Barcoded, table=True):
__tablename__="users"# type: ignorebarcode: str=Field(FKey, primary_key=True)
name: str=Field()
__mapper_args__= {
"polymorphic_identity": BarcodedType.User,
"inherit_condition": None, # masks type error
}
SQLModel.metadata.create_all(bind=engine)
User.__mapper_args__["inherit_condition"] =Barcoded.barcode# if you depend on these classes in other models, e.g. for a relationship,# then you need to decorate the model with @mapped. That fails when any child# class of the same parent has already been mapped. So, declare all the classes,# then map them after declarationTool=default_registry.mapped(Tool)
User=default_registry.mapped(User)
schema
CREATETABLE `barcodes` (
`barcode`varchar(255) NOT NULL,
`type` enum('Tool','User','Team') NOT NULL,
PRIMARY KEY (`barcode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
CREATETABLE `tools` (
`barcode`varchar(255) NOT NULL,
`type` enum('Tool','User','Team') NOT NULL,
`name`varchar(255) NOT NULL,
PRIMARY KEY (`barcode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
emitted warnings
site-packages/pydantic/_internal/_fields.py:200: UserWarning: Field name "barcode" in "Tool" shadows an attribute in parent "Barcoded"
site-packages/pydantic/_internal/_fields.py:200: UserWarning: Field name "type" in "Tool" shadows an attribute in parent "Barcoded"
site-packages/pydantic/_internal/_fields.py:200: UserWarning: Field name "barcode" in "User" shadows an attribute in parent "Barcoded"
model.py:72: SAWarning: Implicitly combining column barcodes.barcode with column tools.barcode under attribute 'barcode'. Please configure one or more attributes for these same-named columns explicitly.
model.py:72: SAWarning: Implicitly combining column barcodes.type with column tools.type under attribute 'type'. Please configure one or more attributes for these same-named columns explicitly.
model.py:73: SAWarning: Implicitly combining column barcodes.barcode with column users.barcode under attribute 'barcode'. Please configure one or more attributes for these same-named columns explicitly.
model.py:73: SAWarning: Implicitly combining column barcodes.type with column users.type under attribute 'type'. Please configure one or more attributes for these same-named columns explicitly.
Activity
movabo commented on Jan 20, 2022
I found a workaround/hack on how to allow Single Table Inheritance as mentioned by you (Joined Table Inheritance does not work):
My example refers to the SQLAlchemy example but should work with your code as well:
The CustomMetaclass basically disables this check and thus allows the inheritance.
It might break some FastAPI functionality as the comment in the code of the check suggests.
Because this is a hack it would be great if we could add an officially supported way of disabling this check to support (at least) single table inheritance. It seems to work well outside of FastAPI (I did not test it with FastAPI).
I see two ways in doing this:
@tiangolo, would you accept a PR with one of the two ideas? If yes, which one would you prefer? Then I would prepare a PR. :)
shatteringlass commented on Feb 20, 2022
I tried this but it didn't seem to work for me on sqlmodel 0.0.6.
Anyway, my project aims to use FastAPI so I am more interested in a proper solution rather than a workaround.
Is this by any chance on the roadmap @tiangolo?
Thanks a bunch!
BigTMiami commented on Mar 14, 2022
I would find this very useful as well.
jkehrbaum commented on Mar 18, 2022
Dear all, is there any workaround available for having polymorphic models? The code from movabo doesn’t seem to work within the latest version of SQLModel. Any ideas or plans to have it in future releases?
thx a lot
shatteringlass commented on Mar 18, 2022
I had posted my solution/workaround here:
https://matrix.to/#/!MYdZNkXpgAXqXRdstX:gitter.im/$cqBN6h_4QSiFRgjivn6rh3lsHAZIdDwvYLlwL8otP8s?via=gitter.im&via=matrix.org&via=averyan.ru
It's not super generic, but it could be useful to other people here as well, so here's the copypasta:
LaQuay commented on Nov 9, 2022
Hi, I am struggling with this problem as well. The proposals provided here don't work either for me.
@shatteringlass Thanks but your "solution" is for FastAPI + SQLAlchemy, with no SQLModel involved in your code. This issue is for FastAPI + SQLModel.
Any ideas? Thanks
DrOncogene commented on Apr 13, 2023
Hello
This is still not working. From my own end, I noticed the problem is not really the polymorphic inheritance.
It is that SQLModel does not support model inheritance that is more than one layer deep, regardless of whether it is polymorphic or not.
I really need this to work for a project I'm currently working on as I want to use it with FastAPI. Thank you
andruli commented on Sep 12, 2023
I have a very ugly but working workaround for 0.0.8 for single table and joined inheritance.
I started from @movabo's answer, sadly that no longer works.
@DrOncogene was also correct, the problem is SQLModel doesn't allow inheritance of tables.
The things the workaround does:
All in all, this is the workaround
And you'd use it like this for joined inheritance
And this for single table inheritance
And you'd use it like this for joined inheritance
It looks like a lot of code, but it's mostly because I needed to copy the original metaclasses and just do a few modifications here and there. The actual changes are very little (<10 lines).
PS. This is a workaround and code WILL MOST LIKELY BREAK when either SQLModel or SQLAlchemy update.
bosukas commented on Dec 17, 2023
Any updates on this one?
hslira commented on Apr 17, 2024
just checking
fsackur commented on Jun 1, 2024
I can't really use metaclasses for my app, so here's an ugly workaround for joined inheritance. I prefer the code from @andruli .
v0.0.18.
ixycoexzckwpmlcu commented on Sep 1, 2024
I couldn't find a solution for single table inheritance for SQLModel 0.0.22, so I implemented my own solution here.
Maybe related: #438, #488
PaleNeutron commented on Nov 27, 2024
I have implement this feature in #1226 .
Can any maintainer review that? @tiangolo
heitorslira commented on Feb 14, 2026
any updates on this being merged?