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_)
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)
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:
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."
Activity
daniil-berg commented on Sep 6, 2022
I can confirm. Here is a simplified version for testing (compatible with Python 3.7+):
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 mappedInterestingly, you get a different error, if you instead just try to instantiate a BarY object:
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:
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.
zopyx commented on Sep 6, 2022
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 }mxdev88 commented on Jul 28, 2023
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?
sunkunxi commented on Jul 3, 2024
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