chore: 添加虚拟环境到仓库

- 添加 backend_service/venv 虚拟环境
- 包含所有Python依赖包
- 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
2025-12-03 10:19:25 +08:00
parent a6c2027caa
commit c4f851d387
12655 changed files with 3009376 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
from overrides.enforce import EnforceOverrides
import sys
if sys.version_info < (3, 11):
from overrides.final import final
else:
from typing import final
from overrides.overrides import __VERSION__, overrides, override
__all__ = [
"__VERSION__",
"override",
"overrides",
"final",
"EnforceOverrides",
]

View File

@@ -0,0 +1,58 @@
from abc import ABCMeta
class EnforceOverridesMeta(ABCMeta):
def __new__(mcls, name, bases, namespace, **kwargs):
# Ignore any methods defined on the metaclass when enforcing overrides.
for method in dir(mcls):
if not method.startswith("__") and method != "mro":
value = getattr(mcls, method)
if not isinstance(value, (bool, str, int, float, tuple, list, dict)):
setattr(getattr(mcls, method), "__ignored__", True)
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
for name, value in namespace.items():
mcls._check_if_overrides_final_method(name, bases)
if not name.startswith("__"):
value = mcls._handle_special_value(value)
mcls._check_if_overrides_without_overrides_decorator(name, value, bases)
return cls
@staticmethod
def _check_if_overrides_without_overrides_decorator(name, value, bases):
is_override = getattr(value, "__override__", False)
for base in bases:
base_class_method = getattr(base, name, False)
if (
not base_class_method
or not callable(base_class_method)
or getattr(base_class_method, "__ignored__", False)
):
continue
if not is_override:
raise TypeError(
f"Method {name} overrides method from {base} but does not have @override decorator"
)
@staticmethod
def _check_if_overrides_final_method(name, bases):
for base in bases:
base_class_method = getattr(base, name, False)
# `__final__` is added by `@final` decorator
if getattr(base_class_method, "__final__", False):
raise TypeError(
f"Method {name} is finalized in {base}, it cannot be overridden"
)
@staticmethod
def _handle_special_value(value):
if isinstance(value, classmethod) or isinstance(value, staticmethod):
value = value.__get__(None, dict)
elif isinstance(value, property):
value = value.fget
return value
class EnforceOverrides(metaclass=EnforceOverridesMeta):
"Use this as the parent class for your custom classes"
pass

View File

@@ -0,0 +1,45 @@
#
# Copyright 2016 Keunhong Lee
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from types import FunctionType
from typing import Callable, TypeVar, Union
_WrappedMethod = TypeVar("_WrappedMethod", bound=Union[FunctionType, Callable])
def final(method: _WrappedMethod) -> _WrappedMethod:
"""Decorator to indicate that the decorated method is finalized and cannot be overridden.
The decorator code is executed while loading class. Using this method
should have minimal runtime performance implications.
Currently, only methods with @override are checked.
How to use:
from overrides import final
class SuperClass(object):
@final
def method(self):
return 2
class SubClass(SuperClass):
@override
def method(self): #causes an error
return 1
:raises AssertionError: if there exists a match in sub classes for the method name
:return: method
"""
setattr(method, "__final__", True)
return method

View File

