Skip to content

Commit 322c6af

Browse files
committed
Field: support schema_extra aliases (v2); add alias tests
1 parent 6b7a0a1 commit 322c6af

File tree

2 files changed

+104
-15
lines changed

2 files changed

+104
-15
lines changed

sqlmodel/main.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ def Field(
398398
schema_extra: Optional[Dict[str, Any]] = None,
399399
) -> Any:
400400
current_schema_extra = schema_extra or {}
401+
# Extract possible alias settings from schema_extra so we can control precedence
402+
schema_validation_alias = current_schema_extra.pop("validation_alias", None)
403+
schema_serialization_alias = current_schema_extra.pop("serialization_alias", None)
401404
field_info_kwargs = {
402405
"alias": alias,
403406
"title": title,
@@ -434,17 +437,25 @@ def Field(
434437
**current_schema_extra,
435438
}
436439
if IS_PYDANTIC_V2:
437-
# Add Pydantic v2 specific parameters
438-
field_info_kwargs.update(
439-
{
440-
"validation_alias": validation_alias,
441-
"serialization_alias": serialization_alias,
442-
}
440+
# explicit params > schema_extra > alias propagation (handled later)
441+
effective_validation_alias = (
442+
validation_alias
443+
if validation_alias is not None
444+
else schema_validation_alias
443445
)
446+
effective_serialization_alias = (
447+
serialization_alias
448+
if serialization_alias is not None
449+
else schema_serialization_alias
450+
)
451+
if effective_validation_alias is not None:
452+
field_info_kwargs["validation_alias"] = effective_validation_alias
453+
if effective_serialization_alias is not None:
454+
field_info_kwargs["serialization_alias"] = effective_serialization_alias
444455
else:
445-
if validation_alias:
456+
if validation_alias or schema_validation_alias is not None:
446457
raise RuntimeError("validation_alias is not supported in Pydantic v1")
447-
if serialization_alias:
458+
if serialization_alias or schema_serialization_alias is not None:
448459
raise RuntimeError("serialization_alias is not supported in Pydantic v1")
449460
field_info = FieldInfo(
450461
default,

tests/test_aliases.py

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import Field as PField
66
from sqlmodel import Field, SQLModel
77

8-
from tests.conftest import needs_pydanticv2
8+
from tests.conftest import needs_pydanticv1, needs_pydanticv2
99

1010
"""
1111
Alias tests for SQLModel and Pydantic compatibility
@@ -21,8 +21,6 @@ class SQLModelUser(SQLModel):
2121

2222

2323
# Models with config (validate_by_name=True)
24-
25-
2624
if VERSION.startswith("2."):
2725

2826
class PydanticUserWithConfig(PydanticUser):
@@ -138,18 +136,16 @@ class SQLModelUserV2(SQLModel):
138136
SQLModelUserV2 = None
139137

140138

139+
@needs_pydanticv1
141140
def test_validation_alias_runtimeerror_pydantic_v1():
142-
if VERSION.startswith("2."):
143-
pytest.skip("Only relevant for Pydantic v1")
144141
with pytest.raises(
145142
RuntimeError, match="validation_alias is not supported in Pydantic v1"
146143
):
147144
Field(validation_alias="foo")
148145

149146

147+
@needs_pydanticv1
150148
def test_serialization_alias_runtimeerror_pydantic_v1():
151-
if VERSION.startswith("2."):
152-
pytest.skip("Only relevant for Pydantic v1")
153149
with pytest.raises(
154150
RuntimeError, match="serialization_alias is not supported in Pydantic v1"
155151
):
@@ -176,3 +172,85 @@ def test_serialize_with_serialization_alias(
176172
assert "firstName" not in data
177173
assert "first_name" not in data
178174
assert data["f_name"] == "Jane"
175+
176+
177+
@needs_pydanticv2
178+
def test_schema_extra_validation_alias_sqlmodel_v2():
179+
class M(SQLModel):
180+
f: str = Field(schema_extra={"validation_alias": "f_alias"})
181+
182+
m = M.model_validate({"f_alias": "asd"})
183+
assert m.f == "asd"
184+
185+
186+
@needs_pydanticv2
187+
def test_schema_extra_serialization_alias_sqlmodel_v2():
188+
class M(SQLModel):
189+
f: str = Field(schema_extra={"serialization_alias": "f_out"})
190+
191+
m = M(f="x")
192+
data = m.model_dump(by_alias=True)
193+
assert "f_out" in data
194+
assert "f" not in data
195+
assert data["f_out"] == "x"
196+
197+
198+
@needs_pydanticv1
199+
def test_schema_extra_validation_alias_runtimeerror_pydantic_v1():
200+
with pytest.raises(
201+
RuntimeError, match="validation_alias is not supported in Pydantic v1"
202+
):
203+
Field(schema_extra={"validation_alias": "x"})
204+
205+
206+
@needs_pydanticv1
207+
def test_schema_extra_serialization_alias_runtimeerror_pydantic_v1():
208+
with pytest.raises(
209+
RuntimeError, match="serialization_alias is not supported in Pydantic v1"
210+
):
211+
Field(schema_extra={"serialization_alias": "y"})
212+
213+
214+
@needs_pydanticv2
215+
def test_alias_plus_validation_alias_prefers_validation_alias_sqlmodel_v2():
216+
class M(SQLModel):
217+
first_name: str = Field(alias="fullName", validation_alias="v_name")
218+
219+
m = M.model_validate({"fullName": "A", "v_name": "B"})
220+
assert m.first_name == "B"
221+
222+
223+
@needs_pydanticv2
224+
def test_alias_plus_serialization_alias_prefers_serialization_alias_sqlmodel_v2():
225+
class M(SQLModel):
226+
first_name: str = Field(alias="fullName", serialization_alias="f_name")
227+
228+
m = M(first_name="Z")
229+
data = m.model_dump(by_alias=True)
230+
assert "f_name" in data
231+
assert "fullName" not in data
232+
assert data["f_name"] == "Z"
233+
234+
235+
@needs_pydanticv2
236+
def test_alias_generator_works_sqlmodel_v2():
237+
class M(SQLModel):
238+
model_config = {"alias_generator": lambda s: "gen_" + s}
239+
f: str = Field()
240+
241+
m = M.model_validate({"gen_f": "ok"})
242+
assert m.f == "ok"
243+
data = m.model_dump(by_alias=True)
244+
assert "gen_f" in data and data["gen_f"] == "ok"
245+
246+
247+
@needs_pydanticv2
248+
def test_alias_generator_with_explicit_alias_prefers_field_alias_sqlmodel_v2():
249+
class M(SQLModel):
250+
model_config = {"alias_generator": lambda s: "gen_" + s}
251+
f: str = Field(alias="custom")
252+
253+
m = M.model_validate({"custom": "ok"})
254+
assert m.f == "ok"
255+
data = m.model_dump(by_alias=True)
256+
assert "custom" in data and "gen_f" not in data

0 commit comments

Comments
 (0)