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

LostInDarkMath / pedantic-python-decorators / 18348656054

08 Oct 2025 02:47PM UTC coverage: 95.926% (-0.008%) from 95.934%
18348656054

Pull #108

github

LostInDarkMath
added support for Python 3.14
Pull Request #108: added support for Python 3.14

742 of 756 branches covered (98.15%)

Branch coverage included in aggregate %.

16 of 16 new or added lines in 4 files covered. (100.0%)

3 existing lines in 1 file now uncovered.

6887 of 7197 relevant lines covered (95.69%)

2.41 hits per line

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

99.0
/pedantic/type_checking_logic/check_types.py
1
"""Idea is taken from: https://stackoverflow.com/a/55504010/10975692"""
2
import inspect
4✔
3
import sys
4✔
4
import types
4✔
5
import typing
4✔
6
from io import BytesIO, StringIO, BufferedWriter, TextIOWrapper
4✔
7
from typing import Any, Dict, Iterable, ItemsView, Callable, Optional, Tuple, Mapping, TypeVar, NewType, \
4✔
8
    _ProtocolMeta
9
import collections
4✔
10

11
from pedantic.type_checking_logic.resolve_forward_ref import resolve_forward_ref
4✔
12
from pedantic.constants import TypeVar as TypeVar_, TYPE_VAR_SELF
4✔
13
from pedantic.exceptions import PedanticTypeCheckException, PedanticTypeVarMismatchException, PedanticException
4✔
14

15

16
def assert_value_matches_type(
4✔
17
        value: Any,
18
        type_: Any,
19
        err: str,
20
        type_vars: Dict[TypeVar_, Any],
21
        key: Optional[str] = None,
22
        msg: Optional[str] = None,
23
        context: Dict[str, Any] = None,
24
) -> None:
25
    if not _check_type(value=value, type_=type_, err=err, type_vars=type_vars, context=context):
4✔
26
        t = type(value)
4✔
27
        value = f'{key}={value}' if key is not None else str(value)
4✔
28

29
        if not msg:
4✔
30
            msg = f'{err}Type hint is incorrect: Argument {value} of type {t} does not match expected type {type_}.'
4✔
31

32
        raise PedanticTypeCheckException(msg)
4✔
33

34

