*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
+ validation_alias: Optional[str] = None,
+ serialization_alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
+ validation_alias: Optional[str] = None,
+ serialization_alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
+ validation_alias: Optional[str] = None,
+ serialization_alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
+ validation_alias: Optional[str] = None,
+ serialization_alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
schema_extra: Optional[Dict[str, Any]] = None,
) -> Any:
current_schema_extra = schema_extra or {}
+ # Extract possible alias settings from schema_extra so we can control precedence
+ schema_validation_alias = current_schema_extra.pop("validation_alias", None)
+ schema_serialization_alias = current_schema_extra.pop("serialization_alias", None)
+ field_info_kwargs = {
+ "alias": alias,
+ "title": title,
+ "description": description,
+ "exclude": exclude,
+ "include": include,
+ "const": const,
+ "gt": gt,
+ "ge": ge,
+ "lt": lt,
+ "le": le,
+ "multiple_of": multiple_of,
+ "max_digits": max_digits,
+ "decimal_places": decimal_places,
+ "min_items": min_items,
+ "max_items": max_items,
+ "unique_items": unique_items,
+ "min_length": min_length,
+ "max_length": max_length,
+ "allow_mutation": allow_mutation,
+ "regex": regex,
+ "discriminator": discriminator,
+ "repr": repr,
+ "primary_key": primary_key,
+ "foreign_key": foreign_key,
+ "ondelete": ondelete,
+ "unique": unique,
+ "nullable": nullable,
+ "index": index,
+ "sa_type": sa_type,
+ "sa_column": sa_column,
+ "sa_column_args": sa_column_args,
+ "sa_column_kwargs": sa_column_kwargs,
+ **current_schema_extra,
+ }
+ if IS_PYDANTIC_V2:
+ # explicit params > schema_extra > alias propagation
+ field_info_kwargs["validation_alias"] = (
+ validation_alias or schema_validation_alias or alias
+ )
+ field_info_kwargs["serialization_alias"] = (
+ serialization_alias or schema_serialization_alias or alias
+ )
+ else:
+ if validation_alias or schema_validation_alias is not None:
+ raise RuntimeError("validation_alias is not supported in Pydantic v1")
+ if serialization_alias or schema_serialization_alias is not None:
+ raise RuntimeError("serialization_alias is not supported in Pydantic v1")
field_info = FieldInfo(
default,
default_factory=default_factory,
- alias=alias,
- title=title,
- description=description,
- exclude=exclude,
- include=include,
- const=const,
- gt=gt,
- ge=ge,
- lt=lt,
- le=le,
- multiple_of=multiple_of,
- max_digits=max_digits,
- decimal_places=decimal_places,
- min_items=min_items,
- max_items=max_items,
- unique_items=unique_items,
- min_length=min_length,
- max_length=max_length,
- allow_mutation=allow_mutation,
- regex=regex,
- discriminator=discriminator,
- repr=repr,
- primary_key=primary_key,
- foreign_key=foreign_key,
- ondelete=ondelete,
- unique=unique,
- nullable=nullable,
- index=index,
- sa_type=sa_type,
- sa_column=sa_column,
- sa_column_args=sa_column_args,
- sa_column_kwargs=sa_column_kwargs,
- **current_schema_extra,
+ **field_info_kwargs,
)
+
post_init_field_info(field_info)
return field_info
--- /dev/null
+from typing import Type, Union
+
+import pytest
+from pydantic import BaseModel, ValidationError
+from pydantic import Field as PField
+from sqlmodel import Field, SQLModel
+from sqlmodel._compat import IS_PYDANTIC_V2
+
+from tests.conftest import needs_pydanticv1, needs_pydanticv2
+
+"""
+Alias tests for SQLModel and Pydantic compatibility
+"""
+
+
+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 IS_PYDANTIC_V2:
+
+ 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
+
+
+@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"
+
+
+@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"
+
+
+@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
+
+
+@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
+def test_dict_default_uses_field_names(
+ model: Union[Type[PydanticUser], Type[SQLModelUser]],
+):
+ user = model(fullName="Dana")
+ if IS_PYDANTIC_V2 or isinstance(user, SQLModel):
+ data = user.model_dump()
+ else:
+ data = user.dict()
+ assert "full_name" in data
+ assert "fullName" not in data
+ assert data["full_name"] == "Dana"
+
+
+@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
+def test_dict_by_alias_uses_aliases(
+ model: Union[Type[PydanticUser], Type[SQLModelUser]],
+):
+ user = model(fullName="Dana")
+ if IS_PYDANTIC_V2 or isinstance(user, SQLModel):
+ data = user.model_dump(by_alias=True)
+ else:
+ data = user.dict(by_alias=True)
+ assert "fullName" in data
+ assert "full_name" not in data
+ assert data["fullName"] == "Dana"
+
+
+@pytest.mark.parametrize("model", [PydanticUser, SQLModelUser])
+def test_json_by_alias(
+ model: Union[Type[PydanticUser], Type[SQLModelUser]],
+):
+ user = model(fullName="Frank")
+ if IS_PYDANTIC_V2:
+ json_data = user.model_dump_json(by_alias=True)
+ else:
+ 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
+
+
+if IS_PYDANTIC_V2:
+
+ class PydanticUserV2(BaseModel):
+ first_name: str = PField(
+ validation_alias="firstName", serialization_alias="f_name"
+ )
+
+ class SQLModelUserV2(SQLModel):
+ first_name: str = Field(
+ validation_alias="firstName", serialization_alias="f_name"
+ )
+else:
+ # Dummy classes for Pydantic v1 to prevent import errors
+ PydanticUserV2 = None
+ SQLModelUserV2 = None
+
+
+@needs_pydanticv1
+def test_validation_alias_runtimeerror_pydantic_v1():
+ with pytest.raises(
+ RuntimeError, match="validation_alias is not supported in Pydantic v1"
+ ):
+ Field(validation_alias="foo")
+
+
+@needs_pydanticv1
+def test_serialization_alias_runtimeerror_pydantic_v1():
+ with pytest.raises(
+ RuntimeError, match="serialization_alias is not supported in Pydantic v1"
+ ):
+ Field(serialization_alias="bar")
+
+
+@needs_pydanticv2
+@pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2])
+def test_create_with_validation_alias(
+ model: Union[Type[PydanticUserV2], Type[SQLModelUserV2]],
+):
+ user = model(firstName="John")
+ assert user.first_name == "John"
+
+
+@needs_pydanticv2
+@pytest.mark.parametrize("model", [PydanticUserV2, SQLModelUserV2])
+def test_serialize_with_serialization_alias(
+ model: Union[Type[PydanticUserV2], Type[SQLModelUserV2]],
+):
+ user = model(firstName="Jane")
+ data = user.model_dump(by_alias=True)
+ assert "f_name" in data
+ assert "firstName" not in data
+ assert "first_name" not in data
+ assert data["f_name"] == "Jane"
+
+
+@needs_pydanticv2
+def test_schema_extra_validation_alias_sqlmodel_v2():
+ class M(SQLModel):
+ f: str = Field(schema_extra={"validation_alias": "f_alias"})
+
+ m = M.model_validate({"f_alias": "asd"})
+ assert m.f == "asd"
+
+
+@needs_pydanticv2
+def test_schema_extra_serialization_alias_sqlmodel_v2():
+ class M(SQLModel):
+ f: str = Field(schema_extra={"serialization_alias": "f_out"})
+
+ m = M(f="x")
+ data = m.model_dump(by_alias=True)
+ assert "f_out" in data
+ assert "f" not in data
+ assert data["f_out"] == "x"
+
+
+@needs_pydanticv1
+def test_schema_extra_validation_alias_runtimeerror_pydantic_v1():
+ with pytest.raises(
+ RuntimeError, match="validation_alias is not supported in Pydantic v1"
+ ):
+ Field(schema_extra={"validation_alias": "x"})
+
+
+@needs_pydanticv1
+def test_schema_extra_serialization_alias_runtimeerror_pydantic_v1():
+ with pytest.raises(
+ RuntimeError, match="serialization_alias is not supported in Pydantic v1"
+ ):
+ Field(schema_extra={"serialization_alias": "y"})
+
+
+@needs_pydanticv2
+def test_alias_plus_validation_alias_prefers_validation_alias_sqlmodel_v2():
+ class M(SQLModel):
+ first_name: str = Field(alias="fullName", validation_alias="v_name")
+
+ m = M.model_validate({"fullName": "A", "v_name": "B"})
+ assert m.first_name == "B"
+
+
+@needs_pydanticv2
+def test_alias_plus_serialization_alias_prefers_serialization_alias_sqlmodel_v2():
+ class M(SQLModel):
+ first_name: str = Field(alias="fullName", serialization_alias="f_name")
+
+ m = M(fullName="Z")
+ data = m.model_dump(by_alias=True)
+ assert "f_name" in data
+ assert "fullName" not in data
+ assert data["f_name"] == "Z"
+
+
+@needs_pydanticv2
+def test_alias_generator_works_sqlmodel_v2():
+ class M(SQLModel):
+ model_config = {"alias_generator": lambda s: "gen_" + s}
+ f: str = Field()
+
+ m = M.model_validate({"gen_f": "ok"})
+ assert m.f == "ok"
+ data = m.model_dump(by_alias=True)
+ assert "gen_f" in data and data["gen_f"] == "ok"
+
+
+@needs_pydanticv2
+def test_alias_generator_with_explicit_alias_prefers_field_alias_sqlmodel_v2():
+ class M(SQLModel):
+ model_config = {"alias_generator": lambda s: "gen_" + s}
+ f: str = Field(alias="custom")
+
+ m = M.model_validate({"custom": "ok"})
+ assert m.f == "ok"
+ data = m.model_dump(by_alias=True)
+ assert "custom" in data and "gen_f" not in data