Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ecfafa2
🎨 `Field()` supports `json_schema_extra` for `Pydantic v2`
KimigaiiWuyi Jul 26, 2024
c9f7ad8
Merge branch 'main' into main
KimigaiiWuyi Dec 28, 2024
60bd15a
🐛 Fix potential conflicts
KimigaiiWuyi Aug 23, 2025
750d1d0
Merge branch 'main' into main
svlandeg Sep 26, 2025
a6497b4
Merge branch 'fastapi:main' into main
KimigaiiWuyi Oct 3, 2025
4595d70
🎨 Increase unit test and improve compatibility
KimigaiiWuyi Oct 3, 2025
7912785
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
f0ae20e
🔥 Remove comments
KimigaiiWuyi Oct 3, 2025
0e48a37
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
249ad47
🎨 Ensure compatibility
KimigaiiWuyi Oct 3, 2025
42c2a9b
🔥 Remove the test configuration file
KimigaiiWuyi Oct 3, 2025
3380fb0
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
fb63890
Merge branch 'main' into main
YuriiMotov Oct 8, 2025
8314d1d
🎨 Update Deprecation Notice
KimigaiiWuyi Oct 8, 2025
de70227
🎨 Update test cases
KimigaiiWuyi Oct 8, 2025
c2c8598
🐛 Fix certain situations
KimigaiiWuyi Oct 8, 2025
522b030
Remove `pydantic_kwargs`, fix backward compatibility, fix tests
YuriiMotov Oct 9, 2025
497aeb1
Rename test file, fix tests for Pydantic V1
YuriiMotov Oct 9, 2025
52ea0df
Merge branch 'main' into main
YuriiMotov Oct 9, 2025
3ad8aa9
Remove unused caplog parameter in tests
YuriiMotov Oct 9, 2025
20188bf
Always remove `json_schema_extra` from `schema_extra` with Pydantic V2
YuriiMotov Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 78 additions & 6 deletions sqlmodel/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import inspect as inspect_module
import ipaddress
import uuid
import warnings
import weakref
from datetime import date, datetime, time, timedelta
from decimal import Decimal
Expand All @@ -28,6 +30,7 @@
)

from pydantic import BaseModel, EmailStr
from pydantic import Field as PydanticField
from pydantic.fields import FieldInfo as PydanticFieldInfo
from sqlalchemy import (
Boolean,
Expand All @@ -54,7 +57,7 @@
from sqlalchemy.orm.instrumentation import is_instrumented
from sqlalchemy.sql.schema import MetaData
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import Literal, TypeAlias, deprecated, get_origin
from typing_extensions import Annotated, Literal, TypeAlias, deprecated, get_origin

from ._compat import ( # type: ignore[attr-defined]
IS_PYDANTIC_V2,
Expand Down Expand Up @@ -100,6 +103,10 @@
]
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]

FIELD_ACCEPTED_KWARGS = set(inspect_module.signature(PydanticField).parameters.keys())
if "schema_extra" in FIELD_ACCEPTED_KWARGS:
FIELD_ACCEPTED_KWARGS.remove("schema_extra")


