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

LostInDarkMath / pedantic-python-decorators / 25822835878

13 May 2026 07:53PM UTC coverage: 99.637% (+0.5%) from 99.14%
25822835878

Pull #115

github

LostInDarkMath
refactor @trace
Pull Request #115: V3: Huge refactoring

274 of 274 branches covered (100.0%)

Branch coverage included in aggregate %.

474 of 478 new or added lines in 49 files covered. (99.16%)

1 existing line in 1 file now uncovered.

1649 of 1656 relevant lines covered (99.58%)

3.98 hits per line

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

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

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

14
# ruff: noqa: UP006, UP035
15

16
def assert_value_matches_type(  # noqa: PLR0913
4✔
17
    value: Any,
18
    type_: Any,
19
    err: str,
20
    type_vars: Dict[TypeVar_, Any],
21
    key: str | None = None,
22
    msg: str | None = None,
23
    context: Dict[str, Any] | None = None,
24
) -> None:
25
    """Checks that the given value matches the given type."""
26

27
    if not _check_type(value=value, type_=type_, err=err, type_vars=type_vars, context=context):
4✔
28
        t = type(value)
4✔
29
        value = f'{key}={value}' if key is not None else str(value)
4✔
30

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

34
        raise PedanticTypeCheckException(msg)
4✔
35

36

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

110
    if type_ is None:
4✔
111
        return value == type_
4✔
112
    if isinstance(type_, str):
4✔
113
        class_name = value.__class__.__name__
4✔
114
        base_class_name = value.__class__.__base__.__name__
4✔
115
        return type_ in (class_name, base_class_name)
4✔
116

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

128

129
def _is_instance(obj: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None) -> bool:  # noqa: PLR0915, PLR0912, PLR0911, C901
4✔
130
    context = context or {}
4✔
131

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

137
    if type_.__module__ == 'typing':
4✔
138
        if _is_generic(type_):
4✔
139
            origin = get_base_generic(type_)
4✔
140
        else:
141
            origin = type_
4✔
142

143
        name = _get_name(origin)
4✔
144

145
        if name in _SPECIAL_INSTANCE_CHECKERS:
4✔
146
            validator = _SPECIAL_INSTANCE_CHECKERS[name]
4✔
147
            return validator(obj, type_, type_vars, context)
4✔
148

149
    if type_.__module__ == 'collections.abc':
4✔
150
        if _is_generic(type_):
4✔
151
            origin = get_base_generic(type_)
4✔
152
        else:
NEW
153
            origin = type_
×
154

155
        name = _get_name(origin)
4✔
156

157
        if name in _SPECIAL_INSTANCE_CHECKERS:
4✔
158
            validator = _SPECIAL_INSTANCE_CHECKERS[name]
4✔
159
            return validator(obj, type_, type_vars, context)
4✔
160

161
    if hasattr(types, 'UnionType') and isinstance(type_, types.UnionType):
4✔
162
        return _instancecheck_union(value=obj, type_=type_, type_vars=type_vars, context=context)
3✔
163

164
    if type_ == typing.BinaryIO:
4✔
165
        return isinstance(obj, (BytesIO, BufferedWriter))
4✔
166
    if type_ == typing.TextIO:
4✔
167
        return isinstance(obj, (StringIO, TextIOWrapper))
4✔
168
    if type_ == typing.NoReturn:
4✔
169
        return False  # we expect an Exception here, but got a value
4✔
170
    if hasattr(typing, 'Never') and type_ == typing.Never:  # since Python 3.11
4✔
171
        return False  # we expect an Exception here, but got a value
4✔
172
    if hasattr(typing, 'LiteralString') and type_ == typing.LiteralString:  # since Python 3.11
4✔
173
        return isinstance(obj, str)  # we cannot distinguish str and LiteralString at runtime
4✔
174
    if hasattr(typing, 'Self') and type_ == typing.Self:  # since Python 3.11
4✔
175
        t = type_vars[TYPE_VAR_SELF]
4✔
176

177
        if t is None:
4✔
178
            return False  # the case if a function outside a class was type hinted with self
4✔
179

180
        return _is_instance(obj=obj, type_=t, type_vars=type_vars, context=context)
4✔
181

182
    if isinstance(type_, TypeVar):
4✔
183
        constraints = type_.__constraints__
4✔
184

185
        if len(constraints) > 0 and type(obj) not in constraints:
4✔
186
            return False
4✔
187

188
        if _is_forward_ref(type_=type_.__bound__):
4✔
189
            resolved = resolve_forward_ref(type_.__bound__.__forward_arg__, context=context)
4✔
190
            return _is_instance(obj=obj, type_=resolved, type_vars=type_vars, context=context)
4✔
191

192
        if type_.__bound__ is not None and not isinstance(obj, type_.__bound__):
4✔
193
            return False
4✔
194

195
        if type_ in type_vars:
4✔
196
            other = type_vars[type_]
4✔
197

198
            if type_.__contravariant__:
4✔
199
                if not _is_subtype(sub_type=other, super_type=obj.__class__):
4✔
200
                    raise PedanticTypeVarMismatchException(
4✔
201
                        f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} '
202
                        f'but TypeVar {type_} was previously matched to type {other}')
203
            elif not _is_instance(obj=obj, type_=other, type_vars=type_vars, context=context):
4✔
204
                raise PedanticTypeVarMismatchException(
4✔
205
                    f'For TypeVar {type_} exists a type conflict: value {obj} has type {type(obj)} '
206
                    f'but TypeVar {type_} was previously matched to type {other}')
207

208
        type_vars[type_] = type(obj)
4✔
209
        return True
4✔
210

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

