Files
pydantic/tests/test_aliases.py
T
Samuel Colvin 2ee6811655 remove DeprecationWarnings from v1 release & fix coverage (#2415)
* remove DeprecationWarnings from v1 release

* coverage on unpickling Undefined private attributes

* coverage on undefined in copy, allow Undefined to be pickled unchanged

* fix coverage of model._iter()
2021-02-26 14:43:47 +00:00

338 lines
9.2 KiB
Python

import re
from typing import Any, List, Optional
import pytest
from pydantic import BaseConfig, BaseModel, Extra, ValidationError
from pydantic.fields import Field
def test_alias_generator():
def to_camel(string: str):
return ''.join(x.capitalize() for x in string.split('_'))
class MyModel(BaseModel):
a: List[str] = None
foo_bar: str
class Config:
alias_generator = to_camel
data = {'A': ['foo', 'bar'], 'FooBar': 'foobar'}
v = MyModel(**data)
assert v.a == ['foo', 'bar']
assert v.foo_bar == 'foobar'
assert v.dict(by_alias=True) == data
def test_alias_generator_with_field_schema():
def to_upper_case(string: str):
return string.upper()
class MyModel(BaseModel):
my_shiny_field: Any # Alias from Config.fields will be used
foo_bar: str # Alias from Config.fields will be used
baz_bar: str # Alias will be generated
another_field: str # Alias will be generated
class Config:
alias_generator = to_upper_case
fields = {'my_shiny_field': 'MY_FIELD', 'foo_bar': {'alias': 'FOO'}, 'another_field': {'not_alias': 'a'}}
data = {'MY_FIELD': ['a'], 'FOO': 'bar', 'BAZ_BAR': 'ok', 'ANOTHER_FIELD': '...'}
m = MyModel(**data)
assert m.dict(by_alias=True) == data
def test_alias_generator_wrong_type_error():
def return_bytes(string):
return b'not a string'
with pytest.raises(TypeError) as e:
class MyModel(BaseModel):
bar: Any
class Config:
alias_generator = return_bytes
assert str(e.value) == "Config.alias_generator must return str, not <class 'bytes'>"
def test_infer_alias():
class Model(BaseModel):
a = 'foobar'
class Config:
fields = {'a': '_a'}
assert Model(_a='different').a == 'different'
assert repr(Model.__fields__['a']) == (
"ModelField(name='a', type=str, required=False, default='foobar', alias='_a')"
)
def test_alias_error():
class Model(BaseModel):
a = 123
class Config:
fields = {'a': '_a'}
assert Model(_a='123').a == 123
with pytest.raises(ValidationError) as exc_info:
Model(_a='foo')
assert exc_info.value.errors() == [
{'loc': ('_a',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
]
def test_annotation_config():
class Model(BaseModel):
b: float
a: int = 10
_c: str
class Config:
fields = {'b': 'foobar'}
assert list(Model.__fields__.keys()) == ['b', 'a']
assert [f.alias for f in Model.__fields__.values()] == ['foobar', 'a']
assert Model(foobar='123').b == 123.0
def test_alias_camel_case():
class Model(BaseModel):
one_thing: int
another_thing: int
class Config(BaseConfig):
@classmethod
def get_field_info(cls, name):
field_config = super().get_field_info(name) or {}
if 'alias' not in field_config:
field_config['alias'] = re.sub(r'(?:^|_)([a-z])', lambda m: m.group(1).upper(), name)
return field_config
v = Model(**{'OneThing': 123, 'AnotherThing': '321'})
assert v.one_thing == 123
assert v.another_thing == 321
assert v == {'one_thing': 123, 'another_thing': 321}
def test_get_field_info_inherit():
class ModelOne(BaseModel):
class Config(BaseConfig):
@classmethod
def get_field_info(cls, name):
field_config = super().get_field_info(name) or {}
if 'alias' not in field_config:
field_config['alias'] = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
return field_config
class ModelTwo(ModelOne):
one_thing: int
another_thing: int
third_thing: int
class Config:
fields = {'third_thing': 'Banana'}
v = ModelTwo(**{'oneThing': 123, 'anotherThing': '321', 'Banana': 1})
assert v == {'one_thing': 123, 'another_thing': 321, 'third_thing': 1}
def test_pop_by_field_name():
class Model(BaseModel):
last_updated_by: Optional[str] = None
class Config:
extra = Extra.forbid
allow_population_by_field_name = True
fields = {'last_updated_by': 'lastUpdatedBy'}
assert Model(lastUpdatedBy='foo').dict() == {'last_updated_by': 'foo'}
assert Model(last_updated_by='foo').dict() == {'last_updated_by': 'foo'}
with pytest.raises(ValidationError) as exc_info:
Model(lastUpdatedBy='foo', last_updated_by='bar')
assert exc_info.value.errors() == [
{'loc': ('last_updated_by',), 'msg': 'extra fields not permitted', 'type': 'value_error.extra'}
]
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'
def test_alias_generator_on_parent():
class Parent(BaseModel):
x: bool = Field(..., alias='a_b_c')
y: str
class Config:
@staticmethod
def alias_generator(x):
return x.upper()
class Child(Parent):
y: str
z: str
assert Parent.__fields__['x'].alias == 'a_b_c'
assert Parent.__fields__['y'].alias == 'Y'
assert Child.__fields__['x'].alias == 'a_b_c'
assert Child.__fields__['y'].alias == 'Y'
assert Child.__fields__['z'].alias == 'Z'
def test_alias_generator_on_child():
class Parent(BaseModel):
x: bool = Field(..., alias='abc')
y: str
class Child(Parent):
y: str
z: str
class Config:
@staticmethod
def alias_generator(x):
return x.upper()
assert [f.alias for f in Parent.__fields__.values()] == ['abc', 'y']
assert [f.alias for f in Child.__fields__.values()] == ['abc', 'Y', 'Z']
def test_low_priority_alias():
class Parent(BaseModel):
x: bool = Field(..., alias='abc', alias_priority=1)
y: str
class Child(Parent):
y: str
z: str
class Config:
@staticmethod
def alias_generator(x):
return x.upper()
assert [f.alias for f in Parent.__fields__.values()] == ['abc', 'y']
assert [f.alias for f in Child.__fields__.values()] == ['X', 'Y', 'Z']
def test_low_priority_alias_config():
class Parent(BaseModel):
x: bool
y: str
class Config:
fields = {'x': dict(alias='abc', alias_priority=1)}
class Child(Parent):
y: str
z: str
class Config:
@staticmethod
def alias_generator(x):
return x.upper()
assert [f.alias for f in Parent.__fields__.values()] == ['abc', 'y']
assert [f.alias for f in Child.__fields__.values()] == ['X', 'Y', 'Z']
def test_field_vs_config():
class Model(BaseModel):
x: str = Field(..., alias='x_on_field')
y: str
z: str
class Config:
fields = {'x': dict(alias='x_on_config'), 'y': dict(alias='y_on_config')}
assert [f.alias for f in Model.__fields__.values()] == ['x_on_field', 'y_on_config', 'z']
def test_alias_priority():
class Parent(BaseModel):
a: str = Field(..., alias='a_field_parent')
b: str = Field(..., alias='b_field_parent')
c: str = Field(..., alias='c_field_parent')
d: str
e: str
class Config:
fields = {
'a': dict(alias='a_config_parent'),
'c': dict(alias='c_config_parent'),
'd': dict(alias='d_config_parent'),
}
@staticmethod
def alias_generator(x):
return f'{x}_generator_parent'
class Child(Parent):
a: str = Field(..., alias='a_field_child')
class Config:
fields = {'a': dict(alias='a_config_child'), 'b': dict(alias='b_config_child')}
@staticmethod
def alias_generator(x):
return f'{x}_generator_child'
# debug([f.alias for f in Parent.__fields__.values()], [f.alias for f in Child.__fields__.values()])
assert [f.alias for f in Parent.__fields__.values()] == [
'a_field_parent',
'b_field_parent',
'c_field_parent',
'd_config_parent',
'e_generator_parent',
]
assert [f.alias for f in Child.__fields__.values()] == [
'a_field_child',
'b_config_child',
'c_field_parent',
'd_config_parent',
'e_generator_child',
]