Skip to content

Commit 4595d70

Browse files
committed
🎨 Increase unit test and improve compatibility
1 parent a6497b4 commit 4595d70

File tree

3 files changed

+166
-8
lines changed

3 files changed

+166
-8
lines changed

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,9 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
135135
[tool.ruff.lint.pyupgrade]
136136
# Preserve types, even if a file imports `from __future__ import annotations`.
137137
keep-runtime-typing = true
138+
139+
[tool.pytest.ini_options]
140+
filterwarnings = [
141+
"always::DeprecationWarning",
142+
"ignore:.*:DeprecationWarning:typing_extensions"
143+
]

sqlmodel/main.py

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ipaddress
22
import uuid
3+
import inspect as inspect_module
34
import weakref
45
from datetime import date, datetime, time, timedelta
56
from decimal import Decimal
@@ -26,6 +27,7 @@
2627
)
2728

2829
from pydantic import BaseModel, EmailStr
30+
from pydantic import Field as PydanticField
2931
from pydantic.fields import FieldInfo as PydanticFieldInfo
3032
from sqlalchemy import (
3133
Boolean,
@@ -52,7 +54,7 @@
5254
from sqlalchemy.orm.instrumentation import is_instrumented
5355
from sqlalchemy.sql.schema import MetaData
5456
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
55-
from typing_extensions import Literal, TypeAlias, deprecated, get_origin
57+
from typing_extensions import Annotated, Literal, TypeAlias, deprecated, get_origin
5658

5759
from ._compat import ( # type: ignore[attr-defined]
5860
IS_PYDANTIC_V2,
@@ -98,6 +100,9 @@
98100
]
99101
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]
100102

103+
FIELD_ACCEPTED_KWARGS = set(inspect_module.signature(PydanticField).parameters.keys())
104+
FIELD_ACCEPTED_KWARGS.remove('json_schema_extra')
105+
101106