214
    if _is_generic(type_) and hasattr(type_, '__origin__'):
4✔
215
        if isinstance(type_, types.GenericAlias):
4✔
216
            return _is_instance(obj=obj, type_=convert_to_typing_types(type_), type_vars=type_vars, context=context)
4✔
217

218
        python_type = type_.__origin__
4✔
219

220
        if not isinstance(obj, python_type):
4✔
221
            return False
4✔
222

223
        base = get_base_generic(type_)
4✔
224
        type_args = get_type_arguments(cls=type_)
4✔
225

226
        if base in _ORIGIN_TYPE_CHECKERS:
4✔
227
            validator = _ORIGIN_TYPE_CHECKERS[base]
4✔
228
            return validator(obj, type_args, type_vars, context)
4✔
229

230
        if base.__base__ != typing.Generic:
4✔
NEW
231
            raise RuntimeError(f'Unknown base: {base}')
×
232
        return isinstance(obj, base)
4✔
233

234
    if _is_forward_ref(type_=type_):
4✔
235
        resolved = resolve_forward_ref(type_.__forward_arg__, context=context)
4✔
236
        return _is_instance(obj=obj, type_=resolved, type_vars=type_vars, context=context)
4✔
237

238
    if _is_type_new_type(type_):
4✔
239
        return isinstance(obj, type_.__supertype__)
4✔
240

241
    if hasattr(obj, '_asdict'):
4✔
242
        if hasattr(type_, '__annotations__'):
4✔
243
            field_types = type_.__annotations__
4✔
244
        else:
245
            return False
4✔
246

247
        if obj._asdict().keys() != field_types.keys():
4✔
248
            return False
4✔
249

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

253
    if type_ in {list, set, dict, frozenset, tuple, type}:
4✔
254
        raise PedanticTypeCheckException('Missing type arguments')
4✔
255

256
    if isinstance(type_, types.GenericAlias):
4✔
UNCOV
257
        return _is_instance(obj=obj, type_=convert_to_typing_types(type_), type_vars=type_vars, context=context)
×
258

259
    try:
4✔
260
        return isinstance(obj, type_)
4✔
261
    except TypeError:
4✔
262
        if isinstance(type_, _ProtocolMeta):
4✔
263
            return True  # we do not check this
4✔
264

265
        raise
×
266

267

268
def _is_forward_ref(type_: Any) -> bool:
4✔
269
    return (hasattr(typing, 'ForwardRef') and isinstance(type_, typing.ForwardRef)) or \
4✔
270
        (hasattr(typing, '_ForwardRef') and isinstance(type_, typing._ForwardRef))  # noqa: SLF001
271

272

273
def _is_type_new_type(type_: Any) -> bool:
4✔
274
    """
275
    >>> from typing import Tuple, Callable, Any, List, NewType
276
    >>> _is_type_new_type(int)
277
    False
278
    >>> UserId = NewType('UserId', int)
279
    >>> _is_type_new_type(UserId)
280
    True
281
    """
282

283
    if isinstance(type_, typing.NewType):
4✔
284
        return True
4✔
285

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

288

289
def _get_name(cls: Any) -> str:
4✔
290
    """
291
    >>> from typing import Tuple, Callable, Any, List
292
    >>> _get_name(int)
293
    'int'
294
    >>> _get_name(Any)
295
    'Any'
296
    >>> _get_name(List)
297
    'List'
298
    >>> _get_name(List[int])
299
    'List'
300
    >>> _get_name(List[Any])
301
    'List'
302
    >>> _get_name(Tuple)
303
    'Tuple'
304
    >>> _get_name(Tuple[int, float])
305
    'Tuple'
306
    >>> _get_name(Tuple[Any, ...])
307
    'Tuple'
308
    >>> _get_name(Callable)
309
    'Callable'
310
    """
311
    if hasattr(cls, '_name'):
4✔
312
        return cls._name
4✔
313
    if hasattr(cls, '__name__'):
4✔
314
        return cls.__name__
4✔
315

316
    return type(cls).__name__[1:]
4✔
317

318

319
def _is_generic(cls: Any) -> bool:
4✔
320
    """
321
    >>> from typing import  List, Callable, Any, Union, Generic, TypeVar
322
    >>> from collections.abc import Callable as ColCallable
323
    >>> _is_generic(int)
324
    False
325
    >>> _is_generic(List)
326
    True
327
    >>> _is_generic(list)
328
    True
329
    >>> _is_generic(List[int])
330
    True
331
    >>> _is_generic(list[int])
332
    True
333
    >>> _is_generic(List[Any])
334
    True
335
    >>> _is_generic(List[List[int]])
336
    True
337
    >>> _is_generic(Any)
338
    False
339
    >>> _is_generic(Tuple)
340
    True
341
    >>> _is_generic(Tuple[Any, ...])
342
    True
343
    >>> _is_generic(Tuple[str, float, int])
344
    True
345
    >>> _is_generic(Callable)
346
    True
347
    >>> _is_generic(ColCallable)
348
    True
349
    >>> _is_generic(Callable[[int], int])
350
    True
351
    >>> _is_generic(ColCallable[[int], int])
352
    True
353
    >>> _is_generic(Union)
354
    True
355
    >>> _is_generic(Union[int, float, str])
356
    True
357
    >>> _is_generic(Dict)
358
    True
359
    >>> _is_generic(Dict[str, str])
360
    True
361
    >>> T = TypeVar("T")
362
    >>> class A(Generic[T]): pass
363
    >>> class B(A[int]): pass
364
    >>> _is_generic(A)
365
    True
366
    >>> _is_generic(B)
367
    True
368
    """
369

370
    origin = typing.get_origin(cls)
