Files
pydantic/tests/test_annotated.py
T
Adrian Garcia Badaracco e5540296cc Fix issue with in-place modification of FieldInfo (#4067)
* Fix info with in-place modification of field info

* add changes

* add test for 3714

* Update changes/4067-adriangb.md

Co-authored-by: Samuel Colvin <samcolvin@gmail.com>

Co-authored-by: Samuel Colvin <samcolvin@gmail.com>
2022-05-13 15:16:32 +01:00

155 lines
4.2 KiB
Python

from typing import List
import pytest
from typing_extensions import Annotated
from pydantic import BaseModel, Field
from pydantic.fields import Undefined
from pydantic.typing import get_all_type_hints
@pytest.mark.parametrize(
['hint_fn', 'value'],
[
# Test Annotated types with arbitrary metadata
pytest.param(
lambda: Annotated[int, 0],
5,
id='misc-default',
),
pytest.param(
lambda: Annotated[int, 0],
Field(default=5, ge=0),
id='misc-field-default-constraint',
),
# Test valid Annotated Field uses
pytest.param(
lambda: Annotated[int, Field(description='Test')], # noqa: F821
5,
id='annotated-field-value-default',
),
pytest.param(
lambda: Annotated[int, Field(default_factory=lambda: 5, description='Test')], # noqa: F821
Undefined,
id='annotated-field-default_factory',
),
],
)
def test_annotated(hint_fn, value):
hint = hint_fn()
class M(BaseModel):
x: hint = value
assert M().x == 5
assert M(x=10).x == 10
assert get_all_type_hints(M)['x'] == hint
@pytest.mark.parametrize(
['hint_fn', 'value', 'subclass_ctx'],
[
pytest.param(
lambda: Annotated[int, Field(5)],
Undefined,
pytest.raises(ValueError, match='`Field` default cannot be set in `Annotated`'),
id='annotated-field-default',
),
pytest.param(
lambda: Annotated[int, Field(), Field()],
Undefined,
pytest.raises(ValueError, match='cannot specify multiple `Annotated` `Field`s'),
id='annotated-field-dup',
),
pytest.param(
lambda: Annotated[int, Field()],
Field(),
pytest.raises(ValueError, match='cannot specify `Annotated` and value `Field`'),
id='annotated-field-value-field-dup',
),
pytest.param(
lambda: Annotated[int, Field(default_factory=lambda: 5)], # The factory is not used
5,
pytest.raises(ValueError, match='cannot specify both default and default_factory'),
id='annotated-field-default_factory-value-default',
),
],
)
def test_annotated_model_exceptions(hint_fn, value, subclass_ctx):
hint = hint_fn()
with subclass_ctx:
class M(BaseModel):
x: hint = value
@pytest.mark.parametrize(
['hint_fn', 'value', 'empty_init_ctx'],
[
pytest.param(
lambda: Annotated[int, 0],
Undefined,
pytest.raises(ValueError, match='field required'),
id='misc-no-default',
),
pytest.param(
lambda: Annotated[int, Field()],
Undefined,
pytest.raises(ValueError, match='field required'),
id='annotated-field-no-default',
),
],
)
def test_annotated_instance_exceptions(hint_fn, value, empty_init_ctx):
hint = hint_fn()
class M(BaseModel):
x: hint = value
with empty_init_ctx:
assert M().x == 5
def test_field_reuse():
field = Field(description='Long description')
class Model(BaseModel):
one: int = field
assert Model(one=1).dict() == {'one': 1}
class AnnotatedModel(BaseModel):
one: Annotated[int, field]
assert AnnotatedModel(one=1).dict() == {'one': 1}
def test_config_field_info():
class Foo(BaseModel):
a: Annotated[int, Field(foobar='hello')] # noqa: F821
class Config:
fields = {'a': {'description': 'descr'}}
assert Foo.schema(by_alias=True)['properties'] == {
'a': {'title': 'A', 'description': 'descr', 'foobar': 'hello', 'type': 'integer'},
}
def test_annotated_alias() -> None:
# https://github.com/samuelcolvin/pydantic/issues/2971
StrAlias = Annotated[str, Field(max_length=3)]
IntAlias = Annotated[int, Field(default_factory=lambda: 2)]
Nested = Annotated[List[StrAlias], Field(description='foo')]
class MyModel(BaseModel):
a: StrAlias = 'abc'
b: StrAlias
c: IntAlias
d: IntAlias
e: Nested
assert MyModel(b='def', e=['xyz']) == MyModel(a='abc', b='def', c=2, d=2, e=['xyz'])