102107
def __dataclass_transform__(
103108
*,
@@ -248,7 +253,19 @@ def Field(
248253
sa_type: Union[Type[Any], UndefinedType] = Undefined,
249254
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
250255
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
251-
schema_extra: Optional[Dict[str, Any]] = None,
256+
schema_extra: Annotated[
257+
Optional[Dict[str, Any]],
258+
deprecated(
259+
"""
260+
This parameter is deprecated.
261+
Use `json_schema_extra` to add extra information to the JSON schema.
262+
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
263+
part of this interface, but accepted by Pydantic's Field.
264+
"""
265+
),
266+
] = None,
267+
json_schema_extra: Optional[Dict[str, Any]] = None,
268+
pydantic_kwargs: Optional[Dict[str, Any]] = None,
252269
) -> Any: ...
253270

254271

@@ -294,8 +311,19 @@ def Field(
294311
sa_type: Union[Type[Any], UndefinedType] = Undefined,
295312
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
296313
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
297-
schema_extra: Optional[Dict[str, Any]] = None,
314+
schema_extra: Annotated[
315+
Optional[Dict[str, Any]],
316+
deprecated(
317+
"""
318+
This parameter is deprecated.
319+
Use `json_schema_extra` to add extra information to the JSON schema.
320+
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
321+
part of this interface, but accepted by Pydantic's Field.
322+
"""
323+
),
324+
] = None,
298325
json_schema_extra: Optional[Dict[str, Any]] = None,
326+
pydantic_kwargs: Optional[Dict[str, Any]] = None,
299327
) -> Any: ...
300328

301329

@@ -341,8 +369,19 @@ def Field(
341369
discriminator: Optional[str] = None,
342370
repr: bool = True,
343371
sa_column: Union[Column[Any], UndefinedType] = Undefined,
344-
schema_extra: Optional[Dict[str, Any]] = None,
372+
schema_extra: Annotated[
373+
Optional[Dict[str, Any]],
374+
deprecated(
375+
"""
376+
This parameter is deprecated.
377+
Use `json_schema_extra` to add extra information to the JSON schema.
378+
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
379+
part of this interface, but accepted by Pydantic's Field.
380+
"""
381+
),
382+
] = None,
345383
json_schema_extra: Optional[Dict[str, Any]] = None,
384+
pydantic_kwargs: Optional[Dict[str, Any]] = None,
346385
) -> Any: ...
347386

348387

@@ -386,12 +425,40 @@ def Field(
386425
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
387426
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
388427
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
389-
schema_extra: Optional[Dict[str, Any]] = None,
428+
schema_extra: Annotated[
429+
Optional[Dict[str, Any]],
430+
deprecated(
431+
"""
432+
This parameter is deprecated.
433+
Use `json_schema_extra` to add extra information to the JSON schema.
434+
Use `pydantic_kwargs` to pass additional parameters to `Field` that are not
435+
part of this interface, but accepted by Pydantic's Field.
436+
"""
437+
),
438+
] = None,
390439
json_schema_extra: Optional[Dict[str, Any]] = None,
440+
pydantic_kwargs: Optional[Dict[str, Any]] = None,
391441
) -> Any:
442+
if schema_extra and (json_schema_extra or pydantic_kwargs):
443+
raise RuntimeError(
444+
"Passing schema_extra is not supported when "
445+
"also passing a json_schema_extra"
446+
)
447+
448+
current_pydantic_kwargs = pydantic_kwargs or {}
449+
current_json_schema_extra = json_schema_extra or {}
392450
current_schema_extra = schema_extra or {}
393-
if json_schema_extra:
394-
current_schema_extra["json_schema_extra"] = json_schema_extra
451+
452+
if current_schema_extra:
453+
for key, value in current_schema_extra.items():
454+
if key in FIELD_ACCEPTED_KWARGS:
455+
current_pydantic_kwargs[key] = value
456+
else:
457+
current_json_schema_extra[key] = value
458+
459+
print(current_pydantic_kwargs)
460+
print(current_json_schema_extra)
461+
395462
field_info = FieldInfo(
396463
default,
397464
default_factory=default_factory,
@@ -427,7 +494,8 @@ def Field(
427494
sa_column=sa_column,
428495
sa_column_args=sa_column_args,
429496
sa_column_kwargs=sa_column_kwargs,
430-
**current_schema_extra,
497+
json_schema_extra=current_json_schema_extra,
498+
**current_pydantic_kwargs,
431499
)
432500
post_init_field_info(field_info)
433501
return field_info
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from sqlmodel import Field, SQLModel
2+
import pytest
3+
4+
5+
def test_json_schema_extra_applied():
6+
'''test json_schema_extra is applied to the field'''
7+
8+
class Item(SQLModel):
9+
name: str = Field(
10+
json_schema_extra={
11+
"example": "Sword of Power",
12+
"x-custom-key": "Important Data",
13+
}
14+
)
15+
16+
schema = Item.model_json_schema()
17+
18+
name_schema = schema["properties"]["name"]
19+
20+
assert name_schema["example"] == "Sword of Power"
21+
assert name_schema["x-custom-key"] == "Important Data"
22+
23+
24+
def test_pydantic_kwargs_applied():
25+
'''test pydantic_kwargs is applied to the field'''
26+
27+
class User(SQLModel):
28+
user_name: str = Field(pydantic_kwargs={"validation_alias": "UserNameInInput"})
29+
30+
field_info = User.model_fields["user_name"]
31+
32+
assert field_info.validation_alias == "UserNameInInput"
33+
34+
data = {"UserNameInInput": "KimigaiiWuyi"}
35+
user = User.model_validate(data)
36+
assert user.user_name == "KimigaiiWuyi"
37+
38+
39+
def test_schema_extra_and_new_param_conflict():
40+
with pytest.raises(RuntimeError) as excinfo:
41+
42+
class ItemA(SQLModel):
43+
name: str = Field(
44+
schema_extra={"legacy": 1},
45+
json_schema_extra={"new": 2},
46+
)
47+
48+
assert "Passing schema_extra is not supported" in str(excinfo.value)
49+
50+
with pytest.raises(RuntimeError) as excinfo:
51+
52+
class ItemB(SQLModel):
53+
name: str = Field(
54+
schema_extra={"legacy": 1},
55+
pydantic_kwargs={"alias": "Alias"},
56+
)
57+
58+
assert "Passing schema_extra is not supported" in str(excinfo.value)
59+
60+
61+
def test_schema_extra_backward_compatibility():
62+
"""
63+
test that schema_extra is backward compatible with json_schema_extra
64+
"""
65+
66+
# 1. 定义一个仅使用 schema_extra 的模型
67+
class LegacyItem(SQLModel):
68+
name: str = Field(
69+
schema_extra={
70+
"example": "Sword of Old",
71+
"x-custom-key": "Important Data",
72+
"serialization_alias": "id_test",
73+
}
74+
)
75+
76+
schema = LegacyItem.model_json_schema()
77+
78+
name_schema = schema["properties"]["name"]
79+
80+
assert name_schema["example"] == "Sword of Old"
81+
assert name_schema["x-custom-key"] == "Important Data"
82+
83+
field_info = LegacyItem.model_fields["name"]
84+
assert field_info.serialization_alias == "id_test"

0 commit comments

Comments
 (0)