From a7fa89d88c24474899805acf33a507de3a0ac6a6 Mon Sep 17 00:00:00 2001 From: zjgemi Date: Tue, 24 Oct 2023 09:58:06 +0800 Subject: [PATCH 1/4] fix: vendor typeguard==2.13.3 fix: add unittest to github actions Signed-off-by: zjgemi --- .github/workflows/ci-test.yml | 21 + requirements.txt | 1 - setup.py | 1 - .../python/_vendor/typeguard/__init__.py | 1258 +++++++++++++++++ .../python/_vendor/typeguard/importhook.py | 162 +++ src/dflow/python/_vendor/typeguard/py.typed | 0 .../python/_vendor/typeguard/pytest_plugin.py | 30 + src/dflow/python/op.py | 3 +- src/dflow/python/python_op_template.py | 5 +- src/dflow/util_ops.py | 2 - 10 files changed, 1473 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci-test.yml create mode 100644 src/dflow/python/_vendor/typeguard/__init__.py create mode 100644 src/dflow/python/_vendor/typeguard/importhook.py create mode 100644 src/dflow/python/_vendor/typeguard/py.typed create mode 100644 src/dflow/python/_vendor/typeguard/pytest_plugin.py diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml new file mode 100644 index 00000000..b0fa9e2d --- /dev/null +++ b/.github/workflows/ci-test.yml @@ -0,0 +1,21 @@ +name: Python unit-tests + +on: + - pull_request + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@master + with: + python-version: 3.8 + - name: Install dependencies + run: | + pip install . + pip install pytest + - name: Test + run: DFLOW_MODE=debug pytest -vs tests diff --git a/requirements.txt b/requirements.txt index 26635762..6a876b7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ six python-dateutil urllib3 certifi -typeguard==2.13.3 argo-workflows==5.0.0 jsonpickle minio diff --git a/setup.py b/setup.py index b9fa325d..bc3c6146 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ "python-dateutil", "urllib3", "certifi", - "typeguard==2.13.3", "argo-workflows==5.0.0", "jsonpickle", "minio", diff --git a/src/dflow/python/_vendor/typeguard/__init__.py b/src/dflow/python/_vendor/typeguard/__init__.py new file mode 100644 index 00000000..5684d638 --- /dev/null +++ b/src/dflow/python/_vendor/typeguard/__init__.py @@ -0,0 +1,1258 @@ +__all__ = ('ForwardRefPolicy', 'TypeHintWarning', 'typechecked', 'check_return_type', + 'check_argument_types', 'check_type', 'TypeWarning', 'TypeChecker', + 'typeguard_ignore') + +import collections.abc +import gc +import inspect +import sys +import threading +from collections import OrderedDict +from enum import Enum +from functools import partial, wraps +from inspect import Parameter, isclass, isfunction, isgeneratorfunction +from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase +from traceback import extract_stack, print_stack +from types import CodeType, FunctionType +from typing import ( + IO, TYPE_CHECKING, AbstractSet, Any, AsyncIterable, AsyncIterator, BinaryIO, Callable, Dict, + Generator, Iterable, Iterator, List, NewType, Optional, Sequence, Set, TextIO, Tuple, Type, + TypeVar, Union, get_type_hints, overload) +from unittest.mock import Mock +from warnings import warn +from weakref import WeakKeyDictionary, WeakValueDictionary + +# Python 3.8+ +try: + from typing_extensions import Literal +except ImportError: + try: + from typing import Literal + except ImportError: + Literal = None + +# Python 3.5.4+ / 3.6.2+ +try: + from typing_extensions import NoReturn +except ImportError: + try: + from typing import NoReturn + except ImportError: + NoReturn = None + +# Python 3.6+ +try: + from inspect import isasyncgen, isasyncgenfunction + from typing import AsyncGenerator +except ImportError: + AsyncGenerator = None + + def isasyncgen(obj): + return False + + def isasyncgenfunction(func): + return False + +# Python 3.8+ +try: + from typing import ForwardRef + evaluate_forwardref = ForwardRef._evaluate +except ImportError: + from typing import _ForwardRef as ForwardRef + evaluate_forwardref = ForwardRef._eval_type + +if sys.version_info >= (3, 10): + from typing import is_typeddict +else: + _typed_dict_meta_types = () + if sys.version_info >= (3, 8): + from typing import _TypedDictMeta + _typed_dict_meta_types += (_TypedDictMeta,) + + try: + from typing_extensions import _TypedDictMeta + _typed_dict_meta_types += (_TypedDictMeta,) + except ImportError: + pass + + def is_typeddict(tp) -> bool: + return isinstance(tp, _typed_dict_meta_types) + + +if TYPE_CHECKING: + _F = TypeVar("_F") + + def typeguard_ignore(f: _F) -> _F: + """This decorator is a noop during static type-checking.""" + return f +else: + from typing import no_type_check as typeguard_ignore + + +_type_hints_map = WeakKeyDictionary() # type: Dict[FunctionType, Dict[str, Any]] +_functions_map = WeakValueDictionary() # type: Dict[CodeType, FunctionType] +_missing = object() + +T_CallableOrType = TypeVar('T_CallableOrType', bound=Callable[..., Any]) + +# Lifted from mypy.sharedparse +BINARY_MAGIC_METHODS = { + "__add__", + "__and__", + "__cmp__", + "__divmod__", + "__div__", + "__eq__", + "__floordiv__", + "__ge__", + "__gt__", + "__iadd__", + "__iand__", + "__idiv__", + "__ifloordiv__", + "__ilshift__", + "__imatmul__", + "__imod__", + "__imul__", + "__ior__", + "__ipow__", + "__irshift__", + "__isub__", + "__itruediv__", + "__ixor__", + "__le__", + "__lshift__", + "__lt__", + "__matmul__", + "__mod__", + "__mul__", + "__ne__", + "__or__", + "__pow__", + "__radd__", + "__rand__", + "__rdiv__", + "__rfloordiv__", + "__rlshift__", + "__rmatmul__", + "__rmod__", + "__rmul__", + "__ror__", + "__rpow__", + "__rrshift__", + "__rshift__", + "__rsub__", + "__rtruediv__", + "__rxor__", + "__sub__", + "__truediv__", + "__xor__", +} + + +class ForwardRefPolicy(Enum): + """Defines how unresolved forward references are handled.""" + + ERROR = 1 #: propagate the :exc:`NameError` from :func:`~typing.get_type_hints` + WARN = 2 #: remove the annotation and emit a TypeHintWarning + #: replace the annotation with the argument's class if the qualified name matches, else remove + #: the annotation + GUESS = 3 + + +class TypeHintWarning(UserWarning): + """ + A warning that is emitted when a type hint in string form could not be resolved to an actual + type. + """ + + +class _TypeCheckMemo: + __slots__ = 'globals', 'locals' + + def __init__(self, globals: Dict[str, Any], locals: Dict[str, Any]): + self.globals = globals + self.locals = locals + + +def _strip_annotation(annotation): + if isinstance(annotation, str): + return annotation.strip("'") + else: + return annotation + + +class _CallMemo(_TypeCheckMemo): + __slots__ = 'func', 'func_name', 'arguments', 'is_generator', 'type_hints' + + def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None, + args: tuple = None, kwargs: Dict[str, Any] = None, + forward_refs_policy=ForwardRefPolicy.ERROR): + super().__init__(func.__globals__, frame_locals) + self.func = func + self.func_name = function_name(func) + self.is_generator = isgeneratorfunction(func) + signature = inspect.signature(func) + + if args is not None and kwargs is not None: + self.arguments = signature.bind(*args, **kwargs).arguments + else: + assert frame_locals is not None, 'frame must be specified if args or kwargs is None' + self.arguments = frame_locals + + self.type_hints = _type_hints_map.get(func) + if self.type_hints is None: + while True: + if sys.version_info < (3, 5, 3): + frame_locals = dict(frame_locals) + + try: + hints = get_type_hints(func, localns=frame_locals) + except NameError as exc: + if forward_refs_policy is ForwardRefPolicy.ERROR: + raise + + typename = str(exc).split("'", 2)[1] + for param in signature.parameters.values(): + if _strip_annotation(param.annotation) == typename: + break + else: + raise + + func_name = function_name(func) + if forward_refs_policy is ForwardRefPolicy.GUESS: + if param.name in self.arguments: + argtype = self.arguments[param.name].__class__ + stripped = _strip_annotation(param.annotation) + if stripped == argtype.__qualname__: + func.__annotations__[param.name] = argtype + msg = ('Replaced forward declaration {!r} in {} with {!r}' + .format(stripped, func_name, argtype)) + warn(TypeHintWarning(msg)) + continue + + msg = 'Could not resolve type hint {!r} on {}: {}'.format( + param.annotation, function_name(func), exc) + warn(TypeHintWarning(msg)) + del func.__annotations__[param.name] + else: + break + + self.type_hints = OrderedDict() + for name, parameter in signature.parameters.items(): + if name in hints: + annotated_type = hints[name] + + # PEP 428 discourages it by MyPy does not complain + if parameter.default is None: + annotated_type = Optional[annotated_type] + + if parameter.kind == Parameter.VAR_POSITIONAL: + self.type_hints[name] = Tuple[annotated_type, ...] + elif parameter.kind == Parameter.VAR_KEYWORD: + self.type_hints[name] = Dict[str, annotated_type] + else: + self.type_hints[name] = annotated_type + + if 'return' in hints: + self.type_hints['return'] = hints['return'] + + _type_hints_map[func] = self.type_hints + + +def resolve_forwardref(maybe_ref, memo: _TypeCheckMemo): + if isinstance(maybe_ref, ForwardRef): + if sys.version_info < (3, 9, 0): + return evaluate_forwardref(maybe_ref, memo.globals, memo.locals) + else: + return evaluate_forwardref(maybe_ref, memo.globals, memo.locals, frozenset()) + + else: + return maybe_ref + + +def get_type_name(type_): + name = (getattr(type_, '__name__', None) or getattr(type_, '_name', None) or + getattr(type_, '__forward_arg__', None)) + if name is None: + origin = getattr(type_, '__origin__', None) + name = getattr(origin, '_name', None) + if name is None and not inspect.isclass(type_): + name = type_.__class__.__name__.strip('_') + + args = getattr(type_, '__args__', ()) or getattr(type_, '__values__', ()) + if args != getattr(type_, '__parameters__', ()): + if name == 'Literal': + formatted_args = ', '.join(str(arg) for arg in args) + else: + formatted_args = ', '.join(get_type_name(arg) for arg in args) + + name = '{}[{}]'.format(name, formatted_args) + + module = getattr(type_, '__module__', None) + if module not in (None, 'typing', 'typing_extensions', 'builtins'): + name = module + '.' + name + + return name + + +def find_function(frame) -> Optional[Callable]: + """ + Return a function object from the garbage collector that matches the frame's code object. + + This process is unreliable as several function objects could use the same code object. + Fortunately the likelihood of this happening with the combination of the function objects + having different type annotations is a very rare occurrence. + + :param frame: a frame object + :return: a function object if one was found, ``None`` if not + + """ + func = _functions_map.get(frame.f_code) + if func is None: + for obj in gc.get_referrers(frame.f_code): + if inspect.isfunction(obj): + if func is None: + # The first match was found + func = obj + else: + # A second match was found + return None + + # Cache the result for future lookups + if func is not None: + _functions_map[frame.f_code] = func + else: + raise LookupError('target function not found') + + return func + + +def qualified_name(obj) -> str: + """ + Return the qualified name (e.g. package.module.Type) for the given object. + + Builtins and types from the :mod:`typing` package get special treatment by having the module + name stripped from the generated name. + + """ + type_ = obj if inspect.isclass(obj) else type(obj) + module = type_.__module__ + qualname = type_.__qualname__ + return qualname if module in ('typing', 'builtins') else '{}.{}'.format(module, qualname) + + +def function_name(func: Callable) -> str: + """ + Return the qualified name of the given function. + + Builtins and types from the :mod:`typing` package get special treatment by having the module + name stripped from the generated name. + + """ + # For partial functions and objects with __call__ defined, __qualname__ does not exist + # For functions run in `exec` with a custom namespace, __module__ can be None + module = getattr(func, '__module__', '') or '' + qualname = (module + '.') if module not in ('builtins', '') else '' + return qualname + getattr(func, '__qualname__', repr(func)) + + +def check_callable(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not callable(value): + raise TypeError('{} must be a callable'.format(argname)) + + if getattr(expected_type, "__args__", None): + try: + signature = inspect.signature(value) + except (TypeError, ValueError): + return + + if hasattr(expected_type, '__result__'): + # Python 3.5 + argument_types = expected_type.__args__ + check_args = argument_types is not Ellipsis + else: + # Python 3.6 + argument_types = expected_type.__args__[:-1] + check_args = argument_types != (Ellipsis,) + + if check_args: + # The callable must not have keyword-only arguments without defaults + unfulfilled_kwonlyargs = [ + param.name for param in signature.parameters.values() if + param.kind == Parameter.KEYWORD_ONLY and param.default == Parameter.empty] + if unfulfilled_kwonlyargs: + raise TypeError( + 'callable passed as {} has mandatory keyword-only arguments in its ' + 'declaration: {}'.format(argname, ', '.join(unfulfilled_kwonlyargs))) + + num_mandatory_args = len([ + param.name for param in signature.parameters.values() + if param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) and + param.default is Parameter.empty]) + has_varargs = any(param for param in signature.parameters.values() + if param.kind == Parameter.VAR_POSITIONAL) + + if num_mandatory_args > len(argument_types): + raise TypeError( + 'callable passed as {} has too many arguments in its declaration; expected {} ' + 'but {} argument(s) declared'.format(argname, len(argument_types), + num_mandatory_args)) + elif not has_varargs and num_mandatory_args < len(argument_types): + raise TypeError( + 'callable passed as {} has too few arguments in its declaration; expected {} ' + 'but {} argument(s) declared'.format(argname, len(argument_types), + num_mandatory_args)) + + +def check_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not isinstance(value, dict): + raise TypeError('type of {} must be a dict; got {} instead'. + format(argname, qualified_name(value))) + + if expected_type is not dict: + if (hasattr(expected_type, "__args__") and + expected_type.__args__ not in (None, expected_type.__parameters__)): + key_type, value_type = expected_type.__args__ + if key_type is not Any or value_type is not Any: + for k, v in value.items(): + check_type('keys of {}'.format(argname), k, key_type, memo) + check_type('{}[{!r}]'.format(argname, k), v, value_type, memo) + + +def check_typed_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + declared_keys = frozenset(expected_type.__annotations__) + if hasattr(expected_type, '__required_keys__'): + required_keys = expected_type.__required_keys__ + else: # py3.8 and lower + required_keys = declared_keys if expected_type.__total__ else frozenset() + + existing_keys = frozenset(value) + extra_keys = existing_keys - declared_keys + if extra_keys: + keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(extra_keys)) + raise TypeError('extra key(s) ({}) in {}'.format(keys_formatted, argname)) + + missing_keys = required_keys - existing_keys + if missing_keys: + keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(missing_keys)) + raise TypeError('required key(s) ({}) missing from {}'.format(keys_formatted, argname)) + + for key, argtype in get_type_hints(expected_type).items(): + argvalue = value.get(key, _missing) + if argvalue is not _missing: + check_type('dict item "{}" for {}'.format(key, argname), argvalue, argtype, memo) + + +def check_list(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not isinstance(value, list): + raise TypeError('type of {} must be a list; got {} instead'. + format(argname, qualified_name(value))) + + if expected_type is not list: + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): + value_type = expected_type.__args__[0] + if value_type is not Any: + for i, v in enumerate(value): + check_type('{}[{}]'.format(argname, i), v, value_type, memo) + + +def check_sequence(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not isinstance(value, collections.abc.Sequence): + raise TypeError('type of {} must be a sequence; got {} instead'. + format(argname, qualified_name(value))) + + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): + value_type = expected_type.__args__[0] + if value_type is not Any: + for i, v in enumerate(value): + check_type('{}[{}]'.format(argname, i), v, value_type, memo) + + +def check_set(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not isinstance(value, AbstractSet): + raise TypeError('type of {} must be a set; got {} instead'. + format(argname, qualified_name(value))) + + if expected_type is not set: + if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ + (None, expected_type.__parameters__): + value_type = expected_type.__args__[0] + if value_type is not Any: + for v in value: + check_type('elements of {}'.format(argname), v, value_type, memo) + + +def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + # Specialized check for NamedTuples + is_named_tuple = False + if sys.version_info < (3, 8, 0): + is_named_tuple = hasattr(expected_type, '_field_types') # deprecated since python 3.8 + else: + is_named_tuple = hasattr(expected_type, '__annotations__') + + if is_named_tuple: + if not isinstance(value, expected_type): + raise TypeError('type of {} must be a named tuple of type {}; got {} instead'. + format(argname, qualified_name(expected_type), qualified_name(value))) + + if sys.version_info < (3, 8, 0): + field_types = expected_type._field_types + else: + field_types = expected_type.__annotations__ + + for name, field_type in field_types.items(): + check_type('{}.{}'.format(argname, name), getattr(value, name), field_type, memo) + + return + elif not isinstance(value, tuple): + raise TypeError('type of {} must be a tuple; got {} instead'. + format(argname, qualified_name(value))) + + if getattr(expected_type, '__tuple_params__', None): + # Python 3.5 + use_ellipsis = expected_type.__tuple_use_ellipsis__ + tuple_params = expected_type.__tuple_params__ + elif getattr(expected_type, '__args__', None): + # Python 3.6+ + use_ellipsis = expected_type.__args__[-1] is Ellipsis + tuple_params = expected_type.__args__[:-1 if use_ellipsis else None] + else: + # Unparametrized Tuple or plain tuple + return + + if use_ellipsis: + element_type = tuple_params[0] + for i, element in enumerate(value): + check_type('{}[{}]'.format(argname, i), element, element_type, memo) + elif tuple_params == ((),): + if value != (): + raise TypeError('{} is not an empty tuple but one was expected'.format(argname)) + else: + if len(value) != len(tuple_params): + raise TypeError('{} has wrong number of elements (expected {}, got {} instead)' + .format(argname, len(tuple_params), len(value))) + + for i, (element, element_type) in enumerate(zip(value, tuple_params)): + check_type('{}[{}]'.format(argname, i), element, element_type, memo) + + +def check_union(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if hasattr(expected_type, '__union_params__'): + # Python 3.5 + union_params = expected_type.__union_params__ + else: + # Python 3.6+ + union_params = expected_type.__args__ + + for type_ in union_params: + try: + check_type(argname, value, type_, memo) + return + except TypeError: + pass + + typelist = ', '.join(get_type_name(t) for t in union_params) + raise TypeError('type of {} must be one of ({}); got {} instead'. + format(argname, typelist, qualified_name(value))) + + +def check_class(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: + if not isclass(value): + raise TypeError('type of {} must be a type; got {} instead'.format( + argname, qualified_name(value))) + + # Needed on Python 3.7+ + if expected_type is Type: + return + + if getattr(expected_type, '__origin__', None) in (Type, type): + expected_class = expected_type.__args__[0] + else: + expected_class = expected_type + + if expected_class is Any: + return + elif isinstance(expected_class, TypeVar): + check_typevar(argname, value, expected_class, memo, True) + elif getattr(expected_class, '__origin__', None) is Union: + for arg in expected_class.__args__: + try: + check_class(argname, value, arg, memo) + break + except TypeError: + pass + else: + formatted_args = ', '.join(get_type_name(arg) for arg in expected_class.__args__) + raise TypeError('{} must match one of the following: ({}); got {} instead'.format( + argname, formatted_args, qualified_name(value) + )) + elif not issubclass(value, expected_class): + raise TypeError('{} must be a subclass of {}; got {} instead'.format( + argname, qualified_name(expected_class), qualified_name(value))) + + +def check_typevar(argname: str, value, typevar: TypeVar, memo: _TypeCheckMemo, + subclass_check: bool = False) -> None: + value_type = value if subclass_check else type(value) + subject = argname if subclass_check else 'type of ' + argname + + if typevar.__bound__ is not None: + bound_type = resolve_forwardref(typevar.__bound__, memo) + if not issubclass(value_type, bound_type): + raise TypeError( + '{} must be {} or one of its subclasses; got {} instead' + .format(subject, qualified_name(bound_type), qualified_name(value_type))) + elif typevar.__constraints__: + constraints = [resolve_forwardref(c, memo) for c in typevar.__constraints__] + for constraint in constraints: + try: + check_type(argname, value, constraint, memo) + except TypeError: + pass + else: + break + else: + formatted_constraints = ', '.join(get_type_name(constraint) + for constraint in constraints) + raise TypeError('{} must match one of the constraints ({}); got {} instead' + .format(subject, formatted_constraints, qualified_name(value_type))) + + +def check_literal(argname: str, value, expected_type, memo: _TypeCheckMemo): + def get_args(literal): + try: + args = literal.__args__ + except AttributeError: + # Instance of Literal from typing_extensions + args = literal.__values__ + + retval = [] + for arg in args: + if isinstance(arg, Literal.__class__) or getattr(arg, '__origin__', None) is Literal: + # The first check works on py3.6 and lower, the second one on py3.7+ + retval.extend(get_args(arg)) + elif isinstance(arg, (int, str, bytes, bool, type(None), Enum)): + retval.append(arg) + else: + raise TypeError('Illegal literal value: {}'.format(arg)) + + return retval + + final_args = tuple(get_args(expected_type)) + if value not in final_args: + raise TypeError('the value of {} must be one of {}; got {} instead'. + format(argname, final_args, value)) + + +def check_number(argname: str, value, expected_type): + if expected_type is complex and not isinstance(value, (complex, float, int)): + raise TypeError('type of {} must be either complex, float or int; got {} instead'. + format(argname, qualified_name(value.__class__))) + elif expected_type is float and not isinstance(value, (float, int)): + raise TypeError('type of {} must be either float or int; got {} instead'. + format(argname, qualified_name(value.__class__))) + + +def check_io(argname: str, value, expected_type): + if expected_type is TextIO: + if not isinstance(value, TextIOBase): + raise TypeError('type of {} must be a text based I/O object; got {} instead'. + format(argname, qualified_name(value.__class__))) + elif expected_type is BinaryIO: + if not isinstance(value, (RawIOBase, BufferedIOBase)): + raise TypeError('type of {} must be a binary I/O object; got {} instead'. + format(argname, qualified_name(value.__class__))) + elif not isinstance(value, IOBase): + raise TypeError('type of {} must be an I/O object; got {} instead'. + format(argname, qualified_name(value.__class__))) + + +def check_protocol(argname: str, value, expected_type): + # TODO: implement proper compatibility checking and support non-runtime protocols + if getattr(expected_type, '_is_runtime_protocol', False): + if not isinstance(value, expected_type): + raise TypeError('type of {} ({}) is not compatible with the {} protocol'. + format(argname, type(value).__qualname__, expected_type.__qualname__)) + + +# Equality checks are applied to these +origin_type_checkers = { + AbstractSet: check_set, + Callable: check_callable, + collections.abc.Callable: check_callable, + dict: check_dict, + Dict: check_dict, + list: check_list, + List: check_list, + Sequence: check_sequence, + collections.abc.Sequence: check_sequence, + collections.abc.Set: check_set, + set: check_set, + Set: check_set, + tuple: check_tuple, + Tuple: check_tuple, + type: check_class, + Type: check_class, + Union: check_union +} +_subclass_check_unions = hasattr(Union, '__union_set_params__') +if Literal is not None: + origin_type_checkers[Literal] = check_literal + +generator_origin_types = (Generator, collections.abc.Generator, + Iterator, collections.abc.Iterator, + Iterable, collections.abc.Iterable) +asyncgen_origin_types = (AsyncIterator, collections.abc.AsyncIterator, + AsyncIterable, collections.abc.AsyncIterable) +if AsyncGenerator is not None: + asyncgen_origin_types += (AsyncGenerator,) +if hasattr(collections.abc, 'AsyncGenerator'): + asyncgen_origin_types += (collections.abc.AsyncGenerator,) + + +def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo] = None, *, + globals: Optional[Dict[str, Any]] = None, + locals: Optional[Dict[str, Any]] = None) -> None: + """ + Ensure that ``value`` matches ``expected_type``. + + The types from the :mod:`typing` module do not support :func:`isinstance` or :func:`issubclass` + so a number of type specific checks are required. This function knows which checker to call + for which type. + + :param argname: name of the argument to check; used for error messages + :param value: value to be checked against ``expected_type`` + :param expected_type: a class or generic type instance + :param globals: dictionary of global variables to use for resolving forward references + (defaults to the calling frame's globals) + :param locals: dictionary of local variables to use for resolving forward references + (defaults to the calling frame's locals) + :raises TypeError: if there is a type mismatch + + """ + if expected_type is Any or isinstance(value, Mock): + return + + if expected_type is None: + # Only happens on < 3.6 + expected_type = type(None) + + if memo is None: + frame = sys._getframe(1) + if globals is None: + globals = frame.f_globals + if locals is None: + locals = frame.f_locals + + memo = _TypeCheckMemo(globals, locals) + + expected_type = resolve_forwardref(expected_type, memo) + origin_type = getattr(expected_type, '__origin__', None) + if origin_type is not None: + checker_func = origin_type_checkers.get(origin_type) + if checker_func: + checker_func(argname, value, expected_type, memo) + else: + check_type(argname, value, origin_type, memo) + elif isclass(expected_type): + if issubclass(expected_type, Tuple): + check_tuple(argname, value, expected_type, memo) + elif issubclass(expected_type, (float, complex)): + check_number(argname, value, expected_type) + elif _subclass_check_unions and issubclass(expected_type, Union): + check_union(argname, value, expected_type, memo) + elif isinstance(expected_type, TypeVar): + check_typevar(argname, value, expected_type, memo) + elif issubclass(expected_type, IO): + check_io(argname, value, expected_type) + elif is_typeddict(expected_type): + check_typed_dict(argname, value, expected_type, memo) + elif getattr(expected_type, '_is_protocol', False): + check_protocol(argname, value, expected_type) + else: + expected_type = (getattr(expected_type, '__extra__', None) or origin_type or + expected_type) + + if expected_type is bytes: + # As per https://github.com/python/typing/issues/552 + if not isinstance(value, (bytearray, bytes, memoryview)): + raise TypeError('type of {} must be bytes-like; got {} instead' + .format(argname, qualified_name(value))) + elif not isinstance(value, expected_type): + raise TypeError( + 'type of {} must be {}; got {} instead'. + format(argname, qualified_name(expected_type), qualified_name(value))) + elif isinstance(expected_type, TypeVar): + # Only happens on < 3.6 + check_typevar(argname, value, expected_type, memo) + elif isinstance(expected_type, Literal.__class__): + # Only happens on < 3.7 when using Literal from typing_extensions + check_literal(argname, value, expected_type, memo) + elif expected_type.__class__ is NewType: + # typing.NewType on Python 3.10+ + return check_type(argname, value, expected_type.__supertype__, memo) + elif (isfunction(expected_type) and + getattr(expected_type, "__module__", None) == "typing" and + getattr(expected_type, "__qualname__", None).startswith("NewType.") and + hasattr(expected_type, "__supertype__")): + # typing.NewType on Python 3.9 and below + return check_type(argname, value, expected_type.__supertype__, memo) + + +def check_return_type(retval, memo: Optional[_CallMemo] = None) -> bool: + """ + Check that the return value is compatible with the return value annotation in the function. + + :param retval: the value about to be returned from the call + :return: ``True`` + :raises TypeError: if there is a type mismatch + + """ + if memo is None: + # faster than inspect.currentframe(), but not officially + # supported in all python implementations + frame = sys._getframe(1) + + try: + func = find_function(frame) + except LookupError: + return True # This can happen with the Pydev/PyCharm debugger extension installed + + memo = _CallMemo(func, frame.f_locals) + + if 'return' in memo.type_hints: + if memo.type_hints['return'] is NoReturn: + raise TypeError('{}() was declared never to return but it did'.format(memo.func_name)) + + try: + check_type('the return value', retval, memo.type_hints['return'], memo) + except TypeError as exc: # suppress unnecessarily long tracebacks + # Allow NotImplemented if this is a binary magic method (__eq__() et al) + if retval is NotImplemented and memo.type_hints['return'] is bool: + # This does (and cannot) not check if it's actually a method + func_name = memo.func_name.rsplit('.', 1)[-1] + if len(memo.arguments) == 2 and func_name in BINARY_MAGIC_METHODS: + return True + + raise TypeError(*exc.args) from None + + return True + + +def check_argument_types(memo: Optional[_CallMemo] = None) -> bool: + """ + Check that the argument values match the annotated types. + + Unless both ``args`` and ``kwargs`` are provided, the information will be retrieved from + the previous stack frame (ie. from the function that called this). + + :return: ``True`` + :raises TypeError: if there is an argument type mismatch + + """ + if memo is None: + # faster than inspect.currentframe(), but not officially + # supported in all python implementations + frame = sys._getframe(1) + + try: + func = find_function(frame) + except LookupError: + return True # This can happen with the Pydev/PyCharm debugger extension installed + + memo = _CallMemo(func, frame.f_locals) + + for argname, expected_type in memo.type_hints.items(): + if argname != 'return' and argname in memo.arguments: + value = memo.arguments[argname] + description = 'argument "{}"'.format(argname) + try: + check_type(description, value, expected_type, memo) + except TypeError as exc: # suppress unnecessarily long tracebacks + raise TypeError(*exc.args) from None + + return True + + +class TypeCheckedGenerator: + def __init__(self, wrapped: Generator, memo: _CallMemo): + rtype_args = [] + if hasattr(memo.type_hints['return'], "__args__"): + rtype_args = memo.type_hints['return'].__args__ + + self.__wrapped = wrapped + self.__memo = memo + self.__yield_type = rtype_args[0] if rtype_args else Any + self.__send_type = rtype_args[1] if len(rtype_args) > 1 else Any + self.__return_type = rtype_args[2] if len(rtype_args) > 2 else Any + self.__initialized = False + + def __iter__(self): + return self + + def __next__(self): + return self.send(None) + + def __getattr__(self, name: str) -> Any: + return getattr(self.__wrapped, name) + + def throw(self, *args): + return self.__wrapped.throw(*args) + + def close(self): + self.__wrapped.close() + + def send(self, obj): + if self.__initialized: + check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) + else: + self.__initialized = True + + try: + value = self.__wrapped.send(obj) + except StopIteration as exc: + check_type('return value', exc.value, self.__return_type, memo=self.__memo) + raise + + check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) + return value + + +class TypeCheckedAsyncGenerator: + def __init__(self, wrapped: AsyncGenerator, memo: _CallMemo): + rtype_args = memo.type_hints['return'].__args__ + self.__wrapped = wrapped + self.__memo = memo + self.__yield_type = rtype_args[0] + self.__send_type = rtype_args[1] if len(rtype_args) > 1 else Any + self.__initialized = False + + def __aiter__(self): + return self + + def __anext__(self): + return self.asend(None) + + def __getattr__(self, name: str) -> Any: + return getattr(self.__wrapped, name) + + def athrow(self, *args): + return self.__wrapped.athrow(*args) + + def aclose(self): + return self.__wrapped.aclose() + + async def asend(self, obj): + if self.__initialized: + check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) + else: + self.__initialized = True + + value = await self.__wrapped.asend(obj) + check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) + return value + + +@overload +def typechecked(*, always: bool = False) -> Callable[[T_CallableOrType], T_CallableOrType]: + ... + + +@overload +def typechecked(func: T_CallableOrType, *, always: bool = False) -> T_CallableOrType: + ... + + +def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = None): + """ + Perform runtime type checking on the arguments that are passed to the wrapped function. + + The return value is also checked against the return annotation if any. + + If the ``__debug__`` global variable is set to ``False``, no wrapping and therefore no type + checking is done, unless ``always`` is ``True``. + + This can also be used as a class decorator. This will wrap all type annotated methods, + including ``@classmethod``, ``@staticmethod``, and ``@property`` decorated methods, + in the class with the ``@typechecked`` decorator. + + :param func: the function or class to enable type checking for + :param always: ``True`` to enable type checks even in optimized mode + + """ + if func is None: + return partial(typechecked, always=always, _localns=_localns) + + if not __debug__ and not always: # pragma: no cover + return func + + if isclass(func): + prefix = func.__qualname__ + '.' + for key, attr in func.__dict__.items(): + if inspect.isfunction(attr) or inspect.ismethod(attr) or inspect.isclass(attr): + if attr.__qualname__.startswith(prefix) and getattr(attr, '__annotations__', None): + setattr(func, key, typechecked(attr, always=always, _localns=func.__dict__)) + elif isinstance(attr, (classmethod, staticmethod)): + if getattr(attr.__func__, '__annotations__', None): + wrapped = typechecked(attr.__func__, always=always, _localns=func.__dict__) + setattr(func, key, type(attr)(wrapped)) + elif isinstance(attr, property): + kwargs = dict(doc=attr.__doc__) + for name in ("fset", "fget", "fdel"): + property_func = kwargs[name] = getattr(attr, name) + if property_func is not None and getattr(property_func, '__annotations__', ()): + kwargs[name] = typechecked( + property_func, always=always, _localns=func.__dict__ + ) + + setattr(func, key, attr.__class__(**kwargs)) + + return func + + if not getattr(func, '__annotations__', None): + warn('no type annotations present -- not typechecking {}'.format(function_name(func))) + return func + + # Find the frame in which the function was declared, for resolving forward references later + if _localns is None: + _localns = sys._getframe(1).f_locals + + # Find either the first Python wrapper or the actual function + python_func = inspect.unwrap(func, stop=lambda f: hasattr(f, '__code__')) + + if not getattr(python_func, '__code__', None): + warn('no code associated -- not typechecking {}'.format(function_name(func))) + return func + + def wrapper(*args, **kwargs): + memo = _CallMemo(python_func, _localns, args=args, kwargs=kwargs) + check_argument_types(memo) + retval = func(*args, **kwargs) + try: + check_return_type(retval, memo) + except TypeError as exc: + raise TypeError(*exc.args) from None + + # If a generator is returned, wrap it if its yield/send/return types can be checked + if inspect.isgenerator(retval) or isasyncgen(retval): + return_type = memo.type_hints.get('return') + if return_type: + origin = getattr(return_type, '__origin__', None) + if origin in generator_origin_types: + return TypeCheckedGenerator(retval, memo) + elif origin is not None and origin in asyncgen_origin_types: + return TypeCheckedAsyncGenerator(retval, memo) + + return retval + + async def async_wrapper(*args, **kwargs): + memo = _CallMemo(python_func, _localns, args=args, kwargs=kwargs) + check_argument_types(memo) + retval = await func(*args, **kwargs) + check_return_type(retval, memo) + return retval + + if inspect.iscoroutinefunction(func): + if python_func.__code__ is not async_wrapper.__code__: + return wraps(func)(async_wrapper) + else: + if python_func.__code__ is not wrapper.__code__: + return wraps(func)(wrapper) + + # the target callable was already wrapped + return func + + +class TypeWarning(UserWarning): + """ + A warning that is emitted when a type check fails. + + :ivar str event: ``call`` or ``return`` + :ivar Callable func: the function in which the violation occurred (the called function if event + is ``call``, or the function where a value of the wrong type was returned from if event is + ``return``) + :ivar str error: the error message contained by the caught :class:`TypeError` + :ivar frame: the frame in which the violation occurred + """ + + __slots__ = ('func', 'event', 'message', 'frame') + + def __init__(self, memo: Optional[_CallMemo], event: str, frame, + exception: Union[str, TypeError]): # pragma: no cover + self.func = memo.func + self.event = event + self.error = str(exception) + self.frame = frame + + if self.event == 'call': + caller_frame = self.frame.f_back + event = 'call to {}() from {}:{}'.format( + function_name(self.func), caller_frame.f_code.co_filename, caller_frame.f_lineno) + else: + event = 'return from {}() at {}:{}'.format( + function_name(self.func), self.frame.f_code.co_filename, self.frame.f_lineno) + + super().__init__('[{thread_name}] {event}: {self.error}'.format( + thread_name=threading.current_thread().name, event=event, self=self)) + + @property + def stack(self): + """Return the stack where the last frame is from the target function.""" + return extract_stack(self.frame) + + def print_stack(self, file: TextIO = None, limit: int = None) -> None: + """ + Print the traceback from the stack frame where the target function was run. + + :param file: an open file to print to (prints to stdout if omitted) + :param limit: the maximum number of stack frames to print + + """ + print_stack(self.frame, limit, file) + + +class TypeChecker: + """ + A type checker that collects type violations by hooking into :func:`sys.setprofile`. + + :param packages: list of top level modules and packages or modules to include for type checking + :param all_threads: ``True`` to check types in all threads created while the checker is + running, ``False`` to only check in the current one + :param forward_refs_policy: how to handle unresolvable forward references in annotations + + .. deprecated:: 2.6 + Use :func:`~.importhook.install_import_hook` instead. This class will be removed in v3.0. + """ + + def __init__(self, packages: Union[str, Sequence[str]], *, all_threads: bool = True, + forward_refs_policy: ForwardRefPolicy = ForwardRefPolicy.ERROR): + assert check_argument_types() + warn('TypeChecker has been deprecated and will be removed in v3.0. ' + 'Use install_import_hook() or the pytest plugin instead.', DeprecationWarning) + self.all_threads = all_threads + self.annotation_policy = forward_refs_policy + self._call_memos = {} # type: Dict[Any, _CallMemo] + self._previous_profiler = None + self._previous_thread_profiler = None + self._active = False + + if isinstance(packages, str): + self._packages = (packages,) + else: + self._packages = tuple(packages) + + @property + def active(self) -> bool: + """Return ``True`` if currently collecting type violations.""" + return self._active + + def should_check_type(self, func: Callable) -> bool: + if not func.__annotations__: + # No point in checking if there are no type hints + return False + elif isasyncgenfunction(func): + # Async generators cannot be supported because the return arg is of an opaque builtin + # type (async_generator_wrapped_value) + return False + else: + # Check types if the module matches any of the package prefixes + return any(func.__module__ == package or func.__module__.startswith(package + '.') + for package in self._packages) + + def start(self): + if self._active: + raise RuntimeError('type checker already running') + + self._active = True + + # Install this instance as the current profiler + self._previous_profiler = sys.getprofile() + sys.setprofile(self) + + # If requested, set this instance as the default profiler for all future threads + # (does not affect existing threads) + if self.all_threads: + self._previous_thread_profiler = threading._profile_hook + threading.setprofile(self) + + def stop(self): + if self._active: + if sys.getprofile() is self: + sys.setprofile(self._previous_profiler) + else: # pragma: no cover + warn('the system profiling hook has changed unexpectedly') + + if self.all_threads: + if threading._profile_hook is self: + threading.setprofile(self._previous_thread_profiler) + else: # pragma: no cover + warn('the threading profiling hook has changed unexpectedly') + + self._active = False + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + + def __call__(self, frame, event: str, arg) -> None: # pragma: no cover + if not self._active: + # This happens if all_threads was enabled and a thread was created when the checker was + # running but was then stopped. The thread's profiler callback can't be reset any other + # way but this. + sys.setprofile(self._previous_thread_profiler) + return + + # If an actual profiler is running, don't include the type checking times in its results + if event == 'call': + try: + func = find_function(frame) + except Exception: + func = None + + if func is not None and self.should_check_type(func): + memo = self._call_memos[frame] = _CallMemo( + func, frame.f_locals, forward_refs_policy=self.annotation_policy) + if memo.is_generator: + return_type_hint = memo.type_hints['return'] + if return_type_hint is not None: + origin = getattr(return_type_hint, '__origin__', None) + if origin in generator_origin_types: + # Check the types of the yielded values + memo.type_hints['return'] = return_type_hint.__args__[0] + else: + try: + check_argument_types(memo) + except TypeError as exc: + warn(TypeWarning(memo, event, frame, exc)) + + if self._previous_profiler is not None: + self._previous_profiler(frame, event, arg) + elif event == 'return': + if self._previous_profiler is not None: + self._previous_profiler(frame, event, arg) + + if arg is None: + # a None return value might mean an exception is being raised but we have no way of + # checking + return + + memo = self._call_memos.get(frame) + if memo is not None: + try: + if memo.is_generator: + check_type('yielded value', arg, memo.type_hints['return'], memo) + else: + check_return_type(arg, memo) + except TypeError as exc: + warn(TypeWarning(memo, event, frame, exc)) + + if not memo.is_generator: + del self._call_memos[frame] + elif self._previous_profiler is not None: + self._previous_profiler(frame, event, arg) diff --git a/src/dflow/python/_vendor/typeguard/importhook.py b/src/dflow/python/_vendor/typeguard/importhook.py new file mode 100644 index 00000000..72e94380 --- /dev/null +++ b/src/dflow/python/_vendor/typeguard/importhook.py @@ -0,0 +1,162 @@ +import ast +import sys +from importlib.abc import MetaPathFinder +from importlib.machinery import SourceFileLoader +from importlib.util import cache_from_source, decode_source +from inspect import isclass +from typing import Iterable, Type +from unittest.mock import patch + + +# The name of this function is magical +def _call_with_frames_removed(f, *args, **kwargs): + return f(*args, **kwargs) + + +def optimized_cache_from_source(path, debug_override=None): + return cache_from_source(path, debug_override, optimization='typeguard') + + +class TypeguardTransformer(ast.NodeVisitor): + def __init__(self) -> None: + self._parents = [] + + def visit_Module(self, node: ast.Module): + # Insert "import typeguard" after any "from __future__ ..." imports + for i, child in enumerate(node.body): + if isinstance(child, ast.ImportFrom) and child.module == '__future__': + continue + elif isinstance(child, ast.Expr) and isinstance(child.value, ast.Str): + continue # module docstring + else: + node.body.insert(i, ast.Import(names=[ast.alias('typeguard', None)])) + break + + self._parents.append(node) + self.generic_visit(node) + self._parents.pop() + return node + + def visit_ClassDef(self, node: ast.ClassDef): + node.decorator_list.append( + ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) + ) + self._parents.append(node) + self.generic_visit(node) + self._parents.pop() + return node + + def visit_FunctionDef(self, node: ast.FunctionDef): + # Let the class level decorator handle the methods of a class + if isinstance(self._parents[-1], ast.ClassDef): + return node + + has_annotated_args = any(arg for arg in node.args.args if arg.annotation) + has_annotated_return = bool(node.returns) + if has_annotated_args or has_annotated_return: + node.decorator_list.insert( + 0, + ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) + ) + + self._parents.append(node) + self.generic_visit(node) + self._parents.pop() + return node + + +class TypeguardLoader(SourceFileLoader): + def source_to_code(self, data, path, *, _optimize=-1): + source = decode_source(data) + tree = _call_with_frames_removed(compile, source, path, 'exec', ast.PyCF_ONLY_AST, + dont_inherit=True, optimize=_optimize) + tree = TypeguardTransformer().visit(tree) + ast.fix_missing_locations(tree) + return _call_with_frames_removed(compile, tree, path, 'exec', + dont_inherit=True, optimize=_optimize) + + def exec_module(self, module): + # Use a custom optimization marker – the import lock should make this monkey patch safe + with patch('importlib._bootstrap_external.cache_from_source', optimized_cache_from_source): + return super().exec_module(module) + + +class TypeguardFinder(MetaPathFinder): + """ + Wraps another path finder and instruments the module with ``@typechecked`` if + :meth:`should_instrument` returns ``True``. + + Should not be used directly, but rather via :func:`~.install_import_hook`. + + .. versionadded:: 2.6 + + """ + + def __init__(self, packages, original_pathfinder): + self.packages = packages + self._original_pathfinder = original_pathfinder + + def find_spec(self, fullname, path=None, target=None): + if self.should_instrument(fullname): + spec = self._original_pathfinder.find_spec(fullname, path, target) + if spec is not None and isinstance(spec.loader, SourceFileLoader): + spec.loader = TypeguardLoader(spec.loader.name, spec.loader.path) + return spec + + return None + + def should_instrument(self, module_name: str) -> bool: + """ + Determine whether the module with the given name should be instrumented. + + :param module_name: full name of the module that is about to be imported (e.g. ``xyz.abc``) + + """ + for package in self.packages: + if module_name == package or module_name.startswith(package + '.'): + return True + + return False + + +class ImportHookManager: + def __init__(self, hook: MetaPathFinder): + self.hook = hook + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + self.uninstall() + + def uninstall(self): + try: + sys.meta_path.remove(self.hook) + except ValueError: + pass # already removed + + +def install_import_hook(packages: Iterable[str], *, + cls: Type[TypeguardFinder] = TypeguardFinder) -> ImportHookManager: + """ + Install an import hook that decorates classes and functions with ``@typechecked``. + + This only affects modules loaded **after** this hook has been installed. + + :return: a context manager that uninstalls the hook on exit (or when you call ``.uninstall()``) + + .. versionadded:: 2.6 + + """ + if isinstance(packages, str): + packages = [packages] + + for i, finder in enumerate(sys.meta_path): + if isclass(finder) and finder.__name__ == 'PathFinder' and hasattr(finder, 'find_spec'): + break + else: + raise RuntimeError('Cannot find a PathFinder in sys.meta_path') + + hook = cls(packages, finder) + sys.meta_path.insert(0, hook) + return ImportHookManager(hook) diff --git a/src/dflow/python/_vendor/typeguard/py.typed b/src/dflow/python/_vendor/typeguard/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/src/dflow/python/_vendor/typeguard/pytest_plugin.py b/src/dflow/python/_vendor/typeguard/pytest_plugin.py new file mode 100644 index 00000000..caa128b5 --- /dev/null +++ b/src/dflow/python/_vendor/typeguard/pytest_plugin.py @@ -0,0 +1,30 @@ +import sys + +from typeguard.importhook import install_import_hook + + +def pytest_addoption(parser): + group = parser.getgroup('typeguard') + group.addoption('--typeguard-packages', action='store', + help='comma separated name list of packages and modules to instrument for ' + 'type checking') + + +def pytest_configure(config): + value = config.getoption("typeguard_packages") + if not value: + return + + packages = [pkg.strip() for pkg in value.split(",")] + + already_imported_packages = sorted( + package for package in packages if package in sys.modules + ) + if already_imported_packages: + message = ( + "typeguard cannot check these packages because they " + "are already imported: {}" + ) + raise RuntimeError(message.format(", ".join(already_imported_packages))) + + install_import_hook(packages=packages) diff --git a/src/dflow/python/op.py b/src/dflow/python/op.py index c0bfebb0..870c238f 100644 --- a/src/dflow/python/op.py +++ b/src/dflow/python/op.py @@ -13,14 +13,13 @@ from pathlib import Path from typing import Dict, List, Set, Union -from typeguard import check_type - from ..argo_objects import ArgoObjectDict from ..config import config from ..context_syntax import GLOBAL_CONTEXT from ..io import (InputArtifact, InputParameter, OutputArtifact, OutputParameter, type_to_str) from ..utils import dict2list, get_key, randstr, s3_config +from ._vendor.typeguard import check_type from .opio import OPIO, Artifact, BigParameter, OPIOSign, Parameter iwd = os.getcwd() diff --git a/src/dflow/python/python_op_template.py b/src/dflow/python/python_op_template.py index 4b79c6fd..94c27a8c 100644 --- a/src/dflow/python/python_op_template.py +++ b/src/dflow/python/python_op_template.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, Type, Union import jsonpickle -import typeguard from .. import __path__ from ..common import S3Artifact @@ -338,7 +337,6 @@ def __init__(self, if upload_dflow: python_packages += __path__ python_packages += jsonpickle.__path__ - python_packages += typeguard.__path__ self.python_packages = None if python_packages: @@ -743,8 +741,7 @@ def convert_to_graph(self): del g["resource"] python_packages = [] for p in self.python_packages: - if self.upload_dflow and Path(p).name in [ - "dflow", "jsonpickle", "typeguard"]: + if self.upload_dflow and Path(p).name in ["dflow", "jsonpickle"]: continue python_packages.append(str(p)) g["type"] = "PythonOPTemplate" diff --git a/src/dflow/util_ops.py b/src/dflow/util_ops.py index 9fe64518..a12daedb 100644 --- a/src/dflow/util_ops.py +++ b/src/dflow/util_ops.py @@ -1,5 +1,4 @@ import jsonpickle -import typeguard from . import __path__ from .common import S3Artifact @@ -81,7 +80,6 @@ def __init__(self, template, image, command, image_pull_policy, key, python_packages = [] python_packages += __path__ python_packages += jsonpickle.__path__ - python_packages += typeguard.__path__ self.python_packages = set(python_packages) self.inputs.artifacts["dflow_python_packages"] = InputArtifact( path="%s/inputs/artifacts/dflow_python_packages" From 9c3a5bf7e1c68b0e5b26bbf4a98e421e4b05a860 Mon Sep 17 00:00:00 2001 From: zjgemi Date: Tue, 24 Oct 2023 10:14:09 +0800 Subject: [PATCH 2/4] fix: rename _vendor -> vendor Signed-off-by: zjgemi --- src/dflow/python/op.py | 2 +- src/dflow/python/{_vendor => vendor}/typeguard/__init__.py | 0 src/dflow/python/{_vendor => vendor}/typeguard/importhook.py | 0 src/dflow/python/{_vendor => vendor}/typeguard/py.typed | 0 src/dflow/python/{_vendor => vendor}/typeguard/pytest_plugin.py | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename src/dflow/python/{_vendor => vendor}/typeguard/__init__.py (100%) rename src/dflow/python/{_vendor => vendor}/typeguard/importhook.py (100%) rename src/dflow/python/{_vendor => vendor}/typeguard/py.typed (100%) rename src/dflow/python/{_vendor => vendor}/typeguard/pytest_plugin.py (100%) diff --git a/src/dflow/python/op.py b/src/dflow/python/op.py index 870c238f..2e4a99a1 100644 --- a/src/dflow/python/op.py +++ b/src/dflow/python/op.py @@ -19,7 +19,7 @@ from ..io import (InputArtifact, InputParameter, OutputArtifact, OutputParameter, type_to_str) from ..utils import dict2list, get_key, randstr, s3_config -from ._vendor.typeguard import check_type +from .vendor.typeguard import check_type from .opio import OPIO, Artifact, BigParameter, OPIOSign, Parameter iwd = os.getcwd() diff --git a/src/dflow/python/_vendor/typeguard/__init__.py b/src/dflow/python/vendor/typeguard/__init__.py similarity index 100% rename from src/dflow/python/_vendor/typeguard/__init__.py rename to src/dflow/python/vendor/typeguard/__init__.py diff --git a/src/dflow/python/_vendor/typeguard/importhook.py b/src/dflow/python/vendor/typeguard/importhook.py similarity index 100% rename from src/dflow/python/_vendor/typeguard/importhook.py rename to src/dflow/python/vendor/typeguard/importhook.py diff --git a/src/dflow/python/_vendor/typeguard/py.typed b/src/dflow/python/vendor/typeguard/py.typed similarity index 100% rename from src/dflow/python/_vendor/typeguard/py.typed rename to src/dflow/python/vendor/typeguard/py.typed diff --git a/src/dflow/python/_vendor/typeguard/pytest_plugin.py b/src/dflow/python/vendor/typeguard/pytest_plugin.py similarity index 100% rename from src/dflow/python/_vendor/typeguard/pytest_plugin.py rename to src/dflow/python/vendor/typeguard/pytest_plugin.py From 35663ef848be35e3062e46decfdaf3dfef2164a5 Mon Sep 17 00:00:00 2001 From: zjgemi Date: Tue, 24 Oct 2023 10:18:29 +0800 Subject: [PATCH 3/4] fix: add dflow/python/vendor/typeguard to packages in setup.py Signed-off-by: zjgemi --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index bc3c6146..013a4c9c 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,8 @@ packages=[ "dflow", "dflow/python", + "dflow/python/vendor", + "dflow/python/vendor/typeguard", "dflow/client", "dflow/plugins", ], From cacb2cf631d9669bb7003421ad9f2fa3c93f982f Mon Sep 17 00:00:00 2001 From: zjgemi Date: Tue, 24 Oct 2023 10:58:41 +0800 Subject: [PATCH 4/4] fix: noqa for typeguard Signed-off-by: zjgemi --- src/dflow/python/vendor/typeguard/__init__.py | 254 +++++++++--------- .../python/vendor/typeguard/importhook.py | 30 +-- .../python/vendor/typeguard/pytest_plugin.py | 4 +- 3 files changed, 144 insertions(+), 144 deletions(-) diff --git a/src/dflow/python/vendor/typeguard/__init__.py b/src/dflow/python/vendor/typeguard/__init__.py index 5684d638..8eb880a1 100644 --- a/src/dflow/python/vendor/typeguard/__init__.py +++ b/src/dflow/python/vendor/typeguard/__init__.py @@ -1,4 +1,4 @@ -__all__ = ('ForwardRefPolicy', 'TypeHintWarning', 'typechecked', 'check_return_type', +__all__ = ('ForwardRefPolicy', 'TypeHintWarning', 'typechecked', 'check_return_type', # noqa: E501 'check_argument_types', 'check_type', 'TypeWarning', 'TypeChecker', 'typeguard_ignore') @@ -15,8 +15,8 @@ from traceback import extract_stack, print_stack from types import CodeType, FunctionType from typing import ( - IO, TYPE_CHECKING, AbstractSet, Any, AsyncIterable, AsyncIterator, BinaryIO, Callable, Dict, - Generator, Iterable, Iterator, List, NewType, Optional, Sequence, Set, TextIO, Tuple, Type, + IO, TYPE_CHECKING, AbstractSet, Any, AsyncIterable, AsyncIterator, BinaryIO, Callable, Dict, # noqa: E501 + Generator, Iterable, Iterator, List, NewType, Optional, Sequence, Set, TextIO, Tuple, Type, # noqa: E501 TypeVar, Union, get_type_hints, overload) from unittest.mock import Mock from warnings import warn @@ -89,7 +89,7 @@ def typeguard_ignore(f: _F) -> _F: from typing import no_type_check as typeguard_ignore -_type_hints_map = WeakKeyDictionary() # type: Dict[FunctionType, Dict[str, Any]] +_type_hints_map = WeakKeyDictionary() # type: Dict[FunctionType, Dict[str, Any]] # noqa: E501 _functions_map = WeakValueDictionary() # type: Dict[CodeType, FunctionType] _missing = object() @@ -153,9 +153,9 @@ def typeguard_ignore(f: _F) -> _F: class ForwardRefPolicy(Enum): """Defines how unresolved forward references are handled.""" - ERROR = 1 #: propagate the :exc:`NameError` from :func:`~typing.get_type_hints` + ERROR = 1 #: propagate the :exc:`NameError` from :func:`~typing.get_type_hints` # noqa: E501 WARN = 2 #: remove the annotation and emit a TypeHintWarning - #: replace the annotation with the argument's class if the qualified name matches, else remove + #: replace the annotation with the argument's class if the qualified name matches, else remove # noqa: E501 #: the annotation GUESS = 3 @@ -164,7 +164,7 @@ class TypeHintWarning(UserWarning): """ A warning that is emitted when a type hint in string form could not be resolved to an actual type. - """ + """ # noqa: E501 class _TypeCheckMemo: @@ -185,7 +185,7 @@ def _strip_annotation(annotation): class _CallMemo(_TypeCheckMemo): __slots__ = 'func', 'func_name', 'arguments', 'is_generator', 'type_hints' - def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None, + def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None, # noqa: E501 args: tuple = None, kwargs: Dict[str, Any] = None, forward_refs_policy=ForwardRefPolicy.ERROR): super().__init__(func.__globals__, frame_locals) @@ -197,7 +197,7 @@ def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None if args is not None and kwargs is not None: self.arguments = signature.bind(*args, **kwargs).arguments else: - assert frame_locals is not None, 'frame must be specified if args or kwargs is None' + assert frame_locals is not None, 'frame must be specified if args or kwargs is None' # noqa: E501 self.arguments = frame_locals self.type_hints = _type_hints_map.get(func) @@ -226,7 +226,7 @@ def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None stripped = _strip_annotation(param.annotation) if stripped == argtype.__qualname__: func.__annotations__[param.name] = argtype - msg = ('Replaced forward declaration {!r} in {} with {!r}' + msg = ('Replaced forward declaration {!r} in {} with {!r}' # noqa: E501 .format(stripped, func_name, argtype)) warn(TypeHintWarning(msg)) continue @@ -265,14 +265,14 @@ def resolve_forwardref(maybe_ref, memo: _TypeCheckMemo): if sys.version_info < (3, 9, 0): return evaluate_forwardref(maybe_ref, memo.globals, memo.locals) else: - return evaluate_forwardref(maybe_ref, memo.globals, memo.locals, frozenset()) + return evaluate_forwardref(maybe_ref, memo.globals, memo.locals, frozenset()) # noqa: E501 else: return maybe_ref def get_type_name(type_): - name = (getattr(type_, '__name__', None) or getattr(type_, '_name', None) or + name = (getattr(type_, '__name__', None) or getattr(type_, '_name', None) or # noqa: E501 getattr(type_, '__forward_arg__', None)) if name is None: origin = getattr(type_, '__origin__', None) @@ -307,7 +307,7 @@ def find_function(frame) -> Optional[Callable]: :param frame: a frame object :return: a function object if one was found, ``None`` if not - """ + """ # noqa: E501 func = _functions_map.get(frame.f_code) if func is None: for obj in gc.get_referrers(frame.f_code): @@ -335,11 +335,11 @@ def qualified_name(obj) -> str: Builtins and types from the :mod:`typing` package get special treatment by having the module name stripped from the generated name. - """ + """ # noqa: E501 type_ = obj if inspect.isclass(obj) else type(obj) module = type_.__module__ qualname = type_.__qualname__ - return qualname if module in ('typing', 'builtins') else '{}.{}'.format(module, qualname) + return qualname if module in ('typing', 'builtins') else '{}.{}'.format(module, qualname) # noqa: E501 def function_name(func: Callable) -> str: @@ -349,15 +349,15 @@ def function_name(func: Callable) -> str: Builtins and types from the :mod:`typing` package get special treatment by having the module name stripped from the generated name. - """ - # For partial functions and objects with __call__ defined, __qualname__ does not exist - # For functions run in `exec` with a custom namespace, __module__ can be None + """ # noqa: E501 + # For partial functions and objects with __call__ defined, __qualname__ does not exist # noqa: E501 + # For functions run in `exec` with a custom namespace, __module__ can be None # noqa: E501 module = getattr(func, '__module__', '') or '' qualname = (module + '.') if module not in ('builtins', '') else '' return qualname + getattr(func, '__qualname__', repr(func)) -def check_callable(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_callable(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not callable(value): raise TypeError('{} must be a callable'.format(argname)) @@ -377,126 +377,126 @@ def check_callable(argname: str, value, expected_type, memo: _TypeCheckMemo) -> check_args = argument_types != (Ellipsis,) if check_args: - # The callable must not have keyword-only arguments without defaults + # The callable must not have keyword-only arguments without defaults # noqa: E501 unfulfilled_kwonlyargs = [ param.name for param in signature.parameters.values() if - param.kind == Parameter.KEYWORD_ONLY and param.default == Parameter.empty] + param.kind == Parameter.KEYWORD_ONLY and param.default == Parameter.empty] # noqa: E501 if unfulfilled_kwonlyargs: raise TypeError( - 'callable passed as {} has mandatory keyword-only arguments in its ' - 'declaration: {}'.format(argname, ', '.join(unfulfilled_kwonlyargs))) + 'callable passed as {} has mandatory keyword-only arguments in its ' # noqa: E501 + 'declaration: {}'.format(argname, ', '.join(unfulfilled_kwonlyargs))) # noqa: E501 num_mandatory_args = len([ param.name for param in signature.parameters.values() - if param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) and + if param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) and # noqa: E501 param.default is Parameter.empty]) has_varargs = any(param for param in signature.parameters.values() if param.kind == Parameter.VAR_POSITIONAL) if num_mandatory_args > len(argument_types): raise TypeError( - 'callable passed as {} has too many arguments in its declaration; expected {} ' - 'but {} argument(s) declared'.format(argname, len(argument_types), + 'callable passed as {} has too many arguments in its declaration; expected {} ' # noqa: E501 + 'but {} argument(s) declared'.format(argname, len(argument_types), # noqa: E501 num_mandatory_args)) elif not has_varargs and num_mandatory_args < len(argument_types): raise TypeError( - 'callable passed as {} has too few arguments in its declaration; expected {} ' - 'but {} argument(s) declared'.format(argname, len(argument_types), + 'callable passed as {} has too few arguments in its declaration; expected {} ' # noqa: E501 + 'but {} argument(s) declared'.format(argname, len(argument_types), # noqa: E501 num_mandatory_args)) -def check_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not isinstance(value, dict): raise TypeError('type of {} must be a dict; got {} instead'. format(argname, qualified_name(value))) if expected_type is not dict: if (hasattr(expected_type, "__args__") and - expected_type.__args__ not in (None, expected_type.__parameters__)): + expected_type.__args__ not in (None, expected_type.__parameters__)): # noqa: E501 key_type, value_type = expected_type.__args__ if key_type is not Any or value_type is not Any: for k, v in value.items(): check_type('keys of {}'.format(argname), k, key_type, memo) - check_type('{}[{!r}]'.format(argname, k), v, value_type, memo) + check_type('{}[{!r}]'.format(argname, k), v, value_type, memo) # noqa: E501 -def check_typed_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_typed_dict(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 declared_keys = frozenset(expected_type.__annotations__) if hasattr(expected_type, '__required_keys__'): required_keys = expected_type.__required_keys__ else: # py3.8 and lower - required_keys = declared_keys if expected_type.__total__ else frozenset() + required_keys = declared_keys if expected_type.__total__ else frozenset() # noqa: E501 existing_keys = frozenset(value) extra_keys = existing_keys - declared_keys if extra_keys: - keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(extra_keys)) - raise TypeError('extra key(s) ({}) in {}'.format(keys_formatted, argname)) + keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(extra_keys)) # noqa: E501 + raise TypeError('extra key(s) ({}) in {}'.format(keys_formatted, argname)) # noqa: E501 missing_keys = required_keys - existing_keys if missing_keys: - keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(missing_keys)) - raise TypeError('required key(s) ({}) missing from {}'.format(keys_formatted, argname)) + keys_formatted = ', '.join('"{}"'.format(key) for key in sorted(missing_keys)) # noqa: E501 + raise TypeError('required key(s) ({}) missing from {}'.format(keys_formatted, argname)) # noqa: E501 for key, argtype in get_type_hints(expected_type).items(): argvalue = value.get(key, _missing) if argvalue is not _missing: - check_type('dict item "{}" for {}'.format(key, argname), argvalue, argtype, memo) + check_type('dict item "{}" for {}'.format(key, argname), argvalue, argtype, memo) # noqa: E501 -def check_list(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_list(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not isinstance(value, list): raise TypeError('type of {} must be a list; got {} instead'. format(argname, qualified_name(value))) if expected_type is not list: if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ - (None, expected_type.__parameters__): + (None, expected_type.__parameters__): # noqa: E501 value_type = expected_type.__args__[0] if value_type is not Any: for i, v in enumerate(value): - check_type('{}[{}]'.format(argname, i), v, value_type, memo) + check_type('{}[{}]'.format(argname, i), v, value_type, memo) # noqa: E501 -def check_sequence(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_sequence(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not isinstance(value, collections.abc.Sequence): raise TypeError('type of {} must be a sequence; got {} instead'. format(argname, qualified_name(value))) if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ - (None, expected_type.__parameters__): + (None, expected_type.__parameters__): # noqa: E501 value_type = expected_type.__args__[0] if value_type is not Any: for i, v in enumerate(value): check_type('{}[{}]'.format(argname, i), v, value_type, memo) -def check_set(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_set(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not isinstance(value, AbstractSet): raise TypeError('type of {} must be a set; got {} instead'. format(argname, qualified_name(value))) if expected_type is not set: if hasattr(expected_type, "__args__") and expected_type.__args__ not in \ - (None, expected_type.__parameters__): + (None, expected_type.__parameters__): # noqa: E501 value_type = expected_type.__args__[0] if value_type is not Any: for v in value: - check_type('elements of {}'.format(argname), v, value_type, memo) + check_type('elements of {}'.format(argname), v, value_type, memo) # noqa: E501 -def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 # Specialized check for NamedTuples is_named_tuple = False if sys.version_info < (3, 8, 0): - is_named_tuple = hasattr(expected_type, '_field_types') # deprecated since python 3.8 + is_named_tuple = hasattr(expected_type, '_field_types') # deprecated since python 3.8 # noqa: E501 else: is_named_tuple = hasattr(expected_type, '__annotations__') if is_named_tuple: if not isinstance(value, expected_type): - raise TypeError('type of {} must be a named tuple of type {}; got {} instead'. - format(argname, qualified_name(expected_type), qualified_name(value))) + raise TypeError('type of {} must be a named tuple of type {}; got {} instead'. # noqa: E501 + format(argname, qualified_name(expected_type), qualified_name(value))) # noqa: E501 if sys.version_info < (3, 8, 0): field_types = expected_type._field_types @@ -504,7 +504,7 @@ def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> Non field_types = expected_type.__annotations__ for name, field_type in field_types.items(): - check_type('{}.{}'.format(argname, name), getattr(value, name), field_type, memo) + check_type('{}.{}'.format(argname, name), getattr(value, name), field_type, memo) # noqa: E501 return elif not isinstance(value, tuple): @@ -526,20 +526,20 @@ def check_tuple(argname: str, value, expected_type, memo: _TypeCheckMemo) -> Non if use_ellipsis: element_type = tuple_params[0] for i, element in enumerate(value): - check_type('{}[{}]'.format(argname, i), element, element_type, memo) + check_type('{}[{}]'.format(argname, i), element, element_type, memo) # noqa: E501 elif tuple_params == ((),): if value != (): - raise TypeError('{} is not an empty tuple but one was expected'.format(argname)) + raise TypeError('{} is not an empty tuple but one was expected'.format(argname)) # noqa: E501 else: if len(value) != len(tuple_params): - raise TypeError('{} has wrong number of elements (expected {}, got {} instead)' + raise TypeError('{} has wrong number of elements (expected {}, got {} instead)' # noqa: E501 .format(argname, len(tuple_params), len(value))) for i, (element, element_type) in enumerate(zip(value, tuple_params)): - check_type('{}[{}]'.format(argname, i), element, element_type, memo) + check_type('{}[{}]'.format(argname, i), element, element_type, memo) # noqa: E501 -def check_union(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_union(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if hasattr(expected_type, '__union_params__'): # Python 3.5 union_params = expected_type.__union_params__ @@ -559,7 +559,7 @@ def check_union(argname: str, value, expected_type, memo: _TypeCheckMemo) -> Non format(argname, typelist, qualified_name(value))) -def check_class(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: +def check_class(argname: str, value, expected_type, memo: _TypeCheckMemo) -> None: # noqa: E501 if not isclass(value): raise TypeError('type of {} must be a type; got {} instead'.format( argname, qualified_name(value))) @@ -585,8 +585,8 @@ def check_class(argname: str, value, expected_type, memo: _TypeCheckMemo) -> Non except TypeError: pass else: - formatted_args = ', '.join(get_type_name(arg) for arg in expected_class.__args__) - raise TypeError('{} must match one of the following: ({}); got {} instead'.format( + formatted_args = ', '.join(get_type_name(arg) for arg in expected_class.__args__) # noqa: E501 + raise TypeError('{} must match one of the following: ({}); got {} instead'.format( # noqa: E501 argname, formatted_args, qualified_name(value) )) elif not issubclass(value, expected_class): @@ -604,9 +604,9 @@ def check_typevar(argname: str, value, typevar: TypeVar, memo: _TypeCheckMemo, if not issubclass(value_type, bound_type): raise TypeError( '{} must be {} or one of its subclasses; got {} instead' - .format(subject, qualified_name(bound_type), qualified_name(value_type))) + .format(subject, qualified_name(bound_type), qualified_name(value_type))) # noqa: E501 elif typevar.__constraints__: - constraints = [resolve_forwardref(c, memo) for c in typevar.__constraints__] + constraints = [resolve_forwardref(c, memo) for c in typevar.__constraints__] # noqa: E501 for constraint in constraints: try: check_type(argname, value, constraint, memo) @@ -617,8 +617,8 @@ def check_typevar(argname: str, value, typevar: TypeVar, memo: _TypeCheckMemo, else: formatted_constraints = ', '.join(get_type_name(constraint) for constraint in constraints) - raise TypeError('{} must match one of the constraints ({}); got {} instead' - .format(subject, formatted_constraints, qualified_name(value_type))) + raise TypeError('{} must match one of the constraints ({}); got {} instead' # noqa: E501 + .format(subject, formatted_constraints, qualified_name(value_type))) # noqa: E501 def check_literal(argname: str, value, expected_type, memo: _TypeCheckMemo): @@ -631,8 +631,8 @@ def get_args(literal): retval = [] for arg in args: - if isinstance(arg, Literal.__class__) or getattr(arg, '__origin__', None) is Literal: - # The first check works on py3.6 and lower, the second one on py3.7+ + if isinstance(arg, Literal.__class__) or getattr(arg, '__origin__', None) is Literal: # noqa: E501 + # The first check works on py3.6 and lower, the second one on py3.7+ # noqa: E501 retval.extend(get_args(arg)) elif isinstance(arg, (int, str, bytes, bool, type(None), Enum)): retval.append(arg) @@ -648,22 +648,22 @@ def get_args(literal): def check_number(argname: str, value, expected_type): - if expected_type is complex and not isinstance(value, (complex, float, int)): - raise TypeError('type of {} must be either complex, float or int; got {} instead'. + if expected_type is complex and not isinstance(value, (complex, float, int)): # noqa: E501 + raise TypeError('type of {} must be either complex, float or int; got {} instead'. # noqa: E501 format(argname, qualified_name(value.__class__))) elif expected_type is float and not isinstance(value, (float, int)): - raise TypeError('type of {} must be either float or int; got {} instead'. + raise TypeError('type of {} must be either float or int; got {} instead'. # noqa: E501 format(argname, qualified_name(value.__class__))) def check_io(argname: str, value, expected_type): if expected_type is TextIO: if not isinstance(value, TextIOBase): - raise TypeError('type of {} must be a text based I/O object; got {} instead'. + raise TypeError('type of {} must be a text based I/O object; got {} instead'. # noqa: E501 format(argname, qualified_name(value.__class__))) elif expected_type is BinaryIO: if not isinstance(value, (RawIOBase, BufferedIOBase)): - raise TypeError('type of {} must be a binary I/O object; got {} instead'. + raise TypeError('type of {} must be a binary I/O object; got {} instead'. # noqa: E501 format(argname, qualified_name(value.__class__))) elif not isinstance(value, IOBase): raise TypeError('type of {} must be an I/O object; got {} instead'. @@ -671,11 +671,11 @@ def check_io(argname: str, value, expected_type): def check_protocol(argname: str, value, expected_type): - # TODO: implement proper compatibility checking and support non-runtime protocols + # TODO: implement proper compatibility checking and support non-runtime protocols # noqa: E501 if getattr(expected_type, '_is_runtime_protocol', False): if not isinstance(value, expected_type): - raise TypeError('type of {} ({}) is not compatible with the {} protocol'. - format(argname, type(value).__qualname__, expected_type.__qualname__)) + raise TypeError('type of {} ({}) is not compatible with the {} protocol'. # noqa: E501 + format(argname, type(value).__qualname__, expected_type.__qualname__)) # noqa: E501 # Equality checks are applied to these @@ -713,7 +713,7 @@ def check_protocol(argname: str, value, expected_type): asyncgen_origin_types += (collections.abc.AsyncGenerator,) -def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo] = None, *, +def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo] = None, *, # noqa: E501 globals: Optional[Dict[str, Any]] = None, locals: Optional[Dict[str, Any]] = None) -> None: """ @@ -732,7 +732,7 @@ def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo (defaults to the calling frame's locals) :raises TypeError: if there is a type mismatch - """ + """ # noqa: E501 if expected_type is Any or isinstance(value, Mock): return @@ -773,18 +773,18 @@ def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo elif getattr(expected_type, '_is_protocol', False): check_protocol(argname, value, expected_type) else: - expected_type = (getattr(expected_type, '__extra__', None) or origin_type or + expected_type = (getattr(expected_type, '__extra__', None) or origin_type or # noqa: E501 expected_type) if expected_type is bytes: # As per https://github.com/python/typing/issues/552 if not isinstance(value, (bytearray, bytes, memoryview)): - raise TypeError('type of {} must be bytes-like; got {} instead' + raise TypeError('type of {} must be bytes-like; got {} instead' # noqa: E501 .format(argname, qualified_name(value))) elif not isinstance(value, expected_type): raise TypeError( 'type of {} must be {}; got {} instead'. - format(argname, qualified_name(expected_type), qualified_name(value))) + format(argname, qualified_name(expected_type), qualified_name(value))) # noqa: E501 elif isinstance(expected_type, TypeVar): # Only happens on < 3.6 check_typevar(argname, value, expected_type, memo) @@ -796,7 +796,7 @@ def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo return check_type(argname, value, expected_type.__supertype__, memo) elif (isfunction(expected_type) and getattr(expected_type, "__module__", None) == "typing" and - getattr(expected_type, "__qualname__", None).startswith("NewType.") and + getattr(expected_type, "__qualname__", None).startswith("NewType.") and # noqa: E501 hasattr(expected_type, "__supertype__")): # typing.NewType on Python 3.9 and below return check_type(argname, value, expected_type.__supertype__, memo) @@ -804,7 +804,7 @@ def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo def check_return_type(retval, memo: Optional[_CallMemo] = None) -> bool: """ - Check that the return value is compatible with the return value annotation in the function. + Check that the return value is compatible with the return value annotation in the function. # noqa: E501 :param retval: the value about to be returned from the call :return: ``True`` @@ -819,22 +819,22 @@ def check_return_type(retval, memo: Optional[_CallMemo] = None) -> bool: try: func = find_function(frame) except LookupError: - return True # This can happen with the Pydev/PyCharm debugger extension installed + return True # This can happen with the Pydev/PyCharm debugger extension installed # noqa: E501 memo = _CallMemo(func, frame.f_locals) if 'return' in memo.type_hints: if memo.type_hints['return'] is NoReturn: - raise TypeError('{}() was declared never to return but it did'.format(memo.func_name)) + raise TypeError('{}() was declared never to return but it did'.format(memo.func_name)) # noqa: E501 try: - check_type('the return value', retval, memo.type_hints['return'], memo) + check_type('the return value', retval, memo.type_hints['return'], memo) # noqa: E501 except TypeError as exc: # suppress unnecessarily long tracebacks - # Allow NotImplemented if this is a binary magic method (__eq__() et al) + # Allow NotImplemented if this is a binary magic method (__eq__() et al) # noqa: E501 if retval is NotImplemented and memo.type_hints['return'] is bool: # This does (and cannot) not check if it's actually a method func_name = memo.func_name.rsplit('.', 1)[-1] - if len(memo.arguments) == 2 and func_name in BINARY_MAGIC_METHODS: + if len(memo.arguments) == 2 and func_name in BINARY_MAGIC_METHODS: # noqa: E501 return True raise TypeError(*exc.args) from None @@ -846,7 +846,7 @@ def check_argument_types(memo: Optional[_CallMemo] = None) -> bool: """ Check that the argument values match the annotated types. - Unless both ``args`` and ``kwargs`` are provided, the information will be retrieved from + Unless both ``args`` and ``kwargs`` are provided, the information will be retrieved from # noqa: E501 the previous stack frame (ie. from the function that called this). :return: ``True`` @@ -861,7 +861,7 @@ def check_argument_types(memo: Optional[_CallMemo] = None) -> bool: try: func = find_function(frame) except LookupError: - return True # This can happen with the Pydev/PyCharm debugger extension installed + return True # This can happen with the Pydev/PyCharm debugger extension installed # noqa: E501 memo = _CallMemo(func, frame.f_locals) @@ -907,17 +907,17 @@ def close(self): def send(self, obj): if self.__initialized: - check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) + check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) # noqa: E501 else: self.__initialized = True try: value = self.__wrapped.send(obj) except StopIteration as exc: - check_type('return value', exc.value, self.__return_type, memo=self.__memo) + check_type('return value', exc.value, self.__return_type, memo=self.__memo) # noqa: E501 raise - check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) + check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) # noqa: E501 return value @@ -947,26 +947,26 @@ def aclose(self): async def asend(self, obj): if self.__initialized: - check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) + check_type('value sent to generator', obj, self.__send_type, memo=self.__memo) # noqa: E501 else: self.__initialized = True value = await self.__wrapped.asend(obj) - check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) + check_type('value yielded from generator', value, self.__yield_type, memo=self.__memo) # noqa: E501 return value @overload -def typechecked(*, always: bool = False) -> Callable[[T_CallableOrType], T_CallableOrType]: +def typechecked(*, always: bool = False) -> Callable[[T_CallableOrType], T_CallableOrType]: # noqa: E501 ... @overload -def typechecked(func: T_CallableOrType, *, always: bool = False) -> T_CallableOrType: +def typechecked(func: T_CallableOrType, *, always: bool = False) -> T_CallableOrType: # noqa: E501 ... -def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = None): +def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = None): # noqa: E501 """ Perform runtime type checking on the arguments that are passed to the wrapped function. @@ -982,7 +982,7 @@ def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = :param func: the function or class to enable type checking for :param always: ``True`` to enable type checks even in optimized mode - """ + """ # noqa: E501 if func is None: return partial(typechecked, always=always, _localns=_localns) @@ -992,20 +992,20 @@ def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = if isclass(func): prefix = func.__qualname__ + '.' for key, attr in func.__dict__.items(): - if inspect.isfunction(attr) or inspect.ismethod(attr) or inspect.isclass(attr): - if attr.__qualname__.startswith(prefix) and getattr(attr, '__annotations__', None): - setattr(func, key, typechecked(attr, always=always, _localns=func.__dict__)) + if inspect.isfunction(attr) or inspect.ismethod(attr) or inspect.isclass(attr): # noqa: E501 + if attr.__qualname__.startswith(prefix) and getattr(attr, '__annotations__', None): # noqa: E501 + setattr(func, key, typechecked(attr, always=always, _localns=func.__dict__)) # noqa: E501 elif isinstance(attr, (classmethod, staticmethod)): if getattr(attr.__func__, '__annotations__', None): - wrapped = typechecked(attr.__func__, always=always, _localns=func.__dict__) + wrapped = typechecked(attr.__func__, always=always, _localns=func.__dict__) # noqa: E501 setattr(func, key, type(attr)(wrapped)) elif isinstance(attr, property): kwargs = dict(doc=attr.__doc__) for name in ("fset", "fget", "fdel"): property_func = kwargs[name] = getattr(attr, name) - if property_func is not None and getattr(property_func, '__annotations__', ()): + if property_func is not None and getattr(property_func, '__annotations__', ()): # noqa: E501 kwargs[name] = typechecked( - property_func, always=always, _localns=func.__dict__ + property_func, always=always, _localns=func.__dict__ # noqa: E501 ) setattr(func, key, attr.__class__(**kwargs)) @@ -1013,10 +1013,10 @@ def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = return func if not getattr(func, '__annotations__', None): - warn('no type annotations present -- not typechecking {}'.format(function_name(func))) + warn('no type annotations present -- not typechecking {}'.format(function_name(func))) # noqa: E501 return func - # Find the frame in which the function was declared, for resolving forward references later + # Find the frame in which the function was declared, for resolving forward references later # noqa: E501 if _localns is None: _localns = sys._getframe(1).f_locals @@ -1024,7 +1024,7 @@ def typechecked(func=None, *, always=False, _localns: Optional[Dict[str, Any]] = python_func = inspect.unwrap(func, stop=lambda f: hasattr(f, '__code__')) if not getattr(python_func, '__code__', None): - warn('no code associated -- not typechecking {}'.format(function_name(func))) + warn('no code associated -- not typechecking {}'.format(function_name(func))) # noqa: E501 return func def wrapper(*args, **kwargs): @@ -1036,7 +1036,7 @@ def wrapper(*args, **kwargs): except TypeError as exc: raise TypeError(*exc.args) from None - # If a generator is returned, wrap it if its yield/send/return types can be checked + # If a generator is returned, wrap it if its yield/send/return types can be checked # noqa: E501 if inspect.isgenerator(retval) or isasyncgen(retval): return_type = memo.type_hints.get('return') if return_type: @@ -1076,7 +1076,7 @@ class TypeWarning(UserWarning): ``return``) :ivar str error: the error message contained by the caught :class:`TypeError` :ivar frame: the frame in which the violation occurred - """ + """ # noqa: E501 __slots__ = ('func', 'event', 'message', 'frame') @@ -1090,17 +1090,17 @@ def __init__(self, memo: Optional[_CallMemo], event: str, frame, if self.event == 'call': caller_frame = self.frame.f_back event = 'call to {}() from {}:{}'.format( - function_name(self.func), caller_frame.f_code.co_filename, caller_frame.f_lineno) + function_name(self.func), caller_frame.f_code.co_filename, caller_frame.f_lineno) # noqa: E501 else: event = 'return from {}() at {}:{}'.format( - function_name(self.func), self.frame.f_code.co_filename, self.frame.f_lineno) + function_name(self.func), self.frame.f_code.co_filename, self.frame.f_lineno) # noqa: E501 super().__init__('[{thread_name}] {event}: {self.error}'.format( - thread_name=threading.current_thread().name, event=event, self=self)) + thread_name=threading.current_thread().name, event=event, self=self)) # noqa: E501 @property def stack(self): - """Return the stack where the last frame is from the target function.""" + """Return the stack where the last frame is from the target function.""" # noqa: E501 return extract_stack(self.frame) def print_stack(self, file: TextIO = None, limit: int = None) -> None: @@ -1110,7 +1110,7 @@ def print_stack(self, file: TextIO = None, limit: int = None) -> None: :param file: an open file to print to (prints to stdout if omitted) :param limit: the maximum number of stack frames to print - """ + """ # noqa: E501 print_stack(self.frame, limit, file) @@ -1125,13 +1125,13 @@ class TypeChecker: .. deprecated:: 2.6 Use :func:`~.importhook.install_import_hook` instead. This class will be removed in v3.0. - """ + """ # noqa: E501 - def __init__(self, packages: Union[str, Sequence[str]], *, all_threads: bool = True, - forward_refs_policy: ForwardRefPolicy = ForwardRefPolicy.ERROR): + def __init__(self, packages: Union[str, Sequence[str]], *, all_threads: bool = True, # noqa: E501 + forward_refs_policy: ForwardRefPolicy = ForwardRefPolicy.ERROR): # noqa: E501 assert check_argument_types() warn('TypeChecker has been deprecated and will be removed in v3.0. ' - 'Use install_import_hook() or the pytest plugin instead.', DeprecationWarning) + 'Use install_import_hook() or the pytest plugin instead.', DeprecationWarning) # noqa: E501 self.all_threads = all_threads self.annotation_policy = forward_refs_policy self._call_memos = {} # type: Dict[Any, _CallMemo] @@ -1154,12 +1154,12 @@ def should_check_type(self, func: Callable) -> bool: # No point in checking if there are no type hints return False elif isasyncgenfunction(func): - # Async generators cannot be supported because the return arg is of an opaque builtin + # Async generators cannot be supported because the return arg is of an opaque builtin # noqa: E501 # type (async_generator_wrapped_value) return False else: # Check types if the module matches any of the package prefixes - return any(func.__module__ == package or func.__module__.startswith(package + '.') + return any(func.__module__ == package or func.__module__.startswith(package + '.') # noqa: E501 for package in self._packages) def start(self): @@ -1172,7 +1172,7 @@ def start(self): self._previous_profiler = sys.getprofile() sys.setprofile(self) - # If requested, set this instance as the default profiler for all future threads + # If requested, set this instance as the default profiler for all future threads # noqa: E501 # (does not affect existing threads) if self.all_threads: self._previous_thread_profiler = threading._profile_hook @@ -1189,7 +1189,7 @@ def stop(self): if threading._profile_hook is self: threading.setprofile(self._previous_thread_profiler) else: # pragma: no cover - warn('the threading profiling hook has changed unexpectedly') + warn('the threading profiling hook has changed unexpectedly') # noqa: E501 self._active = False @@ -1202,13 +1202,13 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __call__(self, frame, event: str, arg) -> None: # pragma: no cover if not self._active: - # This happens if all_threads was enabled and a thread was created when the checker was - # running but was then stopped. The thread's profiler callback can't be reset any other + # This happens if all_threads was enabled and a thread was created when the checker was # noqa: E501 + # running but was then stopped. The thread's profiler callback can't be reset any other # noqa: E501 # way but this. sys.setprofile(self._previous_thread_profiler) return - # If an actual profiler is running, don't include the type checking times in its results + # If an actual profiler is running, don't include the type checking times in its results # noqa: E501 if event == 'call': try: func = find_function(frame) @@ -1217,14 +1217,14 @@ def __call__(self, frame, event: str, arg) -> None: # pragma: no cover if func is not None and self.should_check_type(func): memo = self._call_memos[frame] = _CallMemo( - func, frame.f_locals, forward_refs_policy=self.annotation_policy) + func, frame.f_locals, forward_refs_policy=self.annotation_policy) # noqa: E501 if memo.is_generator: return_type_hint = memo.type_hints['return'] if return_type_hint is not None: origin = getattr(return_type_hint, '__origin__', None) if origin in generator_origin_types: # Check the types of the yielded values - memo.type_hints['return'] = return_type_hint.__args__[0] + memo.type_hints['return'] = return_type_hint.__args__[0] # noqa: E501 else: try: check_argument_types(memo) @@ -1238,7 +1238,7 @@ def __call__(self, frame, event: str, arg) -> None: # pragma: no cover self._previous_profiler(frame, event, arg) if arg is None: - # a None return value might mean an exception is being raised but we have no way of + # a None return value might mean an exception is being raised but we have no way of # noqa: E501 # checking return @@ -1246,7 +1246,7 @@ def __call__(self, frame, event: str, arg) -> None: # pragma: no cover if memo is not None: try: if memo.is_generator: - check_type('yielded value', arg, memo.type_hints['return'], memo) + check_type('yielded value', arg, memo.type_hints['return'], memo) # noqa: E501 else: check_return_type(arg, memo) except TypeError as exc: diff --git a/src/dflow/python/vendor/typeguard/importhook.py b/src/dflow/python/vendor/typeguard/importhook.py index 72e94380..353c76b1 100644 --- a/src/dflow/python/vendor/typeguard/importhook.py +++ b/src/dflow/python/vendor/typeguard/importhook.py @@ -24,12 +24,12 @@ def __init__(self) -> None: def visit_Module(self, node: ast.Module): # Insert "import typeguard" after any "from __future__ ..." imports for i, child in enumerate(node.body): - if isinstance(child, ast.ImportFrom) and child.module == '__future__': + if isinstance(child, ast.ImportFrom) and child.module == '__future__': # noqa: E501 continue - elif isinstance(child, ast.Expr) and isinstance(child.value, ast.Str): + elif isinstance(child, ast.Expr) and isinstance(child.value, ast.Str): # noqa: E501 continue # module docstring else: - node.body.insert(i, ast.Import(names=[ast.alias('typeguard', None)])) + node.body.insert(i, ast.Import(names=[ast.alias('typeguard', None)])) # noqa: E501 break self._parents.append(node) @@ -39,7 +39,7 @@ def visit_Module(self, node: ast.Module): def visit_ClassDef(self, node: ast.ClassDef): node.decorator_list.append( - ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) + ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) # noqa: E501 ) self._parents.append(node) self.generic_visit(node) @@ -51,12 +51,12 @@ def visit_FunctionDef(self, node: ast.FunctionDef): if isinstance(self._parents[-1], ast.ClassDef): return node - has_annotated_args = any(arg for arg in node.args.args if arg.annotation) + has_annotated_args = any(arg for arg in node.args.args if arg.annotation) # noqa: E501 has_annotated_return = bool(node.returns) if has_annotated_args or has_annotated_return: node.decorator_list.insert( 0, - ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) + ast.Attribute(ast.Name(id='typeguard', ctx=ast.Load()), 'typechecked', ast.Load()) # noqa: E501 ) self._parents.append(node) @@ -68,7 +68,7 @@ def visit_FunctionDef(self, node: ast.FunctionDef): class TypeguardLoader(SourceFileLoader): def source_to_code(self, data, path, *, _optimize=-1): source = decode_source(data) - tree = _call_with_frames_removed(compile, source, path, 'exec', ast.PyCF_ONLY_AST, + tree = _call_with_frames_removed(compile, source, path, 'exec', ast.PyCF_ONLY_AST, # noqa: E501 dont_inherit=True, optimize=_optimize) tree = TypeguardTransformer().visit(tree) ast.fix_missing_locations(tree) @@ -76,8 +76,8 @@ def source_to_code(self, data, path, *, _optimize=-1): dont_inherit=True, optimize=_optimize) def exec_module(self, module): - # Use a custom optimization marker – the import lock should make this monkey patch safe - with patch('importlib._bootstrap_external.cache_from_source', optimized_cache_from_source): + # Use a custom optimization marker – the import lock should make this monkey patch safe # noqa: E501 + with patch('importlib._bootstrap_external.cache_from_source', optimized_cache_from_source): # noqa: E501 return super().exec_module(module) @@ -90,7 +90,7 @@ class TypeguardFinder(MetaPathFinder): .. versionadded:: 2.6 - """ + """ # noqa: E501 def __init__(self, packages, original_pathfinder): self.packages = packages @@ -100,7 +100,7 @@ def find_spec(self, fullname, path=None, target=None): if self.should_instrument(fullname): spec = self._original_pathfinder.find_spec(fullname, path, target) if spec is not None and isinstance(spec.loader, SourceFileLoader): - spec.loader = TypeguardLoader(spec.loader.name, spec.loader.path) + spec.loader = TypeguardLoader(spec.loader.name, spec.loader.path) # noqa: E501 return spec return None @@ -111,7 +111,7 @@ def should_instrument(self, module_name: str) -> bool: :param module_name: full name of the module that is about to be imported (e.g. ``xyz.abc``) - """ + """ # noqa: E501 for package in self.packages: if module_name == package or module_name.startswith(package + '.'): return True @@ -137,7 +137,7 @@ def uninstall(self): def install_import_hook(packages: Iterable[str], *, - cls: Type[TypeguardFinder] = TypeguardFinder) -> ImportHookManager: + cls: Type[TypeguardFinder] = TypeguardFinder) -> ImportHookManager: # noqa: E501 """ Install an import hook that decorates classes and functions with ``@typechecked``. @@ -147,12 +147,12 @@ def install_import_hook(packages: Iterable[str], *, .. versionadded:: 2.6 - """ + """ # noqa: E501 if isinstance(packages, str): packages = [packages] for i, finder in enumerate(sys.meta_path): - if isclass(finder) and finder.__name__ == 'PathFinder' and hasattr(finder, 'find_spec'): + if isclass(finder) and finder.__name__ == 'PathFinder' and hasattr(finder, 'find_spec'): # noqa: E501 break else: raise RuntimeError('Cannot find a PathFinder in sys.meta_path') diff --git a/src/dflow/python/vendor/typeguard/pytest_plugin.py b/src/dflow/python/vendor/typeguard/pytest_plugin.py index caa128b5..6f2aceb8 100644 --- a/src/dflow/python/vendor/typeguard/pytest_plugin.py +++ b/src/dflow/python/vendor/typeguard/pytest_plugin.py @@ -6,7 +6,7 @@ def pytest_addoption(parser): group = parser.getgroup('typeguard') group.addoption('--typeguard-packages', action='store', - help='comma separated name list of packages and modules to instrument for ' + help='comma separated name list of packages and modules to instrument for ' # noqa: E501 'type checking') @@ -25,6 +25,6 @@ def pytest_configure(config): "typeguard cannot check these packages because they " "are already imported: {}" ) - raise RuntimeError(message.format(", ".join(already_imported_packages))) + raise RuntimeError(message.format(", ".join(already_imported_packages))) # noqa: E501 install_import_hook(packages=packages)