sqlmodel
sqlmodel copied to clipboard
Class .... is not mapped with polymorphic identity
First Check
- [X] I added a very descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the SQLModel documentation, with the integrated search.
- [X] I already searched in Google "How to X in SQLModel" and didn't find any information.
- [X] I already read and followed all the tutorial in the docs and didn't find an answer.
- [X] I already checked if it is not related to SQLModel but to Pydantic.
- [X] I already checked if it is not related to SQLModel but to SQLAlchemy.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
from typing import Optional
import uuid
from typing import List
from sqlmodel import Field, SQLModel, create_engine, Session, Relationship
from uuid import UUID
class InvoiceRequest(SQLModel, table=True):
__tablename__ = "invoice_requests"
id: UUID = Field(default_factory=uuid.uuid4, primary_key=True)
product: str | None = None
request_type: str | None = None
invoices : List["Invoice"] = Relationship(back_populates="invoice_request")
def add_invoices(self):
self.invoices.append(InvoiceReversal())
self.invoices.append(Invoice())
class Invoice(SQLModel, table=True):
__tablename__ = "invoices"
id: UUID = Field(default_factory=uuid.uuid4, primary_key=True)
invoice_type: str = Field(default="regular")
invoice_request_id: UUID | None = Field(default=None, foreign_key="invoice_requests.id")
invoice_request: InvoiceRequest = Relationship(back_populates="invoices")
__mapper_args__ = {
"polymorphic_on": 'invoice_type',
"polymorphic_identity": "regular",
}
class InvoiceReversal(Invoice, table=True):
__mapper_args__ = {
"polymorphic_identity": "reversal",
}
class InvoiceCorrection(Invoice, table=True):
__mapper_args__ = {
"polymorphic_identity": "correction",
}
db_fn = "db.sqlite"
db_url = f"sqlite:///{db_fn}"
engine = create_engine(db_url, echo=True)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
ivr = InvoiceRequest(product="2", request_type="abc")
ivr.add_invoices()
session.add(ivr)
session.commit()
Description
I have this PoC-style code that would model a 1:n relationship between one InvoiceRequest
and multiple Invoices
.
There are several invoice types ÌnvoiceReversal
and InvoiceCorrection
that are modelled using the inheritance from Invoice
class and polymorphic identity.
However. the derived classes are obviously not mapped:
sqlalchemy.orm.exc.UnmappedClassError: Class '__main__.InvoiceReversal' is not mapped
I could not find a solution how to resolve this.
Operating System
Linux
Operating System Details
Linux
SQLModel Version
SQLAlchemy==1.4.40
Python Version
3.10
Additional Context
python3 foo2.py
2022-09-06 11:55:17,985 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2022-09-06 11:55:17,985 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("invoice_requests")
2022-09-06 11:55:17,985 INFO sqlalchemy.engine.Engine [raw sql] ()
2022-09-06 11:55:17,985 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("invoices")
2022-09-06 11:55:17,985 INFO sqlalchemy.engine.Engine [raw sql] ()
2022-09-06 11:55:17,986 INFO sqlalchemy.engine.Engine COMMIT
Traceback (most recent call last):
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/sonnen/maia/planner/foo2.py", line 63, in <module>
session.add(ivr)
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2626, in add
self._save_or_update_state(state)
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2642, in _save_or_update_state
for o, m, st_, dct_ in mapper.cascade_iterator(
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 3230, in cascade_iterator
queue = deque(
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/orm/relationships.py", line 2020, in cascade_iterator
instance_mapper = instance_state.manager.mapper
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 1113, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "/home/ajung/src/sonnen/sonnen-maia-proof-of-concept/.venv/lib/python3.10/site-packages/sqlalchemy/orm/instrumentation.py", line 204, in mapper
raise exc.UnmappedClassError(self.class_)
I can confirm. Here is a simplified version for testing (compatible with Python 3.7+):
from typing import List, Optional
from sqlmodel import Field, SQLModel, create_engine, Session, Relationship
class Foo(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True)
bars: List["Bar"] = Relationship(back_populates="foo")
class Bar(SQLModel, table=True):
__mapper_args__ = {
"polymorphic_on": "type",
"polymorphic_identity": "x",
}
id: Optional[int] = Field(primary_key=True)
type: str = Field(default="x")
foo_id: Optional[int] = Field(foreign_key="foo.id")
foo: Optional[Foo] = Relationship(back_populates="bars")
class BarY(Bar, table=True):
__mapper_args__ = {
"polymorphic_identity": "y",
}
class BarZ(Bar, table=True):
__mapper_args__ = {
"polymorphic_identity": "z",
}
db_url = f"sqlite:///:memory:"
engine = create_engine(db_url, echo=True)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
foo = Foo()
foo.bars.extend([BarY(), Bar()])
session.add(foo)
session.commit()
The full error: (Click to expand)
Traceback (most recent call last):
File "/home/daniil/coding/sqlmodel/polymorphic.py", line 45, in <module>
session.add(foo)
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2626, in add
self._save_or_update_state(state)
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2642, in _save_or_update_state
for o, m, st_, dct_ in mapper.cascade_iterator(
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 3230, in cascade_iterator
queue = deque(
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/relationships.py", line 2020, in cascade_iterator
instance_mapper = instance_state.manager.mapper
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 1113, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/instrumentation.py", line 204, in mapper
raise exc.UnmappedClassError(self.class_)
sqlalchemy.orm.exc.UnmappedClassError: Class '__main__.BarY' is not mapped
Here is an analogous version in pure SQLAlchemy working with no errors: (Click to expand)
from sqlalchemy.engine.create import create_engine
from sqlalchemy.orm import relationship
from sqlalchemy.orm.decl_api import declarative_base
from sqlalchemy.sql.schema import Column, ForeignKey
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.sqltypes import Integer, String
Base = declarative_base()
class Foo(Base):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
bars = relationship("Bar", back_populates="foo")
class Bar(Base):
__tablename__ = 'bar'
__mapper_args__ = {
"polymorphic_on": "type",
"polymorphic_identity": "x",
}
id = Column(Integer, primary_key=True)
type = Column(String, default="x")
foo_id = Column(ForeignKey("foo.id"))
foo = relationship("Foo", back_populates="bars")
class BarY(Bar):
__mapper_args__ = {
"polymorphic_identity": "y",
}
class BarZ(Bar):
__mapper_args__ = {
"polymorphic_identity": "z",
}
db_url = f"sqlite:///:memory:"
engine = create_engine(db_url, echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
foo = Foo()
foo.bars.extend([BarY(), Bar()])
session.add(foo)
session.commit()
Interestingly, you get a different error, if you instead just try to instantiate a BarY
object:
...
with Session(engine) as session:
bar = BarY()
session.add(bar)
session.commit()
The full error: (Click to expand)
Traceback (most recent call last):
File "/home/daniil/coding/sqlmodel/polymorphic.py", line 46, in <module>
bar = BarY()
File "<string>", line 4, in __init__
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/state.py", line 481, in _initialize_instance
with util.safe_reraise():
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 70, in __exit__
compat.raise_(
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/util/compat.py", line 208, in raise_
raise exception
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/state.py", line 479, in _initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "<string>", line 6, in __init__
File "/home/daniil/coding/sqlmodel/sqlmodel/main.py", line 518, in __init__
setattr(__pydantic_self__, key, value)
File "/home/daniil/coding/sqlmodel/sqlmodel/main.py", line 532, in __setattr__
set_attribute(self, name, value)
File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/attributes.py", line 2256, in set_attribute
state.manager[key].impl.set(state, dict_, value, initiator)
AttributeError: 'NoneType' object has no attribute 'set'
I tried debugging the SQLModelMetaclass.__new__
method, to see if the __mapper_args__
end up on the resulting class, and it seems like they are. I checked this line here:
new_cls = super().__new__(cls, name, bases, dict_used, **config_kwargs)
When setting up Bar
for example, the new_cls.__dict__
contains the key value pair:
__mapper_args__: {'polymorphic_on': 'type', 'polymorphic_identity': 'x'}
So that seems not to be the issue.
This is all I got so far.
Someone helped me out with a slightly modified version that works for me:
from typing import Optional
import uuid
from typing import List
from sqlmodel import Field, SQLModel, create_engine, Session, Relationship
from uuid import UUID
from sqlalchemy.orm import registry
mapper_registry = registry()
class InvoiceRequest(SQLModel, table=True):
__tablename__ = "invoice_requests"
id: UUID = Field(default_factory=uuid.uuid4, primary_key=True)
product: str | None = None
request_type: str | None = None
invoices : List["Invoice"] = Relationship(back_populates="invoice_request")
def add_invoices(self):
self.invoices.append(InvoiceReversal())
self.invoices.append(InvoiceCorrection())
class Invoice(SQLModel, table=True):
# __tablename__ = "invoices"
id: UUID = Field(default_factory=uuid.uuid4, primary_key=True)
invoice_type: str = Field(default="regular")
invoice_request_id: UUID | None = Field(default=None, foreign_key="invoice_requests.id")
invoice_request: InvoiceRequest = Relationship(back_populates="invoices")
__mapper_args__ = {
"polymorphic_on": 'invoice_type',
"polymorphic_identity": "regular",
}
@mapper_registry.mapped
class InvoiceReversal(Invoice, table=True):
invoice_types: str = Field(default="regular")
__mapper_args__ = {
"polymorphic_identity": "reversal",
"inherit_condition": invoice_types == Invoice.invoice_type
}
@mapper_registry.mapped
class InvoiceCorrection(Invoice, table=True):
invoice_types: str = Field(default="correction")
__mapper_args__ = {
"polymorphic_identity": "correction",
"inherit_condition": invoice_types == Invoice.invoice_type
}
I tried this workaround with @mapper_registry.mapped
but I get "sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedColumn) column xxx does not exist".
With "inherit_condition" SQLAlchemy maked the correct join but thinks all columns are in the child table. There also a warning on start up, "SAWarning: Implicitly combining column parent.id with column child.id under attribute 'id'. Please configure one or more attributes for these same-named columns explicitly."
Any idea how to make this work?
Hello~, the current version is sqlmodel 0.0.19. Does anyone have idea about how to solve this?
@mapper_registry.mapped
not work for me