comment here

This commit is contained in:
ton
2023-03-18 20:03:34 +07:00
commit 4553a0a589
14513 changed files with 2685043 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
from collections import OrderedDict
from typing import Optional, Any
class ClsDict(OrderedDict):
"""
ClsDict is a dict that accepts (only) types as keys and will return its
values depending on instance checks rather than equality checks.
"""
def __new__(cls, *args, **kwargs):
"""
Construct a new instance of ``ClsDict``.
:param args: a dict.
:param kwargs: any kwargs that ``dict`` accepts.
:return: a ``ClsDict``.
"""
from typish.functions._is_type_annotation import is_type_annotation
if len(args) > 1:
raise TypeError('TypeDict accepts only one positional argument, '
'which must be a dict.')
if args and not isinstance(args[0], dict):
raise TypeError('TypeDict accepts only a dict as positional '
'argument.')
if not all([is_type_annotation(key) for key in args[0]]):
raise TypeError('The given dict must only hold types as keys.')
return super().__new__(cls, args[0], **kwargs)
def __getitem__(self, item: Any) -> Any:
"""
Return the value of the first encounter of a key for which
``is_instance(item, key)`` holds ``True``.
:param item: any item.
:return: the value of which the type corresponds with item.
"""
from typish.functions._get_type import get_type
from typish.functions._subclass_of import subclass_of
item_type = get_type(item, use_union=True)
for key, value in self.items():
if subclass_of(item_type, key):
return value
raise KeyError('No match for {}'.format(item))
def get(self, item: Any, default: Any = None) -> Optional[Any]:
try:
return self.__getitem__(item)
except KeyError:
return default

View File

@@ -0,0 +1,71 @@
import inspect
from collections import OrderedDict
from typing import Callable, Any, Union, Iterable, Dict, Tuple
from typish._types import Empty
from typish.classes._cls_dict import ClsDict
class ClsFunction:
"""
ClsDict is a callable that takes a ClsDict or a dict. When called, it uses
the first argument to check for the right function in its body, executes it
and returns the result.
"""
def __init__(self, body: Union[ClsDict,
Dict[type, Callable],
Iterable[Tuple[type, Callable]],
Iterable[Callable]]):
from typish.functions._instance_of import instance_of
if isinstance(body, ClsDict):
self.body = body
elif isinstance(body, dict):
self.body = ClsDict(body)
elif instance_of(body, Iterable[Callable]):
list_of_tuples = []
for func in body:
signature = inspect.signature(func)
params = list(signature.parameters.keys())
if not params:
raise TypeError('ClsFunction expects callables that take '
'at least one parameter, {} does not.'
.format(func.__name__))
first_param = signature.parameters[params[0]]
hint = first_param.annotation
key = Any if hint == Empty else hint
list_of_tuples.append((key, func))
self.body = ClsDict(OrderedDict(list_of_tuples))
elif instance_of(body, Iterable[Tuple[type, Callable]]):
self.body = ClsDict(OrderedDict(body))
else:
raise TypeError('ClsFunction expects a ClsDict or a dict that can '
'be turned to a ClsDict or an iterable of '
'callables.')
if not all(isinstance(value, Callable) for value in self.body.values()):
raise TypeError('ClsFunction expects a dict or ClsDict with only '
'callables as values.')
def understands(self, item: Any) -> bool:
"""
Check to see if this ClsFunction can take item.
:param item: the item that is checked.
:return: True if this ClsFunction can take item.
"""
try:
self.body[item]
return True
except KeyError:
return False
def __call__(self, *args, **kwargs):
if not args:
raise TypeError('ClsFunction must be called with at least 1 '
'positional argument.')
callable_ = self.body[args[0]]
try:
return callable_(*args, **kwargs)
except TypeError as err:
raise TypeError('Unable to call function for \'{}\': {}'
.format(args[0], err.args[0]))

View File

@@ -0,0 +1,74 @@
import typing
from typish.classes._subscriptable_type import SubscriptableType
def is_literal_type(cls: typing.Any) -> bool:
"""
Return whether cls is a Literal type.
:param cls: the type that is to be checked.
:return: True if cls is a Literal type.
"""
from typish.functions._get_simple_name import get_simple_name
return get_simple_name(cls) == 'Literal'
class _LiteralMeta(SubscriptableType):
"""
A Metaclass that exists to serve Literal and alter the __args__ attribute.
"""
def __getattribute__(cls, item):
"""
This method makes sure that __args__ is a tuple, like with
typing.Literal.
:param item: the name of the attribute that is obtained.
:return: the attribute.
"""
if item == '__args__':
try:
result = SubscriptableType.__getattribute__(cls, item)
if (result and isinstance(result, tuple)
and isinstance(result[0], tuple)):
result = result[0] # result was a tuple in a tuple.
if result and not isinstance(result, tuple):
result = (result,)
except AttributeError: # pragma: no cover
# In case of Python 3.5
result = tuple()
elif item in ('__origin__', '__name__', '_name'):
result = 'Literal'
else:
result = SubscriptableType.__getattribute__(cls, item)
return result
def __instancecheck__(self, instance):
return self.__args__ and instance in self.__args__
def __str__(self):
args = ', '.join(str(arg) for arg in self.__args__)
return '{}[{}]'.format(self.__name__, args)
def __subclasscheck__(self, subclass: typing.Any) -> bool:
return is_literal_type(subclass)
class LiteralAlias(type, metaclass=_LiteralMeta):
"""
This is a backwards compatible variant of typing.Literal (Python 3.8+).
"""
@staticmethod
def from_literal(literal: typing.Any) -> typing.Type['LiteralAlias']:
"""
Create a LiteralAlias from the given typing.Literal.
:param literal: the typing.Literal type.
:return: a LiteralAlias type.
"""
from typish.functions._get_args import get_args
args = get_args(literal)
return LiteralAlias[args] if args else LiteralAlias
# If Literal is available (Python 3.8+), then return that type instead.
Literal = getattr(typing, 'Literal', LiteralAlias)