4✔
371

372
    # Parameterized generics like List[int], Callable[[...], ...], etc.
373
    if origin is not None:
4✔
374
        return True
4✔
375

376
    # Bare generics like List, Dict, Tuple, Callable, Union
377
    if isinstance(cls, typing._SpecialForm):  # noqa: SLF001 still needed for e.g. Union
4✔
378
        return cls is not Any
4✔
379

380
    # collections.abc.Callable and similar
381
    if hasattr(cls, '__class_getitem__'):  # noqa: SIM103
4✔
382
        return True
4✔
383

384
    return False
4✔
385

386

387
def _has_required_type_arguments(cls: Any) -> bool:
4✔
388
    """
389
    >>> from typing import List, Callable, Tuple, Any
390
    >>> _has_required_type_arguments(int)
391
    True
392
    >>> _has_required_type_arguments(List)
393
    False
394
    >>> _has_required_type_arguments(List[int])
395
    True
396
    >>> _has_required_type_arguments(List[List[int]])
397
    True
398
    >>> _has_required_type_arguments(Tuple)
399
    False
400
    >>> _has_required_type_arguments(Tuple[int])
401
    True
402
    >>> _has_required_type_arguments(Tuple[int, float, str])
403
    True
404
    >>> _has_required_type_arguments(Callable)
405
    False
406
    >>> _has_required_type_arguments(Callable[[int, float], Tuple[float, str]])
407
    True
408
    >>> _has_required_type_arguments(Callable[..., Any])
409
    True
410
    >>> _has_required_type_arguments(Callable[[typing.Any], Tuple[typing.Any, ...]],)
411
    True
412
    """
413

414
    base: str = _get_name(cls=cls)
4✔
415
    num_type_args = len(get_type_arguments(cls=cls))
4✔
416

417
    if base in NUM_OF_REQUIRED_TYPE_ARGS_EXACT:
4✔
418
        return NUM_OF_REQUIRED_TYPE_ARGS_EXACT[base] == num_type_args
4✔
419
    if base in NUM_OF_REQUIRED_TYPE_ARGS_MIN:
4✔
420
        return NUM_OF_REQUIRED_TYPE_ARGS_MIN[base] <= num_type_args
4✔
421
    return True
4✔
422

423

424
def get_type_arguments(cls: Any) -> Tuple[Any, ...]:
4✔
425
    """
426
    Works similar to typing.args()
427
    >>> from typing import Tuple, List, Union, Callable, Any, NewType, TypeVar, Optional, Awaitable, Coroutine
428
    >>> get_type_arguments(int)
429
    ()
430
    >>> get_type_arguments(List[float])
431
    (<class 'float'>,)
432
    >>> get_type_arguments(List[int])
433
    (<class 'int'>,)
434
    >>> UserId = NewType('UserId', int)
435
    >>> get_type_arguments(List[UserId])
436
    (pedantic.type_checking_logic.check_types.UserId,)
437
    >>> get_type_arguments(List)
438
    ()
439
    >>> T = TypeVar('T')
440
    >>> get_type_arguments(List[T])
441
    (~T,)
442
    >>> get_type_arguments(List[List[int]])
443
    (typing.List[int],)
444
    >>> get_type_arguments(List[List[List[int]]])
445
    (typing.List[typing.List[int]],)
446
    >>> get_type_arguments(List[Tuple[float, str]])
447
    (typing.Tuple[float, str],)
448
    >>> get_type_arguments(List[Tuple[Any, ...]])
449
    (typing.Tuple[typing.Any, ...],)
450
    >>> get_type_arguments(Union[str, float, int])
451
    (<class 'str'>, <class 'float'>, <class 'int'>)
452
    >>> get_type_arguments(Union[str, float, List[int], int])
453
    (<class 'str'>, <class 'float'>, typing.List[int], <class 'int'>)
454
    >>> get_type_arguments(Callable)
455
    ()
456
    >>> get_type_arguments(Callable[[int, float], Tuple[float, str]])
457
    ([<class 'int'>, <class 'float'>], typing.Tuple[float, str])
458
    >>> get_type_arguments(Callable[..., str])
459
    (Ellipsis, <class 'str'>)
460
    >>> get_type_arguments(Optional[int])
461
    (<class 'int'>, <class 'NoneType'>)
462
    >>> get_type_arguments(str | int)
463
    (<class 'str'>, <class 'int'>)
464
    >>> get_type_arguments(Awaitable[str])
465
    (<class 'str'>,)
466
    >>> get_type_arguments(Coroutine[int, bool, str])
467
    (<class 'int'>, <class 'bool'>, <class 'str'>)
468
    """
469

470
    result = ()
4✔
471

472
    if hasattr(cls, '__args__'):
4✔
473
        result = cls.__args__
4✔
474
        origin = get_base_generic(cls=cls)
4✔
475

476
        if origin != cls and \
4✔
477
                ((origin is Callable) or (origin is collections.abc.Callable)) and \
478
                result[0] is not Ellipsis:
479
            result = (list(result[:-1]), result[-1])
4✔
480

481
    result = result or ()
4✔
482

483
    if hasattr(types, 'UnionType') and isinstance(cls, types.UnionType):
4✔
484
        return result
4✔
485
    if '[' in str(cls):
4✔
486
        return result
4✔
487

488
    return ()
4✔
489

490

