Sqlmodel 0.0.14 throws strange Pydantic error for relationships when adding another field

658 Views Asked by At

Currently, we use Python 3.12, sqlmodel==0.0.14, and pydantic==2.5.2. The code below works with older sqlmodel versions and pydantic=1.x. However, once we upgrade to the very recent versions the following errors show up.

When executing

from typing import List, Optional

from sqlmodel import Field, Relationship, SQLModel


class HeroTeamLink(SQLModel, table=True):
    team_id: Optional[int] = Field(
        default=None, foreign_key="team.id", primary_key=True
    )
    hero_id: Optional[int] = Field(
        default=None, foreign_key="hero.id", primary_key=True
    )


class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str

    heroes: List["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink)


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)

    teams: List[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)


class HeroOut(Hero):
    is_new: bool = Field(default=False)

we get the following error:

fastapi_test_app-web-1  | /usr/local/lib/python3.12/site-packages/pydantic/_internal/_fields.py:184: UserWarning: Field name "teams" shadows an attribute in parent "Hero"; 
fastapi_test_app-web-1  |   warnings.warn(
fastapi_test_app-web-1  | /usr/local/lib/python3.12/site-packages/pydantic/_internal/_fields.py:184: UserWarning: Field name "id" shadows an attribute in parent "Hero"; 
fastapi_test_app-web-1  |   warnings.warn(
fastapi_test_app-web-1  | /usr/local/lib/python3.12/site-packages/pydantic/_internal/_fields.py:184: UserWarning: Field name "name" shadows an attribute in parent "Hero"; 
fastapi_test_app-web-1  |   warnings.warn(
fastapi_test_app-web-1  | /usr/local/lib/python3.12/site-packages/pydantic/_internal/_fields.py:184: UserWarning: Field name "secret_name" shadows an attribute in parent "Hero"; 
fastapi_test_app-web-1  |   warnings.warn(
fastapi_test_app-web-1  | /usr/local/lib/python3.12/site-packages/pydantic/_internal/_fields.py:184: UserWarning: Field name "age" shadows an attribute in parent "Hero"; 
fastapi_test_app-web-1  |   warnings.warn(
fastapi_test_app-web-1  | Process SpawnProcess-1:
fastapi_test_app-web-1  | Traceback (most recent call last):
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
fastapi_test_app-web-1  |     self.run()
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/multiprocessing/process.py", line 108, in run
fastapi_test_app-web-1  |     self._target(*self._args, **self._kwargs)
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/uvicorn/_subprocess.py", line 76, in subprocess_started
fastapi_test_app-web-1  |     target(sockets=sockets)
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/uvicorn/server.py", line 61, in run
fastapi_test_app-web-1  |     return asyncio.run(self.serve(sockets=sockets))
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
fastapi_test_app-web-1  |     return runner.run(main)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
fastapi_test_app-web-1  |     return self._loop.run_until_complete(task)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/asyncio/base_events.py", line 664, in run_until_complete
fastapi_test_app-web-1  |     return future.result()
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/uvicorn/server.py", line 68, in serve
fastapi_test_app-web-1  |     config.load()
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/uvicorn/config.py", line 467, in load
fastapi_test_app-web-1  |     self.loaded_app = import_from_string(self.app)
fastapi_test_app-web-1  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/uvicorn/importer.py", line 21, in import_from_string
fastapi_test_app-web-1  |     module = importlib.import_module(module_str)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/importlib/__init__.py", line 90, in import_module
fastapi_test_app-web-1  |     return _bootstrap._gcd_import(name[level:], package, level)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap>", line 1381, in _gcd_import
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap>", line 1354, in _find_and_load
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap>", line 1325, in _find_and_load_unlocked
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap>", line 929, in _load_unlocked
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap_external>", line 994, in exec_module
fastapi_test_app-web-1  |   File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
fastapi_test_app-web-1  |   File "/usr/src/app/app/main.py", line 38, in <module>
fastapi_test_app-web-1  |     class HeroOut(Hero):
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/sqlmodel/main.py", line 451, in __new__
fastapi_test_app-web-1  |     new_cls = super().__new__(cls, name, bases, dict_used, **config_kwargs)
fastapi_test_app-web-1  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 182, in __new__
fastapi_test_app-web-1  |     complete_model_class(
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 491, in complete_model_class
fastapi_test_app-web-1  |     schema = cls.__get_pydantic_core_schema__(cls, handler)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/main.py", line 578, in __get_pydantic_core_schema__
fastapi_test_app-web-1  |     return __handler(__source)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py", line 82, in __call__
fastapi_test_app-web-1  |     schema = self._handler(__source_type)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 468, in generate_schema
fastapi_test_app-web-1  |     schema = self._generate_schema(obj)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 700, in _generate_schema
fastapi_test_app-web-1  |     schema = self._post_process_generated_schema(self._generate_schema_inner(obj))
fastapi_test_app-web-1  |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 722, in _generate_schema_inner
fastapi_test_app-web-1  |     return self._model_schema(obj)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 541, in _model_schema
fastapi_test_app-web-1  |     {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
fastapi_test_app-web-1  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 886, in _generate_md_field_schema
fastapi_test_app-web-1  |     common_field = self._common_field_schema(name, field_info, decorators)
fastapi_test_app-web-1  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 951, in _common_field_schema
fastapi_test_app-web-1  |     schema = self._apply_annotations(
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1654, in _apply_annotations
fastapi_test_app-web-1  |     schema = get_inner_schema(source_type)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py", line 82, in __call__
fastapi_test_app-web-1  |     schema = self._handler(__source_type)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1635, in inner_handler
fastapi_test_app-web-1  |     schema = self._generate_schema(obj)
fastapi_test_app-web-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 700, in _generate_schema
fastapi_test_app-web-1  |     schema = self._post_process_generated_schema(self._generate_schema_inner(obj))
fastapi_test_app-web-1  |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 727, in _generate_schema_inner
fastapi_test_app-web-1  |     return self.match_type(obj)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 810, in match_type
fastapi_test_app-web-1  |     return self._match_generic_type(obj, origin)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 858, in _match_generic_type
fastapi_test_app-web-1  |     return self._unknown_type_schema(obj)
fastapi_test_app-web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi_test_app-web-1  |   File "/usr/local/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 366, in _unknown_type_schema
fastapi_test_app-web-1  |     raise PydanticSchemaGenerationError(
fastapi_test_app-web-1  | pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for sqlalchemy.orm.base.Mapped[typing.List[app.main.Team]]. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
fastapi_test_app-web-1  | 
fastapi_test_app-web-1  | 
fastapi_test_app-web-1  | If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
fastapi_test_app-web-1  | 
fastapi_test_app-web-1  | 
fastapi_test_app-web-1  | For further information visit https://errors.pydantic.dev/2.5/u/schema-for-unknown-type\

How can we fix it, such that we can work with HeroOut?

1

There are 1 best solutions below

0
Yurii Motov On BEST ANSWER

According to the documentation of SQLModel you should

only inherit from data models, don't inherit from table models.

But your HeroOut is inherited from table model (table=True).

Try creating HeroBase model and inherit from it, as it's shown in the article.

https://sqlmodel.tiangolo.com/tutorial/fastapi/multiple-models/#only-inherit-from-data-models