• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

avanov / typeit / 6671406849

27 Oct 2023 07:24PM UTC coverage: 91.814% (-0.04%) from 91.858%
6671406849

Pull #84

github

web-flow
Merge 9d31bbc46 into bcad75661
Pull Request #84: Release 3.11.1.3

8 of 8 new or added lines in 1 file covered. (100.0%)

1245 of 1356 relevant lines covered (91.81%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.07
/typeit/parser/__init__.py
1
from types import UnionType
2✔
2
from typing import (
2✔
3
    Type, Tuple, Optional, Any, Union, List, Set,
4
    Dict, Sequence, MutableSet, TypeVar, FrozenSet, Mapping, ForwardRef, NewType,
5
)
6

7
import inspect
2✔
8
import collections
2✔
9

10
import typing_inspect as insp
2✔
11
from pyrsistent import pmap, pvector
2✔
12
from pyrsistent import typing as pyt
2✔
13

14
from .type_info import get_type_attribute_info, AttrInfo, NoneType
2✔
15

16
from ..compat import Literal
2✔
17
from ..definitions import OverridesT
2✔
18
from ..utils import is_named_tuple, clone_schema_node, get_global_name_overrider
2✔
19
from .. import flags
2✔
20
from .. import schema
2✔
21
from .. import sums
2✔
22
from ..schema.meta import TypeExtension
2✔
23
from .. import interface as iface
2✔
24

25

26
T = TypeVar('T')
2✔
27
MemoType = TypeVar('MemoType')
2✔
28

29
OverrideT = (   flags._Flag     # flag override
2✔
30
            |   TypeExtension   # new type extension
31
            |   Union[
32
                    Mapping[property, str],         # overrides syntax for NamedTuples
33
                    Mapping[Tuple[Type, str], str]  # overrides syntax for dataclasses and init-based hints
34
                ]
35
            )
36

37

38
def inner_type_boundaries(typ: Type) -> Tuple:
2✔
39
    return insp.get_args(typ, evaluate=True)
2✔
40

41

42
ForwardRefs = Dict[ForwardRef, Optional[schema.nodes.SchemaNode]]
2✔
43

44

45
def _maybe_node_for_none(
2✔
46
    typ: Union[Type[iface.IType], Any],
47
    overrides: OverridesT,
48
    memo: MemoType,
49
    forward_refs: ForwardRefs,
50
    supported_type: FrozenSet = frozenset([
51
        None,
52
        Type[None]  # special case to support MyPy aliases of None https://github.com/python/mypy/pull/3754
53
    ])
54
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
55
    if typ in supported_type:
2✔
56
        return _maybe_node_for_literal(Literal[None], overrides, memo, forward_refs)
2✔
57
    return None, memo, forward_refs
2✔
58

59

60
def _maybe_node_for_forward_ref(
2✔
61
    typ: Union[ForwardRef, Any],
62
    overrides: OverridesT,
63
    memo: MemoType,
64
    forward_refs: ForwardRefs
65
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
66
    if isinstance(typ, ForwardRef):
2✔
67
        schema_type = schema.types.ForwardReferenceType(forward_ref=typ, ref_registry=forward_refs)
2✔
68
        forward_refs[typ] = None
2✔
69
        return schema.nodes.SchemaNode(schema_type), memo, forward_refs
2✔
70
    return None, memo, forward_refs
2✔
71

72

73
def _maybe_node_for_newtype(
2✔
74
    typ: Union[NewType, Any],
75
    overrides: OverridesT,
76
    memo: MemoType,
77
    forward_refs: ForwardRefs
78
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
79
    """ newtypes do not change the underlying runtime data type that is used in
80
    calls like isinstance(), therefore it's just enough for us to find
81
    a schema node of the underlying type
82
    """
83
    rv = None
2✔
84
    if insp.is_new_type(typ):
2✔
85
        return decide_node_type(typ.__supertype__, overrides, memo, forward_refs)
2✔
86
    return rv, memo, forward_refs
2✔
87

88

89
def _maybe_node_for_primitive(
2✔
90
    typ: Union[Type[iface.IType], Any],
91
    overrides: OverridesT,
92
    memo: MemoType,
93
    forward_refs: ForwardRefs
94
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
95
    """ Check if type could be associated with one of the
96
    built-in type converters (in terms of Python built-ins).
97
    """
98
    registry = schema.primitives.PRIMITIVES_REGISTRY[
2✔
99
        flags.NonStrictPrimitives in overrides
100
    ]
101

102
    try:
2✔
103
        schema_type = registry[typ]
2✔
104
    except KeyError:
2✔
105
        return None, memo, forward_refs
2✔
106

107
    return schema.nodes.SchemaNode(schema_type), memo, forward_refs
2✔
108

109

110
def is_type_var_placeholder(t) -> bool:
2✔
111
    return isinstance(t, TypeVar)
2✔
112

113

114
def _maybe_node_for_type_var(
2✔
115
    typ: Type,
116
    overrides: OverridesT,
117
    memo: MemoType,
118
    forward_refs: ForwardRefs
119
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
120
    """ When we parse Sequence and List definitions without
121
    clarified item type, it is possible that this item is defined
122
    as TypeVar. Since it's an indicator of a generic collection,
123
    we can treat it as typing.Any.
124
    """
125
    if is_type_var_placeholder(typ):
2✔
126
        return _maybe_node_for_primitive(Any, overrides, memo, forward_refs)
×
127
    return None, memo, forward_refs
2✔
128

129

130
def _maybe_node_for_subclass_based(
2✔
131
    typ: Type[iface.IType],
132
    overrides: OverridesT,
133
    memo: MemoType,
134
    forward_refs: ForwardRefs
135
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
136
    rv = None
2✔
137
    for subclasses, schema_typ in schema.types.SUBCLASS_BASED_TO_SCHEMA_TYPE.items():
2✔
138
        try:
2✔
139
            is_target = issubclass(typ, subclasses)
2✔
140
        except TypeError:
2✔
141
            # TypeError: issubclass() arg 1 must be a class
142
            # ``typ`` is not a class, skip the rest
143
            break
2✔
144
        else:
145
            if is_target:
2✔
146
                rv = schema.nodes.SchemaNode(schema_typ(typ, allow_empty=True))
2✔
147
                break
2✔
148

149
    return rv, memo, forward_refs
2✔
150

151

152
# checks for "T1 | T2" syntax instances resulting in UnionType
153
is_py_310_union = lambda instance: type(instance) is UnionType
2✔
154

155

156
def _maybe_node_for_union(
2✔
157
    typ: Type[iface.IType],
158
    overrides: OverridesT,
159
    memo: MemoType,
160
    forward_refs: ForwardRefs,
161
    supported_type=frozenset({}),
162
    supported_origin=frozenset({
163
        Union, UnionType
164
    })
165
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
166
    """ Handles cases where typ is a Union, including the special
167
    case of Optional[Any], which is in essence Union[None, T]
168
    where T is either unknown Any or a concrete type.
169
    """
170
    if typ in supported_type or get_origin_39(typ) in supported_origin or is_py_310_union(typ):
2✔
171
        variants = inner_type_boundaries(typ) or typ.__args__  # 'or x' assumes Python310 union
2✔
172
        if variants in ((NoneType, Any), (Any, NoneType)):
2✔
173
            # Case for Optional[Any] and Union[None, Any] notations
174
            rv = schema.nodes.SchemaNode(
2✔
175
                schema.primitives.AcceptEverything(),
176
                missing=None
177
            )
178
            return rv, memo, forward_refs
2✔
179

180
        allow_empty = NoneType in variants
2✔
181
        # represents a 2-tuple of (type_from_signature, associated_schema_node)
182
        variant_nodes: List[Tuple[Type, schema.nodes.SchemaNode]] = []
2✔
183
        for variant in variants:
2✔
184
            if variant is NoneType:
2✔
185
                continue
2✔
186
            node, memo, forward_refs = decide_node_type(variant, overrides, memo, forward_refs)
2✔
187
            if allow_empty:
2✔
188
                # clonning because we mutate it next, and the node
189
                # might be already from the cache
190
                node = clone_schema_node(node)
2✔
191
                node.missing = None
2✔
192
            variant_nodes.append((variant, node))
2✔
193

194
        primitive_types = schema.primitives.PRIMITIVES_REGISTRY[
2✔
195
            flags.NonStrictPrimitives in overrides
196
        ]
197

198
        union_node = schema.nodes.SchemaNode(
2✔
199
            schema.types.Union(variant_nodes=variant_nodes,
200
                               primitive_types=primitive_types)
201
        )
202
        if allow_empty:
2✔
203
            union_node.missing = None
2✔
204
        return union_node, memo, forward_refs
2✔
205

206
    return None, memo, forward_refs
2✔
207

208

209
def _maybe_node_for_sum_type(
2✔
210
    typ: Union[Type[iface.IType], Type[sums.SumType]],
211
    overrides: OverridesT,
212
    memo: MemoType,
213
    forward_refs: ForwardRefs,
214
    supported_type=frozenset({}),
215
    supported_origin=frozenset({})
216
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
217
    try:
2✔
218
        matched = issubclass(typ, sums.SumType)
2✔
219
    except TypeError:
2✔
220
        # TypeError: issubclass() arg 1 must be a class
221
        # ``typ`` is not a class (or a Generic[T] class), skip the rest
222
        matched = False
2✔
223

224
    sum_node = None
2✔
225
    if matched:
2✔
226
        # represents a 2-tuple of (type_from_signature, associated_schema_node)
227
        variant_nodes: List[Tuple[Type, schema.nodes.SchemaNode]] = []
2✔
228
        for variant in typ:
2✔
229
            node, memo, forward_refs = decide_node_type(
2✔
230
                variant.__variant_meta__.constructor,
231
                overrides,
232
                memo,
233
                forward_refs
234
            )
235
            node.typ.unknown = 'raise'
2✔
236
            variant_nodes.append((variant, node))
2✔
237
        sum_node = schema.nodes.SchemaNode(
2✔
238
            schema.types.Sum(
239
                typ=typ,
240
                variant_nodes=variant_nodes,
241
                as_dict_key=overrides.get(flags.SumTypeDict),
242
            )
243
        )
244
    return sum_node, memo, forward_refs
2✔
245

246

247
def _maybe_node_for_literal(
2✔
248
    typ: Type[iface.IType],
249
    overrides: OverridesT,
250
    memo: MemoType,
251
    forward_refs: ForwardRefs,
252
    supported_type=frozenset({}),
253
    supported_origin=frozenset({
254
        Literal,
255
    }),
256
    _supported_literal_types=frozenset({
257
        bool, int, str, bytes, NoneType,
258
    })
259
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
260
    """ Handles cases where typ is a Literal, according to the allowed
261
    types: https://mypy.readthedocs.io/en/latest/literal_types.html
262
    """
263
    rv = None
2✔
264
    if typ in supported_type \
2✔
265
    or get_origin_39(typ) in supported_origin:
266
        inner = inner_type_boundaries(typ)
2✔
267
        for x in inner:
2✔
268
            if type(x) not in _supported_literal_types:
2✔
269
                raise TypeError(f'Literals cannot be defined with values of type {type(x)}')
×
270
        rv = schema.nodes.SchemaNode(schema.types.Literal(frozenset(inner)))
2✔
271
    return rv, memo, forward_refs
2✔
272

273

274
def _maybe_node_for_sequence(
2✔
275
    typ: Type[iface.IType],
276
    overrides: OverridesT,
277
    memo: MemoType,
278
    forward_refs: ForwardRefs,
279
    supported_type=frozenset({
280
        collections.abc.Sequence,
281
        pyt.PVector,
282
    }),
283
    supported_origin=frozenset({
284
        List,
285
        Sequence,
286
        collections.abc.Sequence,
287
        list,
288
        pyt.PVector,
289
    })
290
) -> Tuple[Optional[schema.nodes.SequenceSchema], MemoType, ForwardRefs]:
291
    rv = None
2✔
292
    # typ is List[T] where T is either unknown Any or a concrete type
293
    if typ in supported_type or get_origin_39(typ) in supported_origin:
2✔
294
        try:
2✔
295
            inner = inner_type_boundaries(typ)[0]
2✔
296
        except IndexError:
2✔
297
            # Python 3.9 access to arguments
298
            try:
2✔
299
                inner = typ.__args__[0]
2✔
300
            except (AttributeError, IndexError):
2✔
301
                inner = Any
2✔
302
        if pyt.PVector in (typ, get_origin_39(typ)):
2✔
303
            seq_type = schema.nodes.PVectorSchema
2✔
304
        else:
305
            seq_type = schema.nodes.SequenceSchema
2✔
306
        node, memo, forward_refs = decide_node_type(inner, overrides, memo, forward_refs)
2✔
307
        rv = seq_type(node)
2✔
308
    return rv, memo, forward_refs
2✔
309

310

311
def get_origin_39(typ: Type[Any]) -> Type[Any]:
2✔
312
    """python3.9 aware origin"""
313
    origin = insp.get_origin(typ)
2✔
314
    if origin is None:
2✔
315
        origin = typ.__origin__ if hasattr(typ, '__origin__') else None
2✔
316
    return origin
2✔
317

318

319
def _maybe_node_for_set(
2✔
320
    typ: Type[iface.IType],
321
    overrides: OverridesT,
322
    memo: MemoType,
323
    forward_refs: ForwardRefs,
324
    supported_type=frozenset({
325
        set,
326
        frozenset,
327
        collections.abc.Set,
328
        collections.abc.MutableSet,
329
    }),
330
    supported_origin=frozenset({
331
        Set,
332
        MutableSet,
333
        FrozenSet,
334
        collections.abc.Set,
335
        collections.abc.MutableSet,
336
        set,
337
        frozenset,
338
    })
339
) -> Tuple[Optional[schema.nodes.SequenceSchema], MemoType, ForwardRefs]:
340
    rv = None
2✔
341
    origin = get_origin_39(typ)
2✔
342
    if typ in supported_type or origin in supported_origin:
2✔
343
        try:
2✔
344
            inner = inner_type_boundaries(typ)[0]
2✔
345
        except IndexError:
2✔
346
            inner = Any
2✔
347
        node, memo, forward_refs = decide_node_type(inner, overrides, memo, forward_refs)
2✔
348
        rv = schema.nodes.SetSchema(
2✔
349
            node,
350
            frozen=(
351
                typ is frozenset or
352
                origin in (frozenset, FrozenSet)
353
            )
354
        )
355
    return rv, memo, forward_refs
2✔
356

357

358
def _maybe_node_for_tuple(
2✔
359
    typ: Type[iface.IType],
360
    overrides: OverridesT,
361
    memo: MemoType,
362
    forward_refs: ForwardRefs,
363
    supported_type=frozenset({
364
        tuple,
365
    }),
366
    supported_origin=frozenset({
367
        tuple, Tuple,
368
    })
369
) -> Tuple[Optional[schema.nodes.TupleSchema], MemoType, ForwardRefs]:
370
    rv = None
2✔
371
    if typ in supported_type or get_origin_39(typ) in supported_origin:
2✔
372
        inner_types = inner_type_boundaries(typ)
2✔
373
        if Ellipsis in inner_types:
2✔
374
            raise TypeError(
2✔
375
                f'You are trying to create a constructor for '
376
                f'the type "{typ}", however, variable-length tuples '
377
                f'are not supported by typeit. '
378
                f'Use Sequence or List if you want to have a '
379
                f'variable-length collection, and consider '
380
                f'pyrsistent.pvector for immutability.'
381
            )
382
        node = schema.nodes.TupleSchema()
2✔
383
        # Add tuple elements to the tuple node definition
384
        for t in inner_types:
2✔
385
            n, memo, forward_refs = decide_node_type(t, overrides, memo, forward_refs)
2✔
386
            node.add(n)
2✔
387
        rv = node
2✔
388

389
    return rv, memo, forward_refs
2✔
390

391

392
def are_generic_bases_match(bases, template) -> bool:
2✔
393
    for base in bases:
2✔
394
        if base in template:
2✔
395
            return True
×
396
    return False
2✔
397

398

399
def is_pmap(typ: Type[Any]) -> bool:
2✔
400
    """python3.9 compatible pmap checker"""
401
    return pyt.PMap in (typ, get_origin_39(typ)) \
2✔
402
        or (hasattr(typ, '__name__') and typ.__name__ == "PMap" and typ.__module__.startswith("pyrsistent."))
403

404

405
def is_39_deprecated_dict(typ: Type[Any]) -> bool:
2✔
406
    """python3.9 deprecated Dict in favor of dict, and now it lacks necessary metadata other than name and module if
407
    there is no other constraints on key and value types, e.g. Dict[Key, Val] can be recognised, however just Dict cannot be.
408
    """
409
    return get_origin_39(typ) is None and hasattr(typ, '_name') and typ._name == 'Dict' and typ.__module__ == 'typing'
2✔
410

411

412
def _maybe_node_for_dict(
2✔
413
    typ: Type[iface.IType],
414
    overrides: OverridesT,
415
    memo: MemoType,
416
    forward_refs: ForwardRefs,
417
    supported_type=frozenset({
418
        dict,
419
        collections.abc.Mapping,
420
        pyt.PMap,
421
    }),
422
    supported_origin=frozenset({
423
        Dict,
424
        dict,
425
        collections.abc.Mapping,
426
        pyt.PMap,
427
    })
428
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
429
    """ This is mainly for cases when a user has manually
430
    specified that a field should be a dictionary, rather than a
431
    strict structure, possibly due to dynamic nature of keys
432
    (for instance, python logging settings that have an infinite
433
    set of possible attributes).
434
    """
435
    rv = None
2✔
436
    # This is a hack for Python 3.9
437
    if insp.is_generic_type(typ):
2✔
438
        generic_bases = [get_origin_39(x) for x in insp.get_generic_bases(typ)]
2✔
439
    else:
440
        generic_bases = []
2✔
441

442
    typ = dict if is_39_deprecated_dict(typ) else typ
2✔
443

444
    if typ in supported_type or get_origin_39(typ) in supported_origin or are_generic_bases_match(generic_bases, supported_origin):
2✔
445
        schema_node_type = schema.nodes.PMapSchema if is_pmap(typ) else schema.nodes.SchemaNode
2✔
446

447
        def maybe_non_specified_arguments(mappingType):
2✔
448
            try:
2✔
449
                kt, kv = insp.get_args(mappingType)
2✔
450
            except ValueError:
2✔
451
                # Mapping doesn't provide key/value types
452
                kt, kv = Any, Any
2✔
453
            return kt, kv
2✔
454

455
        if generic_bases:
2✔
456
            # python 3.9 args
457
            try:
2✔
458
                key_type, value_type = typ.__args__
2✔
459
            except AttributeError:
2✔
460
                # PMap without clarifying type arguments will cause this branch
461
                key_type, value_type = maybe_non_specified_arguments(typ)
2✔
462
        else:
463
            key_type, value_type = maybe_non_specified_arguments(typ)
2✔
464

465
        key_node,   memo, forward_refs = decide_node_type(key_type, overrides, memo, forward_refs)
2✔
466
        value_node, memo, forward_refs = decide_node_type(value_type, overrides, memo, forward_refs)
2✔
467
        mapping_type = schema.types.TypedMapping(key_node=key_node, value_node=value_node)
2✔
468
        rv = schema_node_type(mapping_type)
2✔
469
    return rv, memo, forward_refs
2✔
470

471

472
def _maybe_node_for_user_type(
2✔
473
    typ: Type[iface.IType],
474
    overrides: OverridesT,
475
    memo: MemoType,
476
    forward_refs: ForwardRefs,
477
) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]:
478
    """ Generates a Colander schema for the given user-defined `typ` that is capable
479
    of both constructing (deserializing) and serializing the `typ`.
480
    This includes named tuples and dataclasses.
481
    """
482
    global_name_overrider = get_global_name_overrider(overrides)
2✔
483
    is_generic = insp.is_generic_type(typ)
2✔
484

485
    if is_generic:
2✔
486
        # get the base class that was turned into Generic[T, ...]
487
        # For generic types without clarification this is get_origin, otherwise if the generic type is already
488
        # clarified (via subclassing like class MyClass(GenericBase[ConcreteType]): ... ) the generic hints source is
489
        # going to be the type itself instead of origins. This second version is actually similar to the else clause
490
        # below: "elif is_named_tuple(typ)"
491
        hints_source = get_origin_39(typ) or typ
2✔
492

493
        # now we need to map generic type variables to the bound class types,
494
        # e.g. we map Entity[T,U,V, ...] to actual types of Entity[int, float, str, ...]
495
        generic_repr = insp.get_generic_bases(hints_source)
2✔
496
        generic_vars_ordered = [insp.get_args(x)[0] for x in generic_repr]
2✔
497
        bound_type_args = insp.get_args(typ)
2✔
498
        type_var_to_type = pmap(zip(generic_vars_ordered, bound_type_args))
2✔
499
        if type_var_to_type:
2✔
500
            # Resolve type hints.
501
            # We have to match all generic type parameter placeholders with the actual types passed as implementations
502
            # of the interface. However, we need to keep in mind that not all attributes of the generic type have generic
503
            # placeholders. Hence, in places where we cannot find the generic placeholder name, we just assume that there's
504
            # no placeholder, and therefore ``type_var`` is automatically a concrete type
505
            attribute_hints = [
2✔
506
                (   field_name
507
                ,   type_var_to_type.get(type_var_or_concrete_type) or type_var_or_concrete_type
508
                )
509
                for field_name, type_var_or_concrete_type in (
510
                    (x, raw_type)
511
                    for x, _resolved_type, raw_type in get_type_attribute_info(hints_source)
512
                )
513
            ]
514
        else:
515
            # Here we have a situation with a concrete type after the generic type is clarified
516
            # into a concrete type, i.e. when we have Entity[T] and later:
517
            # class MyType(Entity[MyOtherType]): ...
518
            #
519
            # In this situation type_var_to_type will be empty, and we need to infer attributes
520
            # of MyType via concrete types
521
            clarified = iter(generic_vars_ordered)
2✔
522
            attribute_hints = [
2✔
523
                (attr_name, next(clarified) if is_type_var_placeholder(raw_type) else raw_type)
524
                for attr_name, _resolved_type, raw_type in get_type_attribute_info(hints_source)
525
            ]
526
        # Generic types should not have default values
527
        defaults_source = lambda: ()
2✔
528
        # Overrides should be the same as class-based ones, as Generics are not NamedTuple classes,
529
        # TODO: consider reducing duplication between this and the logic from init-based types (see below)
530
        deserialize_overrides = pmap({
2✔
531
            # try to get a specific override for a field, if it doesn't exist, use the global modifier
532
            overrides.get(
533
                (typ, python_field_name),
534
                global_name_overrider(python_field_name)
535
            ): python_field_name
536
            for python_field_name, _ in attribute_hints
537
        })
538
        # apply a local optimisation that discards `deserialize_overrides`
539
        # if there is no difference with the original field_names;
540
        # it is done to occupy less memory with unnecessary mappings
541
        if deserialize_overrides == pmap({x: x for x, _ in attribute_hints}) and global_name_overrider is flags.Identity:
2✔
542
            deserialize_overrides = pmap({})
2✔
543

544
    elif is_named_tuple(typ):
2✔
545
        hints_source = typ
2✔
546
        attribute_hints = [(x, raw_type) for x, y, raw_type in get_type_attribute_info(hints_source)]
2✔
547
        get_override_identifier = lambda x: getattr(typ, x)
2✔
548
        defaults_source = typ.__new__
2✔
549

550
        deserialize_overrides = pmap({
2✔
551
            # try to get a specific override for a field, if it doesn't exist, use the global modifier
552
            overrides.get(
553
                getattr(typ, python_field_name),
554
                global_name_overrider(python_field_name)
555
            ): python_field_name
556
            for python_field_name in typ._fields
557
        })
558

559
        # apply a local optimisation that discards `deserialize_overrides`
560
        # if there is no difference with the original field_names;
561
        # it is done to occupy less memory with unnecessary mappings
562
        if deserialize_overrides == pmap({x: x for x in typ._fields}) and global_name_overrider is flags.Identity:
2✔
563
            deserialize_overrides = pmap({})
2✔
564
    else:
565
        # use init-based types
566
        hints_source = typ.__init__
2✔
567
        attribute_hints = [(x, raw_type) for x, y, raw_type in get_type_attribute_info(hints_source)]
2✔
568
        get_override_identifier = lambda x: (typ, x)
2✔
569
        defaults_source = typ.__init__
2✔
570

571
        deserialize_overrides = pmap({
2✔
572
            # try to get a specific override for a field, if it doesn't exist, use the global modifier
573
            overrides.get(
574
                (typ, python_field_name),
575
                global_name_overrider(python_field_name)
576
            ): python_field_name
577
            for python_field_name, _ in attribute_hints
578
        })
579
        # apply a local optimisation that discards `deserialize_overrides`
580
        # if there is no difference with the original field_names;
581
        # it is done to occupy less memory with unnecessary mappings
582
        if deserialize_overrides == pmap({x: x for x, _ in attribute_hints}) and global_name_overrider is flags.Identity:
2✔
583
            deserialize_overrides = pmap({})
2✔
584

585
    defaults = {
2✔
586
        k: v.default
587
        for k, v in inspect.signature(defaults_source).parameters.items()
588
        if k != 'self' and v.default != inspect.Parameter.empty
589
    }
590

591
    if is_generic and hints_source in overrides:
2✔
592
        # Generic types may have their own custom Schemas defined
593
        # as a TypeExtension through overrides
594
        overridden: TypeExtension = overrides[hints_source]
2✔
595
        schema_type_type, _node_children_ = overridden.schema
2✔
596
    else:
597
        schema_type_type = schema.types.Structure
2✔
598

599
    schema_type = schema_type_type(
2✔
600
        typ=typ,
601
        attrs=pvector([x[0] for x in attribute_hints]),
602
        deserialize_overrides=deserialize_overrides,
603
    )
604

605
    type_schema = schema.nodes.SchemaNode(schema_type)
2✔
606

607
    for field_name, field_type in attribute_hints:
2✔
608
        globally_modified_field_name = global_name_overrider(field_name)
2✔
609
        # apply field override, if available
610
        if deserialize_overrides:
2✔
611
            field = get_override_identifier(field_name)
2✔
612
            serialized_field_name = overrides.get(field, globally_modified_field_name)
2✔
613
        else:
614
            serialized_field_name = globally_modified_field_name
2✔
615

616
        node, memo, forward_refs = decide_node_type(field_type, overrides, memo, forward_refs)
2✔
617
        if node is None:
2✔
618
            raise TypeError(
×
619
                f'Cannot recognise type "{field_type}" of the field '
620
                f'"{typ.__name__}.{field_name}" (from {typ.__module__})'
621
            )
622
        # clonning because we mutate it next, and the node
623
        # might be from the cache already
624
        node = clone_schema_node(node)
2✔
625
        node.name = serialized_field_name
2✔
626
        node.missing = defaults.get(field_name, node.missing)
2✔
627
        type_schema.add(node)
2✔
628
    return type_schema, memo, forward_refs
2✔
629

630

631
def _maybe_node_for_overridden(
2✔
632
    typ: Type[Any],
633
    overrides: OverridesT,
634
    memo: MemoType,
635
    forward_refs: ForwardRefs,
636
) -> Tuple[Any, MemoType, ForwardRefs]:
637
    rv = None
2✔
638
    if typ in overrides:
2✔
639
        override: schema.TypeExtension = overrides[typ]
2✔
640
        schema_type_type, schema_node_children = override.schema
2✔
641
        type_schema = schema.nodes.SchemaNode(schema_type_type())
2✔
642
        for child in schema_node_children:
2✔
643
            type_schema.add(child)
2✔
644
        rv = type_schema
2✔
645
    return rv, memo, forward_refs
2✔
646

647

648
PARSING_ORDER = pvector([ _maybe_node_for_forward_ref
2✔
649
                        , _maybe_node_for_overridden
650
                        , _maybe_node_for_none
651
                        , _maybe_node_for_primitive
652
                        , _maybe_node_for_type_var
653
                        , _maybe_node_for_newtype
654
                        , _maybe_node_for_union
655
                        , _maybe_node_for_sequence
656
                        , _maybe_node_for_tuple
657
                        , _maybe_node_for_dict
658
                        , _maybe_node_for_set
659
                        , _maybe_node_for_literal
660
                        , _maybe_node_for_sum_type
661
                        , _maybe_node_for_subclass_based
662
                        # at this point it could be a user-defined type,
663
                        # so the parser may do another recursive iteration
664
                        # through the same plan
665
                        , _maybe_node_for_user_type
666
                        ])
667

668

669
CompoundSchema = Union[schema.nodes.SchemaNode, schema.nodes.TupleSchema, schema.nodes.SequenceSchema]
2✔
670

671

672
def decide_node_type(
2✔
673
    typ: Type[iface.IType],
674
    overrides: OverridesT,
675
    memo: MemoType,
676
    forward_refs: ForwardRefs,
677
) -> Tuple[CompoundSchema, MemoType, ForwardRefs]:
678
    # typ is either of:
679
    #  Union[Type[BuiltinTypes],
680
    #        Type[Optional[Any]],
681
    #        Type[List[Any]],
682
    #        Type[Tuple],
683
    #        Type[Set],
684
    #        Type[Union[Enum, SumType]],
685
    #        Type[Dict],
686
    #        NamedTuple]
687
    # I'm not adding ^ to the function signature, because mypy
688
    # is unable to narrow down `typ` to NamedTuple
689
    # at line _node_for_type(typ)
690
    if typ in memo:
2✔
691
        return memo[typ], memo, forward_refs
2✔
692
    for attempt_find in PARSING_ORDER:
2✔
693
        node, memo, forward_refs = attempt_find(typ, overrides, memo, forward_refs)
2✔
694
        if node:
2✔
695
            memo = memo.set(typ, node)
2✔
696
            return node, memo, forward_refs
2✔
697
    raise TypeError(
×
698
        f'Unable to create a node for "{typ}".'
699
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc