From ecfafa255e796a0188843bb47da9354f2b12829c Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 26 Jul 2024 16:36:06 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=F0=9F=8E=A8=20`Field()`=20supports=20`js?= =?UTF-8?q?on=5Fschema=5Fextra`=20for=20`Pydantic=20v2`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index d8fced51fa..59a0f04132 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -292,6 +292,7 @@ def Field( sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined, sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined, schema_extra: Optional[Dict[str, Any]] = None, + json_schema_extra: Optional[Dict[str, Any]] = None, ) -> Any: ... @@ -338,6 +339,7 @@ def Field( repr: bool = True, sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore schema_extra: Optional[Dict[str, Any]] = None, + json_schema_extra: Optional[Dict[str, Any]] = None, ) -> Any: ... @@ -382,8 +384,12 @@ def Field( sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined, sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined, schema_extra: Optional[Dict[str, Any]] = None, + json_schema_extra: Optional[Dict[str, Any]] = None, ) -> Any: - current_schema_extra = schema_extra or {} + if json_schema_extra: + current_schema_extra = {"json_schema_extra": json_schema_extra} + else: + current_schema_extra = schema_extra or {} field_info = FieldInfo( default, default_factory=default_factory, From 60bd15a1ba2a3ccfc68d4c58dbee00a35368d21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wuyi=E6=97=A0=E7=96=91?= <444835641@qq.com> Date: Sat, 23 Aug 2025 14:01:05 +0800 Subject: [PATCH 02/18] =?UTF-8?q?=F0=9F=90=9B=20Fix=20potential=20conflict?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- sqlmodel/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index aad9bc654e..f6439be46f 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -391,10 +391,9 @@ def Field( schema_extra: Optional[Dict[str, Any]] = None, json_schema_extra: Optional[Dict[str, Any]] = None, ) -> Any: + current_schema_extra = schema_extra or {} if json_schema_extra: - current_schema_extra = {"json_schema_extra": json_schema_extra} - else: - current_schema_extra = schema_extra or {} + current_schema_extra["json_schema_extra"] = json_schema_extra field_info = FieldInfo( default, default_factory=default_factory, From 4595d703248d72eff0178019c605d1b1e245f1b3 Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 3 Oct 2025 19:31:37 +0800 Subject: [PATCH 03/18] =?UTF-8?q?=F0=9F=8E=A8=20Increase=20unit=20test=20a?= =?UTF-8?q?nd=20improve=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 6 ++ sqlmodel/main.py | 84 ++++++++++++++++++++++--- tests/test_field_pd_and_json_kwarrgs.py | 84 +++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 tests/test_field_pd_and_json_kwarrgs.py diff --git a/pyproject.toml b/pyproject.toml index 4ae195ac73..a9320a581e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,3 +135,9 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true + +[tool.pytest.ini_options] +filterwarnings = [ + "always::DeprecationWarning", + "ignore:.*:DeprecationWarning:typing_extensions" +] \ No newline at end of file diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 3953d5dc32..5e70ed1434 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -1,5 +1,6 @@ import ipaddress import uuid +import inspect as inspect_module import weakref from datetime import date, datetime, time, timedelta from decimal import Decimal @@ -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, @@ -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, @@ -98,6 +100,9 @@ ] OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"] +FIELD_ACCEPTED_KWARGS = set(inspect_module.signature(PydanticField).parameters.keys()) +FIELD_ACCEPTED_KWARGS.remove('json_schema_extra') + def __dataclass_transform__( *, @@ -248,7 +253,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: ... @@ -294,8 +311,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: ... @@ -341,8 +369,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: ... @@ -386,12 +425,40 @@ 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 json_schema_extra: - current_schema_extra["json_schema_extra"] = json_schema_extra + + 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 + + print(current_pydantic_kwargs) + print(current_json_schema_extra) + field_info = FieldInfo( default, default_factory=default_factory, @@ -427,7 +494,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 diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py new file mode 100644 index 0000000000..3042a24408 --- /dev/null +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -0,0 +1,84 @@ +from sqlmodel import Field, SQLModel +import pytest + + +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(): + 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 + """ + + # 1. 定义一个仅使用 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" From 79127858762c05e702181c7f313ceb45bbfea358 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:31:50 +0000 Subject: [PATCH 04/18] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- sqlmodel/main.py | 4 ++-- tests/test_field_pd_and_json_kwarrgs.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a9320a581e..01009cde86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,4 +140,4 @@ keep-runtime-typing = true filterwarnings = [ "always::DeprecationWarning", "ignore:.*:DeprecationWarning:typing_extensions" -] \ No newline at end of file +] diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 5e70ed1434..ed750c1c8b 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -1,6 +1,6 @@ +import inspect as inspect_module import ipaddress import uuid -import inspect as inspect_module import weakref from datetime import date, datetime, time, timedelta from decimal import Decimal @@ -101,7 +101,7 @@ OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"] FIELD_ACCEPTED_KWARGS = set(inspect_module.signature(PydanticField).parameters.keys()) -FIELD_ACCEPTED_KWARGS.remove('json_schema_extra') +FIELD_ACCEPTED_KWARGS.remove("json_schema_extra") def __dataclass_transform__( diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index 3042a24408..feead62818 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -1,9 +1,9 @@ -from sqlmodel import Field, SQLModel import pytest +from sqlmodel import Field, SQLModel def test_json_schema_extra_applied(): - '''test json_schema_extra is applied to the field''' + """test json_schema_extra is applied to the field""" class Item(SQLModel): name: str = Field( @@ -22,7 +22,7 @@ class Item(SQLModel): def test_pydantic_kwargs_applied(): - '''test pydantic_kwargs is applied to the field''' + """test pydantic_kwargs is applied to the field""" class User(SQLModel): user_name: str = Field(pydantic_kwargs={"validation_alias": "UserNameInInput"}) From f0ae20e65165c9d45b20020e0aeac9d8b5484c33 Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 3 Oct 2025 19:36:10 +0800 Subject: [PATCH 05/18] =?UTF-8?q?=F0=9F=94=A5=20Remove=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/main.py | 3 --- tests/test_field_pd_and_json_kwarrgs.py | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index ed750c1c8b..17ae977615 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -456,9 +456,6 @@ def Field( else: current_json_schema_extra[key] = value - print(current_pydantic_kwargs) - print(current_json_schema_extra) - field_info = FieldInfo( default, default_factory=default_factory, diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index feead62818..812f3287c7 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -37,6 +37,8 @@ class User(SQLModel): 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): @@ -63,7 +65,6 @@ def test_schema_extra_backward_compatibility(): test that schema_extra is backward compatible with json_schema_extra """ - # 1. 定义一个仅使用 schema_extra 的模型 class LegacyItem(SQLModel): name: str = Field( schema_extra={ From 0e48a37ba9a2db6632b3c22341f13106945af853 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:36:22 +0000 Subject: [PATCH 06/18] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_field_pd_and_json_kwarrgs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index 812f3287c7..f2f31509fd 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -37,7 +37,7 @@ class User(SQLModel): def test_schema_extra_and_new_param_conflict(): - '''test that passing schema_extra and new params at the same time raises an error''' + """test that passing schema_extra and new params at the same time raises an error""" with pytest.raises(RuntimeError) as excinfo: From 249ad47368b6bde33b63240742582affc6692b25 Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 3 Oct 2025 19:40:04 +0800 Subject: [PATCH 07/18] =?UTF-8?q?=F0=9F=8E=A8=20Ensure=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 17ae977615..6ff62f566b 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -101,7 +101,8 @@ OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"] FIELD_ACCEPTED_KWARGS = set(inspect_module.signature(PydanticField).parameters.keys()) -FIELD_ACCEPTED_KWARGS.remove("json_schema_extra") +if "schema_extra" in FIELD_ACCEPTED_KWARGS: + FIELD_ACCEPTED_KWARGS.remove("schema_extra") def __dataclass_transform__( From 42c2a9be5cb1dfe3c46328cd03844b2b9ca72ce1 Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 3 Oct 2025 19:49:33 +0800 Subject: [PATCH 08/18] =?UTF-8?q?=F0=9F=94=A5=20Remove=20the=20test=20conf?= =?UTF-8?q?iguration=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 01009cde86..733d88c337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,10 +134,4 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. -keep-runtime-typing = true - -[tool.pytest.ini_options] -filterwarnings = [ - "always::DeprecationWarning", - "ignore:.*:DeprecationWarning:typing_extensions" -] +keep-runtime-typing = true \ No newline at end of file From 3380fb0a72155a3d5c59248f5c8c84ce040d20b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:50:29 +0000 Subject: [PATCH 09/18] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 733d88c337..4ae195ac73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,4 +134,4 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. -keep-runtime-typing = true \ No newline at end of file +keep-runtime-typing = true From 8314d1d97bd78b9bd8b5c5220fcbe2616cba3820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wuyi=E6=97=A0=E7=96=91?= <444835641@qq.com> Date: Wed, 8 Oct 2025 20:34:19 +0800 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=8E=A8=20Update=20Deprecation=20Not?= =?UTF-8?q?ice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- sqlmodel/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 3f751279ca..4ce0a8f7e0 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -446,7 +446,7 @@ def Field( 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" + "also passing a json_schema_extra or pydantic_kwargs" ) current_pydantic_kwargs = pydantic_kwargs or {} From de702275620475d83a5a123bb7b71f702d3151b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wuyi=E6=97=A0=E7=96=91?= <444835641@qq.com> Date: Wed, 8 Oct 2025 20:34:45 +0800 Subject: [PATCH 11/18] =?UTF-8?q?=F0=9F=8E=A8=20Update=20test=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- tests/test_field_pd_and_json_kwarrgs.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index f2f31509fd..b5523901e2 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -40,23 +40,11 @@ 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}, - ) - + 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"}, - ) - + Field(schema_extra={"legacy": 1}, pydantic_kwargs={"alias": "Alias"}) assert "Passing schema_extra is not supported" in str(excinfo.value) From c2c8598ef65390ddefcdd24e923bb05b8628a8ca Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Wed, 8 Oct 2025 22:39:07 +0800 Subject: [PATCH 12/18] =?UTF-8?q?=F0=9F=90=9B=20Fix=20certain=20situations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/main.py | 12 +++++++++--- tests/test_field_pd_and_json_kwarrgs.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 4ce0a8f7e0..efb7d722df 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -453,12 +453,19 @@ def Field( current_json_schema_extra = json_schema_extra or {} current_schema_extra = schema_extra or {} - if current_schema_extra: + if IS_PYDANTIC_V2: for key, value in current_schema_extra.items(): - if key in FIELD_ACCEPTED_KWARGS: + # if schema_extra={"json_schema_extra": {"x-yy-zz": "zz"}} + if key == "json_schema_extra": + current_json_schema_extra.update(value) + elif key in FIELD_ACCEPTED_KWARGS: current_pydantic_kwargs[key] = value else: current_json_schema_extra[key] = value + current_pydantic_kwargs["json_schema_extra"] = current_json_schema_extra + else: + current_pydantic_kwargs.update(current_json_schema_extra) + current_pydantic_kwargs.update(current_schema_extra) field_info = FieldInfo( default, @@ -495,7 +502,6 @@ def Field( sa_column=sa_column, sa_column_args=sa_column_args, sa_column_kwargs=sa_column_kwargs, - json_schema_extra=current_json_schema_extra, **current_pydantic_kwargs, ) post_init_field_info(field_info) diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index b5523901e2..8d90dece85 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -71,3 +71,27 @@ class LegacyItem(SQLModel): field_info = LegacyItem.model_fields["name"] assert field_info.serialization_alias == "id_test" + + +def test_json_schema_extra_mix_in_schema_extra(): + """test that json_schema_extra is applied when it is in schema_extra""" + + 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" From 522b0301f0ee6d1bf7a7ced71b6e81fad17804a4 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 9 Oct 2025 20:17:41 +0200 Subject: [PATCH 13/18] Remove `pydantic_kwargs`, fix backward compatibility, fix tests --- sqlmodel/main.py | 54 +++++++++---------- tests/test_field_pd_and_json_kwarrgs.py | 69 ++++++++++--------------- 2 files changed, 52 insertions(+), 71 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index efb7d722df..28b240b799 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -3,6 +3,7 @@ import inspect as inspect_module import ipaddress import uuid +import warnings import weakref from datetime import date, datetime, time, timedelta from decimal import Decimal @@ -262,14 +263,11 @@ def Field( 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. + Use `json_schema_extra` to add extra information to JSON schema. """ ), ] = None, json_schema_extra: Optional[Dict[str, Any]] = None, - pydantic_kwargs: Optional[Dict[str, Any]] = None, ) -> Any: ... @@ -320,14 +318,11 @@ def Field( 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. + Use `json_schema_extra` to add extra information to JSON schema. """ ), ] = None, json_schema_extra: Optional[Dict[str, Any]] = None, - pydantic_kwargs: Optional[Dict[str, Any]] = None, ) -> Any: ... @@ -378,14 +373,11 @@ def Field( 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. + Use `json_schema_extra` to add extra information to JSON schema. """ ), ] = None, json_schema_extra: Optional[Dict[str, Any]] = None, - pydantic_kwargs: Optional[Dict[str, Any]] = None, ) -> Any: ... @@ -434,38 +426,40 @@ def Field( 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. + Use `json_schema_extra` to add extra information to JSON schema. """ ), ] = 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 or pydantic_kwargs" + if schema_extra: + warnings.warn( + "schema_extra parameter is deprecated. " + "Use json_schema_extra to add extra information to JSON schema.", + DeprecationWarning, + stacklevel=1, ) - current_pydantic_kwargs = pydantic_kwargs or {} + 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: + if not current_json_schema_extra: + current_json_schema_extra = current_schema_extra.pop( + "json_schema_extra" + ) + # Split parameters from schema_extra to field_info_kwargs and json_schema_extra for key, value in current_schema_extra.items(): - # if schema_extra={"json_schema_extra": {"x-yy-zz": "zz"}} - if key == "json_schema_extra": - current_json_schema_extra.update(value) - elif key in FIELD_ACCEPTED_KWARGS: - current_pydantic_kwargs[key] = value + if key in FIELD_ACCEPTED_KWARGS: + field_info_kwargs[key] = value else: current_json_schema_extra[key] = value - current_pydantic_kwargs["json_schema_extra"] = current_json_schema_extra + field_info_kwargs["json_schema_extra"] = current_json_schema_extra else: - current_pydantic_kwargs.update(current_json_schema_extra) - current_pydantic_kwargs.update(current_schema_extra) + field_info_kwargs.update(current_json_schema_extra or current_schema_extra) field_info = FieldInfo( default, @@ -502,7 +496,7 @@ def Field( sa_column=sa_column, sa_column_args=sa_column_args, sa_column_kwargs=sa_column_kwargs, - **current_pydantic_kwargs, + **field_info_kwargs, ) post_init_field_info(field_info) return field_info diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_pd_and_json_kwarrgs.py index 8d90dece85..e3a09f7c17 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_pd_and_json_kwarrgs.py @@ -21,31 +21,14 @@ class Item(SQLModel): 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""" +def test_schema_extra_and_new_param_conflict(caplog): + """ + Test that passing schema_extra and json_schema_extra at the same time produces + a warning. + """ - with pytest.raises(RuntimeError) as excinfo: + with pytest.warns(DeprecationWarning, match="schema_extra parameter is deprecated"): 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: - 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(): @@ -53,14 +36,16 @@ 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", - } - ) + 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", + } + ) schema = LegacyItem.model_json_schema() @@ -76,16 +61,18 @@ class LegacyItem(SQLModel): def test_json_schema_extra_mix_in_schema_extra(): """test that json_schema_extra is applied when it is in schema_extra""" - class Item(SQLModel): - name: str = Field( - schema_extra={ - "json_schema_extra": { - "example": "Sword of Power", - "x-custom-key": "Important Data", - }, - "serialization_alias": "id_test", - } - ) + 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() From 497aeb1ddd73e3cac9b17c4cb87e58053f4cc8d1 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 9 Oct 2025 20:45:22 +0200 Subject: [PATCH 14/18] Rename test file, fix tests for Pydantic V1 --- ...rgs.py => test_field_json_schema_extra.py} | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) rename tests/{test_field_pd_and_json_kwarrgs.py => test_field_json_schema_extra.py} (73%) diff --git a/tests/test_field_pd_and_json_kwarrgs.py b/tests/test_field_json_schema_extra.py similarity index 73% rename from tests/test_field_pd_and_json_kwarrgs.py rename to tests/test_field_json_schema_extra.py index e3a09f7c17..f7fc0d9062 100644 --- a/tests/test_field_pd_and_json_kwarrgs.py +++ b/tests/test_field_json_schema_extra.py @@ -1,5 +1,8 @@ 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(): @@ -13,7 +16,10 @@ class Item(SQLModel): } ) - schema = Item.model_json_schema() + if IS_PYDANTIC_V2: + schema = Item.model_json_schema() + else: + schema = Item.schema() name_schema = schema["properties"]["name"] @@ -21,7 +27,7 @@ class Item(SQLModel): assert name_schema["x-custom-key"] == "Important Data" -def test_schema_extra_and_new_param_conflict(caplog): +def test_schema_extra_and_json_schema_extra_conflict(caplog): """ Test that passing schema_extra and json_schema_extra at the same time produces a warning. @@ -47,19 +53,29 @@ class LegacyItem(SQLModel): } ) - schema = LegacyItem.model_json_schema() + 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" - field_info = LegacyItem.model_fields["name"] - assert field_info.serialization_alias == "id_test" + 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 that json_schema_extra is applied when it is 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"): From 3ad8aa9e1f1a29aeb053e3c6f1d12c9b853db834 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 9 Oct 2025 21:06:26 +0200 Subject: [PATCH 15/18] Remove unused caplog parameter in tests --- tests/test_field_json_schema_extra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_field_json_schema_extra.py b/tests/test_field_json_schema_extra.py index f7fc0d9062..4d0c4ede1e 100644 --- a/tests/test_field_json_schema_extra.py +++ b/tests/test_field_json_schema_extra.py @@ -27,7 +27,7 @@ class Item(SQLModel): assert name_schema["x-custom-key"] == "Important Data" -def test_schema_extra_and_json_schema_extra_conflict(caplog): +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. From 20188bf02064c01ecaa18298b718d5f36e4f104f Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 9 Oct 2025 21:12:20 +0200 Subject: [PATCH 16/18] Always remove `json_schema_extra` from `schema_extra` with Pydantic V2 --- sqlmodel/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 28b240b799..c9c9f81986 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -447,10 +447,11 @@ def Field( 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 = current_schema_extra.pop( - "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: From c8157853df18e0ef6b4dcb47455b2e1aadf645ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:57:37 +0000 Subject: [PATCH 17/18] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 1b0d6f1dc6..69488e4176 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import ( TYPE_CHECKING, + Annotated, Any, Callable, ClassVar, @@ -51,7 +52,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 Annotated, Literal, TypeAlias, deprecated, get_origin +from typing_extensions import Literal, TypeAlias, deprecated, get_origin from ._compat import ( # type: ignore[attr-defined] PYDANTIC_MINOR_VERSION, @@ -469,7 +470,7 @@ def Field( field_info_kwargs["serialization_alias"] = ( serialization_alias or schema_serialization_alias or alias ) - + # 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( From 8186e34a28ce18b2add1468da7adde8f329fcbf6 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:59:45 +0100 Subject: [PATCH 18/18] Remove PYDANTIC_V2 conditions in test Refactor tests to remove Pydantic version checks and simplify schema retrieval. --- tests/test_field_json_schema_extra.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/test_field_json_schema_extra.py b/tests/test_field_json_schema_extra.py index 4d0c4ede1e..0cbe3dafaa 100644 --- a/tests/test_field_json_schema_extra.py +++ b/tests/test_field_json_schema_extra.py @@ -1,8 +1,5 @@ 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(): @@ -16,11 +13,7 @@ class Item(SQLModel): } ) - if IS_PYDANTIC_V2: - schema = Item.model_json_schema() - else: - schema = Item.schema() - + schema = Item.model_json_schema() name_schema = schema["properties"]["name"] assert name_schema["example"] == "Sword of Power" @@ -53,28 +46,20 @@ class LegacyItem(SQLModel): } ) - if IS_PYDANTIC_V2: - schema = LegacyItem.model_json_schema() - else: - schema = LegacyItem.schema() - + 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" - 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" + # With Pydantic V1 serialization_alias from schema_extra is applied + field_info = LegacyItem.model_fields["name"] + assert field_info.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. + Test workaround when json_schema_extra was passed via schema_extra. """ with pytest.warns(DeprecationWarning, match="schema_extra parameter is deprecated"):