mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
594effa279
* 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>
194 lines
5.6 KiB
Python
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'])
|