summaryrefslogtreecommitdiff
blob: 7cd5290cd591210dea77c4850a8fd627b5440b3f (plain)
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
diff --git a/changes/1832-PrettyWood.md b/changes/1832-PrettyWood.md
new file mode 100644
index 000000000..5038a0da0
--- /dev/null
+++ b/changes/1832-PrettyWood.md
@@ -0,0 +1 @@
+add basic support of python 3.9
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 01792b628..c52b34ea4 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -32,6 +32,8 @@
     NoArgAnyCallable,
     NoneType,
     display_as_type,
+    get_args,
+    get_origin,
     is_literal_type,
     is_new_type,
     new_type_supertype,
@@ -411,7 +413,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
         elif is_literal_type(self.type_):
             return
 
-        origin = getattr(self.type_, '__origin__', None)
+        origin = get_origin(self.type_)
         if origin is None:
             # field is not "typing" object eg. Union, Dict, List etc.
             # allow None for virtual superclasses of NoneType, e.g. Hashable
@@ -422,7 +424,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
             return
         if origin is Union:
             types_ = []
-            for type_ in self.type_.__args__:
+            for type_ in get_args(self.type_):
                 if type_ is NoneType:
                     if self.required is Undefined:
                         self.required = False
@@ -444,9 +446,9 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
         if issubclass(origin, Tuple):  # type: ignore
             self.shape = SHAPE_TUPLE
             self.sub_fields = []
-            for i, t in enumerate(self.type_.__args__):
+            for i, t in enumerate(get_args(self.type_)):
                 if t is Ellipsis:
-                    self.type_ = self.type_.__args__[0]
+                    self.type_ = get_args(self.type_)[0]
                     self.shape = SHAPE_TUPLE_ELLIPSIS
                     return
                 self.sub_fields.append(self._create_sub_type(t, f'{self.name}_{i}'))
@@ -460,7 +462,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
                     {f'list_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_LIST
         elif issubclass(origin, Set):
             # Create self validators
@@ -470,22 +472,22 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
                     {f'set_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SET
         elif issubclass(origin, FrozenSet):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_FROZENSET
         elif issubclass(origin, Sequence):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
         elif issubclass(origin, Mapping):
-            self.key_field = self._create_sub_type(self.type_.__args__[0], 'key_' + self.name, for_keys=True)
-            self.type_ = self.type_.__args__[1]
+            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
+            self.type_ = get_args(self.type_)[1]
             self.shape = SHAPE_MAPPING
         # Equality check as almost everything inherits form Iterable, including str
         # check for Iterable and CollectionsIterable, as it could receive one even when declared with the other
         elif origin in {Iterable, CollectionsIterable}:
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_ITERABLE
             self.sub_fields = [self._create_sub_type(self.type_, f'{self.name}_type')]
         elif issubclass(origin, Type):  # type: ignore
@@ -494,7 +496,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
             # Is a Pydantic-compatible generic that handles itself
             # or we have arbitrary_types_allowed = True
             self.shape = SHAPE_GENERIC
-            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(self.type_.__args__)]
+            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(get_args(self.type_))]
             self.type_ = origin
             return
         else:
diff --git a/pydantic/generics.py b/pydantic/generics.py
index 64562227d..0a5e75401 100644
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -3,6 +3,7 @@
 from .class_validators import gather_all_validators
 from .fields import FieldInfo, ModelField
 from .main import BaseModel, create_model
+from .typing import get_origin
 from .utils import lenient_issubclass
 
 _generic_types_cache: Dict[Tuple[Type[Any], Union[Any, Tuple[Any, ...]]], Type[BaseModel]] = {}
@@ -37,7 +38,7 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
         check_parameters_count(cls, params)
         typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params))
         type_hints = get_type_hints(cls).items()
-        instance_type_hints = {k: v for k, v in type_hints if getattr(v, '__origin__', None) is not ClassVar}
+        instance_type_hints = {k: v for k, v in type_hints if get_origin(v) is not ClassVar}
         concrete_type_hints: Dict[str, Type[Any]] = {
             k: resolve_type_hint(v, typevars_map) for k, v in instance_type_hints.items()
         }
@@ -79,7 +80,7 @@ def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
 
 
 def resolve_type_hint(type_: Any, typevars_map: Dict[Any, Any]) -> Type[Any]:
-    if hasattr(type_, '__origin__') and getattr(type_, '__parameters__', None):
+    if get_origin(type_) and getattr(type_, '__parameters__', None):
         concrete_type_args = tuple([typevars_map[x] for x in type_.__parameters__])
         return type_[concrete_type_args]
     return typevars_map.get(type_, type_)
diff --git a/pydantic/main.py b/pydantic/main.py
index c872f1e3b..87299b645 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -33,7 +33,7 @@
 from .parse import Protocol, load_file, load_str_bytes
 from .schema import model_schema
 from .types import PyObject, StrBytes
