metaflow 2.15.5__py2.py3-none-any.whl → 2.15.6__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- metaflow/_vendor/typeguard/_checkers.py +259 -95
- metaflow/_vendor/typeguard/_config.py +4 -4
- metaflow/_vendor/typeguard/_decorators.py +8 -12
- metaflow/_vendor/typeguard/_functions.py +33 -32
- metaflow/_vendor/typeguard/_pytest_plugin.py +40 -13
- metaflow/_vendor/typeguard/_suppression.py +3 -5
- metaflow/_vendor/typeguard/_transformer.py +84 -48
- metaflow/_vendor/typeguard/_union_transformer.py +1 -0
- metaflow/_vendor/typeguard/_utils.py +13 -9
- metaflow/_vendor/typing_extensions.py +1088 -500
- metaflow/_vendor/v3_7/__init__.py +1 -0
- metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
- metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
- metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
- metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
- metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
- metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
- metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
- metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
- metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
- metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
- metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
- metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
- metaflow/_vendor/v3_7/zipp.py +329 -0
- metaflow/cmd/develop/stubs.py +1 -1
- metaflow/extension_support/__init__.py +1 -1
- metaflow/plugins/pypi/utils.py +4 -0
- metaflow/runner/click_api.py +7 -2
- metaflow/vendor.py +1 -0
- metaflow/version.py +1 -1
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/METADATA +2 -2
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/RECORD +51 -25
- {metaflow-2.15.5.data → metaflow-2.15.6.data}/data/share/metaflow/devtools/Makefile +0 -0
- {metaflow-2.15.5.data → metaflow-2.15.6.data}/data/share/metaflow/devtools/Tiltfile +0 -0
- {metaflow-2.15.5.data → metaflow-2.15.6.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/LICENSE +0 -0
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/WHEEL +0 -0
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/entry_points.txt +0 -0
- {metaflow-2.15.5.dist-info → metaflow-2.15.6.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ import warnings
|
|
9
9
|
from enum import Enum
|
10
10
|
from inspect import Parameter, isclass, isfunction
|
11
11
|
from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
|
12
|
+
from itertools import zip_longest
|
12
13
|
from textwrap import indent
|
13
14
|
from typing import (
|
14
15
|
IO,
|
@@ -33,10 +34,13 @@ from typing import (
|
|
33
34
|
)
|
34
35
|
from unittest.mock import Mock
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
from metaflow._vendor import typing_extensions
|
38
|
+
|
39
|
+
# Must use this because typing.is_typeddict does not recognize
|
40
|
+
# TypedDict from typing_extensions, and as of version 4.12.0
|
41
|
+
# typing_extensions.TypedDict is different from typing.TypedDict
|
42
|
+
# on all versions.
|
43
|
+
from metaflow._vendor.typing_extensions import is_typeddict
|
40
44
|
|
41
45
|
from ._config import ForwardRefPolicy
|
42
46
|
from ._exceptions import TypeCheckError, TypeHintWarning
|
@@ -46,22 +50,20 @@ from ._utils import evaluate_forwardref, get_stacklevel, get_type_name, qualifie
|
|
46
50
|
if sys.version_info >= (3, 11):
|
47
51
|
from typing import (
|
48
52
|
Annotated,
|
53
|
+
NotRequired,
|
49
54
|
TypeAlias,
|
50
55
|
get_args,
|
51
56
|
get_origin,
|
52
|
-
get_type_hints,
|
53
|
-
is_typeddict,
|
54
57
|
)
|
55
58
|
|
56
59
|
SubclassableAny = Any
|
57
60
|
else:
|
58
61
|
from metaflow._vendor.typing_extensions import (
|
59
62
|
Annotated,
|
63
|
+
NotRequired,
|
60
64
|
TypeAlias,
|
61
65
|
get_args,
|
62
66
|
get_origin,
|
63
|
-
get_type_hints,
|
64
|
-
is_typeddict,
|
65
67
|
)
|
66
68
|
from metaflow._vendor.typing_extensions import Any as SubclassableAny
|
67
69
|
|
@@ -80,7 +82,9 @@ TypeCheckLookupCallback: TypeAlias = Callable[
|
|
80
82
|
]
|
81
83
|
|
82
84
|
checker_lookup_functions: list[TypeCheckLookupCallback] = []
|
83
|
-
|
85
|
+
generic_alias_types: tuple[type, ...] = (type(List), type(List[Any]))
|
86
|
+
if sys.version_info >= (3, 9):
|
87
|
+
generic_alias_types += (types.GenericAlias,)
|
84
88
|
|
85
89
|
# Sentinel
|
86
90
|
_missing = object()
|
@@ -172,31 +176,29 @@ def check_callable(
|
|
172
176
|
f'{", ".join(unfulfilled_kwonlyargs)}'
|
173
177
|
)
|
174
178
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
param
|
186
|
-
|
187
|
-
|
188
|
-
)
|
189
|
-
|
190
|
-
if num_mandatory_args > len(argument_types):
|
179
|
+
num_positional_args = num_mandatory_pos_args = 0
|
180
|
+
has_varargs = False
|
181
|
+
for param in signature.parameters.values():
|
182
|
+
if param.kind in (
|
183
|
+
Parameter.POSITIONAL_ONLY,
|
184
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
185
|
+
):
|
186
|
+
num_positional_args += 1
|
187
|
+
if param.default is Parameter.empty:
|
188
|
+
num_mandatory_pos_args += 1
|
189
|
+
elif param.kind == Parameter.VAR_POSITIONAL:
|
190
|
+
has_varargs = True
|
191
|
+
|
192
|
+
if num_mandatory_pos_args > len(argument_types):
|
191
193
|
raise TypeCheckError(
|
192
|
-
f"has too many arguments in its declaration;
|
193
|
-
f"{len(argument_types)} but {
|
194
|
-
f"declared"
|
194
|
+
f"has too many mandatory positional arguments in its declaration; "
|
195
|
+
f"expected {len(argument_types)} but {num_mandatory_pos_args} "
|
196
|
+
f"mandatory positional argument(s) declared"
|
195
197
|
)
|
196
|
-
elif not has_varargs and
|
198
|
+
elif not has_varargs and num_positional_args < len(argument_types):
|
197
199
|
raise TypeCheckError(
|
198
200
|
f"has too few arguments in its declaration; expected "
|
199
|
-
f"{len(argument_types)} but {
|
201
|
+
f"{len(argument_types)} but {num_positional_args} argument(s) "
|
200
202
|
f"declared"
|
201
203
|
)
|
202
204
|
|
@@ -247,22 +249,33 @@ def check_typed_dict(
|
|
247
249
|
|
248
250
|
declared_keys = frozenset(origin_type.__annotations__)
|
249
251
|
if hasattr(origin_type, "__required_keys__"):
|
250
|
-
required_keys = origin_type.__required_keys__
|
252
|
+
required_keys = set(origin_type.__required_keys__)
|
251
253
|
else: # py3.8 and lower
|
252
|
-
required_keys = declared_keys if origin_type.__total__ else
|
254
|
+
required_keys = set(declared_keys) if origin_type.__total__ else set()
|
253
255
|
|
254
|
-
existing_keys =
|
256
|
+
existing_keys = set(value)
|
255
257
|
extra_keys = existing_keys - declared_keys
|
256
258
|
if extra_keys:
|
257
259
|
keys_formatted = ", ".join(f'"{key}"' for key in sorted(extra_keys, key=repr))
|
258
260
|
raise TypeCheckError(f"has unexpected extra key(s): {keys_formatted}")
|
259
261
|
|
262
|
+
# Detect NotRequired fields which are hidden by get_type_hints()
|
263
|
+
type_hints: dict[str, type] = {}
|
264
|
+
for key, annotation in origin_type.__annotations__.items():
|
265
|
+
if isinstance(annotation, ForwardRef):
|
266
|
+
annotation = evaluate_forwardref(annotation, memo)
|
267
|
+
if get_origin(annotation) is NotRequired:
|
268
|
+
required_keys.discard(key)
|
269
|
+
annotation = get_args(annotation)[0]
|
270
|
+
|
271
|
+
type_hints[key] = annotation
|
272
|
+
|
260
273
|
missing_keys = required_keys - existing_keys
|
261
274
|
if missing_keys:
|
262
275
|
keys_formatted = ", ".join(f'"{key}"' for key in sorted(missing_keys, key=repr))
|
263
276
|
raise TypeCheckError(f"is missing required key(s): {keys_formatted}")
|
264
277
|
|
265
|
-
for key, argtype in
|
278
|
+
for key, argtype in type_hints.items():
|
266
279
|
argvalue = value.get(key, _missing)
|
267
280
|
if argvalue is not _missing:
|
268
281
|
try:
|
@@ -339,11 +352,7 @@ def check_tuple(
|
|
339
352
|
memo: TypeCheckMemo,
|
340
353
|
) -> None:
|
341
354
|
# Specialized check for NamedTuples
|
342
|
-
field_types
|
343
|
-
if field_types is None and sys.version_info < (3, 8):
|
344
|
-
field_types = getattr(origin_type, "_field_types", None)
|
345
|
-
|
346
|
-
if field_types:
|
355
|
+
if field_types := getattr(origin_type, "__annotations__", None):
|
347
356
|
if not isinstance(value, origin_type):
|
348
357
|
raise TypeCheckError(
|
349
358
|
f"is not a named tuple of type {qualified_name(origin_type)}"
|
@@ -361,7 +370,6 @@ def check_tuple(
|
|
361
370
|
raise TypeCheckError("is not a tuple")
|
362
371
|
|
363
372
|
if args:
|
364
|
-
# Python 3.6+
|
365
373
|
use_ellipsis = args[-1] is Ellipsis
|
366
374
|
tuple_params = args[: -1 if use_ellipsis else None]
|
367
375
|
else:
|
@@ -402,16 +410,19 @@ def check_union(
|
|
402
410
|
memo: TypeCheckMemo,
|
403
411
|
) -> None:
|
404
412
|
errors: dict[str, TypeCheckError] = {}
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
413
|
+
try:
|
414
|
+
for type_ in args:
|
415
|
+
try:
|
416
|
+
check_type_internal(value, type_, memo)
|
417
|
+
return
|
418
|
+
except TypeCheckError as exc:
|
419
|
+
errors[get_type_name(type_)] = exc
|
411
420
|
|
412
|
-
|
413
|
-
|
414
|
-
|
421
|
+
formatted_errors = indent(
|
422
|
+
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
|
423
|
+
)
|
424
|
+
finally:
|
425
|
+
del errors # avoid creating ref cycle
|
415
426
|
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
|
416
427
|
|
417
428
|
|
@@ -441,10 +452,9 @@ def check_class(
|
|
441
452
|
args: tuple[Any, ...],
|
442
453
|
memo: TypeCheckMemo,
|
443
454
|
) -> None:
|
444
|
-
if not isclass(value):
|
455
|
+
if not isclass(value) and not isinstance(value, generic_alias_types):
|
445
456
|
raise TypeCheckError("is not a class")
|
446
457
|
|
447
|
-
# Needed on Python 3.7+
|
448
458
|
if not args:
|
449
459
|
return
|
450
460
|
|
@@ -477,7 +487,7 @@ def check_class(
|
|
477
487
|
raise TypeCheckError(
|
478
488
|
f"did not match any element in the union:\n{formatted_errors}"
|
479
489
|
)
|
480
|
-
elif not issubclass(value, expected_class):
|
490
|
+
elif not issubclass(value, expected_class): # type: ignore[arg-type]
|
481
491
|
raise TypeCheckError(f"is not a subclass of {qualified_name(expected_class)}")
|
482
492
|
|
483
493
|
|
@@ -531,21 +541,8 @@ def check_typevar(
|
|
531
541
|
)
|
532
542
|
|
533
543
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
def _is_literal_type(typ: object) -> bool:
|
538
|
-
return typ is typing.Literal
|
539
|
-
|
540
|
-
else:
|
541
|
-
|
542
|
-
def _is_literal_type(typ: object) -> bool:
|
543
|
-
return typ is typing.Literal or typ is typing_extensions.Literal
|
544
|
-
|
545
|
-
else:
|
546
|
-
|
547
|
-
def _is_literal_type(typ: object) -> bool:
|
548
|
-
return typ is typing_extensions.Literal
|
544
|
+
def _is_literal_type(typ: object) -> bool:
|
545
|
+
return typ is typing.Literal or typ is typing_extensions.Literal
|
549
546
|
|
550
547
|
|
551
548
|
def check_literal(
|
@@ -558,7 +555,6 @@ def check_literal(
|
|
558
555
|
retval: list[Any] = []
|
559
556
|
for arg in literal_args:
|
560
557
|
if _is_literal_type(get_origin(arg)):
|
561
|
-
# The first check works on py3.6 and lower, the second one on py3.7+
|
562
558
|
retval.extend(get_literal_args(arg.__args__))
|
563
559
|
elif arg is None or isinstance(arg, (int, str, bytes, bool, Enum)):
|
564
560
|
retval.append(arg)
|
@@ -638,25 +634,196 @@ def check_io(
|
|
638
634
|
raise TypeCheckError("is not an I/O object")
|
639
635
|
|
640
636
|
|
637
|
+
def check_signature_compatible(
|
638
|
+
subject_callable: Callable[..., Any], protocol: type, attrname: str
|
639
|
+
) -> None:
|
640
|
+
subject_sig = inspect.signature(subject_callable)
|
641
|
+
protocol_sig = inspect.signature(getattr(protocol, attrname))
|
642
|
+
protocol_type: typing.Literal["instance", "class", "static"] = "instance"
|
643
|
+
subject_type: typing.Literal["instance", "class", "static"] = "instance"
|
644
|
+
|
645
|
+
# Check if the protocol-side method is a class method or static method
|
646
|
+
if attrname in protocol.__dict__:
|
647
|
+
descriptor = protocol.__dict__[attrname]
|
648
|
+
if isinstance(descriptor, staticmethod):
|
649
|
+
protocol_type = "static"
|
650
|
+
elif isinstance(descriptor, classmethod):
|
651
|
+
protocol_type = "class"
|
652
|
+
|
653
|
+
# Check if the subject-side method is a class method or static method
|
654
|
+
if inspect.ismethod(subject_callable) and inspect.isclass(
|
655
|
+
subject_callable.__self__
|
656
|
+
):
|
657
|
+
subject_type = "class"
|
658
|
+
elif not hasattr(subject_callable, "__self__"):
|
659
|
+
subject_type = "static"
|
660
|
+
|
661
|
+
if protocol_type == "instance" and subject_type != "instance":
|
662
|
+
raise TypeCheckError(
|
663
|
+
f"should be an instance method but it's a {subject_type} method"
|
664
|
+
)
|
665
|
+
elif protocol_type != "instance" and subject_type == "instance":
|
666
|
+
raise TypeCheckError(
|
667
|
+
f"should be a {protocol_type} method but it's an instance method"
|
668
|
+
)
|
669
|
+
|
670
|
+
expected_varargs = any(
|
671
|
+
param
|
672
|
+
for param in protocol_sig.parameters.values()
|
673
|
+
if param.kind is Parameter.VAR_POSITIONAL
|
674
|
+
)
|
675
|
+
has_varargs = any(
|
676
|
+
param
|
677
|
+
for param in subject_sig.parameters.values()
|
678
|
+
if param.kind is Parameter.VAR_POSITIONAL
|
679
|
+
)
|
680
|
+
if expected_varargs and not has_varargs:
|
681
|
+
raise TypeCheckError("should accept variable positional arguments but doesn't")
|
682
|
+
|
683
|
+
protocol_has_varkwargs = any(
|
684
|
+
param
|
685
|
+
for param in protocol_sig.parameters.values()
|
686
|
+
if param.kind is Parameter.VAR_KEYWORD
|
687
|
+
)
|
688
|
+
subject_has_varkwargs = any(
|
689
|
+
param
|
690
|
+
for param in subject_sig.parameters.values()
|
691
|
+
if param.kind is Parameter.VAR_KEYWORD
|
692
|
+
)
|
693
|
+
if protocol_has_varkwargs and not subject_has_varkwargs:
|
694
|
+
raise TypeCheckError("should accept variable keyword arguments but doesn't")
|
695
|
+
|
696
|
+
# Check that the callable has at least the expect amount of positional-only
|
697
|
+
# arguments (and no extra positional-only arguments without default values)
|
698
|
+
if not has_varargs:
|
699
|
+
protocol_args = [
|
700
|
+
param
|
701
|
+
for param in protocol_sig.parameters.values()
|
702
|
+
if param.kind
|
703
|
+
in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
|
704
|
+
]
|
705
|
+
subject_args = [
|
706
|
+
param
|
707
|
+
for param in subject_sig.parameters.values()
|
708
|
+
if param.kind
|
709
|
+
in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
|
710
|
+
]
|
711
|
+
|
712
|
+
# Remove the "self" parameter from the protocol arguments to match
|
713
|
+
if protocol_type == "instance":
|
714
|
+
protocol_args.pop(0)
|
715
|
+
|
716
|
+
for protocol_arg, subject_arg in zip_longest(protocol_args, subject_args):
|
717
|
+
if protocol_arg is None:
|
718
|
+
if subject_arg.default is Parameter.empty:
|
719
|
+
raise TypeCheckError("has too many mandatory positional arguments")
|
720
|
+
|
721
|
+
break
|
722
|
+
|
723
|
+
if subject_arg is None:
|
724
|
+
raise TypeCheckError("has too few positional arguments")
|
725
|
+
|
726
|
+
if (
|
727
|
+
protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
|
728
|
+
and subject_arg.kind is Parameter.POSITIONAL_ONLY
|
729
|
+
):
|
730
|
+
raise TypeCheckError(
|
731
|
+
f"has an argument ({subject_arg.name}) that should not be "
|
732
|
+
f"positional-only"
|
733
|
+
)
|
734
|
+
|
735
|
+
if (
|
736
|
+
protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
|
737
|
+
and protocol_arg.name != subject_arg.name
|
738
|
+
):
|
739
|
+
raise TypeCheckError(
|
740
|
+
f"has a positional argument ({subject_arg.name}) that should be "
|
741
|
+
f"named {protocol_arg.name!r} at this position"
|
742
|
+
)
|
743
|
+
|
744
|
+
protocol_kwonlyargs = {
|
745
|
+
param.name: param
|
746
|
+
for param in protocol_sig.parameters.values()
|
747
|
+
if param.kind is Parameter.KEYWORD_ONLY
|
748
|
+
}
|
749
|
+
subject_kwonlyargs = {
|
750
|
+
param.name: param
|
751
|
+
for param in subject_sig.parameters.values()
|
752
|
+
if param.kind is Parameter.KEYWORD_ONLY
|
753
|
+
}
|
754
|
+
if not subject_has_varkwargs:
|
755
|
+
# Check that the signature has at least the required keyword-only arguments, and
|
756
|
+
# no extra mandatory keyword-only arguments
|
757
|
+
if missing_kwonlyargs := [
|
758
|
+
param.name
|
759
|
+
for param in protocol_kwonlyargs.values()
|
760
|
+
if param.name not in subject_kwonlyargs
|
761
|
+
]:
|
762
|
+
raise TypeCheckError(
|
763
|
+
"is missing keyword-only arguments: " + ", ".join(missing_kwonlyargs)
|
764
|
+
)
|
765
|
+
|
766
|
+
if not protocol_has_varkwargs:
|
767
|
+
if extra_kwonlyargs := [
|
768
|
+
param.name
|
769
|
+
for param in subject_kwonlyargs.values()
|
770
|
+
if param.default is Parameter.empty
|
771
|
+
and param.name not in protocol_kwonlyargs
|
772
|
+
]:
|
773
|
+
raise TypeCheckError(
|
774
|
+
"has mandatory keyword-only arguments not present in the protocol: "
|
775
|
+
+ ", ".join(extra_kwonlyargs)
|
776
|
+
)
|
777
|
+
|
778
|
+
|
641
779
|
def check_protocol(
|
642
780
|
value: Any,
|
643
781
|
origin_type: Any,
|
644
782
|
args: tuple[Any, ...],
|
645
783
|
memo: TypeCheckMemo,
|
646
784
|
) -> None:
|
647
|
-
|
648
|
-
|
649
|
-
if
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
785
|
+
origin_annotations = typing.get_type_hints(origin_type)
|
786
|
+
for attrname in sorted(typing_extensions.get_protocol_members(origin_type)):
|
787
|
+
if (annotation := origin_annotations.get(attrname)) is not None:
|
788
|
+
try:
|
789
|
+
subject_member = getattr(value, attrname)
|
790
|
+
except AttributeError:
|
791
|
+
raise TypeCheckError(
|
792
|
+
f"is not compatible with the {origin_type.__qualname__} "
|
793
|
+
f"protocol because it has no attribute named {attrname!r}"
|
794
|
+
) from None
|
795
|
+
|
796
|
+
try:
|
797
|
+
check_type_internal(subject_member, annotation, memo)
|
798
|
+
except TypeCheckError as exc:
|
799
|
+
raise TypeCheckError(
|
800
|
+
f"is not compatible with the {origin_type.__qualname__} "
|
801
|
+
f"protocol because its {attrname!r} attribute {exc}"
|
802
|
+
) from None
|
803
|
+
elif callable(getattr(origin_type, attrname)):
|
804
|
+
try:
|
805
|
+
subject_member = getattr(value, attrname)
|
806
|
+
except AttributeError:
|
807
|
+
raise TypeCheckError(
|
808
|
+
f"is not compatible with the {origin_type.__qualname__} "
|
809
|
+
f"protocol because it has no method named {attrname!r}"
|
810
|
+
) from None
|
811
|
+
|
812
|
+
if not callable(subject_member):
|
813
|
+
raise TypeCheckError(
|
814
|
+
f"is not compatible with the {origin_type.__qualname__} "
|
815
|
+
f"protocol because its {attrname!r} attribute is not a callable"
|
816
|
+
)
|
817
|
+
|
818
|
+
# TODO: implement assignability checks for parameter and return value
|
819
|
+
# annotations
|
820
|
+
try:
|
821
|
+
check_signature_compatible(subject_member, origin_type, attrname)
|
822
|
+
except TypeCheckError as exc:
|
823
|
+
raise TypeCheckError(
|
824
|
+
f"is not compatible with the {origin_type.__qualname__} "
|
825
|
+
f"protocol because its {attrname!r} method {exc}"
|
826
|
+
) from None
|
660
827
|
|
661
828
|
|
662
829
|
def check_byteslike(
|
@@ -777,7 +944,7 @@ def check_type_internal(
|
|
777
944
|
if isclass(origin_type):
|
778
945
|
if not isinstance(value, origin_type):
|
779
946
|
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
|
780
|
-
elif type(origin_type) is str:
|
947
|
+
elif type(origin_type) is str: # noqa: E721
|
781
948
|
warnings.warn(
|
782
949
|
f"Skipping type check against {origin_type!r}; this looks like a "
|
783
950
|
f"string-form forward reference imported from another module",
|
@@ -801,6 +968,7 @@ origin_type_checkers = {
|
|
801
968
|
IO: check_io,
|
802
969
|
list: check_list,
|
803
970
|
List: check_list,
|
971
|
+
typing.Literal: check_literal,
|
804
972
|
Mapping: check_mapping,
|
805
973
|
MutableMapping: check_mapping,
|
806
974
|
None: check_none,
|
@@ -817,9 +985,14 @@ origin_type_checkers = {
|
|
817
985
|
type: check_class,
|
818
986
|
Type: check_class,
|
819
987
|
Union: check_union,
|
988
|
+
# On some versions of Python, these may simply be re-exports from "typing",
|
989
|
+
# but exactly which Python versions is subject to change.
|
990
|
+
# It's best to err on the safe side and just always specify these.
|
991
|
+
typing_extensions.Literal: check_literal,
|
992
|
+
typing_extensions.LiteralString: check_literal_string,
|
993
|
+
typing_extensions.Self: check_self,
|
994
|
+
typing_extensions.TypeGuard: check_typeguard,
|
820
995
|
}
|
821
|
-
if sys.version_info >= (3, 8):
|
822
|
-
origin_type_checkers[typing.Literal] = check_literal
|
823
996
|
if sys.version_info >= (3, 10):
|
824
997
|
origin_type_checkers[types.UnionType] = check_uniontype
|
825
998
|
origin_type_checkers[typing.TypeGuard] = check_typeguard
|
@@ -827,16 +1000,6 @@ if sys.version_info >= (3, 11):
|
|
827
1000
|
origin_type_checkers.update(
|
828
1001
|
{typing.LiteralString: check_literal_string, typing.Self: check_self}
|
829
1002
|
)
|
830
|
-
if typing_extensions is not None:
|
831
|
-
# On some Python versions, these may simply be re-exports from typing,
|
832
|
-
# but exactly which Python versions is subject to change,
|
833
|
-
# so it's best to err on the safe side
|
834
|
-
# and update the dictionary on all Python versions
|
835
|
-
# if typing_extensions is installed
|
836
|
-
origin_type_checkers[typing_extensions.Literal] = check_literal
|
837
|
-
origin_type_checkers[typing_extensions.LiteralString] = check_literal_string
|
838
|
-
origin_type_checkers[typing_extensions.Self] = check_self
|
839
|
-
origin_type_checkers[typing_extensions.TypeGuard] = check_typeguard
|
840
1003
|
|
841
1004
|
|
842
1005
|
def builtin_checker_lookup(
|
@@ -848,7 +1011,8 @@ def builtin_checker_lookup(
|
|
848
1011
|
elif is_typeddict(origin_type):
|
849
1012
|
return check_typed_dict
|
850
1013
|
elif isclass(origin_type) and issubclass(
|
851
|
-
origin_type,
|
1014
|
+
origin_type,
|
1015
|
+
Tuple, # type: ignore[arg-type]
|
852
1016
|
):
|
853
1017
|
# NamedTuple
|
854
1018
|
return check_tuple
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from collections.abc import
|
3
|
+
from collections.abc import Iterable
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from enum import Enum, auto
|
6
6
|
from typing import TYPE_CHECKING, TypeVar
|
@@ -49,11 +49,11 @@ class CollectionCheckStrategy(Enum):
|
|
49
49
|
FIRST_ITEM = auto()
|
50
50
|
ALL_ITEMS = auto()
|
51
51
|
|
52
|
-
def iterate_samples(self, collection:
|
52
|
+
def iterate_samples(self, collection: Iterable[T]) -> Iterable[T]:
|
53
53
|
if self is CollectionCheckStrategy.FIRST_ITEM:
|
54
|
-
|
54
|
+
try:
|
55
55
|
return [next(iter(collection))]
|
56
|
-
|
56
|
+
except StopIteration:
|
57
57
|
return ()
|
58
58
|
else:
|
59
59
|
return collection
|
@@ -16,20 +16,18 @@ from ._functions import TypeCheckFailCallback
|
|
16
16
|
from ._transformer import TypeguardTransformer
|
17
17
|
from ._utils import Unset, function_name, get_stacklevel, is_method_of, unset
|
18
18
|
|
19
|
+
T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
|
20
|
+
|
19
21
|
if TYPE_CHECKING:
|
20
22
|
from typeshed.stdlib.types import _Cell
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
def typeguard_ignore(f: _F) -> _F:
|
24
|
+
def typeguard_ignore(f: T_CallableOrType) -> T_CallableOrType:
|
25
25
|
"""This decorator is a noop during static type-checking."""
|
26
26
|
return f
|
27
27
|
|
28
28
|
else:
|
29
29
|
from typing import no_type_check as typeguard_ignore # noqa: F401
|
30
30
|
|
31
|
-
T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
|
32
|
-
|
33
31
|
|
34
32
|
def make_cell(value: object) -> _Cell:
|
35
33
|
return (lambda: value).__closure__[0] # type: ignore[index]
|
@@ -133,13 +131,11 @@ def typechecked(
|
|
133
131
|
typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
|
134
132
|
collection_check_strategy: CollectionCheckStrategy | Unset = unset,
|
135
133
|
debug_instrumentation: bool | Unset = unset,
|
136
|
-
) -> Callable[[T_CallableOrType], T_CallableOrType]:
|
137
|
-
...
|
134
|
+
) -> Callable[[T_CallableOrType], T_CallableOrType]: ...
|
138
135
|
|
139
136
|
|
140
137
|
@overload
|
141
|
-
def typechecked(target: T_CallableOrType) -> T_CallableOrType:
|
142
|
-
...
|
138
|
+
def typechecked(target: T_CallableOrType) -> T_CallableOrType: ...
|
143
139
|
|
144
140
|
|
145
141
|
def typechecked(
|
@@ -215,9 +211,9 @@ def typechecked(
|
|
215
211
|
return target
|
216
212
|
|
217
213
|
# Find either the first Python wrapper or the actual function
|
218
|
-
wrapper_class:
|
219
|
-
staticmethod[Any, Any]
|
220
|
-
|
214
|
+
wrapper_class: (
|
215
|
+
type[classmethod[Any, Any, Any]] | type[staticmethod[Any, Any]] | None
|
216
|
+
) = None
|
221
217
|
if isinstance(target, (classmethod, staticmethod)):
|
222
218
|
wrapper_class = target.__class__
|
223
219
|
target = target.__func__
|