491
def get_base_generic(cls: Any) -> Any:
4✔
492
    """
493
    >>> from typing import List, Union, Tuple, Callable, Dict, Set, Awaitable, Coroutine
494
    >>> get_base_generic(List)
495
    typing.List
496
    >>> get_base_generic(List[float])
497
    typing.List
498
    >>> get_base_generic(List[List[float]])
499
    typing.List
500
    >>> get_base_generic(List[Union[int, float]])
501
    typing.List
502
    >>> get_base_generic(Tuple)
503
    typing.Tuple
504
    >>> get_base_generic(Tuple[float, int])
505
    typing.Tuple
506
    >>> get_base_generic(Tuple[Union[int, float], str])
507
    typing.Tuple
508
    >>> get_base_generic(Callable[..., int])
509
    typing.Callable
510
    >>> get_base_generic(Callable[[Union[int, str], float], int])
511
    typing.Callable
512
    >>> get_base_generic(Dict)
513
    typing.Dict
514
    >>> get_base_generic(Dict[str, str])
515
    typing.Dict
516
    >>> 'typing.Union' in str(get_base_generic(Union))  # 3.13: typing.Union  3.14: <class 'typing.Union'>
517
    True
518
    >>> 'typing.Union' in str(get_base_generic(Union[float, int, str])) # 3.13: typing.Union 3.14:<class 'typing.Union'>
519
    True
520
    >>> get_base_generic(Set)
521
    typing.Set
522
    >>> get_base_generic(Set[int])
523
    typing.Set
524
    >>> get_base_generic(Awaitable[int])
525
    typing.Awaitable
526
    >>> get_base_generic(Coroutine[None, None, int])
527
    typing.Coroutine
528
    """
529

530
    origin = cls.__origin__ if hasattr(cls, '__origin__') else None
4✔
531
    name = cls._name if hasattr(cls, '_name') else None
4✔
532

533
    if name is not None:
4✔
534
        return getattr(typing, name)
4✔
535
    if origin is not None and cls is not typing.Union:
4✔
536
        return origin
4✔
537
    return cls
4✔
538

539

540
def _is_subtype(sub_type: Any, super_type: Any, context: Dict[str, Any] | None = None) -> bool:  # noqa: PLR0911
4✔
541
    """
542
    Examples:
543
    >>> import sys
544
    >>> from typing import Any, List, Callable, Tuple, Union, Optional, Iterable
545
    >>> _is_subtype(float, float)
546
    True
547
    >>> _is_subtype(int, float)
548
    False
549
    >>> _is_subtype(float, int)
550
    False
551
    >>> _is_subtype(int, Any)
552
    True
553
    >>> _is_subtype(Any, int)
554
    False
555
    >>> _is_subtype(Any, Any)
556
    True
557
    >>> _is_subtype(Ellipsis, Ellipsis)
558
    True
559
    >>> _is_subtype(Tuple[float, str], Tuple[float, str])
560
    True
561
    >>> _is_subtype(Tuple[float], Tuple[float, str])
562
    False
563
    >>> _is_subtype(Tuple[float, str], Tuple[str])
564
    False
565
    >>> _is_subtype(Tuple[float, str], Tuple[Any, ...])
566
    True
567
    >>> _is_subtype(Tuple[Any, ...], Tuple[float, str])
568
    False
569
    >>> _is_subtype(Tuple[float, str], Tuple[int, ...])
570
    False
571
    >>> _is_subtype(Tuple[int, str], Tuple[int, ...])
572
    True
573
    >>> _is_subtype(Tuple[int, ...], Tuple[int, str])
574
    False
575
    >>> _is_subtype(Tuple[float, str, bool, int], Tuple[Any, ...])
576
    True
577
    >>> _is_subtype(int, Union[int, float])
578
    True
579
    >>> _is_subtype(int, Union[str, float])
580
    False
581
    >>> _is_subtype(List[int], List[Union[int, float]])
582
    True
583
    >>> _is_subtype(List[Union[int, float]], List[int]) if sys.version_info >= (3, 14) else False
584
    False
585
    >>> class Parent: pass
586
    >>> class Child(Parent): pass
587
    >>> _is_subtype(List[Child], List[Parent])
588
    True
589
    >>> _is_subtype(List[Parent], List[Child])
590
    False
591
    >>> _is_subtype(List[int], Iterable[int])
592
    True
593
    >>> _is_subtype(Iterable[int], List[int])
594
    False
595
    >>> class MyClass: pass
596
    >>> _is_subtype(MyClass, Union[str, MyClass])
597
    True
598
    >>> _is_subtype(None, type(None))
599
    True
600
    >>> _is_subtype(None, Any)
601
    True
602
    >>> _is_subtype(Optional[int], Optional[int])
603
    True
604
    >>> _is_subtype(Optional[int], Union[int, float, None])
605
    True
606
    >>> _is_subtype(int | None, int | None)
607
    True
608
    >>> _is_subtype(int, int | None)
609
    True
610
    >>> _is_subtype(int | None, int)
611
    False
612
    >>> _is_subtype(Optional[int], int)
613
    False
614
    """
615

616
    if sub_type is None:
4✔
617
        sub_type = type(None)
4✔
618

619
    python_sub = _get_class_of_type_annotation(sub_type)
4✔
620
    python_super = _get_class_of_type_annotation(super_type)
4✔
621

622
    if python_super is object:
4✔
623
        return True
4✔
624

625
    if python_super == typing.Union or isinstance(python_super, types.UnionType):
4✔
626
        type_args = get_type_arguments(cls=super_type)
4✔
627

628
        if python_sub == typing.Union or isinstance(python_sub, types.UnionType):
4✔
629
            sub_type_args = get_type_arguments(cls=sub_type)
4✔
630
            return all(x in type_args for x in sub_type_args)
4✔
631

632
        if any(isinstance(ta, _ProtocolMeta) for ta in type_args):
4✔
633
            return True  # shortcut
4✔
634

635
        return sub_type in type_args