35
def _check_type(value: Any, type_: Any, err: str, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
36
    """
37
        >>> from typing import List, Union, Optional, Callable, Any
38
        >>> _check_type(5, int, '', {})
39
        True
40
        >>> _check_type(5, float, '', {})
41
        False
42
        >>> _check_type('hi', str, '', {})
43
        True
44
        >>> _check_type(None, str, '', {})
45
        False
46
        >>> _check_type(None, Any, '', {})
47
        True
48
        >>> _check_type(None, None, '', {})
49
        True
50
        >>> _check_type(5, Any, '', {})
51
        True
52
        >>> _check_type(3.1415, float, '', {})
53
        True
54
        >>> _check_type([1, 2, 3, 4], List[int], '', {})
55
        True
56
        >>> _check_type([1, 2, 3.0, 4], List[int], '', {})
57
        False
58
        >>> _check_type([1, 2, 3.0, 4], List[float], '', {})
59
        False
60
        >>> _check_type([1, 2, 3.0, 4], List[Union[float, int]], '', {})
61
        True
62
        >>> _check_type([[True, False], [False], [True], []], List[List[bool]], '', {})
63
        True
64
        >>> _check_type([[True, False, 1], [False], [True], []], List[List[bool]], '', {})
65
        False
66
        >>> _check_type(5, Union[int, float, bool], '', {})
67
        True
68
        >>> _check_type(5.0, Union[int, float, bool], '', {})
69
        True
70
        >>> _check_type(False, Union[int, float, bool], '', {})
71
        True
72
        >>> _check_type('5', Union[int, float, bool], '', {})
73
        False
74
        >>> def f(a: int, b: bool, c: str) -> float: pass
75
        >>> _check_type(f, Callable[[int, bool, str], float], '', {})
76
        True
77
        >>> _check_type(None, Optional[List[Dict[str, float]]], '', {})
78
        True
79
        >>> _check_type([{'a': 1.2, 'b': 3.4}], Optional[List[Dict[str, float]]], '', {})
80
        True
81
        >>> _check_type([{'a': 1.2, 'b': 3}], Optional[List[Dict[str, float]]], '', {})
82
        False
83
        >>> _check_type({'a': 1.2, 'b': 3.4}, Optional[List[Dict[str, float]]], '', {})
84
        False
85
        >>> _check_type([{'a': 1.2, 7: 3.4}], Optional[List[Dict[str, float]]], '', {})
86
        False
87
        >>> class MyClass: pass
88
        >>> _check_type(MyClass(), 'MyClass', '', {})
89
        True
90
        >>> _check_type(MyClass(), 'MyClas', '', {})
91
        False
92
        >>> _check_type([1, 2, 3], list, '', {})
93
        Traceback (most recent call last):
94
        ...
95
        pedantic.exceptions.PedanticTypeCheckException:  Missing type arguments
96
        >>> _check_type((1, 2, 3), tuple, '', {})
97
        Traceback (most recent call last):
98
        ...
99
        pedantic.exceptions.PedanticTypeCheckException:  Missing type arguments
100
        >>> _check_type({1: 1.0, 2: 2.0, 3: 3.0}, dict, '', {})
101
        Traceback (most recent call last):
102
        ...
103
        pedantic.exceptions.PedanticTypeCheckException:  Missing type arguments
104
    """
105

106
    if type_ is None:
4✔
107
        return value == type_
4✔
108
    elif isinstance(type_, str):
4✔
109
        class_name = value.__class__.__name__
4✔
110
        base_class_name = value.__class__.__base__.__name__
4✔
111
        return class_name == type_ or base_class_name == type_
4✔
112

113
    try:
4✔
114
        return _is_instance(obj=value, type_=type_, type_vars=type_vars, context=context)
4✔
115
    except PedanticTypeCheckException as ex:
4✔
116
        raise PedanticTypeCheckException(f'{err} {ex}')
4✔
117
    except PedanticTypeVarMismatchException as ex:
4✔
118
        raise PedanticTypeVarMismatchException(f'{err} {ex}')
4✔
119
    except (AttributeError, Exception) as ex:
4✔
120
        raise PedanticTypeCheckException(
4✔
121
            f'{err}An error occurred during type hint checking. Value: {value} Annotation: '
122
            f'{type_} Mostly this is caused by an incorrect type annotation. Details: {ex} ')
123

124

125
def _is_instance(obj: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
126
    context = context or {}
4✔
127

128
    if not _has_required_type_arguments(type_):
4✔
129
        raise PedanticTypeCheckException(
4✔
130
            f'The type annotation "{type_}" misses some type arguments e.g. '
131
            f'"typing.Tuple[Any, ...]" or "typing.Callable[..., str]".')
132

133
    if type_.__module__ == 'typing':
4✔
134
        if _is_generic(type_):
4✔
135
            origin = get_base_generic(type_)
4✔
136
        else:
137
            origin = type_
4✔
138

139
        name = _get_name(origin)
4✔
140

141
        if name in _SPECIAL_INSTANCE_CHECKERS:
4✔
142
            validator = _SPECIAL_INSTANCE_CHECKERS[name]
4✔
143
            return validator(obj, type_, type_vars, context)
4✔
144

145
    if hasattr(types, 'UnionType') and isinstance(type_, types.UnionType):
4✔
146
        return _instancecheck_union(value=obj, type_=type_, type_vars=type_vars, context=context)
3✔
147

148
    if type_ == typing.BinaryIO:
4✔
149
        return isinstance(obj, (BytesIO, BufferedWriter))
4✔
150
    elif type_ == typing.TextIO:
4✔
151
        return isinstance(obj, (StringIO, TextIOWrapper))
4✔
152
    elif type_ == typing.NoReturn:
4✔
153
        return False  # we expect an Exception here, but got a value
4✔
154
    elif hasattr(typing, 'Never') and type_ == typing.Never:  # since Python 3.11
4✔
155
        return False  # we expect an Exception here, but got a value
4✔
156
    elif hasattr(typing, 'LiteralString') and type_ == typing.LiteralString:  # since Python 3.11
4✔
157
        return isinstance(obj, str)  # we cannot distinguish str and LiteralString at runtime
4✔
158
    elif hasattr(typing, 'Self') and type_ == typing.Self:  # since Python 3.11
4✔
159
        t = type_vars[TYPE_VAR_SELF]
4✔
160

161
        if t is None:
4✔
162
            return False  # the case if a function outside a class was type hinted with self
4✔
163

164
        return _is_instance(obj=obj, type_=t, type_vars=type_vars, context=context)
4✔
165

166
    if isinstance(type_, TypeVar):
4✔
167
        constraints = type_.__constraints__
4✔
168

169
        if len(constraints) > 0 and type(obj) not in constraints:
4✔
170
            return False
4✔
171

172
        if _is_forward_ref(type_=type_.__bound__):
4✔
173
            resolved = resolve_forward_ref(type_.__bound__.__forward_arg__, context=context)
4✔
174
            return _is_instance(obj=obj, type_=resolved, type_vars=type_vars, context=context)
4✔
175

176
        if type_.__bound__ is not None and not isinstance(obj, type_.__bound__):
4✔
177
            return False
4✔
178

179
        if type_ in type_vars:
4✔
180
            other = type_vars[type_]
4✔
181

182
            if type_.__contravariant__:
4✔
183
                if not _is_subtype(sub_type=other, super_type=obj.__class__):
4✔
184
                    raise PedanticTypeVarMismatchException(
4✔
185
                        f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} but TypeVar {type_} '
186
                        f'was previously matched to type {other}')
187
            else:
188
                if not _is_instance(obj=obj, type_=other, type_vars=type_vars, context=context):
4✔
189
                    raise PedanticTypeVarMismatchException(
4✔
190
                        f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} but TypeVar {type_} '
191
                        f'was previously matched to type {other}')
192

193
        type_vars[type_] = type(obj)
4✔
194
        return True
4✔
195

196
    if hasattr(typing, 'Unpack') and getattr(type_, '__origin__', None) == typing.Unpack:
4✔
197
        return True  # it's too hard to check that at the moment
4✔
198

199
    if _is_generic(type_):
4✔
200
        python_type = type_.__origin__
4✔
201

202
        if not isinstance(obj, python_type):
4✔
203
            return False
4✔
204

205
        base = get_base_generic(type_)
4✔
206
        type_args = get_type_arguments(cls=type_)
4✔
207

208
        if base in _ORIGIN_TYPE_CHECKERS:
4✔
209
            validator = _ORIGIN_TYPE_CHECKERS[base]
4✔
210
            return validator(obj, type_args, type_vars, context)
4✔
211

212
        assert base.__base__ == typing.Generic, f'Unknown base: {base}'
4✔
213
        return isinstance(obj, base)
4✔
214

215
    if _is_forward_ref(type_=type_):
4✔
216
        resolved = resolve_forward_ref(type_.__forward_arg__, context=context)
4✔
217
        return _is_instance(obj=obj, type_=resolved, type_vars=type_vars, context=context)
4✔
218

219
    if _is_type_new_type(type_):
4✔
220
        return isinstance(obj, type_.__supertype__)
4✔
221

222
    if hasattr(obj, '_asdict'):
4✔
223
        if hasattr(type_, '_field_types'):
4✔
224
            field_types = type_._field_types
×
225
        elif hasattr(type_, '__annotations__'):
4✔
226
            field_types = type_.__annotations__
4✔
227
        else:
228
            return False
4✔
229

230
        if not obj._asdict().keys() == field_types.keys():
4✔
231
            return False
4✔
232

233
        return all([_is_instance(obj=obj._asdict()[k], type_=v, type_vars=type_vars, context=context) for k, v in field_types.items()])
4✔
234

