Skip to content

Change alias precedence to prefer child models #904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes/904-samuelcolvin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**Breaking Change:** Change the precedence of aliases so child model aliases override parent aliases,
including using `alias_generator`
3 changes: 1 addition & 2 deletions docs/examples/alias_generator_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
21 changes: 21 additions & 0 deletions docs/examples/alias_precedence.py
Original file line number Diff line number Diff line change
@@ -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))
13 changes: 13 additions & 0 deletions docs/usage/model_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pydantic/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions tests/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'