Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
83 changes: 77 additions & 6 deletions sqlmodel/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import inspect as inspect_module
import ipaddress
import uuid
import weakref
Expand Down Expand Up @@ -26,6 +27,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 @@ -52,7 +54,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 @@ -98,6 +100,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 @@ -248,7 +254,19 @@ 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 the JSON schema.
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
part of this interface, but accepted by Pydantic's Field.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
pydantic_kwargs: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -294,7 +312,19 @@ 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 the JSON schema.
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
part of this interface, but accepted by Pydantic's Field.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
pydantic_kwargs: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -340,7 +370,19 @@ 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 the JSON schema.
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
part of this interface, but accepted by Pydantic's Field.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
pydantic_kwargs: Optional[Dict[str, Any]] = None,
) -> Any: ...


Expand Down Expand Up @@ -384,9 +426,37 @@ 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 the JSON schema.
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
part of this interface, but accepted by Pydantic's Field.
"""
),
] = None,
json_schema_extra: Optional[Dict[str, Any]] = None,
pydantic_kwargs: Optional[Dict[str, Any]] = None,
) -> Any:
if schema_extra and (json_schema_extra or pydantic_kwargs):
raise RuntimeError(
"Passing schema_extra is not supported when "
"also passing a json_schema_extra"
)

current_pydantic_kwargs = pydantic_kwargs or {}
current_json_schema_extra = json_schema_extra or {}
current_schema_extra = schema_extra or {}

if current_schema_extra:
for key, value in current_schema_extra.items():
if key in FIELD_ACCEPTED_KWARGS:
current_pydantic_kwargs[key] = value
else:
current_json_schema_extra[key] = value

field_info = FieldInfo(
default,
default_factory=default_factory,
Expand Down Expand Up @@ -422,7 +492,8 @@ def Field(
sa_column=sa_column,
sa_column_args=sa_column_args,
sa_column_kwargs=sa_column_kwargs,
**current_schema_extra,
json_schema_extra=current_json_schema_extra,
**current_pydantic_kwargs,
)
post_init_field_info(field_info)
return field_info
Expand Down
85 changes: 85 additions & 0 deletions tests/test_field_pd_and_json_kwarrgs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import pytest
from sqlmodel import Field, SQLModel


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",
}
)

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"


def test_pydantic_kwargs_applied():
"""test pydantic_kwargs is applied to the field"""

class User(SQLModel):
user_name: str = Field(pydantic_kwargs={"validation_alias": "UserNameInInput"})

field_info = User.model_fields["user_name"]

assert field_info.validation_alias == "UserNameInInput"

data = {"UserNameInInput": "KimigaiiWuyi"}
user = User.model_validate(data)
assert user.user_name == "KimigaiiWuyi"


def test_schema_extra_and_new_param_conflict():
"""test that passing schema_extra and new params at the same time raises an error"""

with pytest.raises(RuntimeError) as excinfo:

class ItemA(SQLModel):
name: str = Field(
schema_extra={"legacy": 1},
json_schema_extra={"new": 2},
)

assert "Passing schema_extra is not supported" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:

class ItemB(SQLModel):
name: str = Field(
schema_extra={"legacy": 1},
pydantic_kwargs={"alias": "Alias"},
)

assert "Passing schema_extra is not supported" in str(excinfo.value)


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

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

schema = LegacyItem.model_json_schema()

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

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

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