Skip to content

Commit

Permalink
Split up annotated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobHayes committed Jan 5, 2021
1 parent 437619c commit 82216f5
Showing 1 changed file with 68 additions and 82 deletions.
150 changes: 68 additions & 82 deletions tests/test_main.py
Expand Up @@ -3,21 +3,6 @@
from typing import Any, Callable, ClassVar, Dict, List, Mapping, Optional, Type, get_type_hints
from uuid import UUID, uuid4

if sys.version_info >= (3, 7):
from contextlib import nullcontext
else:

class nullcontext:
def __init__(self, enter_result=None):
self.enter_result = enter_result

def __enter__(self):
return self.enter_result

def __exit__(self, *excinfo):
pass


import pytest

from pydantic import (
Expand Down Expand Up @@ -1443,113 +1428,114 @@ class M(BaseModel):
get_type_hints(M.__config__)


_5 = 5


def _Annotated(*args):
return Annotated[args] if Annotated else None


@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
@pytest.mark.parametrize(
['hint', 'value', 'subclass_ctx', 'empty_init_ctx'],
['hint_fn', 'value'],
[
# Test non-pydantic Annotated uses (random metadata)
# Test Annotated types with arbitrary metadata
pytest.param(
_Annotated(int, 0),
Undefined,
None,
pytest.raises(ValueError, match='field required'),
id='misc-no-default',
),
pytest.param(
_Annotated(int, 0),
_5,
None,
None,
lambda: Annotated[int, 0],
5,
id='misc-default',
),
pytest.param(
_Annotated(int, 0),
Field(default=_5, ge=0),
None,
None,
lambda: Annotated[int, 0],
Field(default=5, ge=0),
id='misc-field-default-constraint',
),
# Test valid Annotated Field uses
pytest.param(
_Annotated(int, Field(description='Test')),
_5,
None,
None,
lambda: Annotated[int, Field(description='Test')],
5,
id='annotated-field-value-default',
),
pytest.param(
_Annotated(int, Field(default_factory=lambda: _5, description='Test')),
lambda: Annotated[int, Field(default_factory=lambda: 5, description='Test')],
Undefined,
None,
None,
id='annotated-field-default_factory',
),
# Test invalid Annotated Field uses
pytest.param(
_Annotated(int, Field()),
Undefined,
None,
pytest.raises(ValueError, match='field required'),
id='annotated-field-no-default',
),
],
)
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

# get_type_hints doesn't recognize typing_extensions.Annotated, so will return the full
# annotation. 3.9 w/ stock Annotated will return the wrapped type by default, but return the
# full thing with the new include_extras flag.
if sys.version_info >= (3, 9):
assert get_type_hints(M)['x'] is int
assert get_type_hints(M, include_extras=True)['x'] == hint
else:
assert get_type_hints(M)['x'] == hint


@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
@pytest.mark.parametrize(
['hint_fn', 'value', 'subclass_ctx'],
[
pytest.param(
_Annotated(int, Field(_5)),
lambda: Annotated[int, Field(5)],
Undefined,
pytest.raises(ValueError, match='`Field` default cannot be set in `Annotated`'),
None,
id='annotated-field-default',
),
pytest.param(
_Annotated(int, Field(), Field()),
lambda: Annotated[int, Field(), Field()],
Undefined,
pytest.raises(ValueError, match='cannot specify multiple `Annotated` `Field`s'),
None,
id='annotated-field-dup',
),
pytest.param(
_Annotated(int, Field()),
lambda: Annotated[int, Field()],
Field(),
pytest.raises(ValueError, match='cannot specify `Annotated` and value `Field`'),
None,
id='annotated-field-value-field-dup',
),
pytest.param(
_Annotated(int, Field(default_factory=lambda: _5)), # The factory is not used
_5,
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'),
None,
id='annotated-field-default_factory-value-default',
),
],
)
def test_annotated(hint, value, subclass_ctx, empty_init_ctx):
if hint is None:
pytest.skip('typing_extensions not installed')

M = None
with (subclass_ctx or nullcontext()):
def test_annotated_model_exceptions(hint_fn, value, subclass_ctx):
hint = hint_fn()
with subclass_ctx:

class M(BaseModel):
x: hint = value

if M is None:
return
with (empty_init_ctx or nullcontext()):
assert M().x == _5
assert M(x=10).x == 10

# get_type_hints doesn't recognize typing_extensions.Annotated, so will return the full
# annotation. 3.9 w/ stock Annotated will return the wrapped type by default, but return the
# full thing with the new include_extras flag.
if sys.version_info >= (3, 9):
assert get_type_hints(M)['x'] is int
assert get_type_hints(M, include_extras=True)['x'] == hint
else:
assert get_type_hints(M)['x'] == hint
@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
@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

0 comments on commit 82216f5

Please sign in to comment.