235
    if type_ in {list, set, dict, frozenset, tuple, type}:
4✔
236
        raise PedanticTypeCheckException('Missing type arguments')
4✔
237

238
    if isinstance(type_, types.GenericAlias):
4✔
239
        return _is_instance(obj=obj, type_=convert_to_typing_types(type_), type_vars=type_vars, context=context)
4✔
240

241
    try:
4✔
242
        return isinstance(obj, type_)
4✔
243
    except TypeError:
4✔
244
        if type(type_) == _ProtocolMeta:
4✔
245
            return True  # we do not check this
4✔
246

UNCOV
247
        raise
×
248

249

250
def _is_forward_ref(type_: Any) -> bool:
4✔
251
    return hasattr(typing, 'ForwardRef') and isinstance(type_, typing.ForwardRef) or \
4✔
252
            hasattr(typing, '_ForwardRef') and isinstance(type_, typing._ForwardRef)
253

254

255
def _is_type_new_type(type_: Any) -> bool:
4✔
256
    """
257
        >>> from typing import Tuple, Callable, Any, List, NewType
258
        >>> _is_type_new_type(int)
259
        False
260
        >>> UserId = NewType('UserId', int)
261
        >>> _is_type_new_type(UserId)
262
        True
263
    """
264

265
    if type(type_) == typing.NewType:
4✔
266
        return True
4✔
267

268
    return type_.__qualname__ == NewType('name', int).__qualname__  # arguments of NewType() are arbitrary here
4✔
269

270

271
def _get_name(cls: Any) -> str:
4✔
272
    """
273
        >>> from typing import Tuple, Callable, Any, List
274
        >>> _get_name(int)
275
        'int'
276
        >>> _get_name(Any)
277
        'Any'
278
        >>> _get_name(List)
279
        'List'
280
        >>> _get_name(List[int])
281
        'List'
282
        >>> _get_name(List[Any])
283
        'List'
284
        >>> _get_name(Tuple)
285
        'Tuple'
286
        >>> _get_name(Tuple[int, float])
287
        'Tuple'
288
        >>> _get_name(Tuple[Any, ...])
289
        'Tuple'
290
        >>> _get_name(Callable)
291
        'Callable'
292
    """
293
    if hasattr(cls, '_name'):
4✔
294
        return cls._name
4✔
295
    elif hasattr(cls, '__name__'):
4✔
296
        return cls.__name__
4✔
297
    else:
298
        return type(cls).__name__[1:]
4✔
299

300

301
def _is_generic(cls: Any) -> bool:
4✔
302
    """
303
        >>> from typing import  List, Callable, Any, Union
304
        >>> _is_generic(int)
305
        False
306
        >>> _is_generic(List)
307
        True
308
        >>> _is_generic(List[int])
309
        True
310
        >>> _is_generic(List[Any])
311
        True
312
        >>> _is_generic(List[List[int]])
313
        True
314
        >>> _is_generic(Any)
315
        False
316
        >>> _is_generic(Tuple)
317
        True
318
        >>> _is_generic(Tuple[Any, ...])
319
        True
320
        >>> _is_generic(Tuple[str, float, int])
321
        True
322
        >>> _is_generic(Callable)
323
        True
324
        >>> _is_generic(Callable[[int], int])
325
        True
326
        >>> _is_generic(Union)
327
        True
328
        >>> _is_generic(Union[int, float, str])
329
        True
330
        >>> _is_generic(Dict)
331
        True
332
        >>> _is_generic(Dict[str, str])
333
        True
334
    """
335

336
    if hasattr(typing, '_SpecialGenericAlias') and isinstance(cls, typing._SpecialGenericAlias):
4✔
337
        return True
4✔
338
    elif isinstance(cls, typing._GenericAlias):
4✔
339
        return True
4✔
340
    elif isinstance(cls, typing._SpecialForm):
4✔
341
        return cls not in {Any}
4✔
342
    elif cls is typing.Union or type(cls) is typing.Union:  # for python >= 3.14 Union is no longer a typing._SpecialForm
4✔
343
        return True
1✔
344
    return False
4✔
345

346

347
def _has_required_type_arguments(cls: Any) -> bool:
4✔
348
    """
349
        >>> from typing import List, Callable, Tuple, Any
350
        >>> _has_required_type_arguments(int)
351
        True
352
        >>> _has_required_type_arguments(List)
353
        False
354
        >>> _has_required_type_arguments(List[int])
355
        True
356
        >>> _has_required_type_arguments(List[List[int]])
357
        True
358
        >>> _has_required_type_arguments(Tuple)
359
        False
360
        >>> _has_required_type_arguments(Tuple[int])
361
        True
362
        >>> _has_required_type_arguments(Tuple[int, float, str])
363
        True
364
        >>> _has_required_type_arguments(Callable)
365
        False
366
        >>> _has_required_type_arguments(Callable[[int, float], Tuple[float, str]])
367
        True
368
        >>> _has_required_type_arguments(Callable[..., Any])
369
        True
370
        >>> _has_required_type_arguments(Callable[[typing.Any], Tuple[typing.Any, ...]],)
371
        True
372
    """
373

374
    base: str = _get_name(cls=cls)
4✔
375
    num_type_args = len(get_type_arguments(cls=cls))
4✔
376

377
    if base in NUM_OF_REQUIRED_TYPE_ARGS_EXACT:
4✔
378
        return NUM_OF_REQUIRED_TYPE_ARGS_EXACT[base] == num_type_args
4✔
379
    elif base in NUM_OF_REQUIRED_TYPE_ARGS_MIN:
4✔
380
        return NUM_OF_REQUIRED_TYPE_ARGS_MIN[base] <= num_type_args
4✔
381
    return True
4✔
382

383