4✔
636

637
    if not _is_generic(sub_type):
4✔
638
        try:
4✔
639
            return issubclass(python_sub, python_super)
4✔
640
        except TypeError:
4✔
641
            return isinstance(python_super, _ProtocolMeta)
4✔
642

643

644

645
    sub_args = get_type_arguments(cls=sub_type)
4✔
646
    super_args = get_type_arguments(cls=super_type)
4✔
647

648
    if len(sub_args) != len(super_args) and Ellipsis not in sub_args + super_args:
4✔
649
        return False
4✔
650

651
    if not issubclass(python_sub, python_super):
4✔
652
        return False
4✔
653

654
    return all(_is_subtype(sub_type=sub_arg, super_type=super_arg, context=context)
4✔
655
               for sub_arg, super_arg in zip(sub_args, super_args, strict=False))
656

657

658
def _get_class_of_type_annotation(annotation: Any) -> Any:
4✔
659
    """
660
    >>> from typing import Dict, List, Any, Tuple, Callable, Union
661
    >>> _get_class_of_type_annotation(int)
662
    <class 'int'>
663
    >>> _get_class_of_type_annotation(Any)
664
    <class 'object'>
665
    >>> _get_class_of_type_annotation(Ellipsis)
666
    <class 'object'>
667
    >>> _get_class_of_type_annotation(Dict)
668
    <class 'dict'>
669
    >>> _get_class_of_type_annotation(Dict[str, int])
670
    <class 'dict'>
671
    >>> _get_class_of_type_annotation(List)
672
    <class 'list'>
673
    >>> _get_class_of_type_annotation(List[int])
674
    <class 'list'>
675
    >>> _get_class_of_type_annotation(Tuple)
676
    <class 'tuple'>
677
    >>> _get_class_of_type_annotation(Tuple[int, int])
678
    <class 'tuple'>
679
    >>> _get_class_of_type_annotation(Callable[[int], int])
680
    <class 'collections.abc.Callable'>
681
    >>> _get_class_of_type_annotation(Callable)
682
    <class 'collections.abc.Callable'>
683
    """
684

685
    if annotation in [Any, Ellipsis]:
4✔
686
        return object
4✔
687
    if annotation.__module__ == 'typing' and annotation.__origin__ is not None:
4✔
688
        return annotation.__origin__
4✔
689

690
    return annotation
4✔
691

692

693
def _instancecheck_iterable(
4✔
694
    iterable: Iterable, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
695
) -> bool:
696
    """
697
    >>> from typing import List, Any, Union
698
    >>> _instancecheck_iterable([1.0, -4.2, 5.4], (float,), {})
699
    True
700
    >>> _instancecheck_iterable([1.0, -4.2, 5], (float,), {})
701
    False
702
    >>> _instancecheck_iterable(['1.0', -4.2, 5], (Any,), {})
703
    True
704
    >>> _instancecheck_iterable(['1.0', -4.2, 5], (Union[int, float],), {})
705
    False
706
    >>> _instancecheck_iterable(['1.0', -4.2, 5], (Union[int, float, str],), {})
707
    True
708
    >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[int],), {})
709
    False
710
    >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[List[int]],), {})
711
    True
712
    >>> _instancecheck_iterable([[], [], [[42]], [[]]], (List[List[float]],), {})
713
    False
714
    """
715
    type_ = type_args[0]
4✔
716
    return all(_is_instance(val, type_, type_vars=type_vars, context=context) for val in iterable)
4✔
717

718

719
def _instancecheck_generator(
4✔
720
    generator: typing.Generator, type_args: Tuple, _: Dict[TypeVar_, Any], __: Dict[str, Any] | None = None,
721
) -> bool:
722
    from pedantic.models import GeneratorWrapper  # noqa: PLC0415 must be local due to circular imports
4✔
723
    if not isinstance(generator, GeneratorWrapper):
4✔
NEW
724
        raise TypeError(generator)
×
725
    return (generator.yield_type == type_args[0]
4✔
726
            and generator.send_type == type_args[1]
727
            and generator.return_type == type_args[2])
728

729

730
def _instancecheck_mapping(
4✔
731
    mapping: Mapping, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
732
) -> bool:
733
    """
734
    >>> from typing import Any, Optional
735
    >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3}, (int, Any), {})
736
    True
737
    >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3}, (int, int), {})
738
    True
739
    >>> _instancecheck_mapping({0: 1, 1: 2, 2: 3.0}, (int, int), {})
740
    False
741
    >>> _instancecheck_mapping({0: 1, 1.0: 2, 2: 3}, (int, int), {})
742
    False
743
    >>> _instancecheck_mapping({0: '1', 1: 2, 2: 3}, (int, int), {})
744
    False
745
    >>> _instancecheck_mapping({0: 1, 1: 2, None: 3.0}, (int, int), {})
746
    False
747
    >>> _instancecheck_mapping({0: 1, 1: 2, None: 3.0}, (int, Optional[int]), {})
748
    False
749
    """
750
    return _instancecheck_items_view(mapping.items(), type_args, type_vars=type_vars, context=context)
4✔
751

752

753
def _instancecheck_items_view(
4✔
754
    items_view: ItemsView, type_args: Tuple, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
755
) -> bool:
756
    """
757
    >>> from typing import Any, Optional
758
    >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3}.items(), (int, Any), {})
759
    True
760
    >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3}.items(), (int, int), {})
761
    True
762
    >>> _instancecheck_items_view({0: 1, 1: 2, 2: 3.0}.items(), (int, int), {})
763
    False
764
    >>> _instancecheck_items_view({0: 1, 1.0: 2, 2: 3}.items(), (int, int), {})
765
    False
766
    >>> _instancecheck_items_view({0: '1', 1: 2, 2: 3}.items(), (int, int), {})
767
    False
768
    >>> _instancecheck_items_view({0: 1, 1: 2, None: 3.0}.items(), (int, int), {})
769
    False
770
    >>> _instancecheck_items_view({0: 1, 1: 2, None: 3.0}.items(), (int, Optional[int]), {})
771
    False
772
    """
