diff --git a/changes/904-samuelcolvin.md b/changes/904-samuelcolvin.md new file mode 100644 index 0000000000..e54ecc6f84 --- /dev/null +++ b/changes/904-samuelcolvin.md @@ -0,0 +1,2 @@ +**Breaking Change:** Change the precedence of aliases so child model aliases override parent aliases, +including using `alias_generator` diff --git a/docs/examples/alias_generator_config.py b/docs/examples/alias_generator_config.py index c4f207c643..d957bba604 100644 --- a/docs/examples/alias_generator_config.py +++ b/docs/examples/alias_generator_config.py @@ -5,12 +5,11 @@ def to_camel(string: str) -> str: class Voice(BaseModel): name: str - gender: str language_code: str class Config: alias_generator = to_camel -voice = Voice(Name='Filiz', Gender='Female', LanguageCode='tr-TR') +voice = Voice(Name='Filiz', LanguageCode='tr-TR') print(voice.language_code) print(voice.dict(by_alias=True)) diff --git a/docs/examples/alias_precedence.py b/docs/examples/alias_precedence.py new file mode 100644 index 0000000000..a6407f9b70 --- /dev/null +++ b/docs/examples/alias_precedence.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel + +class Voice(BaseModel): + name: str + language_code: str + + class Config: + @classmethod + def alias_generator(cls, string: str) -> str: + # this is the same as `alias_generator = to_camel` above + return ''.join(word.capitalize() for word in string.split('_')) + +class Character(Voice): + mood: str + + class Config: + fields = {'mood': 'Mood', 'language_code': 'lang'} + +c = Character(Mood='happy', Name='Filiz', lang='tr-TR') +print(c) +print(c.dict(by_alias=True)) diff --git a/docs/usage/model_config.md b/docs/usage/model_config.md index 1c43503619..493350469d 100644 --- a/docs/usage/model_config.md +++ b/docs/usage/model_config.md @@ -98,3 +98,16 @@ you can automatically generate aliases using `alias_generator`: {!.tmp_examples/alias_generator_config.py!} ``` _(This script is complete, it should run "as is")_ + + +## Alias Precedence + +Aliases defined on the `Config` class of child models will take priority over any aliases defined on `Config` of a +parent model: + +```py +{!.tmp_examples/alias_precedence.py!} +``` +_(This script is complete, it should run "as is")_ + +This includes when a child model uses `alias_generator` where the aliases of all parent model fields will be updated. diff --git a/mkdocs.yml b/mkdocs.yml index 1f32f7cff6..398ab9a733 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,7 +29,7 @@ nav: - usage/models.md - 'Field Types': usage/types.md - usage/validators.md - - usage/model_config.md + - 'Model Config': usage/model_config.md - usage/schema.md - usage/exporting_models.md - usage/dataclasses.md diff --git a/pydantic/fields.py b/pydantic/fields.py index 0983aa6949..388655a8c8 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -263,10 +263,10 @@ def infer( def set_config(self, config: Type['BaseConfig']) -> None: self.model_config = config - schema_from_config = config.get_field_info(self.name) - if schema_from_config: + info_from_config = config.get_field_info(self.name) + if info_from_config: self.field_info = cast(FieldInfo, self.field_info) - self.field_info.alias = self.field_info.alias or schema_from_config.get('alias') or self.name + self.field_info.alias = info_from_config.get('alias') or self.field_info.alias or self.name self.alias = cast(str, self.field_info.alias) @property diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 950b7fbfbb..16c98442ed 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -1079,3 +1079,43 @@ class Model(BaseModel): assert Model().__fields__.keys() == {'v'} assert Model.__fields__.keys() == {'v'} + + +def test_alias_child_precedence(): + class Parent(BaseModel): + x: int + + class Config: + fields = {'x': 'x1'} + + class Child(Parent): + y: int + + class Config: + fields = {'y': 'y2', 'x': 'x2'} + + assert Child.__fields__['y'].alias == 'y2' + assert Child.__fields__['x'].alias == 'x2' + + +def test_alias_generator_parent(): + class Parent(BaseModel): + x: int + + class Config: + allow_population_by_field_name = True + + @classmethod + def alias_generator(cls, f_name): + return f_name + '1' + + class Child(Parent): + y: int + + class Config: + @classmethod + def alias_generator(cls, f_name): + return f_name + '2' + + assert Child.__fields__['y'].alias == 'y2' + assert Child.__fields__['x'].alias == 'x2'