comment here
This commit is contained in:
0
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__init__.py
vendored
Normal file
0
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_alias.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_alias.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_args.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_args.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_mro.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_mro.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_origin.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_origin.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_type.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_get_type.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_instance_of.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_instance_of.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_subclass_of.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/__pycache__/_subclass_of.cpython-311.pyc
vendored
Normal file
Binary file not shown.
36
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_common_ancestor.py
vendored
Normal file
36
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_common_ancestor.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import typing
|
||||
|
||||
|
||||
def common_ancestor(*args: object) -> type:
|
||||
"""
|
||||
Get the closest common ancestor of the given objects.
|
||||
:param args: any objects.
|
||||
:return: the ``type`` of the closest common ancestor of the given ``args``.
|
||||
"""
|
||||
return _common_ancestor(args, False)
|
||||
|
||||
|
||||
def common_ancestor_of_types(*args: type) -> type:
|
||||
"""
|
||||
Get the closest common ancestor of the given classes.
|
||||
:param args: any classes.
|
||||
:return: the ``type`` of the closest common ancestor of the given ``args``.
|
||||
"""
|
||||
return _common_ancestor(args, True)
|
||||
|
||||
|
||||
def _common_ancestor(args: typing.Sequence[object], types: bool) -> type:
|
||||
from typish.functions._get_type import get_type
|
||||
from typish.functions._get_mro import get_mro
|
||||
|
||||
if len(args) < 1:
|
||||
raise TypeError('common_ancestor() requires at least 1 argument')
|
||||
tmap = (lambda x: x) if types else get_type
|
||||
mros = [get_mro(tmap(elem)) for elem in args]
|
||||
for cls in mros[0]:
|
||||
for mro in mros:
|
||||
if cls not in mro:
|
||||
break
|
||||
else:
|
||||
# cls is in every mro; a common ancestor is found!
|
||||
return cls
|
||||
40
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_alias.py
vendored
Normal file
40
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_alias.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import typing
|
||||
from functools import lru_cache
|
||||
|
||||
from typish.functions._is_from_typing import is_from_typing
|
||||
|
||||
from typish._types import T
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_alias(cls: T) -> typing.Optional[T]:
|
||||
"""
|
||||
Return the alias from the ``typing`` module for ``cls``. For example, for
|
||||
``cls=list``, the result would be ``typing.List``. If ``cls`` is
|
||||
parameterized (>=3.9), then a parameterized ``typing`` equivalent is
|
||||
returned. If no alias exists for ``cls``, then ``None`` is returned.
|
||||
If ``cls`` already is from ``typing`` it is returned as is.
|
||||
:param cls: the type for which the ``typing`` equivalent is to be found.
|
||||
:return: the alias from ``typing``.
|
||||
"""
|
||||
if is_from_typing(cls):
|
||||
return cls
|
||||
alias = _alias_per_type.get(cls.__name__, None)
|
||||
if alias:
|
||||
args = getattr(cls, '__args__', tuple())
|
||||
if args:
|
||||
alias = alias[args]
|
||||
return alias
|
||||
|
||||
|
||||
_alias_per_type = {
|
||||
'list': typing.List,
|
||||
'tuple': typing.Tuple,
|
||||
'dict': typing.Dict,
|
||||
'set': typing.Set,
|
||||
'frozenset': typing.FrozenSet,
|
||||
'deque': typing.Deque,
|
||||
'defaultdict': typing.DefaultDict,
|
||||
'type': typing.Type,
|
||||
'Set': typing.AbstractSet,
|
||||
}
|
||||
14
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_args.py
vendored
Normal file
14
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_args.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import typing
|
||||
|
||||
|
||||
def get_args(t: type) -> typing.Tuple[type, ...]:
|
||||
"""
|
||||
Get the arguments from a collection type (e.g. ``typing.List[int]``) as a
|
||||
``tuple``.
|
||||
:param t: the collection type.
|
||||
:return: a ``tuple`` containing types.
|
||||
"""
|
||||
args_ = getattr(t, '__args__', tuple()) or tuple()
|
||||
args = tuple([attr for attr in args_
|
||||
if type(attr) != typing.TypeVar])
|
||||
return args
|
||||
31
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_mro.py
vendored
Normal file
31
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_mro.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import typing
|
||||
from inspect import getmro
|
||||
|
||||
|
||||
def get_mro(obj: typing.Any) -> typing.Tuple[type, ...]:
|
||||
"""
|
||||
Return tuple of base classes (including that of obj) in method resolution
|
||||
order. Types from typing are supported as well.
|
||||
:param obj: object or type.
|
||||
:return: a tuple of base classes.
|
||||
"""
|
||||
from typish.functions._get_origin import get_origin
|
||||
|
||||
# Wrapper around ``getmro`` to allow types from ``typing``.
|
||||
if obj is ...:
|
||||
return Ellipsis, object
|
||||
elif obj is typing.Union:
|
||||
# For Python <3.7, we cannot use mro.
|
||||
super_cls = getattr(typing, '_GenericAlias',
|
||||
getattr(typing, 'GenericMeta', None))
|
||||
return typing.Union, super_cls, object
|
||||
|
||||
origin = get_origin(obj)
|
||||
if origin != obj:
|
||||
return get_mro(origin)
|
||||
|
||||
cls = obj
|
||||
if not isinstance(obj, type):
|
||||
cls = type(obj)
|
||||
|
||||
return getmro(cls)
|
||||
39
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_origin.py
vendored
Normal file
39
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_origin.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import typing
|
||||
from collections import deque, defaultdict
|
||||
from collections.abc import Set
|
||||
from inspect import isclass
|
||||
|
||||
from typish.functions._is_from_typing import is_from_typing
|
||||
|
||||
|
||||
def get_origin(t: type) -> type:
|
||||
"""
|
||||
Return the origin of the given (generic) type. For example, for
|
||||
``t=List[str]``, the result would be ``list``.
|
||||
:param t: the type of which the origin is to be found.
|
||||
:return: the origin of ``t`` or ``t`` if it is not generic.
|
||||
"""
|
||||
from typish.functions._get_simple_name import get_simple_name
|
||||
|
||||
simple_name = get_simple_name(t)
|
||||
result = _type_per_alias.get(simple_name, None)
|
||||
if isclass(t) and not is_from_typing(t):
|
||||
# Get the origin in case of a parameterized generic.
|
||||
result = getattr(t, '__origin__', t)
|
||||
elif not result:
|
||||
result = getattr(typing, simple_name, t)
|
||||
return result
|
||||
|
||||
|
||||
_type_per_alias = {
|
||||
'List': list,
|
||||
'Tuple': tuple,
|
||||
'Dict': dict,
|
||||
'Set': set,
|
||||
'FrozenSet': frozenset,
|
||||
'Deque': deque,
|
||||
'DefaultDict': defaultdict,
|
||||
'Type': type,
|
||||
'AbstractSet': Set,
|
||||
'Optional': typing.Union,
|
||||
}
|
||||
17
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_simple_name.py
vendored
Normal file
17
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_simple_name.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from typish._types import NoneType
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_simple_name(cls: type) -> str:
|
||||
cls = cls or NoneType
|
||||
cls_name = getattr(cls, '__name__', None)
|
||||
if not cls_name:
|
||||
cls_name = getattr(cls, '_name', None)
|
||||
if not cls_name:
|
||||
cls_name = repr(cls)
|
||||
cls_name = cls_name.split('[')[0] # Remove generic types.
|
||||
cls_name = cls_name.split('.')[-1] # Remove any . caused by repr.
|
||||
cls_name = cls_name.split(r"'>")[0] # Remove any '>.
|
||||
return cls_name
|
||||
140
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_type.py
vendored
Normal file
140
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_type.py
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
import inspect
|
||||
import types
|
||||
import typing
|
||||
|
||||
from typish._state import DEFAULT, State
|
||||
from typish._types import T, Unknown, KT, NoneType, Empty, VT
|
||||
from typish.classes._union_type import UnionType
|
||||
|
||||
|
||||
def get_type(
|
||||
inst: T,
|
||||
use_union: bool = False,
|
||||
*,
|
||||
state: State = DEFAULT) -> typing.Type[T]:
|
||||
"""
|
||||
Return a type, complete with generics for the given ``inst``.
|
||||
:param inst: the instance for which a type is to be returned.
|
||||
:param use_union: if ``True``, the resulting type can contain a union.
|
||||
:param state: any state that is used by typish.
|
||||
:return: the type of ``inst``.
|
||||
"""
|
||||
|
||||
get_type_for_inst = state.get_type_per_cls.get(type(inst))
|
||||
if get_type_for_inst:
|
||||
return get_type_for_inst(inst)
|
||||
|
||||
if inst is typing.Any:
|
||||
return typing.Any
|
||||
|
||||
if isinstance(inst, UnionType):
|
||||
return UnionType
|
||||
|
||||
result = type(inst)
|
||||
super_types = [
|
||||
(dict, _get_type_dict),
|
||||
(tuple, _get_type_tuple),
|
||||
(str, lambda inst_, _, __: result),
|
||||
(typing.Iterable, _get_type_iterable),
|
||||
(types.FunctionType, _get_type_callable),
|
||||
(types.MethodType, _get_type_callable),
|
||||
(type, lambda inst_, _, __: typing.Type[inst]),
|
||||
]
|
||||
|
||||
try:
|
||||
for super_type, func in super_types:
|
||||
if isinstance(inst, super_type):
|
||||
result = func(inst, use_union, state)
|
||||
break
|
||||
except Exception:
|
||||
# If anything went wrong, return the regular type.
|
||||
# This is to support 3rd party libraries.
|
||||
return type(inst)
|
||||
return result
|
||||
|
||||
|
||||
def _get_type_iterable(
|
||||
inst: typing.Iterable,
|
||||
use_union: bool,
|
||||
state: State) -> type:
|
||||
from typish.functions._get_alias import get_alias
|
||||
from typish.functions._common_ancestor import common_ancestor
|
||||
|
||||
typing_type = get_alias(type(inst))
|
||||
common_cls = Unknown
|
||||
if inst:
|
||||
if use_union:
|
||||
types = [get_type(elem, state=state) for elem in inst]
|
||||
common_cls = typing.Union[tuple(types)]
|
||||
else:
|
||||
common_cls = common_ancestor(*inst)
|
||||
if typing_type:
|
||||
if issubclass(common_cls, typing.Iterable) and typing_type is not str:
|
||||
# Get to the bottom of it; obtain types recursively.
|
||||
common_cls = get_type(common_cls(_flatten(inst)), state=state)
|
||||
result = typing_type[common_cls]
|
||||
return result
|
||||
|
||||
|
||||
def _get_type_tuple(
|
||||
inst: tuple,
|
||||
use_union: bool,
|
||||
state: State) -> typing.Dict[KT, VT]:
|
||||
args = [get_type(elem, state) for elem in inst]
|
||||
return typing.Tuple[tuple(args)]
|
||||
|
||||
|
||||
def _get_type_callable(
|
||||
inst: typing.Callable,
|
||||
use_union: bool,
|
||||
state: State) -> typing.Type[typing.Dict[KT, VT]]:
|
||||
if 'lambda' in str(inst):
|
||||
result = _get_type_lambda(inst, use_union, state)
|
||||
else:
|
||||
result = typing.Callable
|
||||
sig = inspect.signature(inst)
|
||||
args = [_map_empty(param.annotation)
|
||||
for param in sig.parameters.values()]
|
||||
return_type = NoneType
|
||||
if sig.return_annotation != Empty:
|
||||
return_type = sig.return_annotation
|
||||
if args or return_type != NoneType:
|
||||
if inspect.iscoroutinefunction(inst):
|
||||
return_type = typing.Awaitable[return_type]
|
||||
result = typing.Callable[args, return_type]
|
||||
return result
|
||||
|
||||
|
||||
def _get_type_lambda(
|
||||
inst: typing.Callable,
|
||||
use_union: bool,
|
||||
state: State) -> typing.Type[typing.Dict[KT, VT]]:
|
||||
args = [Unknown for _ in inspect.signature(inst).parameters]
|
||||
return_type = Unknown
|
||||
return typing.Callable[args, return_type]
|
||||
|
||||
|
||||
def _get_type_dict(inst: typing.Dict[KT, VT],
|
||||
use_union: bool,
|
||||
state: State) -> typing.Type[typing.Dict[KT, VT]]:
|
||||
from typish.functions._get_args import get_args
|
||||
|
||||
t_list_k = _get_type_iterable(list(inst.keys()), use_union, state)
|
||||
t_list_v = _get_type_iterable(list(inst.values()), use_union, state)
|
||||
t_k_tuple = get_args(t_list_k)
|
||||
t_v_tuple = get_args(t_list_v)
|
||||
return typing.Dict[t_k_tuple[0], t_v_tuple[0]]
|
||||
|
||||
|
||||
def _flatten(l: typing.Iterable[typing.Iterable[typing.Any]]) -> typing.List[typing.Any]:
|
||||
result = []
|
||||
for x in l:
|
||||
result += [*x]
|
||||
return result
|
||||
|
||||
|
||||
def _map_empty(annotation: type) -> type:
|
||||
result = annotation
|
||||
if annotation == Empty:
|
||||
result = typing.Any
|
||||
return result
|
||||
50
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_type_hints_of_callable.py
vendored
Normal file
50
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_get_type_hints_of_callable.py
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import typing
|
||||
|
||||
|
||||
def get_type_hints_of_callable(
|
||||
func: typing.Callable) -> typing.Dict[str, type]:
|
||||
"""
|
||||
Return the type hints of the parameters of the given callable.
|
||||
:param func: the callable of which the type hints are to be returned.
|
||||
:return: a dict with parameter names and their types.
|
||||
"""
|
||||
# Python3.5: get_type_hints raises on classes without explicit constructor
|
||||
try:
|
||||
result = typing.get_type_hints(func)
|
||||
except AttributeError:
|
||||
result = {}
|
||||
return result
|
||||
|
||||
|
||||
def get_args_and_return_type(hint: typing.Type[typing.Callable]) \
|
||||
-> typing.Tuple[typing.Optional[typing.Tuple[type]], typing.Optional[type]]:
|
||||
"""
|
||||
Get the argument types and the return type of a callable type hint
|
||||
(e.g. ``Callable[[int], str]``).
|
||||
|
||||
Example:
|
||||
```
|
||||
arg_types, return_type = get_args_and_return_type(Callable[[int], str])
|
||||
# args_types is (int, )
|
||||
# return_type is str
|
||||
```
|
||||
|
||||
Example for when ``hint`` has no generics:
|
||||
```
|
||||
arg_types, return_type = get_args_and_return_type(Callable)
|
||||
# args_types is None
|
||||
# return_type is None
|
||||
```
|
||||
:param hint: the callable type hint.
|
||||
:return: a tuple of the argument types (as a tuple) and the return type.
|
||||
"""
|
||||
if hint in (callable, typing.Callable):
|
||||
arg_types = None
|
||||
return_type = None
|
||||
elif hasattr(hint, '__result__'):
|
||||
arg_types = hint.__args__
|
||||
return_type = hint.__result__
|
||||
else:
|
||||
arg_types = hint.__args__[0:-1]
|
||||
return_type = hint.__args__[-1]
|
||||
return arg_types, return_type
|
||||
35
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_instance_of.py
vendored
Normal file
35
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_instance_of.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
from typish._state import DEFAULT, State
|
||||
|
||||
|
||||
def instance_of(obj: object, *args: object, state: State = DEFAULT) -> bool:
|
||||
"""
|
||||
Check whether ``obj`` is an instance of all types in ``args``, while also
|
||||
considering generics.
|
||||
|
||||
If you want the instance check to be customized for your type, then make
|
||||
sure it has a __instancecheck__ defined (not in a base class). You will
|
||||
also need to register the get_type function by calling
|
||||
typish.register_get_type with that particular type and a handling callable.
|
||||
:param obj: the object in subject.
|
||||
:param args: the type(s) of which ``obj`` is an instance or not.
|
||||
:param state: any state that is used by typish.
|
||||
:return: ``True`` if ``obj`` is an instance of all types in ``args``.
|
||||
"""
|
||||
return all(_instance_of(obj, clsinfo, state) for clsinfo in args)
|
||||
|
||||
|
||||
def _instance_of(obj: object, clsinfo: object, state: State = DEFAULT) -> bool:
|
||||
from typish.classes._literal import LiteralAlias, is_literal_type
|
||||
from typish.functions._subclass_of import subclass_of
|
||||
from typish.functions._get_type import get_type
|
||||
from typish.functions._is_from_typing import is_from_typing
|
||||
|
||||
if not is_from_typing(clsinfo) and '__instancecheck__' in dir(clsinfo):
|
||||
return isinstance(obj, clsinfo)
|
||||
|
||||
if is_literal_type(clsinfo):
|
||||
alias = LiteralAlias.from_literal(clsinfo)
|
||||
return isinstance(obj, alias)
|
||||
|
||||
type_ = get_type(obj, use_union=True, state=state)
|
||||
return subclass_of(type_, clsinfo)
|
||||
10
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_from_typing.py
vendored
Normal file
10
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_from_typing.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import typing
|
||||
|
||||
|
||||
def is_from_typing(cls: type) -> bool:
|
||||
"""
|
||||
Return True if the given class is from the typing module.
|
||||
:param cls: a type.
|
||||
:return: True if cls is from typing.
|
||||
"""
|
||||
return cls.__module__ == typing.__name__
|
||||
23
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_optional_type.py
vendored
Normal file
23
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_optional_type.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import typing
|
||||
|
||||
from typish import get_origin, get_args, NoneType
|
||||
|
||||
|
||||
def is_optional_type(cls: type) -> bool:
|
||||
"""
|
||||
Return True if the given class is an optional type. A type is considered to
|
||||
be optional if it allows ``None`` as value.
|
||||
|
||||
Example:
|
||||
|
||||
is_optional_type(Optional[str]) # True
|
||||
is_optional_type(Union[str, int, None]) # True
|
||||
is_optional_type(str) # False
|
||||
is_optional_type(Union[str, int]) # False
|
||||
|
||||
:param cls: a type.
|
||||
:return: True if cls is an optional type.
|
||||
"""
|
||||
origin = get_origin(cls)
|
||||
args = get_args(cls)
|
||||
return origin == typing.Union and NoneType in args
|
||||
24
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_type_annotation.py
vendored
Normal file
24
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_is_type_annotation.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import typing
|
||||
|
||||
from typish.classes._union_type import UnionType
|
||||
|
||||
|
||||
def is_type_annotation(item: typing.Any) -> bool:
|
||||
"""
|
||||
Return whether item is a type annotation (a ``type`` or a type from
|
||||
``typing``, such as ``List``).
|
||||
:param item: the item in question.
|
||||
:return: ``True`` is ``item`` is a type annotation.
|
||||
"""
|
||||
from typish.functions._instance_of import instance_of
|
||||
|
||||
# Use _GenericAlias for Python 3.7+ and use GenericMeta for the rest.
|
||||
super_cls = getattr(typing, '_GenericAlias',
|
||||
getattr(typing, 'GenericMeta', None))
|
||||
|
||||
return not isinstance(item, typing.TypeVar) and (
|
||||
item is typing.Any
|
||||
or instance_of(item, type)
|
||||
or instance_of(item, super_cls)
|
||||
or getattr(item, '__module__', None) == 'typing'
|
||||
or isinstance(item, UnionType))
|
||||
158
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_subclass_of.py
vendored
Normal file
158
.CondaPkg/env/lib/python3.11/site-packages/typish/functions/_subclass_of.py
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
import typing
|
||||
|
||||
from typish._types import Unknown
|
||||
from typish.functions._get_alias import get_alias
|
||||
|
||||
|
||||
def subclass_of(cls: object, *args: object) -> bool:
|
||||
"""
|
||||
Return whether ``cls`` is a subclass of all types in ``args`` while also
|
||||
considering generics.
|
||||
|
||||
If you want the subclass check to be customized for your type, then make
|
||||
sure it has a __subclasscheck__ defined (not in a base class).
|
||||
:param cls: the subject.
|
||||
:param args: the super types.
|
||||
:return: True if ``cls`` is a subclass of all types in ``args`` while also
|
||||
considering generics.
|
||||
"""
|
||||
return all(_subclass_of(cls, clsinfo) for clsinfo in args)
|
||||
|
||||
|
||||
def _subclass_of(cls: type, clsinfo: object) -> bool:
|
||||
# Check whether cls is a subtype of clsinfo.
|
||||
from typish.classes._literal import LiteralAlias
|
||||
|
||||
# Translate to typing type if possible.
|
||||
clsinfo = get_alias(clsinfo) or clsinfo
|
||||
|
||||
if _is_true_case(cls, clsinfo):
|
||||
result = True
|
||||
elif issubclass(clsinfo, LiteralAlias):
|
||||
return _check_literal(cls, subclass_of, clsinfo)
|
||||
elif is_issubclass_case(cls, clsinfo):
|
||||
result = issubclass(cls, clsinfo)
|
||||
else:
|
||||
result = _forward_subclass_check(cls, clsinfo)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _forward_subclass_check(cls: type, clsinfo: type) -> bool:
|
||||
# Forward the subclass check for cls and clsinfo to delegates that know how
|
||||
# to check that particular cls/clsinfo type.
|
||||
|
||||
from typish.functions._get_origin import get_origin
|
||||
from typish.functions._get_args import get_args
|
||||
|
||||
clsinfo_origin = get_origin(clsinfo)
|
||||
clsinfo_args = get_args(clsinfo)
|
||||
cls_origin = get_origin(cls)
|
||||
|
||||
if cls_origin is typing.Union:
|
||||
# cls is a Union; all options of that Union must subclass clsinfo.
|
||||
cls_args = get_args(cls)
|
||||
result = all([subclass_of(elem, clsinfo) for elem in cls_args])
|
||||
elif clsinfo_args:
|
||||
result = _subclass_of_generic(cls, clsinfo_origin, clsinfo_args)
|
||||
else:
|
||||
try:
|
||||
result = issubclass(cls_origin, clsinfo_origin)
|
||||
except TypeError:
|
||||
result = False
|
||||
return result
|
||||
|
||||
|
||||
def _subclass_of_generic(
|
||||
cls: type,
|
||||
info_generic_type: type,
|
||||
info_args: typing.Tuple[type, ...]) -> bool:
|
||||
# Check if cls is a subtype of info_generic_type, knowing that the latter
|
||||
# is a generic type.
|
||||
|
||||
from typish.functions._get_origin import get_origin
|
||||
from typish.functions._get_args import get_args
|
||||
|
||||
result = False
|
||||
cls_origin = get_origin(cls)
|
||||
cls_args = get_args(cls)
|
||||
if info_generic_type is tuple:
|
||||
# Special case.
|
||||
result = (subclass_of(cls_origin, tuple)
|
||||
and _subclass_of_tuple(cls_args, info_args))
|
||||
elif info_generic_type is typing.Union:
|
||||
# Another special case.
|
||||
result = any(subclass_of(cls, cls_) for cls_ in info_args)
|
||||
elif cls_origin is tuple and info_generic_type is typing.Iterable:
|
||||
# Another special case.
|
||||
args = _tuple_args(cls_args)
|
||||
|
||||
# Match the number of arguments of info to that of cls.
|
||||
matched_info_args = info_args * len(args)
|
||||
result = _subclass_of_tuple(args, matched_info_args)
|
||||
elif (subclass_of(cls_origin, info_generic_type) and cls_args
|
||||
and len(cls_args) == len(info_args)):
|
||||
result = all(subclass_of(*tup) for tup in zip(cls_args, info_args))
|
||||
# Note that issubtype(list, List[...]) is always False.
|
||||
# Note that the number of arguments must be equal.
|
||||
return result
|
||||
|
||||
|
||||
def _subclass_of_tuple(
|
||||
cls_args: typing.Tuple[type, ...],
|
||||
info_args: typing.Tuple[type, ...]) -> bool:
|
||||
from typish.functions._get_origin import get_origin
|
||||
from typish.functions._common_ancestor import common_ancestor_of_types
|
||||
|
||||
result = False
|
||||
if len(info_args) == 2 and info_args[1] is ...:
|
||||
type_ = get_origin(info_args[0])
|
||||
if type_ is typing.Union:
|
||||
# A heterogeneous tuple: check each element if it subclasses the
|
||||
# union.
|
||||
result = all([subclass_of(elem, info_args[0]) for elem in cls_args])
|
||||
else:
|
||||
result = subclass_of(common_ancestor_of_types(*cls_args), info_args[0])
|
||||
elif len(cls_args) == len(info_args):
|
||||
result = all(subclass_of(c1, c2)
|
||||
for c1, c2 in zip(cls_args, info_args))
|
||||
return result
|
||||
|
||||
|
||||
def _check_literal(obj: object, func: typing.Callable, *args: type) -> bool:
|
||||
# Instance or subclass check for Literal.
|
||||
literal = args[0]
|
||||
leftovers = args[1:]
|
||||
literal_args = getattr(literal, '__args__', None)
|
||||
result = False
|
||||
if literal_args:
|
||||
literal_arg = literal_args[0]
|
||||
result = (obj == literal_arg
|
||||
and (not leftovers or func(obj, *leftovers)))
|
||||
return result
|
||||
|
||||
|
||||
def _is_true_case(cls: type, clsinfo: type) -> bool:
|
||||
# Return whether subclass_of(cls, clsinfo) holds a case that must always be
|
||||
# True, without the need of further checking.
|
||||
return cls == clsinfo or cls is Unknown or clsinfo in (typing.Any, object)
|
||||
|
||||
|
||||
def is_issubclass_case(cls: type, clsinfo: type) -> bool:
|
||||
# Return whether subclass_of(cls, clsinfo) holds a case that can be handled
|
||||
# by the builtin issubclass.
|
||||
from typish.functions._is_from_typing import is_from_typing
|
||||
|
||||
return (not is_from_typing(clsinfo)
|
||||
and isinstance(cls, type)
|
||||
and clsinfo is not type
|
||||
and '__subclasscheck__' in dir(clsinfo))
|
||||
|
||||
|
||||
def _tuple_args(
|
||||
cls_args: typing.Iterable[typing.Any]) -> typing.Iterable[type]:
|
||||
# Get the argument types from a tuple, even if the form is Tuple[int, ...].
|
||||
result = cls_args
|
||||
if len(cls_args) > 1 and cls_args[1] is ...:
|
||||
result = [cls_args[0]]
|
||||
return result
|
||||
Reference in New Issue
Block a user