773
    key_type, value_type = type_args
4✔
774
    return all(_is_instance(obj=key, type_=key_type, type_vars=type_vars, context=context) and
4✔
775
               _is_instance(obj=val, type_=value_type, type_vars=type_vars, context=context)
776
               for key, val in items_view)
777

778

779
def _instancecheck_tuple(
4✔
780
    tup: Tuple, type_args: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
781
) -> bool:
782
    """
783
    >>> from typing import Any
784
    >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(Any, Ellipsis), type_vars={})
785
    True
786
    >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(Any,), type_vars={})
787
    False
788
    >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, int, str, str), type_vars={})
789
    True
790
    >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, float, str, str), type_vars={})
791
    False
792
    >>> _instancecheck_tuple(tup=(42.0, 43, 'hi', 'you'), type_args=(float, int, str, int), type_vars={})
793
    False
794
    """
795
    if Ellipsis in type_args:
4✔
796
        return all(_is_instance(obj=val, type_=type_args[0], type_vars=type_vars, context=context) for val in tup)
4✔
797

798
    if len(tup) != len(type_args):
4✔
799
        return False
4✔
800

801
    return all(_is_instance(obj=val, type_=type_, type_vars=type_vars, context=context)
4✔
802
               for val, type_ in zip(tup, type_args, strict=True))
803

804

805
def _instancecheck_union(
4✔
806
    value: Any, type_: Any, type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
807
) -> bool:
808
    """
809
    >>> from typing import Union, TypeVar, Any, Optional
810
    >>> NoneType = type(None)
811
    >>> _instancecheck_union(3.0, Union[int, float], {})
812
    True
813
    >>> _instancecheck_union(3, Union[int, float], {})
814
    True
815
    >>> _instancecheck_union('3', Union[int, float], {})
816
    False
817
    >>> _instancecheck_union(None, Union[int, NoneType], {})
818
    True
819
    >>> _instancecheck_union(None, Union[float, NoneType], {})
820
    True
821
    >>> S = TypeVar('S')
822
    >>> T = TypeVar('T')
823
    >>> U = TypeVar('U')
824
    >>> _instancecheck_union(42, Union[T, NoneType], {})
825
    True
826
    >>> _instancecheck_union(None, Union[T, NoneType], {})
827
    True
828
    >>> _instancecheck_union(None, Union[T, NoneType], {T: int})
829
    True
830
    >>> _instancecheck_union('None', Union[T, NoneType], {T: int})
831
    False
832
    >>> _instancecheck_union(42, Union[T, S], {})
833
    True
834
    >>> _instancecheck_union(42, Union[T, S], {T: int})
835
    True
836
    >>> _instancecheck_union(42, Union[T, S], {T: str})
837
    True
838
    >>> _instancecheck_union(42, Union[T, S], {T: int, S: float})
839
    True
840
    >>> _instancecheck_union(42, Union[T, S], {T: str, S: float})
841
    False
842
    >>> _instancecheck_union(42.8, Union[T, S], {T: str, S: float})
843
    True
844
    >>> _instancecheck_union(None, Union[T, S], {T: str, S: float})
845
    False
846
    >>> _instancecheck_union(None, Union[T, S], {})
847
    True
848
    >>> _instancecheck_union(None, Union[T, NoneType], {T: int})
849
    True
850
    >>> _instancecheck_union('None', Union[T, NoneType, S], {T: int})
851
    True
852
    >>> _instancecheck_union(42, Union[T, Any], {})
853
    True
854
    >>> _instancecheck_union(42, Union[T, Any], {T: float})
855
    True
856
    >>> _instancecheck_union(None, Optional[Callable[[float], float]], {})
857
    True
858
    >>> _instancecheck_union(3.0, int | float, {})
859
    True
860
    >>> _instancecheck_union(3, int | float, {})
861
    True
862
    >>> _instancecheck_union('3', int | float, {})
863
    False
864
    >>> _instancecheck_union(None, int | NoneType, {})
865
    True
866
    >>> _instancecheck_union(None, float | NoneType, {})
867
    True
868
    >>> S = TypeVar('S')
869
    >>> T = TypeVar('T')
870
    >>> U = TypeVar('U')
871
    >>> _instancecheck_union(42, T | NoneType, {})
872
    True
873
    >>> _instancecheck_union(None, T | NoneType, {})
874
    True
875
    >>> _instancecheck_union(None, T | NoneType, {T: int})
876
    True
877
    >>> _instancecheck_union('None', T | NoneType, {T: int})
878
    False
879
    >>> _instancecheck_union(42, T | S, {})
880
    True
881
    >>> _instancecheck_union(42, T | S, {T: int})
882
    True
883
    >>> _instancecheck_union(42, T | S, {T: str})
884
    True
885
    >>> _instancecheck_union(42, T | S, {T: int, S: float})
886
    True
887
    >>> _instancecheck_union(42, T | S, {T: str, S: float})
888
    False
889
    >>> _instancecheck_union(42.8, T | S, {T: str, S: float})
890
    True
891
    >>> _instancecheck_union(None, T | S, {T: str, S: float})
892
    False
893
    >>> _instancecheck_union(None, T | S, {})
894
    True
895
    >>> _instancecheck_union(None, T | NoneType, {T: int})
896
    True
897
    >>> _instancecheck_union('None', T | NoneType | S, {T: int})
898
    True
899
    >>> _instancecheck_union(42, T | Any, {})
900
    True
901
    >>> _instancecheck_union(42, T | Any, {T: float})
902
    True
903
    >>> _instancecheck_union(None, Optional[Callable[[float], float]], {})
904
    True
905
    """