384
def get_type_arguments(cls: Any) -> Tuple[Any, ...]:
4✔
385
    """ Works similar to typing.args()
386
        >>> from typing import Tuple, List, Union, Callable, Any, NewType, TypeVar, Optional, Awaitable, Coroutine
387
        >>> get_type_arguments(int)
388
        ()
389
        >>> get_type_arguments(List[float])
390
        (<class 'float'>,)
391
        >>> get_type_arguments(List[int])
392
        (<class 'int'>,)
393
        >>> UserId = NewType('UserId', int)
394
        >>> get_type_arguments(List[UserId])
395
        (pedantic.type_checking_logic.check_types.UserId,)
396
        >>> get_type_arguments(List)
397
        ()
398
        >>> T = TypeVar('T')
399
        >>> get_type_arguments(List[T])
400
        (~T,)
401
        >>> get_type_arguments(List[List[int]])
402
        (typing.List[int],)
403
        >>> get_type_arguments(List[List[List[int]]])
404
        (typing.List[typing.List[int]],)
405
        >>> get_type_arguments(List[Tuple[float, str]])
406
        (typing.Tuple[float, str],)
407
        >>> get_type_arguments(List[Tuple[Any, ...]])
408
        (typing.Tuple[typing.Any, ...],)
409
        >>> get_type_arguments(Union[str, float, int])
410
        (<class 'str'>, <class 'float'>, <class 'int'>)
411
        >>> get_type_arguments(Union[str, float, List[int], int])
412
        (<class 'str'>, <class 'float'>, typing.List[int], <class 'int'>)
413
        >>> get_type_arguments(Callable)
414
        ()
415
        >>> get_type_arguments(Callable[[int, float], Tuple[float, str]])
416
        ([<class 'int'>, <class 'float'>], typing.Tuple[float, str])
417
        >>> get_type_arguments(Callable[..., str])
418
        (Ellipsis, <class 'str'>)
419
        >>> get_type_arguments(Optional[int])
420
        (<class 'int'>, <class 'NoneType'>)
421
        >>> get_type_arguments(str | int)
422
        (<class 'str'>, <class 'int'>)
423
        >>> get_type_arguments(Awaitable[str])
424
        (<class 'str'>,)
425
        >>> get_type_arguments(Coroutine[int, bool, str])
426
        (<class 'int'>, <class 'bool'>, <class 'str'>)
427
    """
428

429
    result = ()
4✔
430

431
    if hasattr(cls, '__args__'):
4✔
432
        result = cls.__args__
4✔
433
        origin = get_base_generic(cls=cls)
4✔
434

435
        if origin != cls and \
4✔
436
                ((origin is Callable) or (origin is collections.abc.Callable)) and \
437
                result[0] is not Ellipsis:
438
            result = (list(result[:-1]), result[-1])
4✔
439

440
    result = result or ()
4✔
441

442
    if hasattr(types, 'UnionType') and isinstance(cls, types.UnionType):
4✔
443
        return result
4✔
444
    elif '[' in str(cls):
4✔
445
        return result
4✔
446

447
    return ()
4✔
448

449

450
def get_base_generic(cls: Any) -> Any:
4✔
451
    """
452
        >>> from typing import List, Union, Tuple, Callable, Dict, Set, Awaitable, Coroutine
453
        >>> get_base_generic(List)
454
        typing.List
455
        >>> get_base_generic(List[float])
456
        typing.List
457
        >>> get_base_generic(List[List[float]])
458
        typing.List
459
        >>> get_base_generic(List[Union[int, float]])
460
        typing.List
461
        >>> get_base_generic(Tuple)
462
        typing.Tuple
463
        >>> get_base_generic(Tuple[float, int])
464
        typing.Tuple
465
        >>> get_base_generic(Tuple[Union[int, float], str])
466
        typing.Tuple
467
        >>> get_base_generic(Callable[..., int])
468
        typing.Callable
469
        >>> get_base_generic(Callable[[Union[int, str], float], int])
470
        typing.Callable
471
        >>> get_base_generic(Dict)
472
        typing.Dict
473
        >>> get_base_generic(Dict[str, str])
474
        typing.Dict
475
        >>> 'typing.Union' in str(get_base_generic(Union))  # 3.13: typing.Union  3.14: <class 'typing.Union'>
476
        True
477
        >>> 'typing.Union' in str(get_base_generic(Union[float, int, str])) # 3.13: typing.Union  3.14: <class 'typing.Union'>
478
        True
479
        >>> get_base_generic(Set)
480
        typing.Set
481
        >>> get_base_generic(Set[int])
482
        typing.Set
483
        >>> get_base_generic(Awaitable[int])
484
        typing.Awaitable
485
        >>> get_base_generic(Coroutine[None, None, int])
486
        typing.Coroutine
487
    """
488

489
    origin = cls.__origin__ if hasattr(cls, '__origin__') else None
4✔
490
    name = cls._name if hasattr(cls, '_name') else None
4✔
491

492
    if name is not None:
4✔
493
        return getattr(typing, name)
4✔
494
    elif origin is not None and cls is not typing.Union:
4✔
495
        return origin
4✔
496
    return cls
4✔
497

498