-from .typing import AnyCallable, ForwardRef, is_classvar, resolve_annotations, update_field_forward_refs
+from .typing import AnyCallable, ForwardRef, get_origin, is_classvar, resolve_annotations, update_field_forward_refs
 from .utils import (
     ClassAttribute,
     GetterDict,
@@ -256,7 +256,7 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
                     if (
                         isinstance(value, untouched_types)
                         and ann_type != PyObject
-                        and not lenient_issubclass(getattr(ann_type, '__origin__', None), Type)
+                        and not lenient_issubclass(get_origin(ann_type), Type)
                     ):
                         continue
                     fields[ann_name] = inferred = ModelField.infer(
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 27c66b2bd..4f6258ab1 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -55,7 +55,7 @@
     conset,
     constr,
 )
-from .typing import ForwardRef, Literal, is_callable_type, is_literal_type, literal_values
+from .typing import ForwardRef, Literal, get_args, get_origin, is_callable_type, is_literal_type, literal_values
 from .utils import get_model, lenient_issubclass, sequence_like
 
 if TYPE_CHECKING:
@@ -803,9 +803,9 @@ def go(type_: Any) -> Type[Any]:
             or lenient_issubclass(type_, (ConstrainedList, ConstrainedSet))
         ):
             return type_
-        origin = getattr(type_, '__origin__', None)
+        origin = get_origin(type_)
         if origin is not None:
-            args: Tuple[Any, ...] = type_.__args__
+            args: Tuple[Any, ...] = get_args(type_)
             if any(isinstance(a, ForwardRef) for a in args):
                 # forward refs cause infinite recursion below
                 return type_
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 070691eeb..729ebd71b 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -44,12 +44,19 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
         return type_._eval_type(globalns, localns)
 
 
-else:
+elif sys.version_info < (3, 9):
 
     def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
         return type_._evaluate(globalns, localns)
 
 
+else:
+
+    # TODO: remove the pragma: no cover once we can run CI on python 3.9
+    def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:  # pragma: no cover
+        return type_._evaluate(globalns, localns, set())
+
+
 if sys.version_info < (3, 7):
     from typing import Callable as Callable
 
@@ -70,8 +77,50 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
             from typing_extensions import Literal
         except ImportError:
             Literal = None
+
+    def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+        return getattr(t, '__args__', ())
+
+    def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
+        return getattr(t, '__origin__', None)
+
+
 else:
-    from typing import Literal
+    from typing import Literal, get_args as typing_get_args, get_origin as typing_get_origin
+
+    def get_origin(tp: Type[Any]) -> Type[Any]:
+        return typing_get_origin(tp) or getattr(tp, '__origin__', None)
+
+    def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """
+        In python 3.9, `typing.Dict`, `typing.List`, ...
+        do have an empty `__args__` by default (instead of the generic ~T for example).
+        In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
+        we retrieve the `_nparams` value that tells us how many parameters it needs.
+        """
+        # TODO: remove the pragma: no cover once we can run CI on python 3.9
+        if hasattr(tp, '_nparams'):  # pragma: no cover
+            return (Any,) * tp._nparams
+        return ()
+
+    def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """Get type arguments with all substitutions performed.
+
+        For unions, basic simplifications used by Union constructor are performed.
+        Examples::
+            get_args(Dict[str, int]) == (str, int)
+            get_args(int) == ()
+            get_args(Union[int, Union[T, int], str][int]) == (int, str)
+            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+            get_args(Callable[[], T][int]) == ([], int)
+        """
+        try:
+            args = typing_get_args(tp)
+        # TODO: remove the pragma: no cover once we can run CI on python 3.9
+        except IndexError:  # pragma: no cover
+            args = ()
+        return args or getattr(tp, '__args__', ()) or generic_get_args(tp)
+
 
 if TYPE_CHECKING:
     from .fields import ModelField
@@ -115,6 +164,8 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
     'CallableGenerator',
     'ReprArgs',
     'CallableGenerator',
+    'get_args',
+    'get_origin',
 )
 
 
@@ -167,16 +218,16 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti
 
 
 def is_callable_type(type_: Type[Any]) -> bool:
-    return type_ is Callable or getattr(type_, '__origin__', None) is Callable
+    return type_ is Callable or get_origin(type_) is Callable
 
 
 if sys.version_info >= (3, 7):
 
     def is_literal_type(type_: Type[Any]) -> bool:
-        return Literal is not None and getattr(type_, '__origin__', None) is Literal
+        return Literal is not None and get_origin(type_) is Literal
 
     def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
-        return type_.__args__
+        return get_args(type_)
 
 
 else:
@@ -217,12 +268,15 @@ def new_type_supertype(type_: Type[Any]) -> Type[Any]:
     return type_
 
 
-def _check_classvar(v: Type[Any]) -> bool:
+def _check_classvar(v: Optional[Type[Any]]) -> bool:
+    if v is None:
+        return False
+
     return v.__class__ == ClassVar.__class__ and (sys.version_info < (3, 7) or getattr(v, '_name', None) == 'ClassVar')
 
 
 def is_classvar(ann_type: Type[Any]) -> bool:
-    return _check_classvar(ann_type) or _check_classvar(getattr(ann_type, '__origin__', None))
+    return _check_classvar(ann_type) or _check_classvar(get_origin(ann_type))
 
 
 def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None:
@@ -243,13 +297,13 @@ def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
     without brackets. Otherwise returns None.
     """
     try:
-        origin = getattr(type_, '__origin__')
+        origin = get_origin(type_)
         if origin is None:  # Python 3.6
             origin = type_
         if issubclass(origin, Type):  # type: ignore
-            if type_.__args__ is None or not isinstance(type_.__args__[0], type):
+            if not get_args(type_) or not isinstance(get_args(type_)[0], type):
                 return True
-            return type_.__args__[0]
-    except AttributeError:
+            return get_args(type_)[0]
+    except (AttributeError, TypeError):
         pass
     return None