906

907
    type_args = get_type_arguments(cls=type_)
4✔
908
    return _check_union(value=value, type_args=type_args, type_vars=type_vars, context=context)
4✔
909

910

911
def _check_union(
4✔
912
    value: Any, type_args: Tuple[Any, ...], type_vars: Dict[TypeVar_, Any], context: Dict[str, Any] | None = None,
913
) -> bool:
914
    args_non_type_vars = [type_arg for type_arg in type_args if not isinstance(type_arg, TypeVar)]
4✔
915
    args_type_vars = [type_arg for type_arg in type_args if isinstance(type_arg, TypeVar)]
4✔
916
    args_type_vars_bounded = [type_var for type_var in args_type_vars if type_var in type_vars]
4✔
917
    args_type_vars_unbounded = [type_var for type_var in args_type_vars if type_var not in args_type_vars_bounded]
4✔
918
    matches_non_type_var = any(
4✔
919
        _is_instance(obj=value, type_=typ, type_vars=type_vars, context=context)
920
        for typ in args_non_type_vars
921
    )
922

923
    if matches_non_type_var:
4✔
924
        return True
4✔
925

926
    for bounded_type_var in args_type_vars_bounded:
4✔
927
        try:
4✔
928
            _is_instance(obj=value, type_=bounded_type_var, type_vars=type_vars, context=context)
4✔
929

930
        except PedanticException:
4✔
931
            pass
4✔
932
        else:
933
            return True
4✔
934

935
    if not args_type_vars_unbounded:
4✔
936
        return False
4✔
937
    if len(args_type_vars_unbounded) == 1:
4✔
938
        return _is_instance(obj=value, type_=args_type_vars_unbounded[0], type_vars=type_vars, context=context)
4✔
939
    return True  # it is impossible to figure out, how to bound these type variables correctly
4✔
940

941

942
def _instancecheck_literal(value: Any, type_: Any, _: Dict[TypeVar_, Any], __: Dict[str, Any] | None = None) -> bool:
4✔
943
    type_args = get_type_arguments(cls=type_)
4✔
944
    return value in type_args
4✔
945

946

947
def _instancecheck_callable( # noqa: PLR0911, C901
4✔
948
    value: Callable | None, type_: Any, _: dict[TypeVar, Any], __: Dict[str, Any] | None = None,
949
) -> bool:
950
    """
951
    Examples:
952
    >>> from typing import Tuple, Callable, Any
953
    >>> def f(x: int, y: float) -> Tuple[float, str]:
954
    ...       return float(x), str(y)
955
    >>> _instancecheck_callable(f, Callable[[int, float], Tuple[float, str]], {})
956
    True
957
    >>> _instancecheck_callable(f, Callable[[int, float], Tuple[int, str]], {})
958
    False
959
    >>> _instancecheck_callable(f, Callable[[int, int], Tuple[float, str]], {})
960
    False
961
    >>> _instancecheck_callable(f, Callable[..., Tuple[float, str]], {})
962
    True
963
    >>> _instancecheck_callable(f, Callable[..., Tuple[int, str]], {})
964
    False
965
    >>> _instancecheck_callable(f, Callable[..., Any], {})
966
    True
967
    >>> _instancecheck_callable(f, Callable[[int, int, int], Tuple[float, str]], {})
968
    False
969
    >>> _instancecheck_callable(None, Callable[..., Any], {})
970
    False
971
    """
972

973
    if value is None:
4✔
974
        return False
4✔
975

976
    if _is_lambda(obj=value):
4✔
977
        return True
4✔
978

979
    param_types, ret_type = get_type_arguments(cls=type_)
4✔
980

981
    try:
4✔
982
        sig = inspect.signature(obj=value)
4✔
983
    except TypeError:
4✔
984
        return False
4✔
985

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

988
    if param_types is not Ellipsis:
4✔
989
        if len(param_types) != len(non_optional_params):
4✔
990
            return False
4✔
991

992
        for param, expected_type in zip(sig.parameters.values(), param_types, strict=False):
4✔
993
            if not _is_subtype(sub_type=param.annotation, super_type=expected_type):
4✔
994
                return False
4✔
995

996
    if not inspect.iscoroutinefunction(value):
4✔
997
        return _is_subtype(sub_type=sig.return_annotation, super_type=ret_type)
4✔
998

999
    base = get_base_generic(ret_type)
4✔
1000

1001
    if base == typing.Awaitable:
4✔
1002
        arg = get_type_arguments(ret_type)[0]
4✔
1003
    elif base == typing.Coroutine:
4✔
1004
        arg = get_type_arguments(ret_type)[2]
4✔
1005
    else:
1006
        return False
4✔
1007

1008
    return _is_subtype(sub_type=sig.return_annotation, super_type=arg)
4✔
1009

1010

1011
def _is_lambda(obj: Any) -> bool:
4✔
1012
    return callable(obj) and obj.__name__ == '<lambda>'
4✔
1013

1014

1015
def _instancecheck_type(value: Any, type_: Any, _: dict, context: dict[str, Any] | None = None) -> bool:
4✔
1016
    type_ = type_[0]
4✔
1017

1018
    if type_ == Any or isinstance(type_, typing.TypeVar):
4✔
1019
        return True
4✔
1020