View File

@@ -0,0 +1,122 @@
import types
from collections import OrderedDict
from typing import Any, Dict, Callable, Tuple
from typish.classes._subscriptable_type import SubscriptableType
class _SomethingMeta(SubscriptableType):
"""
This metaclass is coupled to ``Something``.
"""
def __instancecheck__(self, instance: object) -> bool:
# Check if all attributes from self.signature are also present in
# instance and also check that their types correspond.
from typish.functions._instance_of import instance_of
sig = self.signature()
for key in sig:
attr = getattr(instance, key, None)
if not attr or not instance_of(attr, sig[key]):
return False
return True
def __subclasscheck__(self, subclass: type) -> bool:
# If an instance of type subclass is an instance of self, then subclass
# is a sub class of self.
from typish.functions._subclass_of import subclass_of
from typish.functions._get_type_hints_of_callable import get_args_and_return_type
self_sig = self.signature()
other_sig = Something.like(subclass).signature()
for attr in self_sig:
if attr in other_sig:
attr_sig = other_sig[attr]
if (not isinstance(subclass.__dict__[attr], staticmethod)
and not isinstance(subclass.__dict__[attr], classmethod)
and subclass_of(attr_sig, Callable)):
# The attr must be a regular method or class method, so the
# first parameter should be ignored.
args, rt = get_args_and_return_type(attr_sig)
attr_sig = Callable[list(args[1:]), rt]
if not subclass_of(attr_sig, self_sig[attr]):
return False
return True
def __eq__(self, other: 'Something') -> bool:
return (isinstance(other, _SomethingMeta)
and self.signature() == other.signature())
def __repr__(self):
sig = self.signature()
sig_ = ', '.join(["'{}': {}".format(k, self._type_repr(sig[k]))
for k in sig])
return 'typish.Something[{}]'.format(sig_)
def __hash__(self):
# This explicit super call is required for Python 3.5 and 3.6.
return super.__hash__(self)
def _type_repr(self, obj):
"""Return the repr() of an object, special-casing types (internal helper).
If obj is a type, we return a shorter version than the default
type.__repr__, based on the module and qualified name, which is
typically enough to uniquely identify a type. For everything
else, we fall back on repr(obj).
"""
if isinstance(obj, type) and not issubclass(obj, Callable):
return obj.__qualname__
if obj is ...:
return '...'
if isinstance(obj, types.FunctionType):
return obj.__name__
return repr(obj)
class Something(type, metaclass=_SomethingMeta):
"""
This class allows one to define an interface for something that has some
attributes, such as objects or classes or maybe even modules.
"""
@classmethod
def signature(mcs) -> Dict[str, type]:
"""
Return the signature of this ``Something`` as a dict.
:return: a dict with attribute names as keys and types as values.
"""
result = OrderedDict()
args = mcs.__args__
if isinstance(mcs.__args__, slice):
args = (mcs.__args__,)
arg_keys = sorted(args)
if isinstance(mcs.__args__, dict):
for key in arg_keys:
result[key] = mcs.__args__[key]
else:
for slice_ in arg_keys:
result[slice_.start] = slice_.stop
return result
def __getattr__(cls, item):
# This method exists solely to fool the IDE into believing that
# Something can have any attribute.
return type.__getattr__(cls, item) # pragma: no cover
@staticmethod
def like(obj: Any, exclude_privates: bool = True) -> 'Something':
"""
Return a ``Something`` for the given ``obj``.
:param obj: the object of which a ``Something`` is to be made.
:param exclude_privates: if ``True``, private variables are excluded.
:return: a ``Something`` that corresponds to ``obj``.
"""
from typish.functions._get_type import get_type
signature = {attr: get_type(getattr(obj, attr)) for attr in dir(obj)
if not exclude_privates or not attr.startswith('_')}
return Something[signature]
TypingType = Something['__origin__': type, '__args__': Tuple[type, ...]]

View File

@@ -0,0 +1,50 @@
class _SubscribedType(type):
"""
This class is a placeholder to let the IDE know the attributes of the
returned type after a __getitem__.
"""
__origin__ = None
__args__ = None
class SubscriptableType(type):
"""
This metaclass will allow a type to become subscriptable.
>>> class SomeType(metaclass=SubscriptableType):
... pass
>>> SomeTypeSub = SomeType['some args']
>>> SomeTypeSub.__args__
'some args'
>>> SomeTypeSub.__origin__.__name__
'SomeType'
"""
def __init_subclass__(mcs, **kwargs):
mcs._hash = None
mcs.__args__ = None
mcs.__origin__ = None
def __getitem__(self, item) -> _SubscribedType:
body = {
**self.__dict__,
'__args__': item,
'__origin__': self,
}
bases = self, *self.__bases__
result = type(self.__name__, bases, body)
if hasattr(result, '_after_subscription'):
# TODO check if _after_subscription is static
result._after_subscription(item)
return result
def __eq__(self, other):
self_args = getattr(self, '__args__', None)
self_origin = getattr(self, '__origin__', None)
other_args = getattr(other, '__args__', None)
other_origin = getattr(other, '__origin__', None)
return self_args == other_args and self_origin == other_origin
def __hash__(self):
if not getattr(self, '_hash', None):
self._hash = hash('{}{}'.format(self.__origin__, self.__args__))
return self._hash

View File

@@ -0,0 +1,7 @@
class _UnionType(type):
def __instancecheck__(self, instance):
return str(instance).startswith('typing.Union')
class UnionType(type, metaclass=_UnionType):
...