Skip to content

Commit

Permalink
Fix: allow empty string aliases with AliasGenerator (#8810)
Browse files Browse the repository at this point in the history
  • Loading branch information
sydney-runkle committed Feb 15, 2024
1 parent e99bf8a commit 42ec73d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 5 deletions.
19 changes: 14 additions & 5 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -296,6 +296,15 @@ def push(self, for_type: type[Any]):
self._types_namespace_stack.pop()


def _get_first_non_null(a: Any, b: Any) -> Any:
"""Return the first argument if it is not None, otherwise return the second argument.
Use case: serialization_alias (argument a) and alias (argument b) are both defined, and serialization_alias is ''.
This function will return serialization_alias, which is the first argument, even though it is an empty string.
"""
return a if a is not None else b


class GenerateSchema:
"""Generate core schema for a Pydantic model, dataclass and types like `str`, `datetime`, ... ."""

Expand Down Expand Up @@ -997,17 +1006,17 @@ def _apply_alias_generator_to_field_info(

# if the priority is 1, then we set the aliases to the generated alias
if field_info.alias_priority == 1:
field_info.serialization_alias = serialization_alias or alias
field_info.validation_alias = validation_alias or alias
field_info.serialization_alias = _get_first_non_null(serialization_alias, alias)
field_info.validation_alias = _get_first_non_null(validation_alias, alias)
field_info.alias = alias

# if any of the aliases are not set, then we set them to the corresponding generated alias
if field_info.alias is None:
field_info.alias = alias
if field_info.serialization_alias is None:
field_info.serialization_alias = serialization_alias or alias
field_info.serialization_alias = _get_first_non_null(serialization_alias, alias)
if field_info.validation_alias is None:
field_info.validation_alias = validation_alias or alias
field_info.validation_alias = _get_first_non_null(validation_alias, alias)

@staticmethod
def _apply_alias_generator_to_computed_field_info(
Expand Down Expand Up @@ -1050,7 +1059,7 @@ def _apply_alias_generator_to_computed_field_info(
# note that we use the serialization_alias with priority over alias, as computed_field
# aliases are used for serialization only (not validation)
if computed_field_info.alias_priority == 1:
computed_field_info.alias = serialization_alias or alias
computed_field_info.alias = _get_first_non_null(serialization_alias, alias)

def _common_field_schema( # C901
self, name: str, field_info: FieldInfo, decorators: DecoratorInfos
Expand Down
32 changes: 32 additions & 0 deletions tests/test_aliases.py
Expand Up @@ -725,3 +725,35 @@ def area(self) -> int:

r = Rectangle(width_val_alias=10, height_val_alias=20)
assert r.model_dump(by_alias=True) == {'width_ser_alias': 10, 'height_ser_alias': 20, 'area_ser_alias': 200}


empty_str_alias_generator = AliasGenerator(
validation_alias=lambda x: '', alias=lambda x: f'{x}_alias', serialization_alias=lambda x: ''
)


def test_alias_gen_with_empty_string() -> None:
class Model(BaseModel):
a: str

model_config = ConfigDict(alias_generator=empty_str_alias_generator)

assert Model.model_fields['a'].validation_alias == ''
assert Model.model_fields['a'].serialization_alias == ''
assert Model.model_fields['a'].alias == 'a_alias'


def test_alias_gen_with_empty_string_and_computed_field() -> None:
class Model(BaseModel):
model_config = ConfigDict(alias_generator=empty_str_alias_generator)

a: str

@computed_field
def b(self) -> str:
return self.a

assert Model.model_fields['a'].validation_alias == ''
assert Model.model_fields['a'].serialization_alias == ''
assert Model.model_fields['a'].alias == 'a_alias'
assert Model.model_computed_fields['b'].alias == ''

0 comments on commit 42ec73d

Please sign in to comment.