1021
    return _is_subtype(sub_type=value, super_type=type_, context=context)
4✔
1022

1023

1024
_ORIGIN_TYPE_CHECKERS = {}
4✔
1025
for class_path, _check_func in {
4✔
1026
    'typing.Container': _instancecheck_iterable,
1027
    'typing.Collection': _instancecheck_iterable,
1028
    'typing.AbstractSet': _instancecheck_iterable,
1029
    'typing.MutableSet': _instancecheck_iterable,
1030
    'typing.Sequence': _instancecheck_iterable,
1031
    'typing.Iterable': _instancecheck_iterable,
1032
    'typing.MutableSequence': _instancecheck_iterable,
1033
    'typing.Deque': _instancecheck_iterable,
1034
    'typing.List': _instancecheck_iterable,
1035
    'typing.Set': _instancecheck_iterable,
1036
    'typing.FrozenSet': _instancecheck_iterable,
1037
    'typing.KeysView': _instancecheck_iterable,
1038
    'typing.ValuesView': _instancecheck_iterable,
1039
    'typing.AsyncIterable': _instancecheck_iterable,
1040

1041
    'typing.Mapping': _instancecheck_mapping,
1042
    'typing.MutableMapping': _instancecheck_mapping,
1043
    'typing.MappingView': _instancecheck_mapping,
1044
    'typing.ItemsView': _instancecheck_items_view,
1045
    'typing.Dict': _instancecheck_mapping,
1046
    'typing.DefaultDict': _instancecheck_mapping,
1047
    'typing.Counter': _instancecheck_mapping,
1048
    'typing.ChainMap': _instancecheck_mapping,
1049

1050
    'typing.Tuple': _instancecheck_tuple,
1051
    'typing.Type': _instancecheck_type,
1052

1053
    'typing.Generator': _instancecheck_generator,
1054
}.items():
1055
    class_ = eval(class_path)  # noqa: S307
4✔
1056
    _ORIGIN_TYPE_CHECKERS[class_] = _check_func
4✔
1057

1058
_SPECIAL_INSTANCE_CHECKERS = {
4✔
1059
    'Union': _instancecheck_union,
1060
    'Optional': _instancecheck_union,
1061
    'Literal': _instancecheck_literal,
1062
    'Callable': _instancecheck_callable,
1063
    'Any': lambda _, __, ___, ____: True,
1064
}
1065

1066
NUM_OF_REQUIRED_TYPE_ARGS_EXACT = {
4✔
1067
    'Callable': 2,
1068
    'List': 1,
1069
    'Set': 1,
1070
    'FrozenSet': 1,
1071
    'Iterable': 1,
1072
    'Sequence': 1,
1073
    'Dict': 2,
1074
    'Optional': 2,  # because _get_type_arguments(Optional[int]) returns (int, None)
1075
}
1076
NUM_OF_REQUIRED_TYPE_ARGS_MIN = {
4✔
1077
    'Tuple': 1,
1078
    'Union': 2,
1079
}
1080

1081

1082
def convert_to_typing_types(x: type) -> type:  # noqa: PLR0911
4✔
1083
    """
1084
    Converts a given type to the typing module equivalent type.
1085

1086
    Example:
1087
    >>> convert_to_typing_types(int)
1088
    <class 'int'>
1089
    >>> convert_to_typing_types(list)
1090
    Traceback (most recent call last):
1091
    ...
1092
    ValueError: Missing type arguments
1093
    >>> convert_to_typing_types(list[int])
1094
    typing.List[int]
1095
    >>> convert_to_typing_types(set[int])
1096
    typing.Set[int]
1097
    >>> convert_to_typing_types(frozenset[int])
1098
    typing.FrozenSet[int]
1099
    >>> convert_to_typing_types(tuple[int])
1100
    typing.Tuple[int]
1101
    >>> convert_to_typing_types(type[int])
1102
    typing.Type[int]
1103
    >>> convert_to_typing_types(type[int | float])
1104
    typing.Type[int | float]
1105
    >>> convert_to_typing_types(tuple[int, float])
1106
    typing.Tuple[int, float]
1107
    >>> convert_to_typing_types(dict[int, float])
1108
    typing.Dict[int, float]
1109
    >>> convert_to_typing_types(list[dict[int, float]])
1110
    typing.List[typing.Dict[int, float]]
1111
    >>> convert_to_typing_types(list[dict[int, tuple[float, str]]])
1112
    typing.List[typing.Dict[int, typing.Tuple[float, str]]]
1113
    """
1114

1115
    if x in {list, set, dict, frozenset, tuple, type}:
4✔
1116
        raise ValueError('Missing type arguments')
4✔
1117

1118
    if not hasattr(x, '__origin__'):
4✔
1119
        return x
4✔
1120

1121
    origin = x.__origin__
4✔
1122
    args = [convert_to_typing_types(a) for a in x.__args__]
4✔
1123

1124
    if origin is list:
4✔
1125
        return typing.List[tuple(args)]
4✔
1126
    if origin is set:
4✔
1127
        return typing.Set[tuple(args)]
4✔
1128
    if origin is dict:
4✔
1129
        return typing.Dict[tuple(args)]
4✔
1130
    if origin is tuple:
4✔
1131
        return typing.Tuple[tuple(args)]
4✔
1132
    if origin is frozenset:
4✔
1133
        return typing.FrozenSet[tuple(args)]
4✔
1134
    if origin is type:
4✔
1135
        return typing.Type[tuple(args)]
4✔
NEW
1136
    if origin is typing.Union:
1✔
1137
        return x  # new since Python 3.14
1✔
1138

1139
    raise RuntimeError(x)
×
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