File: test_typing.py

package info (click to toggle)
pydantic 2.12.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,628 kB
  • sloc: python: 75,989; javascript: 181; makefile: 115; sh: 38
file content (138 lines) | stat: -rw-r--r-- 4,395 bytes parent folder | download
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