ob-metaflow 2.11.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__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/R.py +10 -7
- metaflow/__init__.py +40 -25
- metaflow/_vendor/imghdr/__init__.py +186 -0
- metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/typeguard/__init__.py +48 -0
- metaflow/_vendor/typeguard/_checkers.py +1070 -0
- metaflow/_vendor/typeguard/_config.py +108 -0
- metaflow/_vendor/typeguard/_decorators.py +233 -0
- metaflow/_vendor/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/typeguard/_functions.py +308 -0
- metaflow/_vendor/typeguard/_importhook.py +213 -0
- metaflow/_vendor/typeguard/_memo.py +48 -0
- metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
- metaflow/_vendor/typeguard/_suppression.py +86 -0
- metaflow/_vendor/typeguard/_transformer.py +1229 -0
- metaflow/_vendor/typeguard/_union_transformer.py +55 -0
- metaflow/_vendor/typeguard/_utils.py +173 -0
- metaflow/_vendor/typeguard/py.typed +0 -0
- metaflow/_vendor/typing_extensions.py +3641 -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/yaml/__init__.py +427 -0
- metaflow/_vendor/yaml/composer.py +139 -0
- metaflow/_vendor/yaml/constructor.py +748 -0
- metaflow/_vendor/yaml/cyaml.py +101 -0
- metaflow/_vendor/yaml/dumper.py +62 -0
- metaflow/_vendor/yaml/emitter.py +1137 -0
- metaflow/_vendor/yaml/error.py +75 -0
- metaflow/_vendor/yaml/events.py +86 -0
- metaflow/_vendor/yaml/loader.py +63 -0
- metaflow/_vendor/yaml/nodes.py +49 -0
- metaflow/_vendor/yaml/parser.py +589 -0
- metaflow/_vendor/yaml/reader.py +185 -0
- metaflow/_vendor/yaml/representer.py +389 -0
- metaflow/_vendor/yaml/resolver.py +227 -0
- metaflow/_vendor/yaml/scanner.py +1435 -0
- metaflow/_vendor/yaml/serializer.py +111 -0
- metaflow/_vendor/yaml/tokens.py +104 -0
- metaflow/cards.py +5 -0
- metaflow/cli.py +331 -785
- metaflow/cli_args.py +17 -0
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +96 -0
- metaflow/cli_components/init_cmd.py +52 -0
- metaflow/cli_components/run_cmds.py +546 -0
- metaflow/cli_components/step_cmd.py +334 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +467 -73
- metaflow/client/filecache.py +75 -35
- metaflow/clone_util.py +7 -1
- metaflow/cmd/code/__init__.py +231 -0
- metaflow/cmd/develop/stub_generator.py +756 -288
- metaflow/cmd/develop/stubs.py +12 -28
- metaflow/cmd/main_cli.py +6 -4
- metaflow/cmd/make_wrapper.py +78 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +41 -10
- metaflow/datastore/datastore_set.py +11 -2
- metaflow/datastore/flow_datastore.py +156 -10
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +154 -39
- metaflow/debug.py +5 -0
- metaflow/decorators.py +404 -78
- metaflow/exception.py +8 -2
- metaflow/extension_support/__init__.py +527 -376
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/extension_support/plugins.py +49 -31
- metaflow/flowspec.py +482 -33
- metaflow/graph.py +210 -42
- metaflow/includefile.py +84 -40
- metaflow/lint.py +141 -22
- metaflow/meta_files.py +13 -0
- metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
- metaflow/{metadata → metadata_provider}/metadata.py +86 -1
- metaflow/metaflow_config.py +175 -28
- metaflow/metaflow_config_funcs.py +51 -3
- metaflow/metaflow_current.py +4 -10
- metaflow/metaflow_environment.py +139 -53
- metaflow/metaflow_git.py +115 -0
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +150 -66
- metaflow/mflog/__init__.py +4 -3
- metaflow/mflog/save_logs.py +2 -2
- metaflow/multicore_utils.py +31 -14
- metaflow/package/__init__.py +673 -0
- metaflow/packaging_sys/__init__.py +880 -0
- metaflow/packaging_sys/backend.py +128 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +99 -0
- metaflow/packaging_sys/utils.py +54 -0
- metaflow/packaging_sys/v1.py +527 -0
- metaflow/parameters.py +149 -28
- metaflow/plugins/__init__.py +74 -5
- metaflow/plugins/airflow/airflow.py +40 -25
- metaflow/plugins/airflow/airflow_cli.py +22 -5
- metaflow/plugins/airflow/airflow_decorator.py +1 -1
- metaflow/plugins/airflow/airflow_utils.py +5 -3
- metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
- metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
- metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
- metaflow/plugins/argo/argo_client.py +78 -33
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +2410 -527
- metaflow/plugins/argo/argo_workflows_cli.py +571 -121
- metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
- metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
- metaflow/plugins/argo/capture_error.py +73 -0
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/jobset_input_paths.py +15 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +10 -3
- metaflow/plugins/aws/aws_utils.py +55 -2
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +33 -10
- metaflow/plugins/aws/batch/batch_client.py +4 -3
- metaflow/plugins/aws/batch/batch_decorator.py +102 -35
- metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
- metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
- metaflow/plugins/aws/step_functions/production_token.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions.py +65 -8
- metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
- metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
- metaflow/plugins/azure/azure_exceptions.py +1 -1
- metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
- metaflow/plugins/azure/azure_tail.py +1 -1
- metaflow/plugins/azure/includefile_support.py +2 -0
- metaflow/plugins/cards/card_cli.py +66 -30
- metaflow/plugins/cards/card_creator.py +25 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +132 -8
- metaflow/plugins/cards/card_modules/basic.py +112 -17
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/card.py +16 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +665 -28
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
- metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
- metaflow/plugins/cards/card_modules/main.css +1 -0
- metaflow/plugins/cards/card_modules/main.js +68 -49
- metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
- metaflow/plugins/cards/card_modules/test_cards.py +26 -12
- metaflow/plugins/cards/card_server.py +39 -14
- metaflow/plugins/cards/component_serializer.py +2 -9
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/azure_storage.py +10 -1
- metaflow/plugins/datastores/gs_storage.py +6 -2
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/local.py +2 -0
- metaflow/plugins/datatools/s3/s3.py +126 -75
- metaflow/plugins/datatools/s3/s3op.py +254 -121
- metaflow/plugins/env_escape/__init__.py +3 -3
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/env_escape/server.py +7 -0
- metaflow/plugins/env_escape/stub.py +24 -5
- metaflow/plugins/events_decorator.py +343 -185
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/gcp/__init__.py +1 -1
- metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
- metaflow/plugins/gcp/gs_tail.py +10 -6
- metaflow/plugins/gcp/includefile_support.py +3 -0
- metaflow/plugins/kubernetes/kube_utils.py +108 -0
- metaflow/plugins/kubernetes/kubernetes.py +411 -130
- metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
- metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
- metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
- metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
- metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
- metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
- metaflow/plugins/logs_cli.py +359 -0
- metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
- metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +128 -11
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/project_decorator.py +51 -5
- metaflow/plugins/pypi/bootstrap.py +357 -105
- metaflow/plugins/pypi/conda_decorator.py +82 -81
- metaflow/plugins/pypi/conda_environment.py +187 -52
- metaflow/plugins/pypi/micromamba.py +157 -47
- metaflow/plugins/pypi/parsers.py +268 -0
- metaflow/plugins/pypi/pip.py +88 -13
- metaflow/plugins/pypi/pypi_decorator.py +37 -1
- metaflow/plugins/pypi/utils.py +48 -2
- metaflow/plugins/resources_decorator.py +2 -2
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +26 -181
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/tag_cli.py +4 -7
- metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
- metaflow/plugins/timeout_decorator.py +3 -3
- metaflow/plugins/uv/__init__.py +0 -0
- metaflow/plugins/uv/bootstrap.py +128 -0
- metaflow/plugins/uv/uv_environment.py +72 -0
- metaflow/procpoll.py +1 -1
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/__init__.py +0 -0
- metaflow/runner/click_api.py +717 -0
- metaflow/runner/deployer.py +470 -0
- metaflow/runner/deployer_impl.py +201 -0
- metaflow/runner/metaflow_runner.py +714 -0
- metaflow/runner/nbdeploy.py +132 -0
- metaflow/runner/nbrun.py +225 -0
- metaflow/runner/subprocess_manager.py +650 -0
- metaflow/runner/utils.py +335 -0
- metaflow/runtime.py +1078 -260
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/system/__init__.py +5 -0
- metaflow/system/system_logger.py +85 -0
- metaflow/system/system_monitor.py +108 -0
- metaflow/system/system_utils.py +19 -0
- metaflow/task.py +521 -225
- metaflow/tracing/__init__.py +7 -7
- metaflow/tracing/span_exporter.py +31 -38
- metaflow/tracing/tracing_modules.py +38 -43
- metaflow/tuple_util.py +27 -0
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_options.py +563 -0
- metaflow/user_configs/config_parameters.py +598 -0
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +512 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +264 -0
- metaflow/user_decorators/user_step_decorator.py +749 -0
- metaflow/util.py +243 -27
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
- ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
- ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
- metaflow/_vendor/v3_5/__init__.py +0 -1
- metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
- metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
- metaflow/package.py +0 -188
- ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
- ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
- /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
- /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
- /metaflow/{metadata → metadata_provider}/util.py +0 -0
- /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections.abc
|
|
4
|
+
import inspect
|
|
5
|
+
import sys
|
|
6
|
+
import types
|
|
7
|
+
import typing
|
|
8
|
+
import warnings
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from inspect import Parameter, isclass, isfunction
|
|
11
|
+
from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
|
|
12
|
+
from itertools import zip_longest
|
|
13
|
+
from textwrap import indent
|
|
14
|
+
from typing import (
|
|
15
|
+
IO,
|
|
16
|
+
AbstractSet,
|
|
17
|
+
Any,
|
|
18
|
+
BinaryIO,
|
|
19
|
+
Callable,
|
|
20
|
+
Dict,
|
|
21
|
+
ForwardRef,
|
|
22
|
+
List,
|
|
23
|
+
Mapping,
|
|
24
|
+
MutableMapping,
|
|
25
|
+
NewType,
|
|
26
|
+
Optional,
|
|
27
|
+
Sequence,
|
|
28
|
+
Set,
|
|
29
|
+
TextIO,
|
|
30
|
+
Tuple,
|
|
31
|
+
Type,
|
|
32
|
+
TypeVar,
|
|
33
|
+
Union,
|
|
34
|
+
)
|
|
35
|
+
from unittest.mock import Mock
|
|
36
|
+
|
|
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
|
|
44
|
+
|
|
45
|
+
from ._config import ForwardRefPolicy
|
|
46
|
+
from ._exceptions import TypeCheckError, TypeHintWarning
|
|
47
|
+
from ._memo import TypeCheckMemo
|
|
48
|
+
from ._utils import evaluate_forwardref, get_stacklevel, get_type_name, qualified_name
|
|
49
|
+
|
|
50
|
+
if sys.version_info >= (3, 11):
|
|
51
|
+
from typing import (
|
|
52
|
+
Annotated,
|
|
53
|
+
NotRequired,
|
|
54
|
+
TypeAlias,
|
|
55
|
+
get_args,
|
|
56
|
+
get_origin,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
SubclassableAny = Any
|
|
60
|
+
else:
|
|
61
|
+
from metaflow._vendor.typing_extensions import (
|
|
62
|
+
Annotated,
|
|
63
|
+
NotRequired,
|
|
64
|
+
TypeAlias,
|
|
65
|
+
get_args,
|
|
66
|
+
get_origin,
|
|
67
|
+
)
|
|
68
|
+
from metaflow._vendor.typing_extensions import Any as SubclassableAny
|
|
69
|
+
|
|
70
|
+
if sys.version_info >= (3, 10):
|
|
71
|
+
from importlib.metadata import entry_points
|
|
72
|
+
from typing import ParamSpec
|
|
73
|
+
else:
|
|
74
|
+
from metaflow._vendor.importlib_metadata import entry_points
|
|
75
|
+
from metaflow._vendor.typing_extensions import ParamSpec
|
|
76
|
+
|
|
77
|
+
TypeCheckerCallable: TypeAlias = Callable[
|
|
78
|
+
[Any, Any, Tuple[Any, ...], TypeCheckMemo], Any
|
|
79
|
+
]
|
|
80
|
+
TypeCheckLookupCallback: TypeAlias = Callable[
|
|
81
|
+
[Any, Tuple[Any, ...], Tuple[Any, ...]], Optional[TypeCheckerCallable]
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
checker_lookup_functions: list[TypeCheckLookupCallback] = []
|
|
85
|
+
generic_alias_types: tuple[type, ...] = (type(List), type(List[Any]))
|
|
86
|
+
if sys.version_info >= (3, 9):
|
|
87
|
+
generic_alias_types += (types.GenericAlias,)
|
|
88
|
+
|
|
89
|
+
# Sentinel
|
|
90
|
+
_missing = object()
|
|
91
|
+
|
|
92
|
+
# Lifted from mypy.sharedparse
|
|
93
|
+
BINARY_MAGIC_METHODS = {
|
|
94
|
+
"__add__",
|
|
95
|
+
"__and__",
|
|
96
|
+
"__cmp__",
|
|
97
|
+
"__divmod__",
|
|
98
|
+
"__div__",
|
|
99
|
+
"__eq__",
|
|
100
|
+
"__floordiv__",
|
|
101
|
+
"__ge__",
|
|
102
|
+
"__gt__",
|
|
103
|
+
"__iadd__",
|
|
104
|
+
"__iand__",
|
|
105
|
+
"__idiv__",
|
|
106
|
+
"__ifloordiv__",
|
|
107
|
+
"__ilshift__",
|
|
108
|
+
"__imatmul__",
|
|
109
|
+
"__imod__",
|
|
110
|
+
"__imul__",
|
|
111
|
+
"__ior__",
|
|
112
|
+
"__ipow__",
|
|
113
|
+
"__irshift__",
|
|
114
|
+
"__isub__",
|
|
115
|
+
"__itruediv__",
|
|
116
|
+
"__ixor__",
|
|
117
|
+
"__le__",
|
|
118
|
+
"__lshift__",
|
|
119
|
+
"__lt__",
|
|
120
|
+
"__matmul__",
|
|
121
|
+
"__mod__",
|
|
122
|
+
"__mul__",
|
|
123
|
+
"__ne__",
|
|
124
|
+
"__or__",
|
|
125
|
+
"__pow__",
|
|
126
|
+
"__radd__",
|
|
127
|
+
"__rand__",
|
|
128
|
+
"__rdiv__",
|
|
129
|
+
"__rfloordiv__",
|
|
130
|
+
"__rlshift__",
|
|
131
|
+
"__rmatmul__",
|
|
132
|
+
"__rmod__",
|
|
133
|
+
"__rmul__",
|
|
134
|
+
"__ror__",
|
|
135
|
+
"__rpow__",
|
|
136
|
+
"__rrshift__",
|
|
137
|
+
"__rshift__",
|
|
138
|
+
"__rsub__",
|
|
139
|
+
"__rtruediv__",
|
|
140
|
+
"__rxor__",
|
|
141
|
+
"__sub__",
|
|
142
|
+
"__truediv__",
|
|
143
|
+
"__xor__",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def check_callable(
|
|
148
|
+
value: Any,
|
|
149
|
+
origin_type: Any,
|
|
150
|
+
args: tuple[Any, ...],
|
|
151
|
+
memo: TypeCheckMemo,
|
|
152
|
+
) -> None:
|
|
153
|
+
if not callable(value):
|
|
154
|
+
raise TypeCheckError("is not callable")
|
|
155
|
+
|
|
156
|
+
if args:
|
|
157
|
+
try:
|
|
158
|
+
signature = inspect.signature(value)
|
|
159
|
+
except (TypeError, ValueError):
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
argument_types = args[0]
|
|
163
|
+
if isinstance(argument_types, list) and not any(
|
|
164
|
+
type(item) is ParamSpec for item in argument_types
|
|
165
|
+
):
|
|
166
|
+
# The callable must not have keyword-only arguments without defaults
|
|
167
|
+
unfulfilled_kwonlyargs = [
|
|
168
|
+
param.name
|
|
169
|
+
for param in signature.parameters.values()
|
|
170
|
+
if param.kind == Parameter.KEYWORD_ONLY
|
|
171
|
+
and param.default == Parameter.empty
|
|
172
|
+
]
|
|
173
|
+
if unfulfilled_kwonlyargs:
|
|
174
|
+
raise TypeCheckError(
|
|
175
|
+
f"has mandatory keyword-only arguments in its declaration: "
|
|
176
|
+
f'{", ".join(unfulfilled_kwonlyargs)}'
|
|
177
|
+
)
|
|
178
|
+
|
|
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):
|
|
193
|
+
raise TypeCheckError(
|
|
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"
|
|
197
|
+
)
|
|
198
|
+
elif not has_varargs and num_positional_args < len(argument_types):
|
|
199
|
+
raise TypeCheckError(
|
|
200
|
+
f"has too few arguments in its declaration; expected "
|
|
201
|
+
f"{len(argument_types)} but {num_positional_args} argument(s) "
|
|
202
|
+
f"declared"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def check_mapping(
|
|
207
|
+
value: Any,
|
|
208
|
+
origin_type: Any,
|
|
209
|
+
args: tuple[Any, ...],
|
|
210
|
+
memo: TypeCheckMemo,
|
|
211
|
+
) -> None:
|
|
212
|
+
if origin_type is Dict or origin_type is dict:
|
|
213
|
+
if not isinstance(value, dict):
|
|
214
|
+
raise TypeCheckError("is not a dict")
|
|
215
|
+
if origin_type is MutableMapping or origin_type is collections.abc.MutableMapping:
|
|
216
|
+
if not isinstance(value, collections.abc.MutableMapping):
|
|
217
|
+
raise TypeCheckError("is not a mutable mapping")
|
|
218
|
+
elif not isinstance(value, collections.abc.Mapping):
|
|
219
|
+
raise TypeCheckError("is not a mapping")
|
|
220
|
+
|
|
221
|
+
if args:
|
|
222
|
+
key_type, value_type = args
|
|
223
|
+
if key_type is not Any or value_type is not Any:
|
|
224
|
+
samples = memo.config.collection_check_strategy.iterate_samples(
|
|
225
|
+
value.items()
|
|
226
|
+
)
|
|
227
|
+
for k, v in samples:
|
|
228
|
+
try:
|
|
229
|
+
check_type_internal(k, key_type, memo)
|
|
230
|
+
except TypeCheckError as exc:
|
|
231
|
+
exc.append_path_element(f"key {k!r}")
|
|
232
|
+
raise
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
check_type_internal(v, value_type, memo)
|
|
236
|
+
except TypeCheckError as exc:
|
|
237
|
+
exc.append_path_element(f"value of key {k!r}")
|
|
238
|
+
raise
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def check_typed_dict(
|
|
242
|
+
value: Any,
|
|
243
|
+
origin_type: Any,
|
|
244
|
+
args: tuple[Any, ...],
|
|
245
|
+
memo: TypeCheckMemo,
|
|
246
|
+
) -> None:
|
|
247
|
+
if not isinstance(value, dict):
|
|
248
|
+
raise TypeCheckError("is not a dict")
|
|
249
|
+
|
|
250
|
+
declared_keys = frozenset(origin_type.__annotations__)
|
|
251
|
+
if hasattr(origin_type, "__required_keys__"):
|
|
252
|
+
required_keys = set(origin_type.__required_keys__)
|
|
253
|
+
else: # py3.8 and lower
|
|
254
|
+
required_keys = set(declared_keys) if origin_type.__total__ else set()
|
|
255
|
+
|
|
256
|
+
existing_keys = set(value)
|
|
257
|
+
extra_keys = existing_keys - declared_keys
|
|
258
|
+
if extra_keys:
|
|
259
|
+
keys_formatted = ", ".join(f'"{key}"' for key in sorted(extra_keys, key=repr))
|
|
260
|
+
raise TypeCheckError(f"has unexpected extra key(s): {keys_formatted}")
|
|
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
|
+
|
|
273
|
+
missing_keys = required_keys - existing_keys
|
|
274
|
+
if missing_keys:
|
|
275
|
+
keys_formatted = ", ".join(f'"{key}"' for key in sorted(missing_keys, key=repr))
|
|
276
|
+
raise TypeCheckError(f"is missing required key(s): {keys_formatted}")
|
|
277
|
+
|
|
278
|
+
for key, argtype in type_hints.items():
|
|
279
|
+
argvalue = value.get(key, _missing)
|
|
280
|
+
if argvalue is not _missing:
|
|
281
|
+
try:
|
|
282
|
+
check_type_internal(argvalue, argtype, memo)
|
|
283
|
+
except TypeCheckError as exc:
|
|
284
|
+
exc.append_path_element(f"value of key {key!r}")
|
|
285
|
+
raise
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def check_list(
|
|
289
|
+
value: Any,
|
|
290
|
+
origin_type: Any,
|
|
291
|
+
args: tuple[Any, ...],
|
|
292
|
+
memo: TypeCheckMemo,
|
|
293
|
+
) -> None:
|
|
294
|
+
if not isinstance(value, list):
|
|
295
|
+
raise TypeCheckError("is not a list")
|
|
296
|
+
|
|
297
|
+
if args and args != (Any,):
|
|
298
|
+
samples = memo.config.collection_check_strategy.iterate_samples(value)
|
|
299
|
+
for i, v in enumerate(samples):
|
|
300
|
+
try:
|
|
301
|
+
check_type_internal(v, args[0], memo)
|
|
302
|
+
except TypeCheckError as exc:
|
|
303
|
+
exc.append_path_element(f"item {i}")
|
|
304
|
+
raise
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def check_sequence(
|
|
308
|
+
value: Any,
|
|
309
|
+
origin_type: Any,
|
|
310
|
+
args: tuple[Any, ...],
|
|
311
|
+
memo: TypeCheckMemo,
|
|
312
|
+
) -> None:
|
|
313
|
+
if not isinstance(value, collections.abc.Sequence):
|
|
314
|
+
raise TypeCheckError("is not a sequence")
|
|
315
|
+
|
|
316
|
+
if args and args != (Any,):
|
|
317
|
+
samples = memo.config.collection_check_strategy.iterate_samples(value)
|
|
318
|
+
for i, v in enumerate(samples):
|
|
319
|
+
try:
|
|
320
|
+
check_type_internal(v, args[0], memo)
|
|
321
|
+
except TypeCheckError as exc:
|
|
322
|
+
exc.append_path_element(f"item {i}")
|
|
323
|
+
raise
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def check_set(
|
|
327
|
+
value: Any,
|
|
328
|
+
origin_type: Any,
|
|
329
|
+
args: tuple[Any, ...],
|
|
330
|
+
memo: TypeCheckMemo,
|
|
331
|
+
) -> None:
|
|
332
|
+
if origin_type is frozenset:
|
|
333
|
+
if not isinstance(value, frozenset):
|
|
334
|
+
raise TypeCheckError("is not a frozenset")
|
|
335
|
+
elif not isinstance(value, AbstractSet):
|
|
336
|
+
raise TypeCheckError("is not a set")
|
|
337
|
+
|
|
338
|
+
if args and args != (Any,):
|
|
339
|
+
samples = memo.config.collection_check_strategy.iterate_samples(value)
|
|
340
|
+
for v in samples:
|
|
341
|
+
try:
|
|
342
|
+
check_type_internal(v, args[0], memo)
|
|
343
|
+
except TypeCheckError as exc:
|
|
344
|
+
exc.append_path_element(f"[{v}]")
|
|
345
|
+
raise
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def check_tuple(
|
|
349
|
+
value: Any,
|
|
350
|
+
origin_type: Any,
|
|
351
|
+
args: tuple[Any, ...],
|
|
352
|
+
memo: TypeCheckMemo,
|
|
353
|
+
) -> None:
|
|
354
|
+
# Specialized check for NamedTuples
|
|
355
|
+
if field_types := getattr(origin_type, "__annotations__", None):
|
|
356
|
+
if not isinstance(value, origin_type):
|
|
357
|
+
raise TypeCheckError(
|
|
358
|
+
f"is not a named tuple of type {qualified_name(origin_type)}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
for name, field_type in field_types.items():
|
|
362
|
+
try:
|
|
363
|
+
check_type_internal(getattr(value, name), field_type, memo)
|
|
364
|
+
except TypeCheckError as exc:
|
|
365
|
+
exc.append_path_element(f"attribute {name!r}")
|
|
366
|
+
raise
|
|
367
|
+
|
|
368
|
+
return
|
|
369
|
+
elif not isinstance(value, tuple):
|
|
370
|
+
raise TypeCheckError("is not a tuple")
|
|
371
|
+
|
|
372
|
+
if args:
|
|
373
|
+
use_ellipsis = args[-1] is Ellipsis
|
|
374
|
+
tuple_params = args[: -1 if use_ellipsis else None]
|
|
375
|
+
else:
|
|
376
|
+
# Unparametrized Tuple or plain tuple
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
if use_ellipsis:
|
|
380
|
+
element_type = tuple_params[0]
|
|
381
|
+
samples = memo.config.collection_check_strategy.iterate_samples(value)
|
|
382
|
+
for i, element in enumerate(samples):
|
|
383
|
+
try:
|
|
384
|
+
check_type_internal(element, element_type, memo)
|
|
385
|
+
except TypeCheckError as exc:
|
|
386
|
+
exc.append_path_element(f"item {i}")
|
|
387
|
+
raise
|
|
388
|
+
elif tuple_params == ((),):
|
|
389
|
+
if value != ():
|
|
390
|
+
raise TypeCheckError("is not an empty tuple")
|
|
391
|
+
else:
|
|
392
|
+
if len(value) != len(tuple_params):
|
|
393
|
+
raise TypeCheckError(
|
|
394
|
+
f"has wrong number of elements (expected {len(tuple_params)}, got "
|
|
395
|
+
f"{len(value)} instead)"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
for i, (element, element_type) in enumerate(zip(value, tuple_params)):
|
|
399
|
+
try:
|
|
400
|
+
check_type_internal(element, element_type, memo)
|
|
401
|
+
except TypeCheckError as exc:
|
|
402
|
+
exc.append_path_element(f"item {i}")
|
|
403
|
+
raise
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def check_union(
|
|
407
|
+
value: Any,
|
|
408
|
+
origin_type: Any,
|
|
409
|
+
args: tuple[Any, ...],
|
|
410
|
+
memo: TypeCheckMemo,
|
|
411
|
+
) -> None:
|
|
412
|
+
errors: dict[str, TypeCheckError] = {}
|
|
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
|
|
420
|
+
|
|
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
|
|
426
|
+
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def check_uniontype(
|
|
430
|
+
value: Any,
|
|
431
|
+
origin_type: Any,
|
|
432
|
+
args: tuple[Any, ...],
|
|
433
|
+
memo: TypeCheckMemo,
|
|
434
|
+
) -> None:
|
|
435
|
+
errors: dict[str, TypeCheckError] = {}
|
|
436
|
+
for type_ in args:
|
|
437
|
+
try:
|
|
438
|
+
check_type_internal(value, type_, memo)
|
|
439
|
+
return
|
|
440
|
+
except TypeCheckError as exc:
|
|
441
|
+
errors[get_type_name(type_)] = exc
|
|
442
|
+
|
|
443
|
+
formatted_errors = indent(
|
|
444
|
+
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
|
|
445
|
+
)
|
|
446
|
+
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def check_class(
|
|
450
|
+
value: Any,
|
|
451
|
+
origin_type: Any,
|
|
452
|
+
args: tuple[Any, ...],
|
|
453
|
+
memo: TypeCheckMemo,
|
|
454
|
+
) -> None:
|
|
455
|
+
if not isclass(value) and not isinstance(value, generic_alias_types):
|
|
456
|
+
raise TypeCheckError("is not a class")
|
|
457
|
+
|
|
458
|
+
if not args:
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
if isinstance(args[0], ForwardRef):
|
|
462
|
+
expected_class = evaluate_forwardref(args[0], memo)
|
|
463
|
+
else:
|
|
464
|
+
expected_class = args[0]
|
|
465
|
+
|
|
466
|
+
if expected_class is Any:
|
|
467
|
+
return
|
|
468
|
+
elif getattr(expected_class, "_is_protocol", False):
|
|
469
|
+
check_protocol(value, expected_class, (), memo)
|
|
470
|
+
elif isinstance(expected_class, TypeVar):
|
|
471
|
+
check_typevar(value, expected_class, (), memo, subclass_check=True)
|
|
472
|
+
elif get_origin(expected_class) is Union:
|
|
473
|
+
errors: dict[str, TypeCheckError] = {}
|
|
474
|
+
for arg in get_args(expected_class):
|
|
475
|
+
if arg is Any:
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
check_class(value, type, (arg,), memo)
|
|
480
|
+
return
|
|
481
|
+
except TypeCheckError as exc:
|
|
482
|
+
errors[get_type_name(arg)] = exc
|
|
483
|
+
else:
|
|
484
|
+
formatted_errors = indent(
|
|
485
|
+
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
|
|
486
|
+
)
|
|
487
|
+
raise TypeCheckError(
|
|
488
|
+
f"did not match any element in the union:\n{formatted_errors}"
|
|
489
|
+
)
|
|
490
|
+
elif not issubclass(value, expected_class): # type: ignore[arg-type]
|
|
491
|
+
raise TypeCheckError(f"is not a subclass of {qualified_name(expected_class)}")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def check_newtype(
|
|
495
|
+
value: Any,
|
|
496
|
+
origin_type: Any,
|
|
497
|
+
args: tuple[Any, ...],
|
|
498
|
+
memo: TypeCheckMemo,
|
|
499
|
+
) -> None:
|
|
500
|
+
check_type_internal(value, origin_type.__supertype__, memo)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def check_instance(
|
|
504
|
+
value: Any,
|
|
505
|
+
origin_type: Any,
|
|
506
|
+
args: tuple[Any, ...],
|
|
507
|
+
memo: TypeCheckMemo,
|
|
508
|
+
) -> None:
|
|
509
|
+
if not isinstance(value, origin_type):
|
|
510
|
+
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def check_typevar(
|
|
514
|
+
value: Any,
|
|
515
|
+
origin_type: TypeVar,
|
|
516
|
+
args: tuple[Any, ...],
|
|
517
|
+
memo: TypeCheckMemo,
|
|
518
|
+
*,
|
|
519
|
+
subclass_check: bool = False,
|
|
520
|
+
) -> None:
|
|
521
|
+
if origin_type.__bound__ is not None:
|
|
522
|
+
annotation = (
|
|
523
|
+
Type[origin_type.__bound__] if subclass_check else origin_type.__bound__
|
|
524
|
+
)
|
|
525
|
+
check_type_internal(value, annotation, memo)
|
|
526
|
+
elif origin_type.__constraints__:
|
|
527
|
+
for constraint in origin_type.__constraints__:
|
|
528
|
+
annotation = Type[constraint] if subclass_check else constraint
|
|
529
|
+
try:
|
|
530
|
+
check_type_internal(value, annotation, memo)
|
|
531
|
+
except TypeCheckError:
|
|
532
|
+
pass
|
|
533
|
+
else:
|
|
534
|
+
break
|
|
535
|
+
else:
|
|
536
|
+
formatted_constraints = ", ".join(
|
|
537
|
+
get_type_name(constraint) for constraint in origin_type.__constraints__
|
|
538
|
+
)
|
|
539
|
+
raise TypeCheckError(
|
|
540
|
+
f"does not match any of the constraints " f"({formatted_constraints})"
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _is_literal_type(typ: object) -> bool:
|
|
545
|
+
return typ is typing.Literal or typ is typing_extensions.Literal
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def check_literal(
|
|
549
|
+
value: Any,
|
|
550
|
+
origin_type: Any,
|
|
551
|
+
args: tuple[Any, ...],
|
|
552
|
+
memo: TypeCheckMemo,
|
|
553
|
+
) -> None:
|
|
554
|
+
def get_literal_args(literal_args: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
555
|
+
retval: list[Any] = []
|
|
556
|
+
for arg in literal_args:
|
|
557
|
+
if _is_literal_type(get_origin(arg)):
|
|
558
|
+
retval.extend(get_literal_args(arg.__args__))
|
|
559
|
+
elif arg is None or isinstance(arg, (int, str, bytes, bool, Enum)):
|
|
560
|
+
retval.append(arg)
|
|
561
|
+
else:
|
|
562
|
+
raise TypeError(
|
|
563
|
+
f"Illegal literal value: {arg}"
|
|
564
|
+
) # TypeError here is deliberate
|
|
565
|
+
|
|
566
|
+
return tuple(retval)
|
|
567
|
+
|
|
568
|
+
final_args = tuple(get_literal_args(args))
|
|
569
|
+
try:
|
|
570
|
+
index = final_args.index(value)
|
|
571
|
+
except ValueError:
|
|
572
|
+
pass
|
|
573
|
+
else:
|
|
574
|
+
if type(final_args[index]) is type(value):
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
formatted_args = ", ".join(repr(arg) for arg in final_args)
|
|
578
|
+
raise TypeCheckError(f"is not any of ({formatted_args})") from None
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def check_literal_string(
|
|
582
|
+
value: Any,
|
|
583
|
+
origin_type: Any,
|
|
584
|
+
args: tuple[Any, ...],
|
|
585
|
+
memo: TypeCheckMemo,
|
|
586
|
+
) -> None:
|
|
587
|
+
check_type_internal(value, str, memo)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def check_typeguard(
|
|
591
|
+
value: Any,
|
|
592
|
+
origin_type: Any,
|
|
593
|
+
args: tuple[Any, ...],
|
|
594
|
+
memo: TypeCheckMemo,
|
|
595
|
+
) -> None:
|
|
596
|
+
check_type_internal(value, bool, memo)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def check_none(
|
|
600
|
+
value: Any,
|
|
601
|
+
origin_type: Any,
|
|
602
|
+
args: tuple[Any, ...],
|
|
603
|
+
memo: TypeCheckMemo,
|
|
604
|
+
) -> None:
|
|
605
|
+
if value is not None:
|
|
606
|
+
raise TypeCheckError("is not None")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def check_number(
|
|
610
|
+
value: Any,
|
|
611
|
+
origin_type: Any,
|
|
612
|
+
args: tuple[Any, ...],
|
|
613
|
+
memo: TypeCheckMemo,
|
|
614
|
+
) -> None:
|
|
615
|
+
if origin_type is complex and not isinstance(value, (complex, float, int)):
|
|
616
|
+
raise TypeCheckError("is neither complex, float or int")
|
|
617
|
+
elif origin_type is float and not isinstance(value, (float, int)):
|
|
618
|
+
raise TypeCheckError("is neither float or int")
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def check_io(
|
|
622
|
+
value: Any,
|
|
623
|
+
origin_type: Any,
|
|
624
|
+
args: tuple[Any, ...],
|
|
625
|
+
memo: TypeCheckMemo,
|
|
626
|
+
) -> None:
|
|
627
|
+
if origin_type is TextIO or (origin_type is IO and args == (str,)):
|
|
628
|
+
if not isinstance(value, TextIOBase):
|
|
629
|
+
raise TypeCheckError("is not a text based I/O object")
|
|
630
|
+
elif origin_type is BinaryIO or (origin_type is IO and args == (bytes,)):
|
|
631
|
+
if not isinstance(value, (RawIOBase, BufferedIOBase)):
|
|
632
|
+
raise TypeCheckError("is not a binary I/O object")
|
|
633
|
+
elif not isinstance(value, IOBase):
|
|
634
|
+
raise TypeCheckError("is not an I/O object")
|
|
635
|
+
|
|
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
|
+
|
|
779
|
+
def check_protocol(
|
|
780
|
+
value: Any,
|
|
781
|
+
origin_type: Any,
|
|
782
|
+
args: tuple[Any, ...],
|
|
783
|
+
memo: TypeCheckMemo,
|
|
784
|
+
) -> None:
|
|
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
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def check_byteslike(
|
|
830
|
+
value: Any,
|
|
831
|
+
origin_type: Any,
|
|
832
|
+
args: tuple[Any, ...],
|
|
833
|
+
memo: TypeCheckMemo,
|
|
834
|
+
) -> None:
|
|
835
|
+
if not isinstance(value, (bytearray, bytes, memoryview)):
|
|
836
|
+
raise TypeCheckError("is not bytes-like")
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def check_self(
|
|
840
|
+
value: Any,
|
|
841
|
+
origin_type: Any,
|
|
842
|
+
args: tuple[Any, ...],
|
|
843
|
+
memo: TypeCheckMemo,
|
|
844
|
+
) -> None:
|
|
845
|
+
if memo.self_type is None:
|
|
846
|
+
raise TypeCheckError("cannot be checked against Self outside of a method call")
|
|
847
|
+
|
|
848
|
+
if isclass(value):
|
|
849
|
+
if not issubclass(value, memo.self_type):
|
|
850
|
+
raise TypeCheckError(
|
|
851
|
+
f"is not an instance of the self type "
|
|
852
|
+
f"({qualified_name(memo.self_type)})"
|
|
853
|
+
)
|
|
854
|
+
elif not isinstance(value, memo.self_type):
|
|
855
|
+
raise TypeCheckError(
|
|
856
|
+
f"is not an instance of the self type ({qualified_name(memo.self_type)})"
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def check_paramspec(
|
|
861
|
+
value: Any,
|
|
862
|
+
origin_type: Any,
|
|
863
|
+
args: tuple[Any, ...],
|
|
864
|
+
memo: TypeCheckMemo,
|
|
865
|
+
) -> None:
|
|
866
|
+
pass # No-op for now
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def check_instanceof(
|
|
870
|
+
value: Any,
|
|
871
|
+
origin_type: Any,
|
|
872
|
+
args: tuple[Any, ...],
|
|
873
|
+
memo: TypeCheckMemo,
|
|
874
|
+
) -> None:
|
|
875
|
+
if not isinstance(value, origin_type):
|
|
876
|
+
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def check_type_internal(
|
|
880
|
+
value: Any,
|
|
881
|
+
annotation: Any,
|
|
882
|
+
memo: TypeCheckMemo,
|
|
883
|
+
) -> None:
|
|
884
|
+
"""
|
|
885
|
+
Check that the given object is compatible with the given type annotation.
|
|
886
|
+
|
|
887
|
+
This function should only be used by type checker callables. Applications should use
|
|
888
|
+
:func:`~.check_type` instead.
|
|
889
|
+
|
|
890
|
+
:param value: the value to check
|
|
891
|
+
:param annotation: the type annotation to check against
|
|
892
|
+
:param memo: a memo object containing configuration and information necessary for
|
|
893
|
+
looking up forward references
|
|
894
|
+
"""
|
|
895
|
+
|
|
896
|
+
if isinstance(annotation, ForwardRef):
|
|
897
|
+
try:
|
|
898
|
+
annotation = evaluate_forwardref(annotation, memo)
|
|
899
|
+
except NameError:
|
|
900
|
+
if memo.config.forward_ref_policy is ForwardRefPolicy.ERROR:
|
|
901
|
+
raise
|
|
902
|
+
elif memo.config.forward_ref_policy is ForwardRefPolicy.WARN:
|
|
903
|
+
warnings.warn(
|
|
904
|
+
f"Cannot resolve forward reference {annotation.__forward_arg__!r}",
|
|
905
|
+
TypeHintWarning,
|
|
906
|
+
stacklevel=get_stacklevel(),
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
return
|
|
910
|
+
|
|
911
|
+
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
|
|
912
|
+
return
|
|
913
|
+
|
|
914
|
+
# Skip type checks if value is an instance of a class that inherits from Any
|
|
915
|
+
if not isclass(value) and SubclassableAny in type(value).__bases__:
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
extras: tuple[Any, ...]
|
|
919
|
+
origin_type = get_origin(annotation)
|
|
920
|
+
if origin_type is Annotated:
|
|
921
|
+
annotation, *extras_ = get_args(annotation)
|
|
922
|
+
extras = tuple(extras_)
|
|
923
|
+
origin_type = get_origin(annotation)
|
|
924
|
+
else:
|
|
925
|
+
extras = ()
|
|
926
|
+
|
|
927
|
+
if origin_type is not None:
|
|
928
|
+
args = get_args(annotation)
|
|
929
|
+
|
|
930
|
+
# Compatibility hack to distinguish between unparametrized and empty tuple
|
|
931
|
+
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
|
|
932
|
+
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
|
|
933
|
+
args = ((),)
|
|
934
|
+
else:
|
|
935
|
+
origin_type = annotation
|
|
936
|
+
args = ()
|
|
937
|
+
|
|
938
|
+
for lookup_func in checker_lookup_functions:
|
|
939
|
+
checker = lookup_func(origin_type, args, extras)
|
|
940
|
+
if checker:
|
|
941
|
+
checker(value, origin_type, args, memo)
|
|
942
|
+
return
|
|
943
|
+
|
|
944
|
+
if isclass(origin_type):
|
|
945
|
+
if not isinstance(value, origin_type):
|
|
946
|
+
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
|
|
947
|
+
elif type(origin_type) is str: # noqa: E721
|
|
948
|
+
warnings.warn(
|
|
949
|
+
f"Skipping type check against {origin_type!r}; this looks like a "
|
|
950
|
+
f"string-form forward reference imported from another module",
|
|
951
|
+
TypeHintWarning,
|
|
952
|
+
stacklevel=get_stacklevel(),
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
# Equality checks are applied to these
|
|
957
|
+
origin_type_checkers = {
|
|
958
|
+
bytes: check_byteslike,
|
|
959
|
+
AbstractSet: check_set,
|
|
960
|
+
BinaryIO: check_io,
|
|
961
|
+
Callable: check_callable,
|
|
962
|
+
collections.abc.Callable: check_callable,
|
|
963
|
+
complex: check_number,
|
|
964
|
+
dict: check_mapping,
|
|
965
|
+
Dict: check_mapping,
|
|
966
|
+
float: check_number,
|
|
967
|
+
frozenset: check_set,
|
|
968
|
+
IO: check_io,
|
|
969
|
+
list: check_list,
|
|
970
|
+
List: check_list,
|
|
971
|
+
typing.Literal: check_literal,
|
|
972
|
+
Mapping: check_mapping,
|
|
973
|
+
MutableMapping: check_mapping,
|
|
974
|
+
None: check_none,
|
|
975
|
+
collections.abc.Mapping: check_mapping,
|
|
976
|
+
collections.abc.MutableMapping: check_mapping,
|
|
977
|
+
Sequence: check_sequence,
|
|
978
|
+
collections.abc.Sequence: check_sequence,
|
|
979
|
+
collections.abc.Set: check_set,
|
|
980
|
+
set: check_set,
|
|
981
|
+
Set: check_set,
|
|
982
|
+
TextIO: check_io,
|
|
983
|
+
tuple: check_tuple,
|
|
984
|
+
Tuple: check_tuple,
|
|
985
|
+
type: check_class,
|
|
986
|
+
Type: check_class,
|
|
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,
|
|
995
|
+
}
|
|
996
|
+
if sys.version_info >= (3, 10):
|
|
997
|
+
origin_type_checkers[types.UnionType] = check_uniontype
|
|
998
|
+
origin_type_checkers[typing.TypeGuard] = check_typeguard
|
|
999
|
+
if sys.version_info >= (3, 11):
|
|
1000
|
+
origin_type_checkers.update(
|
|
1001
|
+
{typing.LiteralString: check_literal_string, typing.Self: check_self}
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def builtin_checker_lookup(
|
|
1006
|
+
origin_type: Any, args: tuple[Any, ...], extras: tuple[Any, ...]
|
|
1007
|
+
) -> TypeCheckerCallable | None:
|
|
1008
|
+
checker = origin_type_checkers.get(origin_type)
|
|
1009
|
+
if checker is not None:
|
|
1010
|
+
return checker
|
|
1011
|
+
elif is_typeddict(origin_type):
|
|
1012
|
+
return check_typed_dict
|
|
1013
|
+
elif isclass(origin_type) and issubclass(
|
|
1014
|
+
origin_type,
|
|
1015
|
+
Tuple, # type: ignore[arg-type]
|
|
1016
|
+
):
|
|
1017
|
+
# NamedTuple
|
|
1018
|
+
return check_tuple
|
|
1019
|
+
elif getattr(origin_type, "_is_protocol", False):
|
|
1020
|
+
return check_protocol
|
|
1021
|
+
elif isinstance(origin_type, ParamSpec):
|
|
1022
|
+
return check_paramspec
|
|
1023
|
+
elif isinstance(origin_type, TypeVar):
|
|
1024
|
+
return check_typevar
|
|
1025
|
+
elif origin_type.__class__ is NewType:
|
|
1026
|
+
# typing.NewType on Python 3.10+
|
|
1027
|
+
return check_newtype
|
|
1028
|
+
elif (
|
|
1029
|
+
isfunction(origin_type)
|
|
1030
|
+
and getattr(origin_type, "__module__", None) == "typing"
|
|
1031
|
+
and getattr(origin_type, "__qualname__", "").startswith("NewType.")
|
|
1032
|
+
and hasattr(origin_type, "__supertype__")
|
|
1033
|
+
):
|
|
1034
|
+
# typing.NewType on Python 3.9 and below
|
|
1035
|
+
return check_newtype
|
|
1036
|
+
|
|
1037
|
+
return None
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
checker_lookup_functions.append(builtin_checker_lookup)
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def load_plugins() -> None:
|
|
1044
|
+
"""
|
|
1045
|
+
Load all type checker lookup functions from entry points.
|
|
1046
|
+
|
|
1047
|
+
All entry points from the ``typeguard.checker_lookup`` group are loaded, and the
|
|
1048
|
+
returned lookup functions are added to :data:`typeguard.checker_lookup_functions`.
|
|
1049
|
+
|
|
1050
|
+
.. note:: This function is called implicitly on import, unless the
|
|
1051
|
+
``TYPEGUARD_DISABLE_PLUGIN_AUTOLOAD`` environment variable is present.
|
|
1052
|
+
"""
|
|
1053
|
+
|
|
1054
|
+
for ep in entry_points(group="typeguard.checker_lookup"):
|
|
1055
|
+
try:
|
|
1056
|
+
plugin = ep.load()
|
|
1057
|
+
except Exception as exc:
|
|
1058
|
+
warnings.warn(
|
|
1059
|
+
f"Failed to load plugin {ep.name!r}: " f"{qualified_name(exc)}: {exc}",
|
|
1060
|
+
stacklevel=2,
|
|
1061
|
+
)
|
|
1062
|
+
continue
|
|
1063
|
+
|
|
1064
|
+
if not callable(plugin):
|
|
1065
|
+
warnings.warn(
|
|
1066
|
+
f"Plugin {ep} returned a non-callable object: {plugin!r}", stacklevel=2
|
|
1067
|
+
)
|
|
1068
|
+
continue
|
|
1069
|
+
|
|
1070
|
+
checker_lookup_functions.insert(0, plugin)
|