499
def _is_subtype(sub_type: Any, super_type: Any, context: Dict[str, Any] = None) -> bool:
4✔
500
    """
501
        >>> from typing import Any, List, Callable, Tuple, Union, Optional, Iterable
502
        >>> _is_subtype(float, float)
503
        True
504
        >>> _is_subtype(int, float)
505
        False
506
        >>> _is_subtype(float, int)
507
        False
508
        >>> _is_subtype(int, Any)
509
        True
510
        >>> _is_subtype(Any, int)
511
        False
512
        >>> _is_subtype(Any, Any)
513
        True
514
        >>> _is_subtype(Ellipsis, Ellipsis)
515
        True
516
        >>> _is_subtype(Tuple[float, str], Tuple[float, str])
517
        True
518
        >>> _is_subtype(Tuple[float], Tuple[float, str])
519
        False
520
        >>> _is_subtype(Tuple[float, str], Tuple[str])
521
        False
522
        >>> _is_subtype(Tuple[float, str], Tuple[Any, ...])
523
        True
524
        >>> _is_subtype(Tuple[Any, ...], Tuple[float, str])
525
        False
526
        >>> _is_subtype(Tuple[float, str], Tuple[int, ...])
527
        False
528
        >>> _is_subtype(Tuple[int, str], Tuple[int, ...])
529
        True
530
        >>> _is_subtype(Tuple[int, ...], Tuple[int, str])
531
        False
532
        >>> _is_subtype(Tuple[float, str, bool, int], Tuple[Any, ...])
533
        True
534
        >>> _is_subtype(int, Union[int, float])
535
        True
536
        >>> _is_subtype(int, Union[str, float])
537
        False
538
        >>> _is_subtype(List[int], List[Union[int, float]])
539
        True
540
        >>> _is_subtype(List[Union[int, float]], List[int]) if sys.version_info >= (3, 14) else False
541
        False
542
        >>> class Parent: pass
543
        >>> class Child(Parent): pass
544
        >>> _is_subtype(List[Child], List[Parent])
545
        True
546
        >>> _is_subtype(List[Parent], List[Child])
547
        False
548
        >>> _is_subtype(List[int], Iterable[int])
549
        True
550
        >>> _is_subtype(Iterable[int], List[int])
551
        False
552
        >>> class MyClass: pass
553
        >>> _is_subtype(MyClass, Union[str, MyClass])
554
        True
555
        >>> _is_subtype(None, type(None))
556
        True
557
        >>> _is_subtype(None, Any)
558
        True
559
        >>> _is_subtype(Optional[int], Optional[int])
560
        True
561
        >>> _is_subtype(Optional[int], Union[int, float, None])
562
        True
563
        >>> _is_subtype(int | None, int | None)
564
        True
565
        >>> _is_subtype(int, int | None)
566
        True
567
        >>> _is_subtype(int | None, int)
568
        False
569
    """
570

571
    if sub_type is None:
4✔
572
        sub_type = type(None)
4✔
573

574
    python_sub = _get_class_of_type_annotation(sub_type)
4✔
575
    python_super = _get_class_of_type_annotation(super_type)
4✔
576

577
    if python_super is object:
4✔
578
        return True
4✔
579

580
    if python_super == typing.Union or isinstance(python_super, types.UnionType):
4✔
581
        type_args = get_type_arguments(cls=super_type)
4✔
582

583
        if python_sub == typing.Union or isinstance(python_sub, types.UnionType):
4✔
584
            sub_type_args = get_type_arguments(cls=sub_type)
4✔
585
            return all([x in type_args for x in sub_type_args])
4✔
586

587
        if any([type(ta) == _ProtocolMeta for ta in type_args]):
4✔
588
            return True  # shortcut
4✔
589

590
        return sub_type in type_args
4✔
591

592
    if not _is_generic(sub_type):
4✔
593
        try:
4✔
594
            return issubclass(python_sub, python_super)
4✔
595
        except TypeError:
4✔
596
            if type(python_super) == _ProtocolMeta:
4✔
597
                return True
4✔
598

599
            return False
3✔
600

601
    if not issubclass(python_sub, python_super):
4✔
602
        return False
4✔
603

604
    sub_args = get_type_arguments(cls=sub_type)
4✔
605
    super_args = get_type_arguments(cls=super_type)
4✔
606

607
    if len(sub_args) != len(super_args) and Ellipsis not in sub_args + super_args:
4✔
608
        return False
4✔
609

610
    return all(_is_subtype(sub_type=sub_arg, super_type=super_arg, context=context) for sub_arg, super_arg in zip(sub_args, super_args))
4✔
611

612

613
def _get_class_of_type_annotation(annotation: Any) -> Any:
4✔
614
    """
615
        >>> from typing import Dict, List, Any, Tuple, Callable, Union
616
        >>> _get_class_of_type_annotation(int)
617
        <class 'int'>
618
        >>> _get_class_of_type_annotation(Any)
619
        <class 'object'>
620
        >>> _get_class_of_type_annotation(Ellipsis)
621
        <class 'object'>
622
        >>> _get_class_of_type_annotation(Dict)
623
        <class 'dict'>
624
        >>> _get_class_of_type_annotation(Dict[str, int])
625
        <class 'dict'>
626
        >>> _get_class_of_type_annotation(List)
627
        <class 'list'>
628
        >>> _get_class_of_type_annotation(List[int])
629
        <class 'list'>
630
        >>> _get_class_of_type_annotation(Tuple)
631
        <class 'tuple'>
632
        >>> _get_class_of_type_annotation(Tuple[int, int])
633
        <class 'tuple'>
634
        >>> _get_class_of_type_annotation(Callable[[int], int])
635
        <class 'collections.abc.Callable'>
636
        >>> _get_class_of_type_annotation(Callable)
637
        <class 'collections.abc.Callable'>
638
    """
639

640
    if annotation in [Any, Ellipsis]:
4✔
641
        return object
4✔
642
    elif annotation.__module__ == 'typing' and annotation.__origin__ is not None:
4✔
643
        return annotation.__origin__
4✔
644

645
    return annotation
4✔
646

647

