1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
import sys
from collections import namedtuple
from typing import Annotated, Callable, ClassVar, ForwardRef, Literal, NamedTuple
import pytest
from pydantic import BaseModel, Field # noqa: F401
from pydantic._internal._typing_extra import (
NoneType,
get_function_type_hints,
is_classvar_annotation,
is_namedtuple,
is_none_type,
parent_frame_namespace,
)
try:
from typing import TypedDict as typing_TypedDict
except ImportError:
typing_TypedDict = None
try:
from typing_extensions import TypedDict as typing_extensions_TypedDict
except ImportError:
typing_extensions_TypedDict = None
ALL_TYPEDDICT_KINDS = (typing_TypedDict, typing_extensions_TypedDict)
def test_is_namedtuple():
class Employee(NamedTuple):
name: str
id: int = 3
assert is_namedtuple(namedtuple('Point', 'x y')) is True
assert is_namedtuple(Employee) is True
assert is_namedtuple(NamedTuple('Employee', [('name', str), ('id', int)])) is True
class Other(tuple):
name: str
id: int
assert is_namedtuple(Other) is False
def test_is_none_type():
assert is_none_type(Literal[None]) is True
assert is_none_type(None) is True
assert is_none_type(type(None)) is True
assert is_none_type(6) is False
assert is_none_type({}) is False
# WARNING: It's important to test `typing.Callable` not
# `collections.abc.Callable` (even with python >= 3.9) as they behave
# differently
assert is_none_type(Callable) is False
@pytest.mark.parametrize(
['ann_type', 'expected'],
(
(None, False),
(ForwardRef('Other[int]'), False),
(ForwardRef('Other[ClassVar[int]]'), False),
(ForwardRef('ClassVar[int]'), True),
(ForwardRef('t.ClassVar[int]'), True),
(ForwardRef('typing.ClassVar[int]'), True),
(ForwardRef('Annotated[ClassVar[int], ...]'), True),
(ForwardRef('Annotated[t.ClassVar[int], ...]'), True),
(ForwardRef('t.Annotated[t.ClassVar[int], ...]'), True),
(ClassVar[int], True),
(Annotated[ClassVar[int], ...], True),
),
)
def test_is_classvar_annotation(ann_type, expected):
assert is_classvar_annotation(ann_type) is expected
def test_get_function_type_hints_none_type():
def f(x: int, y: None) -> int:
return x
assert get_function_type_hints(f) == {'return': int, 'x': int, 'y': NoneType}
@pytest.mark.skipif(sys.version_info >= (3, 10), reason='testing using a feature not supported by older Python')
def test_eval_type_backport_not_installed():
sys.modules['eval_type_backport'] = None
try:
with pytest.raises(TypeError) as exc_info:
class _Model(BaseModel):
foo: 'int | str'
assert str(exc_info.value) == (
"Unable to evaluate type annotation 'int | str'. If you are making use "
'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting '
'since Python 3.9), you should either replace the use of new syntax with the existing '
'`typing` constructs or install the `eval_type_backport` package.'
)
finally:
del sys.modules['eval_type_backport']
def test_func_ns_excludes_default_globals() -> None:
foo = 'foo'
func_ns = parent_frame_namespace(parent_depth=1)
assert func_ns is not None
assert func_ns['foo'] == foo
# there are more default global variables, but these are examples of well known ones
for default_global_var in ['__name__', '__doc__', '__package__', '__builtins__']:
assert default_global_var not in func_ns
def test_parent_frame_namespace(create_module) -> None:
"""Parent frame namespace should be `None` because we skip fetching data from the top module level."""
@create_module
def mod1() -> None:
from pydantic._internal._typing_extra import parent_frame_namespace
module_foo = 'global_foo' # noqa: F841
module_ns = parent_frame_namespace(parent_depth=1) # noqa: F841
module_ns_force = parent_frame_namespace(parent_depth=1, force=True) # noqa: F841
assert mod1.module_ns is None
assert mod1.module_ns_force is not None
def test_exotic_localns() -> None:
__foo_annotation__ = str
class Model(BaseModel):
foo: __foo_annotation__
assert Model.model_fields['foo'].annotation == str
|