Files
pydantic/tests/test_annotated.py
T
Samuel Colvin 594effa279 Switching to pydantic_core (#4516)
* working on core schema generation

* adapting main.py

* getting tests to run

* fix tests

* disable pyright, fix mypy

* moving to class-based model generation

* working on validators

* change how models are created

* start fixing test_main.py

* fixing mypy

* SelfType

* recursive models working, more tests fixed

* fix tests on <3.10

* get docs build to pass

* starting to cleanup types.py

* starting works on custom types

* working on using annotated-types

* using annoated types for constraints

* lots of cleanup, fixing network tests

* network tests passing 🎉

* working on types

* working on types and cleanup

* fixing UUID type, restructing again

* more types and newer pydantic-core

* working on Iterable

* more test_types tests

* support newer pydantic-core, fixing more test_types.py

* working through more test_types.py

* test_types.py at last passing locally 🎉

* fixing more tests in test_types.py

* fix datetime_parse tests and linting

* get tests running again, rename to test_datetime.py

* renaming internal modules

* working through mypy errors

* fixing mypy

* refactoring _generate_schema.py

* test_main.py passing

* uprev deps

* fix conftest and linting?

* importing Annotated

* ltining

* import Annotated from typing_extensions

* fixing 3.7 compatibility

* fixing tests on 3.9

* fix linting

* fixing SecretField and 3.9 tests

* customising get_type_hints

* ignore warnings on 3.11

* spliting repr out of utils

* removing unused bits of _repr, fix tests for 3.7

* more cleanup, removing many type aliases

* clean up repr

* support namedtuples and typeddicts

* test is_union

* removing errors, uprev pydantic-core

* fix tests on 3.8

* fixing private attributes and model_post_init

* renaming and cleanup

* remove unnecessary PydanticMetadata inheritance

* fixing forward refs and mypy tests

* fix signatures, change how xfail works

* revert mypy tests to 3.7 syntax

* correct model title

* try to fix tests

* fixing ClassVar forward refs

* uprev pydantic-core, new error format

* add "force" argument to model_rebuild

* Apply suggestions from code review

Suggestions from @tiangolo and @hramezani 🙏

Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>

* more suggestions from @tiangolo

* extra -> json_schema_extra on Field

Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-11-02 12:01:17 +00:00

194 lines
5.6 KiB
Python

import sys
from typing import List
import pytest
from annotated_types import Gt, Lt
from typing_extensions import Annotated
from pydantic import BaseModel, Field
from pydantic.errors import PydanticSchemaGenerationError
from pydantic.fields import Undefined
NO_VALUE = object()
@pytest.mark.parametrize(
'hint_fn,value,expected_repr',
[
(
lambda: Annotated[int, Gt(0)],
5,
'FieldInfo(annotation=int, required=False, default=5, metadata=[Gt(gt=0)])',
),
(
lambda: Annotated[int, Field(gt=0)],
5,
'FieldInfo(annotation=int, required=False, default=5, metadata=[Gt(gt=0)])',
),
(
lambda: int,
Field(5, gt=0),
'FieldInfo(annotation=int, required=False, default=5, metadata=[Gt(gt=0)])',
),
(
lambda: int,
Field(default_factory=lambda: 5, gt=0),
'FieldInfo(annotation=int, required=False, default_factory=<lambda>, metadata=[Gt(gt=0)])',
),
(
lambda: Annotated[int, Lt(2)],
Field(5, gt=0),
'FieldInfo(annotation=int, required=False, default=5, metadata=[Gt(gt=0), Lt(lt=2)])',
),
(
lambda: Annotated[int, Gt(0)],
NO_VALUE,
'FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)])',
),
(
lambda: Annotated[int, Gt(0)],
Field(),
'FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)])',
),
(
lambda: int,
Field(gt=0),
'FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)])',
),
(
lambda: Annotated[int, Gt(0)],
Undefined,
'FieldInfo(annotation=int, required=True, metadata=[Gt(gt=0)])',
),
(
lambda: Annotated[int, Field(gt=0), Lt(2)],
5,
'FieldInfo(annotation=int, required=False, default=5, metadata=[Gt(gt=0), Lt(lt=2)])',
),
],
)
def test_annotated(hint_fn, value, expected_repr):
hint = hint_fn()
if value is NO_VALUE:
class M(BaseModel):
x: hint
else:
class M(BaseModel):
x: hint = value
assert repr(M.__fields__['x']) == expected_repr
@pytest.mark.parametrize(
'hint_fn,value,exc_handler',
[
(
lambda: Annotated[int, Field(0)],
Field(default=5, ge=0),
pytest.raises(TypeError, match='Field may not be used twice on the same field'),
),
(
lambda: Annotated[int, Field(0)],
5,
pytest.raises(TypeError, match='Default may not be specified twice on the same field'),
),
(
lambda: Annotated[int, 0],
5,
pytest.raises(PydanticSchemaGenerationError, match=r'Metadata must be instances of annotated_types\.'),
),
],
)
def test_annotated_model_exceptions(hint_fn, value, exc_handler):
hint = hint_fn()
with exc_handler:
class M(BaseModel):
x: hint = value
@pytest.mark.parametrize(
['hint_fn', 'value', 'empty_init_ctx'],
[
(
lambda: int,
Undefined,
pytest.raises(ValueError, match=r'Field required \[type=missing,'),
),
(
lambda: Annotated[int, Field()],
Undefined,
pytest.raises(ValueError, match=r'Field required \[type=missing,'),
),
],
)
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}
@pytest.mark.skip(reason='TODO JSON Schema')
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'},
}
@pytest.mark.skipif(sys.version_info < (3, 10), reason='repr different on older versions')
def test_annotated_alias() -> None:
# https://github.com/pydantic/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
fields_repr = {k: repr(v) for k, v in MyModel.__fields__.items()}
assert fields_repr == {
'a': "FieldInfo(annotation=str, required=False, default='abc', metadata=[MaxLen(max_length=3)])",
'b': 'FieldInfo(annotation=str, required=True, metadata=[MaxLen(max_length=3)])',
'c': 'FieldInfo(annotation=int, required=False, default_factory=<lambda>)',
'd': 'FieldInfo(annotation=int, required=False, default_factory=<lambda>)',
'e': (
'FieldInfo(annotation=List[Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MaxLe'
"n(max_length=3)])]], required=True, description='foo')"
),
}
assert MyModel(b='def', e=['xyz']).dict() == dict(a='abc', b='def', c=2, d=2, e=['xyz'])