Search code examples
python-3.xsqlalchemyfastapipydanticsqlmodel

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


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?


Solution

  • 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