648
def _instancecheck_iterable(iterable: Iterable, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
649
    """
650
        >>> from typing import List, Any, Union
651
        >>> _instancecheck_iterable([1.0, -4.2, 5.4], (float,), {})
652
        True
653
        >>> _instancecheck_iterable([1.0, -4.2, 5], (float,), {})
654
        False
655
        >>> _instancecheck_iterable(['1.0', -4.2, 5], (Any,), {})
656
        True
657
        >>> _instancecheck_iterable(['1.0', -4.2, 5], (Union[int, float],), {})
658
        False
659
        >>> _instancecheck_iterable(['1.0', -4.2, 5], (Union[int, float, str],), {})
660
        True
661
        >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[int],), {})
662
        False
663
        >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[List[int]],), {})
664
        True
665
        >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[List[float]],), {})
666
        False
667
    """
668
    type_ = type_args[0]
4✔
669
    return all(_is_instance(val, type_, type_vars=type_vars, context=context) for val in iterable)
4✔
670

671

672
def _instancecheck_generator(generator: typing.Generator, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
673
    from pedantic.models import GeneratorWrapper
4✔
674

675
    assert isinstance(generator, GeneratorWrapper)
4✔
676
    return generator._yield_type == type_args[0] and generator._send_type == type_args[1] and generator._return_type == type_args[2]
4✔
677

678

679
def _instancecheck_mapping(mapping: Mapping, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
680
    """
681
        >>> from typing import Any, Optional
682
        >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3}, (int, Any), {})
683
        True
684
        >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3}, (int, int), {})
685
        True
686
        >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3.0}, (int, int), {})
687
        False
688
        >>> _instancecheck_mapping({0: 1, 1.0: 2, 2: 3}, (int, int), {})
689
        False
690
        >>> _instancecheck_mapping({0: '1', 1: 2, 2: 3}, (int, int), {})
691
        False
692
        >>> _instancecheck_mapping({0: 1, 1: 2, None: 3.0}, (int, int), {})
693
        False
694
        >>> _instancecheck_mapping({0: 1, 1: 2, None: 3.0}, (int, Optional[int]), {})
695
        False
696
    """
697
    return _instancecheck_items_view(mapping.items(), type_args, type_vars=type_vars, context=context)
4✔
698

699

700
def _instancecheck_items_view(items_view: ItemsView, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
701
    """
702
        >>> from typing import Any, Optional
703
        >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3}.items(), (int, Any), {})
704
        True
705
        >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3}.items(), (int, int), {})
706
        True
707
        >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3.0}.items(), (int, int), {})
708
        False
709
        >>> _instancecheck_items_view({0: 1, 1.0: 2, 2: 3}.items(), (int, int), {})
710
        False
711
        >>> _instancecheck_items_view({0: '1', 1: 2, 2: 3}.items(), (int, int), {})
712
        False
713
        >>> _instancecheck_items_view({0: 1, 1: 2, None: 3.0}.items(), (int, int), {})
714
        False
715
        >>> _instancecheck_items_view({0: 1, 1: 2, None: 3.0}.items(), (int, Optional[int]), {})
716
        False
717
    """
718
    key_type, value_type = type_args
4✔
719
    return all(_is_instance(obj=key, type_=key_type, type_vars=type_vars, context=context) and
4✔
720
               _is_instance(obj=val, type_=value_type, type_vars=type_vars, context=context)
721
               for key, val in items_view)
722

723

724
def _instancecheck_tuple(tup: Tuple, type_args: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
725
    """
726
        >>> from typing import Any
727
        >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(Any, Ellipsis), type_vars={})
728
        True
729
        >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(Any,), type_vars={})
730
        False
731
        >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, int, str, str), type_vars={})
732
        True
733
        >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, float, str, str), type_vars={})
734
        False
735
        >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, int, str, int), type_vars={})
736
        False
737
    """
738
    if Ellipsis in type_args:
4✔
739
        return all(_is_instance(obj=val, type_=type_args[0], type_vars=type_vars, context=context) for val in tup)
4✔
740

741
    if tup == () and type_args == ((),):
4✔
UNCOV
742
        return True
×
743

744
    if len(tup) != len(type_args):
4✔
745
        return False
4✔
746

747
    return all(_is_instance(obj=val, type_=type_, type_vars=type_vars, context=context) for val, type_ in zip(tup, type_args))
4✔
748

749

750
def _instancecheck_union(value: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
751
    """
752
        >>> from typing import Union, TypeVar, Any
753
        >>> NoneType = type(None)
754
        >>> _instancecheck_union(3.0, Union[int, float], {})
755
        True
756
        >>> _instancecheck_union(3, Union[int, float], {})
757
        True
758
        >>> _instancecheck_union('3', Union[int, float], {})
759
        False
760
        >>> _instancecheck_union(None, Union[int, NoneType], {})
761
        True
762
        >>> _instancecheck_union(None, Union[float, NoneType], {})
763
        True
764
        >>> S = TypeVar('S')
765
        >>> T = TypeVar('T')
766
        >>> U = TypeVar('U')
767
        >>> _instancecheck_union(42, Union[T, NoneType], {})
768
        True
769
        >>> _instancecheck_union(None, Union[T, NoneType], {})
770
        True
771
        >>> _instancecheck_union(None, Union[T, NoneType], {T: int})
772
        True
773
        >>> _instancecheck_union('None', Union[T, NoneType], {T: int})
774
        False
775
        >>> _instancecheck_union(42, Union[T, S], {})
776
        True
777
        >>> _instancecheck_union(42, Union[T, S], {T: int})
778
        True
779
        >>> _instancecheck_union(42, Union[T, S], {T: str})
780
        True
781
        >>> _instancecheck_union(42, Union[T, S], {T: int, S: float})
782
        True
783
        >>> _instancecheck_union(42, Union[T, S], {T: str, S: float})
784
        False
785
        >>> _instancecheck_union(42.8, Union[T, S], {T: str, S: float})
786
        True
787
        >>> _instancecheck_union(None, Union[T, S], {T: str, S: float})
788
        False
789
        >>> _instancecheck_union(None, Union[T, S], {})
790
        True
791
        >>> _instancecheck_union(None, Union[T, NoneType], {T: int})
792
        True
793
        >>> _instancecheck_union('None', Union[T, NoneType, S], {T: int})
794
        True
795
        >>> _instancecheck_union(42, Union[T, Any], {})
796
        True
797
        >>> _instancecheck_union(42, Union[T, Any], {T: float})
798
        True
799
        >>> _instancecheck_union(None, Optional[Callable[[float], float]], {})
800
        True
801
        >>> _instancecheck_union(3.0, int | float, {})
802
        True
803
        >>> _instancecheck_union(3, int | float, {})
804
        True
805
        >>> _instancecheck_union('3', int | float, {})
806
        False
807
        >>> _instancecheck_union(None, int | NoneType, {})
808
        True
809
        >>> _instancecheck_union(None, float | NoneType, {})
810
        True
811
        >>> S = TypeVar('S')
812
        >>> T = TypeVar('T')
813
        >>> U = TypeVar('U')
814
        >>> _instancecheck_union(42, T | NoneType, {})
815
        True
816
        >>> _instancecheck_union(None, T | NoneType, {})
817
        True
818
        >>> _instancecheck_union(None, T | NoneType, {T: int})
819
        True
820
        >>> _instancecheck_union('None', T | NoneType, {T: int})
821
        False
822
        >>> _instancecheck_union(42, T | S, {})
823
        True
824
        >>> _instancecheck_union(42, T | S, {T: int})
825
        True
826
        >>> _instancecheck_union(42, T | S, {T: str})
827
        True
828
        >>> _instancecheck_union(42, T | S, {T: int, S: float})
829
        True
830
        >>> _instancecheck_union(42, T | S, {T: str, S: float})
831
        False
832
        >>> _instancecheck_union(42.8, T | S, {T: str, S: float})
833
        True
834
        >>> _instancecheck_union(None, T | S, {T: str, S: float})
835
        False
836
        >>> _instancecheck_union(None, T | S, {})
837
        True
838
        >>> _instancecheck_union(None, T | NoneType, {T: int})
839
        True
840
        >>> _instancecheck_union('None', T | NoneType | S, {T: int})
841
        True
842
        >>> _instancecheck_union(42, T | Any, {})
843
        True
844
        >>> _instancecheck_union(42, T | Any, {T: float})
845
        True
846
        >>> _instancecheck_union(None, Optional[Callable[[float], float]], {})
847
        True
848
    """
849

850
    type_args = get_type_arguments(cls=type_)
4✔
851
    return _check_union(value=value, type_args=type_args, type_vars=type_vars, context=context)
4✔
852

853

854
def _check_union(value: Any, type_args: Tuple[Any, ...], type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
855
    args_non_type_vars = [type_arg for type_arg in type_args if not isinstance(type_arg, TypeVar)]
4✔
856
    args_type_vars = [type_arg for type_arg in type_args if isinstance(type_arg, TypeVar)]
4✔
857
    args_type_vars_bounded = [type_var for type_var in args_type_vars if type_var in type_vars]
4✔
858
    args_type_vars_unbounded = [type_var for type_var in args_type_vars if type_var not in args_type_vars_bounded]
4✔
859
    matches_non_type_var = any([_is_instance(obj=value, type_=typ, type_vars=type_vars, context=context) for typ in args_non_type_vars])
4✔
860

861
    if matches_non_type_var:
4✔
862
        return True
4✔
863

864
    for bounded_type_var in args_type_vars_bounded:
4✔
865
        try:
4✔
866
            _is_instance(obj=value, type_=bounded_type_var, type_vars=type_vars, context=context)
4✔
867
            return True
4✔
868
        except PedanticException:
4✔
869
            pass
4✔
870

871
    if not args_type_vars_unbounded:
4✔
872
        return False
4✔
873
    if len(args_type_vars_unbounded) == 1:
4✔
874
        return _is_instance(obj=value, type_=args_type_vars_unbounded[0], type_vars=type_vars, context=context)
4✔
875
    return True  # it is impossible to figure out, how to bound these type variables correctly
4✔
876

877

878
def _instancecheck_literal(value: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] = None) -> bool:
4✔
879
    type_args = get_type_arguments(cls=type_)
4✔
880
    return value in type_args
4✔
881

882

883
def _instancecheck_callable(value: Optional[Callable], type_: Any, _, context: Dict[str, Any] = None) -> bool:
4✔
884
    """
885
        >>> from typing import Tuple, Callable, Any
886
        >>> def f(x: int, y: float) -> Tuple[float, str]:
887
        ...       return float(x), str(y)
888
        >>> _instancecheck_callable(f, Callable[[int, float], Tuple[float, str]], {})
889
        True
890
        >>> _instancecheck_callable(f, Callable[[int, float], Tuple[int, str]], {})
891
        False
892
        >>> _instancecheck_callable(f, Callable[[int, int], Tuple[float, str]], {})
893
        False
894
        >>> _instancecheck_callable(f, Callable[..., Tuple[float, str]], {})
895
        True
896
        >>> _instancecheck_callable(f, Callable[..., Tuple[int, str]], {})
897
        False
898
        >>> _instancecheck_callable(f, Callable[..., Any], {})
899
        True
900
        >>> _instancecheck_callable(f, Callable[[int, int, int], Tuple[float, str]], {})
901
        False
902
        >>> _instancecheck_callable(None, Callable[..., Any], {})
903
        False
904
    """
905

906
    if value is None:
4✔
907
        return False
4✔
908

909
    if _is_lambda(obj=value):
4✔
910
        return True
4✔
911

912
    param_types, ret_type = get_type_arguments(cls=type_)
4✔
913

914
    try:
4✔
915
        sig = inspect.signature(obj=value)
4✔
916
    except TypeError:
4✔
917
        return False
4✔
918

919
    non_optional_params = {k: v for k, v in sig.parameters.items() if v.default == sig.empty}
4✔
920

921
    if param_types is not Ellipsis:
4✔
922
        if len(param_types) != len(non_optional_params):
4✔
923
            return False
4✔
924

925
        for param, expected_type in zip(sig.parameters.values(), param_types):
4✔
926
            if not _is_subtype(sub_type=param.annotation, super_type=expected_type):
4✔
927
                return False
4✔
928

929
    if not inspect.iscoroutinefunction(value):
4✔
930
        return _is_subtype(sub_type=sig.return_annotation, super_type=ret_type)
4✔
931

932
    base = get_base_generic(ret_type)
4✔
933

934
    if base == typing.Awaitable:
4✔
935
        arg = get_type_arguments(ret_type)[0]
4✔
936
    elif base == typing.Coroutine:
4✔
937
        arg = get_type_arguments(ret_type)[2]
4✔
938
    else:
939
        return False
4✔
940

941
    return _is_subtype(sub_type=sig.return_annotation, super_type=arg)
4✔
942

943

944
def _is_lambda(obj: Any) -> bool:
4✔
945
    return callable(obj) and obj.__name__ == '<lambda>'
4✔
946

947

948
def _instancecheck_type(value: Any, type_: Any, type_vars: Dict, context: Dict[str, Any] = None) -> bool:
4✔
949
    type_ = type_[0]
4✔
950

951
    if type_ == Any or isinstance(type_, typing.TypeVar):
4✔
952
        return True
4✔
953

954
    return _is_subtype(sub_type=value, super_type=type_, context=context)
4✔
955

956

957
_ORIGIN_TYPE_CHECKERS = {}
4✔
958
for class_path, _check_func in {
4✔
959
    'typing.Container': _instancecheck_iterable,
960
    'typing.Collection': _instancecheck_iterable,
961
    'typing.AbstractSet': _instancecheck_iterable,
962
    'typing.MutableSet': _instancecheck_iterable,
963
    'typing.Sequence': _instancecheck_iterable,
964
    'typing.Iterable': _instancecheck_iterable,
965
    'typing.MutableSequence': _instancecheck_iterable,
966
    'typing.Deque': _instancecheck_iterable,
967
    'typing.List': _instancecheck_iterable,
968
    'typing.Set': _instancecheck_iterable,
969
    'typing.FrozenSet': _instancecheck_iterable,
970
    'typing.KeysView': _instancecheck_iterable,
971
    'typing.ValuesView': _instancecheck_iterable,
972
    'typing.AsyncIterable': _instancecheck_iterable,
973

974
    'typing.Mapping': _instancecheck_mapping,
975
    'typing.MutableMapping': _instancecheck_mapping,
976
    'typing.MappingView': _instancecheck_mapping,
977
    'typing.ItemsView': _instancecheck_items_view,
978
    'typing.Dict': _instancecheck_mapping,
979
    'typing.DefaultDict': _instancecheck_mapping,
980
    'typing.Counter': _instancecheck_mapping,
981
    'typing.ChainMap': _instancecheck_mapping,
982

983
    'typing.Tuple': _instancecheck_tuple,
984
    'typing.Type': _instancecheck_type,
985

986
    'typing.Generator': _instancecheck_generator,
987
}.items():
988
    class_ = eval(class_path)
4✔
989
    _ORIGIN_TYPE_CHECKERS[class_] = _check_func
4✔
990

991
_SPECIAL_INSTANCE_CHECKERS = {
4✔
992
    'Union': _instancecheck_union,
993
    'Optional': _instancecheck_union,
994
    'Literal': _instancecheck_literal,
995
    'Callable': _instancecheck_callable,
996
    'Any': lambda v, t, tv, c: True,
997
}
998

999
NUM_OF_REQUIRED_TYPE_ARGS_EXACT = {
4✔
1000
    'Callable': 2,
1001
    'List': 1,
1002
    'Set': 1,
1003
    'FrozenSet': 1,
1004
    'Iterable': 1,
1005
    'Sequence': 1,
1006
    'Dict': 2,
1007
    'Optional': 2,  # because _get_type_arguments(Optional[int]) returns (int, None)
1008
}
1009
NUM_OF_REQUIRED_TYPE_ARGS_MIN = {
4✔
1010
    'Tuple': 1,
1011
    'Union': 2,
1012
}
1013

1014

1015
def convert_to_typing_types(x: typing.Type) -> typing.Type:
4✔
1016
    """
1017
        Example:
1018
        >>> convert_to_typing_types(int)
1019
        <class 'int'>
1020
        >>> convert_to_typing_types(list)
1021
        Traceback (most recent call last):
1022
        ...
1023
        ValueError: Missing type arguments
1024
        >>> convert_to_typing_types(list[int])
1025
        typing.List[int]
1026
        >>> convert_to_typing_types(set[int])
1027
        typing.Set[int]
1028
        >>> convert_to_typing_types(frozenset[int])
1029
        typing.FrozenSet[int]
1030
        >>> convert_to_typing_types(tuple[int])
1031
        typing.Tuple[int]
1032
        >>> convert_to_typing_types(type[int])
1033
        typing.Type[int]
1034
        >>> convert_to_typing_types(type[int | float])
1035
        typing.Type[int | float]
1036
        >>> convert_to_typing_types(tuple[int, float])
1037
        typing.Tuple[int, float]
1038
        >>> convert_to_typing_types(dict[int, float])
1039
        typing.Dict[int, float]
1040
        >>> convert_to_typing_types(list[dict[int, float]])
1041
        typing.List[typing.Dict[int, float]]
1042
        >>> convert_to_typing_types(list[dict[int, tuple[float, str]]])
1043
        typing.List[typing.Dict[int, typing.Tuple[float, str]]]
1044
    """
1045

1046
    if x in {list, set, dict, frozenset, tuple, type}:
4✔
1047
        raise ValueError('Missing type arguments')
4✔
1048

1049
    if not hasattr(x, '__origin__'):
4✔
1050
        return x
4✔
1051

1052
    origin = x.__origin__  # type: ignore # checked above
4✔
1053
    args = [convert_to_typing_types(a) for a in x.__args__]  # type: ignore
4✔
1054

1055
    if origin is list:
4✔
1056
        return typing.List[tuple(args)]
4✔
1057
    elif origin is set:
4✔
1058
        return typing.Set[tuple(args)]
4✔
1059
    elif origin is dict:
4✔
1060
        return typing.Dict[tuple(args)]
4✔
1061
    elif origin is tuple:
4✔
1062
        return typing.Tuple[tuple(args)]
4✔
1063
    elif origin is frozenset:
4✔
1064
        return typing.FrozenSet[tuple(args)]
4✔
1065
    elif origin is type:
4✔
1066
        return typing.Type[tuple(args)]
4✔
1067
    elif origin is typing.Union:
1!
1068
        return x
1✔
1069

UNCOV
1070
    raise RuntimeError(x)
×
1071

1072

1073
if __name__ == "__main__":
1074
    import doctest
1075
    doctest.testmod(verbose=False, optionflags=doctest.ELLIPSIS)
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