@@ -0,0 +1,248 @@
#
# Copyright 2019 Mikko Korpela
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import dis
import functools
import inspect
import sys
from types import FrameType, FunctionType
from typing import Callable, List, Optional, Tuple, TypeVar, Union, overload
__VERSION__ = "7.7.0"
from overrides.signature import ensure_signature_is_compatible
_WrappedMethod = TypeVar("_WrappedMethod", bound=Union[FunctionType, Callable])
_DecoratorMethod = Callable[[_WrappedMethod], _WrappedMethod]
@overload
def overrides(
method: None = None,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> _DecoratorMethod:
...
@overload
def overrides(
method: _WrappedMethod,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> _WrappedMethod:
...
def overrides(
method: Optional[_WrappedMethod] = None,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> Union[_DecoratorMethod, _WrappedMethod]:
"""Decorator to indicate that the decorated method overrides a method in
superclass.
The decorator code is executed while loading class. Using this method
should have minimal runtime performance implications.
How to use:
from overrides import overrides
class SuperClass(object):
def method(self):
return 2
class SubClass(SuperClass):
@overrides
def method(self):
return 1
:param check_signature: Whether or not to check the signature of the overridden method.
:param check_at_runtime: Whether or not to check the overridden method at runtime.
:raises AssertionError: if no match in super classes for the method name
:return: method with possibly added (if the method doesn't have one)
docstring from super class
"""
if method is not None:
return _overrides(method, check_signature, check_at_runtime)
else:
return functools.partial(
overrides,
check_signature=check_signature,
check_at_runtime=check_at_runtime,
)
@overload
def override(
method: None = None,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> _DecoratorMethod:
...
@overload
def override(
method: _WrappedMethod,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> _WrappedMethod:
...
def override(
method: Optional[_WrappedMethod] = None,
*,
check_signature: bool = True,
check_at_runtime: bool = False,
) -> Union[_DecoratorMethod, _WrappedMethod]:
"""Decorator to indicate that the decorated method overrides a method in
superclass.
The decorator code is executed while loading class. Using this method
should have minimal runtime performance implications.
How to use:
from overrides import override
class SuperClass(object):
def method(self):
return 2
class SubClass(SuperClass):
@override
def method(self):
return 1
:param check_signature: Whether or not to check the signature of the overridden method.
:param check_at_runtime: Whether or not to check the overridden method at runtime.
:raises AssertionError: if no match in super classes for the method name
:return: method with possibly added (if the method doesn't have one)
docstring from super class
"""
if method is not None:
return _overrides(method, check_signature, check_at_runtime)
else:
return functools.partial(
overrides,
check_signature=check_signature,
check_at_runtime=check_at_runtime,
)
def _overrides(
method: _WrappedMethod,
check_signature: bool,
check_at_runtime: bool,
) -> _WrappedMethod:
setattr(method, "__override__", True)
global_vars = getattr(method, "__globals__", None)
if global_vars is None:
global_vars = vars(sys.modules[method.__module__])
for super_class in _get_base_classes(sys._getframe(3), global_vars):
if hasattr(super_class, method.__name__):
if check_at_runtime:
@functools.wraps(method)
def wrapper(*args, **kwargs):
_validate_method(method, super_class, check_signature)
return method(*args, **kwargs)
return wrapper # type: ignore
else:
_validate_method(method, super_class, check_signature)
return method
raise TypeError(f"{method.__qualname__}: No super class method found")
def _validate_method(method, super_class, check_signature):
super_method = getattr(super_class, method.__name__)
is_static = isinstance(
inspect.getattr_static(super_class, method.__name__), staticmethod
)
if getattr(super_method, "__final__", False):
raise TypeError(f"{method.__name__}: is finalized in {super_class}")
if not method.__doc__:
method.__doc__ = super_method.__doc__
if (
check_signature
and not method.__name__.startswith("__")
and not isinstance(super_method, property)
):
ensure_signature_is_compatible(super_method, method, is_static)
def _get_base_classes(frame, namespace):
return [
_get_base_class(class_name_components, namespace)
for class_name_components in _get_base_class_names(frame)
]
def _get_base_class_names(frame: FrameType) -> List[List[str]]:
"""Get baseclass names from the code object"""
current_item: List[str] = []
items: List[List[str]] = []
add_last_step = True
for instruction in dis.get_instructions(frame.f_code):
if instruction.offset > frame.f_lasti:
break
if instruction.opcode not in dis.hasname:
continue
if not add_last_step:
items = []
add_last_step = True
# Combine LOAD_NAME and LOAD_GLOBAL as they have similar functionality
if instruction.opname in ["LOAD_NAME", "LOAD_GLOBAL"]:
if current_item:
items.append(current_item)
current_item = [instruction.argval]
elif instruction.opname == "LOAD_ATTR" and current_item:
current_item.append(instruction.argval)
# Reset on other instructions
else:
if current_item:
items.append(current_item)
current_item = []
add_last_step = False
if current_item:
items.append(current_item)
return items
def _get_base_class(components, namespace):
try:
obj = namespace[components[0]]
except KeyError:
if isinstance(namespace["__builtins__"], dict):
obj = namespace["__builtins__"][components[0]]
else:
obj = getattr(namespace["__builtins__"], components[0])
for component in components[1:]:
if hasattr(obj, component):
obj = getattr(obj, component)
return obj

View File

@@ -0,0 +1,305 @@
import inspect
from inspect import Parameter
from types import FunctionType
from typing import Callable, Dict, Optional, Tuple, Type, TypeVar, Union, get_type_hints
from .typing_utils import get_args, issubtype
_WrappedMethod = TypeVar("_WrappedMethod", bound=Union[FunctionType, Callable])
_WrappedMethod2 = TypeVar("_WrappedMethod2", bound=Union[FunctionType, Callable])
def _contains_unbound_typevar(t: Type) -> bool:
"""Recursively check if `t` or any types contained by `t` is a `TypeVar`.
Examples where we return `True`: `T`, `Optional[T]`, `Tuple[Optional[T], ...]`, ...
Examples where we return `False`: `int`, `Optional[str]`, ...
:param t: Type to evaluate.
:return: `True` if the input type contains an unbound `TypeVar`, `False` otherwise.
"""
# Check self
if isinstance(t, TypeVar):
return True
# Check children
for arg in get_args(t):
if _contains_unbound_typevar(arg):
return True
return False
def _issubtype(left, right):
if _contains_unbound_typevar(left):
return True
if right is None:
return True
if _contains_unbound_typevar(right):
return True
try:
return issubtype(left, right)
except TypeError:
# Ignore all broken cases
return True
def _get_type_hints(callable) -> Optional[Dict]:
try:
return get_type_hints(callable)
except (NameError, TypeError):
return None
def _is_same_module(callable1: _WrappedMethod, callable2: _WrappedMethod2) -> bool:
mod1 = callable1.__module__.split(".")[0]
# "__module__" attribute may be missing in CPython or it can be None
# in PyPy: https://github.com/mkorpela/overrides/issues/118
mod2 = getattr(callable2, "__module__", None)
if mod2 is None:
return False
mod2 = mod2.split(".")[0]
return mod1 == mod2
def ensure_signature_is_compatible(
super_callable: _WrappedMethod,
sub_callable: _WrappedMethod2,
is_static: bool = False,
) -> None:
"""Ensure that the signature of `sub_callable` is compatible with the signature of `super_callable`.
Guarantees that any call to `super_callable` will work on `sub_callable` by checking the following criteria:
1. The return type of `sub_callable` is a subtype of the return type of `super_callable`.
2. All parameters of `super_callable` are present in `sub_callable`, unless `sub_callable`
declares `*args` or `**kwargs`.
3. All positional parameters of `super_callable` appear in the same order in `sub_callable`.
4. All parameters of `super_callable` are a subtype of the corresponding parameters of `sub_callable`.
5. All required parameters of `sub_callable` are present in `super_callable`, unless `super_callable`
declares `*args` or `**kwargs`.
:param super_callable: Function to check compatibility with.
:param sub_callable: Function to check compatibility of.
:param is_static: True if staticmethod and should check first argument.
"""
super_callable = _unbound_func(super_callable)
sub_callable = _unbound_func(sub_callable)
try:
super_sig = inspect.signature(super_callable)
except ValueError:
return
super_type_hints = _get_type_hints(super_callable)
sub_sig = inspect.signature(sub_callable)
sub_type_hints = _get_type_hints(sub_callable)
method_name = sub_callable.__qualname__
same_main_module = _is_same_module(sub_callable, super_callable)
if super_type_hints is not None and sub_type_hints is not None:
ensure_return_type_compatibility(super_type_hints, sub_type_hints, method_name)
ensure_all_kwargs_defined_in_sub(
super_sig, sub_sig, super_type_hints, sub_type_hints, is_static, method_name
)
ensure_all_positional_args_defined_in_sub(
super_sig,
sub_sig,
super_type_hints,
sub_type_hints,
is_static,
same_main_module,
method_name,
)
ensure_no_extra_args_in_sub(super_sig, sub_sig, is_static, method_name)
def _unbound_func(callable: _WrappedMethod) -> _WrappedMethod:
if hasattr(callable, "__self__") and hasattr(callable, "__func__"):
return callable.__func__ # type: ignore
return callable
def ensure_all_kwargs_defined_in_sub(
super_sig: inspect.Signature,
sub_sig: inspect.Signature,
super_type_hints: Dict,
sub_type_hints: Dict,
check_first_parameter: bool,
method_name: str,
):
sub_has_var_kwargs = any(
p.kind == Parameter.VAR_KEYWORD for p in sub_sig.parameters.values()
)
for super_index, (name, super_param) in enumerate(super_sig.parameters.items()):
if super_index == 0 and not check_first_parameter:
continue
if super_param.kind == Parameter.VAR_POSITIONAL:
continue
if super_param.kind == Parameter.POSITIONAL_ONLY:
continue
if not is_param_defined_in_sub(
name, True, sub_has_var_kwargs, sub_sig, super_param
):
raise TypeError(f"{method_name}: `{name}` is not present.")
elif name in sub_sig.parameters and super_param.kind != Parameter.VAR_KEYWORD:
sub_index = list(sub_sig.parameters.keys()).index(name)
sub_param = sub_sig.parameters[name]
if super_param.kind != sub_param.kind and not (
super_param.kind == Parameter.KEYWORD_ONLY
and sub_param.kind == Parameter.POSITIONAL_OR_KEYWORD
):
raise TypeError(f"{method_name}: `{name}` is not `{super_param.kind}`")
elif super_index > sub_index and super_param.kind != Parameter.KEYWORD_ONLY:
raise TypeError(
f"{method_name}: `{name}` is not parameter at index `{super_index}`"
)
elif (
name in super_type_hints
and name in sub_type_hints
and not _issubtype(super_type_hints[name], sub_type_hints[name])
):
raise TypeError(
f"`{method_name}: {name} must be a supertype of `{super_param.annotation}` but is `{sub_param.annotation}`"
)
def ensure_all_positional_args_defined_in_sub(
super_sig: inspect.Signature,
sub_sig: inspect.Signature,
super_type_hints: Dict,
sub_type_hints: Dict,
check_first_parameter: bool,
is_same_main_module: bool,
method_name: str,
):
sub_parameter_values = [
v
for v in sub_sig.parameters.values()
if v.kind not in (Parameter.KEYWORD_ONLY, Parameter.VAR_KEYWORD)
]
super_parameter_values = [
v
for v in super_sig.parameters.values()
if v.kind not in (Parameter.KEYWORD_ONLY, Parameter.VAR_KEYWORD)
]
sub_has_var_args = any(
p.kind == Parameter.VAR_POSITIONAL for p in sub_parameter_values
)
super_has_var_args = any(
p.kind == Parameter.VAR_POSITIONAL for p in super_parameter_values
)
if not sub_has_var_args and len(sub_parameter_values) < len(super_parameter_values):
raise TypeError(f"{method_name}: parameter list too short")
super_shift = 0
for index, sub_param in enumerate(sub_parameter_values):
if index == 0 and not check_first_parameter:
continue
if index + super_shift >= len(super_parameter_values):
if sub_param.kind == Parameter.VAR_POSITIONAL:
continue
if (
sub_param.kind == Parameter.POSITIONAL_ONLY
and sub_param.default != Parameter.empty
):
continue
if sub_param.kind == Parameter.POSITIONAL_OR_KEYWORD:
continue # Assume use as keyword
raise TypeError(
f"{method_name}: `{sub_param.name}` positionally required in subclass but not in supertype"
)
if sub_param.kind == Parameter.VAR_POSITIONAL:
return
super_param = super_parameter_values[index + super_shift]
if super_param.kind == Parameter.VAR_POSITIONAL:
super_shift -= 1
if super_param.kind == Parameter.VAR_POSITIONAL:
if not sub_has_var_args:
raise TypeError(f"{method_name}: `{super_param.name}` must be present")
continue
if (
super_param.kind != sub_param.kind
and not (
super_param.kind == Parameter.POSITIONAL_ONLY
and sub_param.kind == Parameter.POSITIONAL_OR_KEYWORD
)
and not (sub_param.kind == Parameter.POSITIONAL_ONLY and super_has_var_args)
):
raise TypeError(
f"{method_name}: `{sub_param.name}` is not `{super_param.kind}` and is `{sub_param.kind}`"
)
elif (
super_param.name in super_type_hints or is_same_main_module
) and not _issubtype(
super_type_hints.get(super_param.name, None),
sub_type_hints.get(sub_param.name, None),
):
raise TypeError(
f"`{method_name}: {sub_param.name} overriding must be a supertype of `{super_param.annotation}` but is `{sub_param.annotation}`"
)
def is_param_defined_in_sub(
name: str,
sub_has_var_args: bool,
sub_has_var_kwargs: bool,
sub_sig: inspect.Signature,
super_param: inspect.Parameter,
) -> bool:
return (
name in sub_sig.parameters
or (super_param.kind == Parameter.VAR_POSITIONAL and sub_has_var_args)
or (super_param.kind == Parameter.VAR_KEYWORD and sub_has_var_kwargs)
or (super_param.kind == Parameter.POSITIONAL_ONLY and sub_has_var_args)
or (
super_param.kind == Parameter.POSITIONAL_OR_KEYWORD
and sub_has_var_args
and sub_has_var_kwargs
)
or (super_param.kind == Parameter.KEYWORD_ONLY and sub_has_var_kwargs)
)
def ensure_no_extra_args_in_sub(
super_sig: inspect.Signature,
sub_sig: inspect.Signature,
check_first_parameter: bool,
method_name: str,
) -> None:
super_params = super_sig.parameters.values()
super_var_args = any(p.kind == Parameter.VAR_POSITIONAL for p in super_params)
super_var_kwargs = any(p.kind == Parameter.VAR_KEYWORD for p in super_params)
for sub_index, (name, sub_param) in enumerate(sub_sig.parameters.items()):
if (
sub_param.kind == Parameter.POSITIONAL_ONLY
and len(super_params) > sub_index
and list(super_params)[sub_index].kind == Parameter.POSITIONAL_ONLY
):
continue
if (
name not in super_sig.parameters
and sub_param.default == Parameter.empty
and sub_param.kind != Parameter.VAR_POSITIONAL
and sub_param.kind != Parameter.VAR_KEYWORD
and not (sub_param.kind == Parameter.KEYWORD_ONLY and super_var_kwargs)
and not (sub_param.kind == Parameter.POSITIONAL_ONLY and super_var_args)
and not (
sub_param.kind == Parameter.POSITIONAL_OR_KEYWORD and super_var_args
)
and (sub_index > 0 or check_first_parameter)
):
raise TypeError(f"{method_name}: `{name}` is not a valid parameter.")
def ensure_return_type_compatibility(
super_type_hints: Dict, sub_type_hints: Dict, method_name: str
):
super_return = super_type_hints.get("return", None)
sub_return = sub_type_hints.get("return", None)
if not _issubtype(sub_return, super_return) and super_return is not None:
raise TypeError(
f"{method_name}: return type `{sub_return}` is not a `{super_return}`."
)

View File

@@ -0,0 +1,471 @@
"""
Backport Python3.8+ typing utils &amp; issubtype &amp; more
![Python 3.6](https://github.com/bojiang/typing_utils/workflows/Python%203.6/badge.svg)
![Python 3.7](https://github.com/bojiang/typing_utils/workflows/Python%203.7/badge.svg)
![Python 3.8](https://github.com/bojiang/typing_utils/workflows/Python%203.8/badge.svg)
## Install
``` bash
pip install typing_utils
```
"""
import collections.abc
import io
import itertools
import types
import typing
if hasattr(typing, "ForwardRef"): # python3.8
ForwardRef = getattr(typing, "ForwardRef")
elif hasattr(typing, "_ForwardRef"): # python3.6
ForwardRef = getattr(typing, "_ForwardRef")
else:
raise NotImplementedError()
if hasattr(typing, "Literal"):
Literal = getattr(typing, "Literal")
else:
Literal = None
if hasattr(typing, "_TypedDictMeta"):
_TypedDictMeta = getattr(typing, "_TypedDictMeta")
else:
_TypedDictMeta = None
if hasattr(types, "UnionType"):
UnionType = getattr(types, "UnionType")
else:
UnionType = None
unknown = None
BUILTINS_MAPPING = {
typing.List: list,
typing.Set: set,
typing.Dict: dict,
typing.Tuple: tuple,
typing.ByteString: bytes, # https://docs.python.org/3/library/typing.html#typing.ByteString
typing.Callable: collections.abc.Callable,
typing.Sequence: collections.abc.Sequence,
type(None): None,
}
STATIC_SUBTYPE_MAPPING: typing.Dict[type, typing.Type] = {
io.TextIOWrapper: typing.TextIO,
io.TextIOBase: typing.TextIO,
io.StringIO: typing.TextIO,
io.BufferedReader: typing.BinaryIO,
io.BufferedWriter: typing.BinaryIO,
io.BytesIO: typing.BinaryIO,
}
if UnionType:
def is_union(element: object) -> bool:
return element is typing.Union or element is UnionType
else:
def is_union(element: object) -> bool:
return element is typing.Union
def optional_all(elements) -> typing.Optional[bool]:
if all(elements):
return True
if all(e is False for e in elements):
return False
return unknown
def optional_any(elements) -> typing.Optional[bool]:
if any(elements):
return True
if any(e is None for e in elements):
return unknown
return False
def _hashable(value):
"""Determine whether `value` can be hashed."""
try:
hash(value)
except TypeError:
return False
return True
get_type_hints = typing.get_type_hints
GenericClass = type(typing.List)
UnionClass = type(typing.Union)
Type = typing.Union[None, type, "typing.TypeVar"]
OriginType = typing.Union[None, type]
TypeArgs = typing.Union[type, typing.AbstractSet[type], typing.Sequence[type]]
def _normalize_aliases(type_: Type) -> Type:
if isinstance(type_, typing.TypeVar):
return type_
assert _hashable(type_), "_normalize_aliases should only be called on element types"
if type_ in BUILTINS_MAPPING:
return BUILTINS_MAPPING[type_] # type: ignore
return type_
def get_origin(type_):
"""Get the unsubscripted version of a type.
This supports generic types, Callable, Tuple, Union, Literal, Final and ClassVar.
Return None for unsupported types.
Examples:
```python
from typing_utils import get_origin
get_origin(Literal[42]) is Literal
get_origin(int) is None
get_origin(ClassVar[int]) is ClassVar
get_origin(Generic) is Generic
get_origin(Generic[T]) is Generic
get_origin(Union[T, int]) is Union
get_origin(List[Tuple[T, T]][int]) == list
```
"""
if hasattr(typing, "get_origin"): # python 3.8+
_getter = getattr(typing, "get_origin")
ori = _getter(type_)
elif hasattr(typing.List, "_special"): # python 3.7
if isinstance(type_, GenericClass) and not type_._special:
ori = type_.__origin__
elif hasattr(type_, "_special") and type_._special:
ori = type_
elif type_ is typing.Generic:
ori = typing.Generic
else:
ori = None
else: # python 3.6
if isinstance(type_, GenericClass):
ori = type_.__origin__
if ori is None:
ori = type_
elif isinstance(type_, UnionClass):
ori = type_.__origin__
elif type_ is typing.Generic:
ori = typing.Generic
else:
ori = None
if ori is None and _TypedDictMeta and isinstance(type_, _TypedDictMeta):
ori = dict
return _normalize_aliases(ori)
def get_args(type_) -> typing.Tuple:
"""Get type arguments with all substitutions performed.
For unions, basic simplifications used by Union constructor are performed.
Examples:
```python
from typing_utils import get_args
get_args(Dict[str, int]) == (str, int)
get_args(int) == ()
get_args(Union[int, Union[T, int], str][int]) == (int, str)
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
get_args(Callable[[], T][int]) == ([], int)
```
"""
if hasattr(typing, "get_args"): # python 3.8+
_getter = getattr(typing, "get_args")
res = _getter(type_)
elif hasattr(typing.List, "_special"): # python 3.7
if (
isinstance(type_, GenericClass) and not type_._special # type: ignore
): # backport for python 3.8
res = type_.__args__ # type: ignore
if get_origin(type_) is collections.abc.Callable and res[0] is not Ellipsis:
res = (list(res[:-1]), res[-1])
else:
res = ()
else: # python 3.6
if isinstance(type_, (GenericClass, UnionClass)): # backport for python 3.8
res = type_.__args__ # type: ignore
if get_origin(type_) is collections.abc.Callable and res[0] is not Ellipsis:
res = (list(res[:-1]), res[-1])
else:
res = ()
if _TypedDictMeta and isinstance(type_, _TypedDictMeta):
return str, typing.Any
return () if res is None else res
def eval_forward_ref(ref, forward_refs=None):
"""
eval forward_refs in all cPython versions
"""
localns = forward_refs or {}
if hasattr(typing, "_eval_type"): # python3.8 & python 3.9
_eval_type = getattr(typing, "_eval_type")
return _eval_type(ref, globals(), localns)
if hasattr(ref, "_eval_type"): # python3.6
_eval_type = getattr(ref, "_eval_type")
return _eval_type(globals(), localns)
raise NotImplementedError()
class NormalizedType(typing.NamedTuple):
"""
Normalized type, made it possible to compare, hash between types.
"""
origin: Type
args: typing.Union[tuple, frozenset] = tuple()
def __eq__(self, other):
if isinstance(other, NormalizedType):
if self.origin != other.origin:
return False
if isinstance(self.args, frozenset) and isinstance(other.args, frozenset):
return self.args <= other.args and other.args <= self.args
return self.origin == other.origin and self.args == other.args
if not self.args:
return self.origin == other
return False
def __hash__(self) -> int:
if not self.args:
return hash(self.origin)
return hash((self.origin, self.args))
def __repr__(self):
if not self.args:
return f"{self.origin}"
return f"{self.origin}[{self.args}])"
def _normalize_args(tps: TypeArgs):
if isinstance(tps, str):
return tps
if isinstance(tps, collections.abc.Sequence):
return tuple(_normalize_args(type_) for type_ in tps)
if isinstance(tps, collections.abc.Set):
return frozenset(_normalize_args(type_) for type_ in tps)
return normalize(tps)
def normalize(type_: Type) -> NormalizedType:
"""
convert types to NormalizedType instances.
"""
args = get_args(type_)
origin = get_origin(type_)
if not origin:
return NormalizedType(_normalize_aliases(type_))
origin = _normalize_aliases(origin)
if is_union(origin): # sort args when the origin is Union
args = _normalize_args(frozenset(args))
else:
args = _normalize_args(args)
return NormalizedType(origin, args)
def _is_origin_subtype(left: OriginType, right: OriginType) -> bool:
if left is right:
return True
if (
left is not None
and left in STATIC_SUBTYPE_MAPPING
and right == STATIC_SUBTYPE_MAPPING[left]
):
return True
if hasattr(left, "mro"):
for parent in left.mro(): # type: ignore
if parent == right:
return True
if isinstance(left, type) and isinstance(right, type):
return issubclass(left, right)
return left == right
NormalizedTypeArgs = typing.Union[
typing.Tuple[typing.Any, ...],
typing.FrozenSet[NormalizedType],
NormalizedType,
]
def _is_origin_subtype_args(
left: "NormalizedTypeArgs",
right: "NormalizedTypeArgs",
forward_refs: typing.Optional[typing.Mapping[str, type]],
) -> typing.Optional[bool]:
if isinstance(left, frozenset):
if not isinstance(right, frozenset):
return False
excluded = left - right
if not excluded:
# Union[str, int] <> Union[int, str]
return True
# Union[list, int] <> Union[typing.Sequence, int]
return all(
any(_is_normal_subtype(e, r, forward_refs) for r in right) for e in excluded
)
if isinstance(left, collections.abc.Sequence) and not isinstance(
left, NormalizedType
):
if not isinstance(right, collections.abc.Sequence) or isinstance(
right, NormalizedType
):
return False
if (
left
and left[-1].origin is not Ellipsis
and right
and right[-1].origin is Ellipsis
):
# Tuple[type, type] <> Tuple[type, ...]
return all(_is_origin_subtype_args(l, right[0], forward_refs) for l in left)
if len(left) != len(right):
return False
return all(
l is not None
and r is not None
and _is_origin_subtype_args(l, r, forward_refs)
for l, r in itertools.zip_longest(left, right)
)
assert isinstance(left, NormalizedType)
assert isinstance(right, NormalizedType)
return _is_normal_subtype(left, right, forward_refs)
def _is_normal_subtype(
left: NormalizedType,
right: NormalizedType,
forward_refs: typing.Optional[typing.Mapping[str, type]],
) -> typing.Optional[bool]:
if isinstance(left.origin, ForwardRef):
left = normalize(eval_forward_ref(left.origin, forward_refs=forward_refs))
if isinstance(right.origin, ForwardRef):
right = normalize(eval_forward_ref(right.origin, forward_refs=forward_refs))
# Any
if right.origin is typing.Any:
return True
# Union
if is_union(right.origin) and is_union(left.origin):
return _is_origin_subtype_args(left.args, right.args, forward_refs)
if is_union(right.origin):
return optional_any(
_is_normal_subtype(left, a, forward_refs) for a in right.args
)
if is_union(left.origin):
return optional_all(
_is_normal_subtype(a, right, forward_refs) for a in left.args
)
# Literal
if right.origin is Literal:
if left.origin is not Literal:
return False
return set(left.args).issubset(set(right.args))
# TypeVar
if isinstance(left.origin, typing.TypeVar) and isinstance(
right.origin, typing.TypeVar
):
if left.origin is right.origin:
return True
left_bound = getattr(left.origin, "__bound__", None)
right_bound = getattr(right.origin, "__bound__", None)
if right_bound is None or left_bound is None:
return unknown
return _is_normal_subtype(
normalize(left_bound), normalize(right_bound), forward_refs
)
if isinstance(right.origin, typing.TypeVar):
return unknown
if isinstance(left.origin, typing.TypeVar):
left_bound = getattr(left.origin, "__bound__", None)
if left_bound is None:
return unknown
return _is_normal_subtype(normalize(left_bound), right, forward_refs)
if not left.args and not right.args:
return _is_origin_subtype(left.origin, right.origin)
if not right.args:
return _is_origin_subtype(left.origin, right.origin)
if _is_origin_subtype(left.origin, right.origin):
return _is_origin_subtype_args(left.args, right.args, forward_refs)
return False
def issubtype(
left: Type,
right: Type,
forward_refs: typing.Optional[dict] = None,
) -> typing.Optional[bool]:
"""Check that the left argument is a subtype of the right.
For unions, check if the type arguments of the left is a subset of the right.
Also works for nested types including ForwardRefs.
Examples:
```python
from typing_utils import issubtype
issubtype(typing.List, typing.Any) == True
issubtype(list, list) == True
issubtype(list, typing.List) == True
issubtype(list, typing.Sequence) == True
issubtype(typing.List[int], list) == True
issubtype(typing.List[typing.List], list) == True
issubtype(list, typing.List[int]) == False
issubtype(list, typing.Union[typing.Tuple, typing.Set]) == False
issubtype(typing.List[typing.List], typing.List[typing.Sequence]) == True
JSON = typing.Union[
int, float, bool, str, None, typing.Sequence["JSON"],
typing.Mapping[str, "JSON"]
]
issubtype(str, JSON, forward_refs={'JSON': JSON}) == True
issubtype(typing.Dict[str, str], JSON, forward_refs={'JSON': JSON}) == True
issubtype(typing.Dict[str, bytes], JSON, forward_refs={'JSON': JSON}) == False
```
"""
return _is_normal_subtype(normalize(left), normalize(right), forward_refs)
__all__ = [
"issubtype",
"get_origin",
"get_args",
"get_type_hints",
]