def __dataclass_transform__(
*,
Expand Down Expand Up @@ -251,7 +258,16 @@ def Field(
sa_type: Union[Type[Any], UndefinedType] = Undefined,
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
schema_extra: Annotated[
Optional[Dict[str, Any]],
deprecated(
"""
This parameter is deprecated.
Use `json_schema_extra` to add extra information to JSON schema.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -297,7 +313,16 @@ def Field(
sa_type: Union[Type[Any], UndefinedType] = Undefined,
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
schema_extra: Annotated[
Optional[Dict[str, Any]],
deprecated(
"""
This parameter is deprecated.
Use `json_schema_extra` to add extra information to JSON schema.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -343,7 +368,16 @@ def Field(
discriminator: Optional[str] = None,
repr: bool = True,
sa_column: Union[Column[Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
schema_extra: Annotated[
Optional[Dict[str, Any]],
deprecated(
"""
This parameter is deprecated.
Use `json_schema_extra` to add extra information to JSON schema.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -387,9 +421,47 @@ def Field(
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
schema_extra: Annotated[
Optional[Dict[str, Any]],
deprecated(
"""
This parameter is deprecated.
Use `json_schema_extra` to add extra information to JSON schema.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
) -> Any:
if schema_extra:
warnings.warn(
"schema_extra parameter is deprecated. "
"Use json_schema_extra to add extra information to JSON schema.",
DeprecationWarning,
stacklevel=1,
)

field_info_kwargs = {}
current_json_schema_extra = json_schema_extra or {}
current_schema_extra = schema_extra or {}

if IS_PYDANTIC_V2:
# Handle a workaround when json_schema_extra was passed via schema_extra
if "json_schema_extra" in current_schema_extra:
json_schema_extra_from_schema_extra = current_schema_extra.pop(
"json_schema_extra"
)
if not current_json_schema_extra:
current_json_schema_extra = json_schema_extra_from_schema_extra
# Split parameters from schema_extra to field_info_kwargs and json_schema_extra
for key, value in current_schema_extra.items():
if key in FIELD_ACCEPTED_KWARGS:
field_info_kwargs[key] = value
else:
current_json_schema_extra[key] = value
field_info_kwargs["json_schema_extra"] = current_json_schema_extra
else:
field_info_kwargs.update(current_json_schema_extra or current_schema_extra)

field_info = FieldInfo(
default,
default_factory=default_factory,
Expand Down Expand Up @@ -425,7 +497,7 @@ def Field(
sa_column=sa_column,
sa_column_args=sa_column_args,
sa_column_kwargs=sa_column_kwargs,
**current_schema_extra,
**field_info_kwargs,
)
post_init_field_info(field_info)
return field_info
Expand Down
100 changes: 100 additions & 0 deletions tests/test_field_json_schema_extra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import pytest
from sqlmodel import Field, SQLModel
from sqlmodel._compat import IS_PYDANTIC_V2

from tests.conftest import needs_pydanticv2


def test_json_schema_extra_applied():
"""test json_schema_extra is applied to the field"""

class Item(SQLModel):
name: str = Field(
json_schema_extra={
"example": "Sword of Power",
"x-custom-key": "Important Data",
}
)

if IS_PYDANTIC_V2:
schema = Item.model_json_schema()
else:
schema = Item.schema()

name_schema = schema["properties"]["name"]

assert name_schema["example"] == "Sword of Power"
assert name_schema["x-custom-key"] == "Important Data"


def test_schema_extra_and_json_schema_extra_conflict():
"""
Test that passing schema_extra and json_schema_extra at the same time produces
a warning.
"""

with pytest.warns(DeprecationWarning, match="schema_extra parameter is deprecated"):
Field(schema_extra={"legacy": 1}, json_schema_extra={"new": 2})


def test_schema_extra_backward_compatibility():
"""
test that schema_extra is backward compatible with json_schema_extra
"""

with pytest.warns(DeprecationWarning, match="schema_extra parameter is deprecated"):

class LegacyItem(SQLModel):
name: str = Field(
schema_extra={
"example": "Sword of Old",
"x-custom-key": "Important Data",
"serialization_alias": "id_test",
}
)

if IS_PYDANTIC_V2:
schema = LegacyItem.model_json_schema()
else:
schema = LegacyItem.schema()

name_schema = schema["properties"]["name"]

assert name_schema["example"] == "Sword of Old"
assert name_schema["x-custom-key"] == "Important Data"

if IS_PYDANTIC_V2:
# With Pydantic V1 serialization_alias from schema_extra is applied
field_info = LegacyItem.model_fields["name"]
assert field_info.serialization_alias == "id_test"
else: # With Pydantic V1 it just goes to schema
assert name_schema["serialization_alias"] == "id_test"


@needs_pydanticv2
def test_json_schema_extra_mix_in_schema_extra():
"""
Test workaround when json_schema_extra was passed via schema_extra with Pydantic v2.
"""

with pytest.warns(DeprecationWarning, match="schema_extra parameter is deprecated"):

class Item(SQLModel):
name: str = Field(
schema_extra={
"json_schema_extra": {
"example": "Sword of Power",
"x-custom-key": "Important Data",
},
"serialization_alias": "id_test",
}
)

schema = Item.model_json_schema()

name_schema = schema["properties"]["name"]
assert name_schema["example"] == "Sword of Power"
assert name_schema["x-custom-key"] == "Important Data"

field_info = Item.model_fields["name"]
assert field_info.serialization_alias == "id_test"
Loading