From b15644e81953636a964fd6c68ad08798fcc93673 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 28 Jan 2026 21:50:45 +0100 Subject: [PATCH] Allow `AliasChoices` and `AliasPath` for `validation_alias` in `Field` --- sqlmodel/__init__.py | 4 ++++ sqlmodel/main.py | 10 +++++----- tests/test_aliases.py | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index d4210b06d8..d5041da43d 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -1,5 +1,9 @@ __version__ = "0.0.31" +# Re-export from Pydantic +from pydantic import AliasChoices as AliasChoices +from pydantic import AliasPath as AliasPath + # Re-export from SQLAlchemy from sqlalchemy.engine import create_engine as create_engine from sqlalchemy.engine import create_mock_engine as create_mock_engine diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 84478f24cf..550cadd82b 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -22,7 +22,7 @@ overload, ) -from pydantic import BaseModel, EmailStr +from pydantic import AliasChoices, AliasPath, BaseModel, EmailStr from pydantic.fields import FieldInfo as PydanticFieldInfo from sqlalchemy import ( Boolean, @@ -207,7 +207,7 @@ def Field( *, default_factory: Optional[NoArgAnyCallable] = None, alias: Optional[str] = None, - validation_alias: Optional[str] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -250,7 +250,7 @@ def Field( *, default_factory: Optional[NoArgAnyCallable] = None, alias: Optional[str] = None, - validation_alias: Optional[str] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -302,7 +302,7 @@ def Field( *, default_factory: Optional[NoArgAnyCallable] = None, alias: Optional[str] = None, - validation_alias: Optional[str] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, @@ -335,7 +335,7 @@ def Field( *, default_factory: Optional[NoArgAnyCallable] = None, alias: Optional[str] = None, - validation_alias: Optional[str] = None, + validation_alias: Union[str, AliasPath, AliasChoices, None] = None, serialization_alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, diff --git a/tests/test_aliases.py b/tests/test_aliases.py index f123251e09..266bbfb5e4 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -1,9 +1,11 @@ -from typing import Union +from typing import Optional, Union import pytest +from pydantic import AliasChoices as PAliasChoices +from pydantic import AliasPath as PAliasPath from pydantic import BaseModel, ValidationError from pydantic import Field as PField -from sqlmodel import Field, SQLModel +from sqlmodel import AliasChoices, AliasPath, Field, SQLModel """ Alias tests for SQLModel and Pydantic compatibility @@ -99,10 +101,22 @@ def test_json_by_alias( class PydanticUserV2(BaseModel): first_name: str = PField(validation_alias="firstName", serialization_alias="f_name") + second_name: Optional[str] = PField( + default=None, validation_alias=PAliasChoices("secondName", "surname") + ) + nickname: Optional[str] = PField( + default=None, validation_alias=PAliasPath("names", 2) + ) class SQLModelUserV2(SQLModel): first_name: str = Field(validation_alias="firstName", serialization_alias="f_name") + second_name: Optional[str] = Field( + default=None, validation_alias=AliasChoices("secondName", "surname") + ) + nickname: Optional[str] = Field( + default=None, validation_alias=AliasPath("names", 2) + ) @pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2]) @@ -113,6 +127,27 @@ def test_create_with_validation_alias( assert user.first_name == "John" +@pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2]) +def test_create_with_validation_alias_alias_choices( + model: Union[type[PydanticUserV2], type[SQLModelUserV2]], +): + user = model.model_validate({"firstName": "John", "secondName": "Doe"}) + assert user.second_name == "Doe" + + user2 = model.model_validate({"firstName": "John", "surname": "Doe"}) + assert user2.second_name == "Doe" + + +@pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2]) +def test_create_with_validation_alias_alias_path( + model: Union[type[PydanticUserV2], type[SQLModelUserV2]], +): + user = model.model_validate( + {"firstName": "John", "names": ["John", "Doe", "Johnny"]} + ) + assert user.nickname == "Johnny" + + @pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2]) def test_serialize_with_serialization_alias( model: Union[type[PydanticUserV2], type[SQLModelUserV2]],