sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

`Field(alias="")` doesn't work with Pydantic V2

Open YuriiMotov opened this issue 4 months ago • 5 comments

Privileged issue

  • [x] I'm @tiangolo or he asked me directly to create an issue here.

Issue Content

Currently alias parameter doesn't work as expected with Pydantic V2 installed. Let's use this issue to track this.

👇 Here is a list of tests that showcase the problem (in the details):

from typing import Type, Union

import pytest
from pydantic import VERSION, BaseModel, ValidationError
from pydantic import Field as PField
from sqlmodel import Field, SQLModel

# -----------------------------------------------------------------------------------
# Models


class PydanticUser(BaseModel):
    full_name: str = PField(alias="fullName")


class SQLModelUser(SQLModel):
    full_name: str = Field(alias="fullName")


# Models with config (validate_by_name=True)


if VERSION.startswith("2."):

    class PydanticUserWithConfig(PydanticUser):
        model_config = {"validate_by_name": True}

    class SQLModelUserWithConfig(SQLModelUser):
        model_config = {"validate_by_name": True}

else:

    class PydanticUserWithConfig(PydanticUser):
        class Config:
            allow_population_by_field_name = True

    class SQLModelUserWithConfig(SQLModelUser):
        class Config:
            allow_population_by_field_name = True


# -----------------------------------------------------------------------------------
# Tests

# Test validate by name


@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
def test_create_with_field_name(model: Union[Type[PydanticUser], Type[SQLModelUser]]):
    with pytest.raises(ValidationError):
        model(full_name="Alice")


@pytest.mark.parametrize("model", [PydanticUserWithConfig, SQLModelUserWithConfig])
def test_create_with_field_name_with_config(
    model: Union[Type[PydanticUserWithConfig], Type[SQLModelUserWithConfig]],
):
    user = model(full_name="Alice")
    assert user.full_name == "Alice"


# Test validate by alias


@pytest.mark.parametrize(
    "model",
    [PydanticUser, SQLModelUser, PydanticUserWithConfig, SQLModelUserWithConfig],
)
def test_create_with_alias(
    model: Union[
        Type[PydanticUser],
        Type[SQLModelUser],
        Type[PydanticUserWithConfig],
        Type[SQLModelUserWithConfig],
    ],
):
    user = model(fullName="Bob")  # using alias
    assert user.full_name == "Bob"


# Test validate by name and alias


@pytest.mark.parametrize("model", [PydanticUserWithConfig, SQLModelUserWithConfig])
def test_create_with_both_prefers_alias(
    model: Union[Type[PydanticUserWithConfig], Type[SQLModelUserWithConfig]],
):
    user = model(full_name="IGNORED", fullName="Charlie")
    assert user.full_name == "Charlie"  # alias should take precedence


# Test serialize


@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
def test_dict_default_uses_field_names(
    model: Union[Type[PydanticUser], Type[SQLModelUser]],
):
    user = model(fullName="Dana")
    data = user.dict()
    assert "full_name" in data
    assert "fullName" not in data
    assert data["full_name"] == "Dana"


# Test serialize by alias


@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
def test_dict_default_uses_aliases(
    model: Union[Type[PydanticUser], Type[SQLModelUser]],
):
    user = model(fullName="Dana")
    data = user.dict(by_alias=True)
    assert "fullName" in data
    assert "full_name" not in data
    assert data["fullName"] == "Dana"


# Test json by alias


@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
def test_json_by_alias(
    model: Union[Type[PydanticUser], Type[SQLModelUser]],
):
    user = model(fullName="Frank")
    json_data = user.json(by_alias=True)
    assert ('"fullName":"Frank"' in json_data) or ('"fullName": "Frank"' in json_data)
    assert "full_name" not in json_data

When run with Pydantic V2:

t.py::test_create_with_field_name[PydanticUser] PASSED
t.py::test_create_with_field_name[SQLModelUser] FAILED
t.py::test_create_with_field_name_with_config[PydanticUserWithConfig] PASSED
t.py::test_create_with_field_name_with_config[SQLModelUserWithConfig] PASSED
t.py::test_create_with_alias[PydanticUser] PASSED
t.py::test_create_with_alias[SQLModelUser] FAILED
t.py::test_create_with_alias[PydanticUserWithConfig] PASSED
t.py::test_create_with_alias[SQLModelUserWithConfig] FAILED
t.py::test_create_with_both_prefers_alias[PydanticUserWithConfig] PASSED
t.py::test_create_with_both_prefers_alias[SQLModelUserWithConfig] FAILED
t.py::test_dict_default_uses_field_names[PydanticUser] PASSED
t.py::test_dict_default_uses_field_names[SQLModelUser] FAILED
t.py::test_dict_default_uses_aliases[PydanticUser] PASSED
t.py::test_dict_default_uses_aliases[SQLModelUser] FAILED
t.py::test_json_by_alias[PydanticUser] PASSED
t.py::test_json_by_alias[SQLModelUser] FAILED

All tests parameterized with both, Pydantic model and SQLModel model, to show the difference in behavior.

With Pydantic V1 all tests pass. With Pydantic V2 most of tests fail with SQLModel model.

I would also expect that alias is used as a default value of column name. But it should be possible to override it by providing sa_column=Column("column_name") or sa_column_kwargs={"name": "column_name"}.

Any feedback is welcome. Please, review the set of tests, suggest use-cases that are currently not covered.

Would be nice to add set of tests for validation_alias and serialization_alias as well.

YuriiMotov avatar Aug 26 '25 13:08 YuriiMotov

Facing to the same issue to be able to use aliases in my project. In the Fields(), the alias=xx is not enough, the schema_extra={"validation_alias": "xx", "serialization_alias": "xx"} must be added.

gillespilloudkerlink avatar Sep 17 '25 06:09 gillespilloudkerlink

Any updates on this? Facing the same problem.

Ox54 avatar Sep 20 '25 23:09 Ox54

Hey @svlandeg is the issue fixed now?

mahimairaja avatar Sep 22 '25 08:09 mahimairaja

@YuriiMotov @svlandeg

Hi! I've attempted to create a fix for this issue in PR #1577: https://github.com/fastapi/sqlmodel/pull/1577

I hope this approach helps address the problem where Field(alias="...") wasn't working as expected in Pydantic v2.

Proposed Solution

The fix tries to address the core issue by:

  • Automatic alias propagation: When using Pydantic v2, SQLModel automatically sets validation_alias and serialization_alias from the alias parameter if they're not explicitly provided
  • Backward compatibility: Maintains compatibility with Pydantic v1 by filtering out v2-specific parameters
  • Additional v2 support: Enables the new validation_alias and serialization_alias parameters for users who want more granular control
  • Test coverage: Added tests covering the scenarios mentioned in this issue

ravishan16 avatar Sep 25 '25 23:09 ravishan16

Thanks for the PR @ravishan16! We'll have a look when we can and leave feedback on the PR directly 🙏

svlandeg avatar Sep 26 '25 09:09 svlandeg