pax_global_header00006660000000000000000000000064141030456250014512gustar00rootroot0000000000000052 comment=7875850f55e2df8a9e2426e2d484ab618e347c7f typish-1.9.3/000077500000000000000000000000001410304562500130445ustar00rootroot00000000000000typish-1.9.3/.coveragerc000066400000000000000000000001131410304562500151600ustar00rootroot00000000000000# .coveragerc to control coverage.py [run] omit = setup.py tests/* typish-1.9.3/.github/000077500000000000000000000000001410304562500144045ustar00rootroot00000000000000typish-1.9.3/.github/workflows/000077500000000000000000000000001410304562500164415ustar00rootroot00000000000000typish-1.9.3/.github/workflows/pythonapp.yml000066400000000000000000000014521410304562500212100ustar00rootroot00000000000000name: typish on: [push] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: python-version: [ '3.5', '3.6', '3.7', '3.8' ] os: [ ubuntu-latest, macOS-latest, windows-latest ] name: Python ${{ matrix.python-version }} on ${{ matrix.os }} steps: - uses: actions/checkout@master - name: Setup python uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[test] - name: Test run: | python setup.py test coverage run setup.py test codecov - name: Coverage uses: codecov/codecov-action@v1 typish-1.9.3/.gitignore000066400000000000000000000000661410304562500150360ustar00rootroot00000000000000__pycache__/* *.swp *.swo ._* *.pyc *.*~ *~ .DS_Store typish-1.9.3/LICENSE000066400000000000000000000020401410304562500140450ustar00rootroot00000000000000Copyright 2020, Ramon Hagenaars Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. typish-1.9.3/README.md000066400000000000000000000136041410304562500143270ustar00rootroot00000000000000[![image](https://img.shields.io/pypi/pyversions/typish.svg)](https://pypi.org/project/typish/) [![Downloads](https://pepy.tech/badge/typish)](https://pepy.tech/project/typish) [![Pypi version](https://badge.fury.io/py/typish.svg)](https://badge.fury.io/py/typish) [![codecov](https://codecov.io/gh/ramonhagenaars/typish/branch/master/graph/badge.svg)](https://codecov.io/gh/ramonhagenaars/typish) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ramonhagenaars/typish/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ramonhagenaars/typish/?branch=master) # Typish * Functions for thorough checks on types * Instance checks considering generics * Typesafe Duck-typing ## Example ```python >>> from typing import Iterable >>> from typish import instance_of >>> instance_of([1, 2, 3], Iterable[int]) True ``` ## Installation ``` pip install typish ``` ## Content ### Functions | Function | Description |---|--- | ``subclass_of(cls: type, *args: type) -> bool`` | Returns whether ``cls`` is a sub type of *all* types in ``args`` | ``instance_of(obj: object, *args: type) -> bool`` | Returns whether ``cls`` is an instance of *all* types in ``args`` | ``get_origin(t: type) -> type`` | Return the "origin" of a generic type. E.g. ``get_origin(List[str])`` gives ``list``. | ``get_args(t: type) -> typing.Tuple[type, ...]`` | Return the arguments of a generic type. E.g. ``get_args(List[str])`` gives ``(str, )``. | ``get_alias(cls: T) -> typing.Optional[T]`` | Return the ``typing`` alias for a type. E.g ``get_alias(list)`` gives ``List``. | ``get_type(inst: T, use_union: bool = False) -> typing.Type[T]`` | Return the (generic) type of an instance. E.g. a list of ints will give ``List[int]``. | ``common_ancestor(*args: object) -> type`` | Return the closest common ancestor of the given instances. | ``common_ancestor_of_types(*args: type) -> type`` | Return the closest common ancestor of the given classes. | ``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]``). | ``get_type_hints_of_callable(func: typing.Callable) -> typing.Dict[str, type]`` | Return the type hints of the parameters of the given callable. | ``is_type_annotation(item: typing.Any) -> bool`` | Returns whether ``item`` is a ``type`` or a ``typing`` type. | ``is_from_typing(cls: type) -> bool`` | Returns whether ``cls`` is from the ``typing`` module. | ``is_optional_type(cls: type) -> bool`` | Returns whether ``cls`` is considered to be an optional type. | ``get_mro(obj: typing.Any) -> typing.Tuple[type, ...]`` | Wrapper around ``getmro`` from ``inspect`` to also support ``typing`` types. ### Types | Type | Description |---|---| | ``T`` | A generic Type var. | ``KT`` | A Type var for keys in a dict. | ``VT`` | A type var for values in a dict. | ``Empty`` | The type of emptiness (= ``Parameter.empty``). | ``Unknown`` | The type of something unknown. | ``Module`` | The type of a module. | ``NoneType`` | The type of ``None``. | ``EllipsisType`` | The type of ``...``. ### Decorators #### hintable This decorator allows one to capture the type hint of a variable that calls a function. If no hint is provided, `None` is passed as a value for `hint`. Just remember: with great power comes great responsibility. Use this functionality wisely. You may want to make sure that if you hinted a variable with a certain type, your `hintable` function does indeed return a value of that type. ```python @hintable def cast(arg: Any, hint: Type[T]) -> T: return hint(arg) # The type hint on x is passed to cast: x: int = cast('42') # It works with MyPy hints as well: y = cast('42') # type: int # Not something you would normally do, but the type hint takes precedence: z: int = cast('42') # type: str ``` ### Classes #### SubscriptableType This metaclass allows a type to become subscriptable. *Example:* ```python class MyClass(metaclass=SubscriptableType): ... ``` Now you can do: ```python MyClass2 = MyClass['some args'] print(MyClass2.__args__) print(MyClass2.__origin__) ``` Output: ``` some args ``` #### Something Define an interface with ``typish.Something``. *Example:* ```python Duck = Something['walk': Callable[[], None], 'quack': Callable[[], None]] ``` Anything that has the attributes defined in ``Something`` with the right type is considered an instance of that ``Something`` (classes, objects, even modules...). The builtin ``isinstance`` is supported as well as ``typish.instance_of``. #### ClsDict A dictionary that uses instance checking to determine which value to return. It only accepts types as keys. This is particularly useful when a function accepts multiple types for an argument and you want to split the implementation into separate functions. *Example:* ```python def _handle_str(item): ... def _handle_int(item): ... def func(item): # Suppose item can be a string or an int, you can use ClsDict to # pick a handler function. cd = ClsDict({ str: _handle_str, int: _handle_int, }) handler = cd[item] # Pick the right handler. handler(item) # Call that handler. ``` #### ClsFunction A callable that uses `ClsDict` to call the right function. Below is the same example as above, but slightly modified in that it uses `ClsFunction`. *Example:* ```python def _handle_str(item): ... def _handle_int(item): ... def func(item): # Suppose item can be a string or an int, you can use ClsFunction to # delegate to the right handler function. function = ClsFunction({ str: _handle_str, int: _handle_int, }) function(item) ``` #### Literal A backwards compatible variant of typing.Literal (Python3.8). When importing `Literal` from `typish`, you will get the `typing.Literal` if it is available. typish-1.9.3/setup.py000066400000000000000000000031541410304562500145610ustar00rootroot00000000000000import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) meta_info = {} with open(os.path.join(here, 'typish', '_meta.py'), 'r') as f: exec(f.read(), meta_info) with open('README.md', 'r') as fh: long_description = fh.read() requirements = [] test_requirements = [ 'numpy', 'nptyping>=1.3.0', 'pycodestyle', 'pylint', 'mypy', 'pytest', 'coverage', 'codecov', ] extras = { 'test': test_requirements, } setup( name=meta_info['__title__'], version=meta_info['__version__'], author=meta_info['__author__'], author_email=meta_info['__author_email__'], description=meta_info['__description__'], url=meta_info['__url__'], license=meta_info['__license__'], long_description=long_description, long_description_content_type='text/markdown', packages=find_packages(exclude=('tests', 'tests.*', 'test_resources', 'test_resources.*')), install_requires=requirements, tests_require=test_requirements, extras_require=extras, test_suite='tests', zip_safe=False, classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ] ) typish-1.9.3/test_resources/000077500000000000000000000000001410304562500161155ustar00rootroot00000000000000typish-1.9.3/test_resources/__init__.py000066400000000000000000000000001410304562500202140ustar00rootroot00000000000000typish-1.9.3/test_resources/hintable.py000066400000000000000000000105311410304562500202550ustar00rootroot00000000000000from typing import Type from unittest import TestCase from typish import T, hintable class C: def __init__(self, subject): self.subject = subject @hintable def cast(subject, hint: Type[T]) -> T: return hint(subject) @hintable def some_func(hint: Type[T]) -> Type[T]: """Some docstring""" return hint @hintable(param='cls') def some_func_with_custom_param_name(cls): return cls class SomeClass: @hintable def some_method(self, hint): return hint @staticmethod @hintable def some_static_method(hint): return hint @classmethod @hintable def some_class_method(cls, hint): return hint class TestHintable(TestCase): def test_hintable(self): # Test that a function can be decorated and receives a hint. x: int = cast('42') y: str = cast(42) z: ' str ' = cast(42) # Even a sloppy hint should work. self.assertEqual(42, x) self.assertEqual('42', y) self.assertEqual('42', z) def test_hintable_with_parentheses(self): # Test that hintable can be used with parentheses as well. @hintable() # Note the parentheses. def some_function(hint): return hint x: int = some_function() self.assertEqual(int, x) def test_hintable_with_custom_param_name(self): # Test that functions can customize the parameter name that receives # the type hint. x: int = some_func_with_custom_param_name() self.assertEqual(int, x) def test_hintable_method(self): # Test that methods can be hintable as well. sc = SomeClass() x: int = sc.some_method() y: float = SomeClass.some_static_method() z: str = SomeClass.some_class_method() self.assertEqual(int, x) self.assertEqual(float, y) self.assertEqual(str, z) def test_hintable_with_custom_type(self): # Test that a custom type can be used as hint without a problem. x: C = cast(42) y: 'C' = cast(42) self.assertTrue(isinstance(x, C)) self.assertTrue(isinstance(y, C)) def test_hintable_with_textual_hint(self): # Test that textual hints are received as strings. x: 'some rubbish' = some_func() y: "'some even greater rubbish'" = some_func() self.assertEqual('some rubbish', x) self.assertEqual('\'some even greater rubbish\'', y) def test_hintable_with_comment_hint(self): # Test that hints in MyPy style work as well. x = some_func() # type: int y = some_func() # type: rubbish_again # The type hint should take precedence of MyPy-styled-hints: z: int = some_func() # type: str self.assertEqual(int, x) self.assertEqual('rubbish_again', y) self.assertEqual(int, z) def test_override_with_custom_hint(self): # Test that you can still override the hint. x = some_func(hint=int) y: int = some_func(hint=str) # It's allowed, but is it a good idea? self.assertEqual(int, x) self.assertEqual(str, y) def test_as_parameter_in_a_function(self): # Test that a hintable function should also work as default argument. def func_with_default(arg1: int = some_func(), arg2: float = some_func(), arg3: str = some_func()): return arg1, arg2, arg3 x, y, z = func_with_default() x2, y2, z2 = func_with_default() x3, y3, z3 = func_with_default() self.assertEqual(int, x) self.assertEqual(float, y) self.assertEqual(str, z) self.assertEqual(int, x2) self.assertEqual(float, y2) self.assertEqual(str, z2) self.assertEqual(int, x3) self.assertEqual(float, y3) self.assertEqual(str, z3) def test_multiple_on_a_line(self): # Test that multiple hintable calls on a line work. # Yes, this IS valid Python. No it is NOT recommended! x: int = some_func(); y: str = some_func() self.assertEqual(int, x) self.assertEqual(str, y) def test_multiline_wont_break(self): # Test that multiline code at least doesnt break # This is just too crazy. If you write code like this, you're on your # own. x: \ int \ = \ some_func() self.assertEqual(None, x) typish-1.9.3/test_resources/some_module.py000066400000000000000000000000751410304562500210010ustar00rootroot00000000000000a = 10 def b(x: int, y: int) -> str: return str(x + y) typish-1.9.3/tests/000077500000000000000000000000001410304562500142065ustar00rootroot00000000000000typish-1.9.3/tests/__init__.py000066400000000000000000000000001410304562500163050ustar00rootroot00000000000000typish-1.9.3/tests/classes/000077500000000000000000000000001410304562500156435ustar00rootroot00000000000000typish-1.9.3/tests/classes/__init__.py000066400000000000000000000000001410304562500177420ustar00rootroot00000000000000typish-1.9.3/tests/classes/test_cls_dict.py000066400000000000000000000032511410304562500210410ustar00rootroot00000000000000from collections import OrderedDict from typing import List, Union, Tuple from unittest import TestCase from typish import ClsDict class TestClsDict(TestCase): def test_invalid_initialization(self): # Only one positional argument is accepted. with self.assertRaises(TypeError): ClsDict({}, {}) # The positional argument must be a dict with self.assertRaises(TypeError): ClsDict(1) # The dict must have types as keys with self.assertRaises(TypeError): ClsDict({ int: 1, str: 2, 'float': 3, }) def test_getitem(self): cd = ClsDict({int: 123, str: '456'}) self.assertEqual(123, cd[42]) self.assertEqual('456', cd['test']) def test_getitem_more_complicated(self): cd = ClsDict({ List[Union[str, int]]: 1, Tuple[float, ...]: 2, }) self.assertEqual(1, cd[[1, 2, '3', 4]]) self.assertEqual(2, cd[(1.0, 2.0, 3.0, 4.0)]) with self.assertRaises(KeyError): self.assertEqual(2, cd[(1.0, 2.0, '3.0', 4.0)]) def test_no_match(self): cd = ClsDict({str: '456'}) with self.assertRaises(KeyError): cd[42] def test_get(self): cd = ClsDict({str: '456'}) self.assertEqual('456', cd.get('test')) self.assertEqual(None, cd.get(42)) self.assertEqual(123, cd.get(42, 123)) def test_ordereddict(self): od = OrderedDict([ (int, 1), (object, 2), ]) cd = ClsDict(od) self.assertEqual(1, cd[123]) self.assertEqual(2, cd['123']) typish-1.9.3/tests/classes/test_cls_function.py000066400000000000000000000076301410304562500217500ustar00rootroot00000000000000from typing import Tuple, Any, Union from unittest import TestCase from typish import ClsDict, EllipsisType, Literal, ClsFunction class TestClsFunction(TestCase): def test_invalid_initialization(self): # Only ClsDict or dict is allowed with self.assertRaises(TypeError): ClsFunction(123) def test_instantiation_with_no_callable(self): with self.assertRaises(TypeError): ClsFunction({ int: lambda: 1, str: lambda: 2, object: 3, # Invalid! }) def test_with_dict(self): function = ClsFunction({ int: lambda x: x * 2, str: lambda x: '{}_'.format(x), }) self.assertEqual(4, function(2)) self.assertEqual('2_', function('2')) def test_with_cls_dict(self): function = ClsFunction(ClsDict({ int: lambda x: x * 2, str: lambda x: '{}_'.format(x), })) self.assertEqual(4, function(2)) self.assertEqual('2_', function('2')) def test_with_iterable_of_tuples(self): body = [ (int, lambda x: x * 2), (str, lambda x: '{}_'.format(x)), ] function_tuple = ClsFunction(body) function_set = ClsFunction(set(body)) function_list = ClsFunction(list(body)) self.assertEqual(4, function_tuple(2)) self.assertEqual('2_', function_tuple('2')) self.assertEqual(4, function_set(2)) self.assertEqual('2_', function_set('2')) self.assertEqual(4, function_list(2)) self.assertEqual('2_', function_list('2')) def test_with_callables(self): def f1(x: int): return 1 class C: def m1(self, x: str): return 2 @classmethod def m2(cls, x: float): return 3 @staticmethod def m3(x: list): return 4 def f2(x): # No type hint return 5 function = ClsFunction([f1, C().m1, C.m2, C.m3, f2]) self.assertEqual(1, function(42)) self.assertEqual(2, function('hello')) self.assertEqual(3, function(42.0)) self.assertEqual(4, function([42])) self.assertEqual(5, function({})) def test_with_invalid_callables(self): def f(): ... with self.assertRaises(TypeError): ClsFunction([f]) def test_multiple_args(self): function = ClsFunction({ int: lambda x, y: x * y, str: lambda x, y: '{}{}'.format(x, y), }) self.assertEqual(6, function(2, 3)) self.assertEqual('23', function('2', 3)) def test_understands(self): function = ClsFunction({ int: lambda _: ..., str: lambda _: ..., }) self.assertTrue(function.understands(1)) self.assertTrue(function.understands('2')) self.assertTrue(not function.understands(3.0)) def test_call_no_args(self): function = ClsFunction({ int: lambda x: 1, }) with self.assertRaises(TypeError): function() def test_call_invalid_args(self): function = ClsFunction({ int: lambda x: 1, }) with self.assertRaises(TypeError): function(1, 2) # The lambda expects only 1 argument. def test_complex_cls_function(self): # Test if a more complex ClsFunction can be created without problems. _Size = Union[int, Literal[Any]] _Type = Union[type, Literal[Any]] ClsFunction({ _Size: lambda: 1, _Type: lambda: 2, Tuple[_Size, _Type]: lambda: 3, Tuple[_Size, ...]: lambda: 4, Tuple[Tuple[_Size, ...], _Type]: lambda: 5, Tuple[Tuple[_Size, EllipsisType], _Type]: lambda: 6, Tuple[Tuple[Literal[Any], EllipsisType], Literal[Any]]: lambda: 7, }) typish-1.9.3/tests/classes/test_literal_meta.py000066400000000000000000000014171410304562500217210ustar00rootroot00000000000000from unittest import TestCase from typish import LiteralAlias class TestLiteralMeta(TestCase): def test_from_literal(self): class LiteralMock: __args__ = (42,) alias = LiteralAlias.from_literal(LiteralMock) self.assertTrue(isinstance(42, alias)) def test_str(self): self.assertEqual('Literal[42]', str(LiteralAlias[42])) def test_multiple_args(self): self.assertTrue(isinstance(1, LiteralAlias[1, 2])) self.assertTrue(isinstance(2, LiteralAlias[1, 2])) self.assertTrue(isinstance(1, LiteralAlias[(1, 2)])) self.assertTrue(isinstance(2, LiteralAlias[(1, 2)])) self.assertTrue(isinstance(1, LiteralAlias[((1, 2),)])) self.assertTrue(isinstance(2, LiteralAlias[((1, 2),)])) typish-1.9.3/tests/classes/test_something.py000066400000000000000000000051671410304562500212620ustar00rootroot00000000000000from typing import Callable, List, Set from unittest import TestCase from test_resources import some_module from typish import Something, TypingType Inyerface = Something[{ 'a': int, 'b': Callable[[int, int], str], }] class C1: a = 42 def b(self, a: int, b: int) -> str: return str(a + b) class C2: def __init__(self): self.a = 42 def b(self, a: int, b: int) -> str: return str(a + b) class C3: def __init__(self): self.a = 42 def b(self, a, b): return str(a + b) class C4: def __init__(self): self.a = 42 def b(self, a, b: str): return str(a + b) class C5: a = 42 @staticmethod def b(a: int, b: int) -> str: return str(a + b) class C6: a = 42 @classmethod def b(cls, a: int, b: int) -> str: return str(a + b) class TestSomething(TestCase): def test_something_with_slices(self): self.assertEqual(Inyerface, Something['a': int, 'b': Callable[[int, int], str]]) def test_something_instance_check(self): self.assertTrue(isinstance(C1(), Inyerface)) self.assertTrue(isinstance(C2(), Inyerface)) self.assertTrue(not isinstance(C3(), Inyerface)) self.assertTrue(not isinstance(C4(), Inyerface)) self.assertTrue(isinstance(C5(), Inyerface)) self.assertTrue(isinstance(C6(), Inyerface)) def test_something_subclass_check(self): self.assertTrue(issubclass(C1, Inyerface)) self.assertTrue(issubclass(C2, Inyerface)) self.assertTrue(not issubclass(C3, Inyerface)) self.assertTrue(not issubclass(C4, Inyerface)) self.assertTrue(issubclass(C5, Inyerface)) self.assertTrue(issubclass(C6, Inyerface)) def test_module_something_instance_check(self): self.assertTrue(isinstance(some_module, Inyerface)) def test_something_repr(self): self.assertEqual("typish.Something['x': int]", str(Something['x': int])) self.assertEqual("typish.Something['a': int, 'b': typing.Callable[[int, int], str]]", repr(Inyerface)) self.assertEqual("typish.Something['x': ...]", str(Something['x': ...])) self.assertEqual("typish.Something['x': C1]", str(Something['x': C1])) self.assertEqual("typish.Something['x': b]", str(Something['x': C5.b])) def test_isinstance_generic_collection(self): isinstance(List[int], TypingType) isinstance(Set[str], TypingType) def test_hash_something(self): # If the lines below raise no errors, the test succeeds. hash(Something['abc': int]) L = List[Something['abc': int]] typish-1.9.3/tests/classes/test_subscribtable_type.py000066400000000000000000000014151410304562500231420ustar00rootroot00000000000000from unittest import TestCase from typish import SubscriptableType class TestSubscriptableType(TestCase): def test_subscribing(self): class C(metaclass=SubscriptableType): ... self.assertEqual('arg', C['arg'].__args__) self.assertEqual(C, C['arg'].__origin__) def test_after_subscription(self): class C(metaclass=SubscriptableType): @staticmethod def _after_subscription(item): C.item = item C2 = C['arg'] self.assertEqual('arg', C2.item) def test_equility(self): class SomeType(metaclass=SubscriptableType): ... self.assertEqual(SomeType['test'], SomeType['test']) self.assertNotEqual(SomeType['test1'], SomeType['test2']) typish-1.9.3/tests/decorators/000077500000000000000000000000001410304562500163535ustar00rootroot00000000000000typish-1.9.3/tests/decorators/__init__.py000066400000000000000000000000001410304562500204520ustar00rootroot00000000000000typish-1.9.3/tests/decorators/test_hintable.py000066400000000000000000000030171410304562500215530ustar00rootroot00000000000000import sys from typing import Type from unittest import TestCase from typish import hintable, T if sys.version_info.major * 10 + sys.version_info.minor > 35: # All Python 3.5+ specific tests are moved to a separate module. from test_resources.hintable import TestHintable as Base else: Base = TestCase @hintable def some_func(hint: Type[T]) -> Type[T]: """Some docstring""" return hint class TestHintable(Base): def test_hintable_without_any_hint(self): # Test that when a hintable function is called without hint, it # receives None. x = some_func() self.assertEqual(None, x) def test_hintable_class(self): # Test that decorating a class raises an error. with self.assertRaises(TypeError): @hintable class DecoratedClass: ... def test_meta_data(self): # Test that any meta data is copied properly. self.assertEqual('Some docstring', some_func.__doc__) def test_hintable_with_flawed_function(self): with self.assertRaises(TypeError): @hintable def some_flawed_func(): ... def test_hintable_with_flawed_custom_param_name(self): # Test that when a custom param name is used, it is checked if a # parameter with that name is accepted by the decorated function. with self.assertRaises(TypeError): @hintable(param='cls') def some_func_with_flawed_custom_param_name(hint): return hint typish-1.9.3/tests/functions/000077500000000000000000000000001410304562500162165ustar00rootroot00000000000000typish-1.9.3/tests/functions/__init__.py000066400000000000000000000000001410304562500203150ustar00rootroot00000000000000typish-1.9.3/tests/functions/test_common_ancestor.py000066400000000000000000000023231410304562500230150ustar00rootroot00000000000000from typing import Type from unittest import TestCase from typish import common_ancestor, common_ancestor_of_types, NoneType class A: pass class B(A): pass class C(B): pass class D(C): pass class E(D): pass class TestCommonAncestor(TestCase): def test_common_ancestor(self): self.assertEqual(C, common_ancestor(E(), C(), D(), E())) self.assertEqual(B, common_ancestor(E(), C(), D(), E(), B())) self.assertEqual(object, common_ancestor(E(), C(), D(), E(), B(), 42)) def test_common_ancestor_of_types(self): self.assertEqual(C, common_ancestor_of_types(E, C, D, E)) self.assertEqual(object, common_ancestor_of_types(int, str)) common_ancestor_of_types(list, tuple) def test_common_ancestor_of_typing_types(self): self.assertEqual(type, common_ancestor_of_types(Type[int], Type[str])) def test_common_acestor_of_collections(self): self.assertEqual(list, common_ancestor([1, 2, 3], ['a', 'b', 'c'])) def test_special_args(self): self.assertEqual(NoneType, common_ancestor(None, None)) self.assertEqual(int, common_ancestor(42)) def test_invalid(self): with self.assertRaises(TypeError): common_ancestor() typish-1.9.3/tests/functions/test_get_args_and_return_type.py000066400000000000000000000023521410304562500247060ustar00rootroot00000000000000from typing import Callable from unittest import TestCase from typish import NoneType, get_args_and_return_type class TestGetArgsAndReturnType(TestCase): def test_get_args_and_return_type(self): arg_types, return_type = get_args_and_return_type( Callable[[int, str], float]) self.assertEqual((int, str), arg_types) self.assertEqual(float, return_type) def test_get_args_and_return_type_no_args(self): arg_types, return_type = get_args_and_return_type( Callable[[], float]) self.assertEqual(tuple(), arg_types) self.assertEqual(float, return_type) def test_get_args_and_return_type_no_return_type(self): arg_types, return_type = get_args_and_return_type( Callable[[int, str], None]) self.assertEqual((int, str), arg_types) self.assertEqual(NoneType, return_type) def test_get_args_and_return_type_no_hints(self): arg_types1, return_type1 = get_args_and_return_type(Callable) arg_types2, return_type2 = get_args_and_return_type(callable) self.assertEqual(None, arg_types1) self.assertEqual(None, return_type1) self.assertEqual(None, arg_types2) self.assertEqual(None, return_type2) typish-1.9.3/tests/functions/test_get_mro.py000066400000000000000000000013521410304562500212640ustar00rootroot00000000000000import typing from typing import Union from unittest import TestCase from typish import get_mro class A: ... class B(A): ... class TestGetMRO(TestCase): def test_get_mro(self): mro_b = get_mro(B) self.assertTupleEqual((B, A, object), mro_b) def test_get_mro_union(self): mro_u = get_mro(Union[int, str]) # Below is to stay compatible with Python 3.5+ super_cls = getattr(typing, '_GenericAlias', getattr(typing, 'GenericMeta', None)) expected = (typing.Union, super_cls, object) self.assertTupleEqual(expected, mro_u) def test_get_mro_object(self): mro_b = get_mro(B()) self.assertTupleEqual((B, A, object), mro_b) typish-1.9.3/tests/functions/test_get_type.py000066400000000000000000000065121410304562500214530ustar00rootroot00000000000000from typing import Dict, Set, List, Tuple, Type, Callable, Any, Awaitable, Union from unittest import TestCase import numpy from typish import get_type, instance_of, NoneType, Unknown class TestGetType(TestCase): def test_get_type_list(self): self.assertEqual(List[int], get_type([1, 2, 3])) self.assertEqual(List[object], get_type([1, 2, '3'])) self.assertEqual(List[Unknown], get_type([])) def test_get_type_list_with_union(self): self.assertEqual(List[Union[int, str, float]], get_type([1, '2', 3.0], True)) def test_get_type_set(self): self.assertEqual(Set[int], get_type({1, 2, 3})) self.assertEqual(Set[object], get_type({1, 2, '3'})) self.assertEqual(Set[Unknown], get_type(set([]))) def test_get_type_dict(self): self.assertEqual(Dict[str, int], get_type({'a': 1, 'b': 2})) self.assertEqual(Dict[int, object], get_type({1: 'a', 2: 2})) self.assertEqual(Dict[Unknown, Unknown], get_type({})) def test_get_type_tuple(self): self.assertEqual(Tuple[int, int, int], get_type((1, 2, 3))) self.assertEqual(Tuple[int, str, str], get_type((1, '2', '3'))) def test_get_type_callable(self): def func1(x: int, y: str) -> object: pass def func2() -> int: pass def func3(x: int): pass def func4(): pass def func5(x): pass def func6(l: List[List[List[int]]]): pass self.assertEqual(Callable[[int, str], object], get_type((func1))) self.assertEqual(Callable[[], int], get_type(func2)) self.assertEqual(Callable[[int], NoneType], get_type(func3)) self.assertEqual(Callable, get_type(func4)) self.assertEqual(Callable[[Any], NoneType], get_type(func5)) self.assertEqual(Callable[[List[List[List[int]]]], NoneType], get_type(func6)) def test_get_type_async(self): async def func(x: int) -> str: return '42' self.assertEqual(Callable[[int], Awaitable[str]], get_type(func)) def test_get_type_lambda(self): self.assertEqual(Callable[[Unknown, Unknown], Unknown], get_type(lambda x, y: 42)) self.assertEqual(Callable[[], Unknown], get_type(lambda: 42)) def test_get_type_deep(self): self.assertEqual(List[List[int]], get_type([[1, 2], [3]])) self.assertEqual(List[List[object]], get_type([[1, 2], ['3']])) deep = [[[1, 2]], [[3]], [[4], [5, '6']]] self.assertEqual(List[List[List[object]]], get_type(deep)) self.assertEqual(Dict[str, List[List[List[object]]]], get_type({'a': deep})) def test_get_type_type(self): self.assertEqual(Type[int], get_type(int)) def test_get_type_some_class(self): class SomeRandomClass: pass self.assertEqual(SomeRandomClass, get_type(SomeRandomClass())) def test_get_type_any(self): any_type = get_type(Any) self.assertEqual(Any, any_type) def test_get_type_union(self): union_type = get_type(Union[int, str]) # The following line should not raise. Union[(union_type,)] self.assertTrue(instance_of(Union[int, str], union_type)) def test_get_type_of_ndarray(self): arr_type = get_type(numpy.array([1, 2, 3])) self.assertEqual(numpy.ndarray, arr_type) typish-1.9.3/tests/functions/test_get_type_hints_of_callable.py000066400000000000000000000031361410304562500251620ustar00rootroot00000000000000from typing import Callable from unittest import TestCase from typish import get_type_hints_of_callable, get_args_and_return_type class TestGetTypeHintsOfCallable(TestCase): def test_get_type_hints_of_callable(self): def func(x: int, y: int) -> str: return '{}{}'.format(x, y) hints = get_type_hints_of_callable(func) self.assertEqual(int, hints['x']) self.assertEqual(int, hints['y']) self.assertEqual(str, hints['return']) def test_get_type_hints_of_empty_callable(self): def func(): return 42 hints = get_type_hints_of_callable(func) self.assertEqual({}, hints) def test_get_args_and_return_type(self): args, return_type = get_args_and_return_type(Callable[[int, int], str]) self.assertTupleEqual((int, int), args) self.assertEqual(str, return_type) def test_get_args_and_return_type_with_explicit_result(self): class CallableMock: __args__ = (int, int) __result__ = str args, return_type = get_args_and_return_type(CallableMock) self.assertTupleEqual((int, int), args) self.assertEqual(str, return_type) def test_get_type_hints_of_callable_with_raising_callable(self): # Python3.5: get_type_hints raises on classes without explicit constructor class CallableMetaMock(type): def __getattribute__(self, item): raise AttributeError('') class CallableMock(metaclass=CallableMetaMock): ... self.assertEqual({}, get_type_hints_of_callable(CallableMock)) typish-1.9.3/tests/functions/test_instance_of.py000066400000000000000000000127321410304562500221240ustar00rootroot00000000000000from sys import version_info from typing import List, Dict, Union, Optional, Callable, Any, Tuple, Type, Iterable from unittest import TestCase, skipUnless import nptyping as nptyping import numpy from typish import Literal, instance_of, State, register_get_type class A: pass class B(A): pass class C(B): pass class D(C): pass class E(D): pass class F(E, str): pass class TestInstanceOf(TestCase): def test_instance_of(self): self.assertTrue(instance_of([[[1], [2]]], List[List[List[int]]])) self.assertTrue(instance_of([[[1], ['2']]], List[List[List[object]]])) self.assertTrue(instance_of([], List[int])) self.assertTrue(instance_of({}, Dict[int, int])) def test_instance_of_multiple(self): self.assertTrue(instance_of(F(), A)) self.assertTrue(instance_of(F(), str)) self.assertTrue(instance_of(F(), A, str)) self.assertTrue(not instance_of(F(), A, str, int)) def test_instance_of_union(self): self.assertTrue(instance_of(F(), Union[int, A])) self.assertTrue(instance_of(F(), Union[A, int])) self.assertTrue(instance_of(F(), Union[A, str])) self.assertTrue(instance_of(F(), Optional[str])) self.assertTrue(instance_of(None, Optional[str])) self.assertTrue(instance_of(Any, Union[int, Literal[Any]])) def test_instance_of_callable(self): def func1(x: int, y: str) -> object: pass def func2() -> int: pass def func3(x: int): pass def func4(): pass def func5(x): pass def func6(l: List[List[List[int]]]): pass self.assertTrue(instance_of(func1, Callable[[int, str], object])) self.assertTrue(instance_of(func1, Callable[[object, str], object])) self.assertTrue(not instance_of(func1, Callable[[str, str], object])) self.assertTrue(not instance_of(func1, Callable[[str, str], int])) self.assertTrue(instance_of(func2, Callable[[], int])) self.assertTrue(instance_of(func3, Callable[[int], Any])) self.assertTrue(instance_of(func4, Callable)) self.assertTrue(instance_of(func5, Callable[[Any], Any])) self.assertTrue(instance_of(func6, Callable[[List[List[List[int]]]], Any])) def test_lambda_instance_of_callable(self): self.assertTrue(instance_of(lambda x, y: 42, Callable[[int, str], str])) self.assertTrue(instance_of(lambda: 42, Callable[[], str])) def test_instance_of_type(self): self.assertTrue(instance_of(int, Type)) self.assertTrue(instance_of(int, Type[int])) self.assertTrue(not instance_of(str, Type[int])) def test_instance_of_tuple(self): self.assertTrue(instance_of((1,), Tuple[int])) self.assertTrue(instance_of((1, 2, 3), Tuple[int, ...])) def test_instance_of_list_with_union(self): self.assertTrue(instance_of([1, '2', 3], List[Union[int, str]])) self.assertTrue(not instance_of([1, '2', 3], List[Union[int, float]])) def test_instance_of_tuple_with_union(self): self.assertTrue(instance_of((1, '2', 3), Tuple[Union[int, str], ...])) self.assertTrue(not instance_of((1, '2', 3), Tuple[Union[int, float], ...])) def test_instance_of_iterable(self): self.assertTrue(instance_of([1, 2, 3], Iterable[int])) self.assertTrue(instance_of((1, 2, 3), Iterable[int])) def test_instance_of_literal(self): self.assertTrue(instance_of(42, Literal[42])) self.assertTrue(instance_of(42, Literal[42], int)) self.assertTrue(not instance_of(43, Literal[42])) self.assertTrue(not instance_of(42, Literal[42], str)) self.assertTrue(not instance_of(42, Literal)) self.assertTrue(instance_of(Any, Literal[Any])) self.assertTrue(not instance_of(42, Literal[Any])) self.assertTrue(not instance_of(42, Literal)) self.assertTrue(instance_of(2, Literal[1, 2])) def test_instance_of_typing_literal(self): # This is to mock Python 3.8 Literal. class LiteralMockMeta(type): __name__ = 'Literal' def __instancecheck__(self, instance): raise Exception('typing.Literal does not allow instance checks.') class LiteralMock(metaclass=LiteralMockMeta): __args__ = (42,) self.assertTrue(instance_of(42, LiteralMock)) def test_instance_of_numpy(self): self.assertTrue(instance_of(numpy.array([1, 2, 3]), numpy.ndarray)) def test_instance_of_nptyping_ndarray(self): local_state = State() register_get_type(numpy.ndarray, nptyping.NDArray.type_of, local_state) arr = numpy.array([1, 2, 3]) arr_type = nptyping.NDArray[(3,), int] self.assertTrue(instance_of(arr, arr_type, state=local_state)) self.assertTrue(instance_of([arr], List[arr_type], state=local_state)) self.assertTrue(instance_of([arr], List[nptyping.NDArray], state=local_state)) self.assertTrue(not instance_of([arr], List[nptyping.NDArray[(4,), float]], state=local_state)) @skipUnless(10 * version_info.major + version_info.minor >= 39, 'PEP-585') def test_instance_of_py39_types(self): self.assertTrue(instance_of({'42': 42}, Dict[str, int])) self.assertTrue(not instance_of({'42': 42}, Dict[str, str])) self.assertTrue(not instance_of({42: 42}, Dict[str, int])) self.assertTrue(instance_of([1, 2, 3], list[int])) self.assertTrue(not instance_of([1, 2, 3], list[str])) typish-1.9.3/tests/functions/test_is_optional_type.py000066400000000000000000000010311410304562500232030ustar00rootroot00000000000000from typing import Optional, Union from unittest import TestCase from typish import ( NoneType, ) from typish.functions._is_optional_type import is_optional_type class TestIsOptionalType(TestCase): def test_is_optional_type(self): self.assertTrue(is_optional_type(Optional[str])) self.assertTrue(is_optional_type(Union[str, None])) self.assertTrue(is_optional_type(Union[str, NoneType])) self.assertTrue(not is_optional_type(str)) self.assertTrue(not is_optional_type(Union[str, int])) typish-1.9.3/tests/functions/test_is_type_annotation.py000066400000000000000000000017741410304562500235460ustar00rootroot00000000000000from typing import List from unittest import TestCase from typish import ( is_type_annotation, T, KT, VT, Unknown, Module, NoneType, EllipsisType, Empty, ) class TestIsTypeAnnotation(TestCase): def test_is_type_annotation(self): # builtins and typing. self.assertTrue(is_type_annotation(int)) self.assertTrue(is_type_annotation(List)) # Typish types. self.assertTrue(is_type_annotation(Empty)) self.assertTrue(is_type_annotation(Unknown)) self.assertTrue(is_type_annotation(Module)) self.assertTrue(is_type_annotation(NoneType)) self.assertTrue(is_type_annotation(EllipsisType)) # No instances. self.assertTrue(not is_type_annotation(123)) self.assertTrue(not is_type_annotation('No way')) # Typevars do not count. self.assertTrue(not is_type_annotation(T)) self.assertTrue(not is_type_annotation(KT)) self.assertTrue(not is_type_annotation(VT)) typish-1.9.3/tests/functions/test_origin_and_alias.py000066400000000000000000000036461410304562500231220ustar00rootroot00000000000000from collections import deque, defaultdict from collections.abc import Set from typing import ( Dict, List, Tuple, FrozenSet, Deque, DefaultDict, Type, AbstractSet, Set as TypingSet ) from unittest import TestCase from typish import get_origin, get_alias class Union: """To shadow typing.Union.""" class MetaMock(type): __name__ = 'list' __args__ = (str,) class ListMock(metaclass=MetaMock): ... class TestOriginAndAlias(TestCase): def test_get_origin(self): self.assertEqual(list, get_origin(List[int])) self.assertEqual(tuple, get_origin(Tuple[int, ...])) self.assertEqual(dict, get_origin(Dict[str, int])) self.assertEqual(set, get_origin(TypingSet)) self.assertEqual(deque, get_origin(Deque)) self.assertEqual(defaultdict, get_origin(DefaultDict)) self.assertEqual(type, get_origin(Type[int])) self.assertEqual(Set, get_origin(AbstractSet)) self.assertIn('test_origin_and_alias', str(get_origin(Union))) try: self.assertEqual(dict, get_origin(dict[str, str])) self.assertEqual(list, get_origin(list[int])) except TypeError as err: ... # On <3.9 def test_get_alias(self): self.assertEqual(List, get_alias(list)) self.assertEqual(Tuple, get_alias(tuple)) self.assertEqual(Dict, get_alias(dict)) self.assertEqual(TypingSet, get_alias(set)) self.assertEqual(FrozenSet, get_alias(frozenset)) self.assertEqual(Deque, get_alias(deque)) self.assertEqual(DefaultDict, get_alias(defaultdict)) self.assertEqual(Type, get_alias(type)) self.assertEqual(AbstractSet, get_alias(Set)) self.assertEqual(List, get_alias(List)) self.assertEqual(Dict, get_alias(Dict)) def test_get_alias_from_parameterized_standard_list(self): self.assertEqual(List[str], get_alias(ListMock)) typish-1.9.3/tests/functions/test_subclass_of.py000066400000000000000000000102341410304562500221320ustar00rootroot00000000000000import sys from typing import List, Tuple, Union, Optional, Iterable, Any from unittest import TestCase from typish import Literal, subclass_of, Unknown, NoneType class A: pass class B(A): pass class C(B): pass class D(C): pass class E(D): pass class F(E, str): pass class TestSubclassOf(TestCase): def test_subclass_of(self): self.assertTrue(not subclass_of(int, str)) self.assertTrue(subclass_of(E, A)) self.assertTrue(subclass_of(str, object)) self.assertTrue(subclass_of(list, List)) self.assertTrue(subclass_of(List[int], List[int])) self.assertTrue(not subclass_of(List[int], List[str])) self.assertTrue(subclass_of(List[List[List[int]]], List[List[List[int]]])) self.assertTrue(subclass_of(List[int], List[object])) self.assertTrue(not subclass_of(List[object], List[int])) self.assertTrue(subclass_of(List[Unknown], List[int])) self.assertTrue(not subclass_of('test', str)) def test_subclass_of_tuple(self): self.assertTrue(subclass_of(Tuple[int, int], Tuple[int, ...])) self.assertTrue(subclass_of(Tuple[int, ...], Tuple[int, ...])) self.assertTrue(subclass_of(Tuple[int, int], Tuple[object, ...])) self.assertTrue(subclass_of(Tuple[int, ...], Tuple[object, ...])) self.assertTrue(subclass_of(Tuple[A, B], Tuple[A, ...])) self.assertTrue(subclass_of(Tuple[int, int], Tuple[int, int])) self.assertTrue(subclass_of(Tuple[int, int], Tuple[object, int])) self.assertTrue(not subclass_of(Tuple[int, int], Tuple[str, int])) self.assertTrue(not subclass_of(Tuple[int, int], Tuple[int, int, int])) self.assertTrue(not subclass_of(Tuple[int, int, int], Tuple[int, int])) self.assertTrue(not subclass_of(Tuple[int, str], Tuple[int, ...])) def test_list_subclass_of_tuple(self): self.assertTrue(not subclass_of(List[int], Tuple[int, ...])) self.assertTrue(not subclass_of(List[int], Tuple[int, int])) def test_tuple_subclass_of_list(self): self.assertTrue(not subclass_of(Tuple[int, ...], List[int])) def test_subclass_of_iterable(self): self.assertTrue(subclass_of(List[int], Iterable[int])) self.assertTrue(subclass_of(Tuple[int, int, int], Iterable[int])) self.assertTrue(subclass_of(Tuple[int, ...], Iterable[int])) self.assertTrue(subclass_of(Tuple[B, C, D], Iterable[B])) self.assertTrue(subclass_of(Tuple[B, C, D], Iterable[A])) self.assertTrue(subclass_of(List[Tuple[int, str]], Iterable[Tuple[int, str]])) self.assertTrue(subclass_of(Tuple[Tuple[int, str]], Iterable[Tuple[int, str]])) def test_subclass_of_multiple(self): self.assertTrue(subclass_of(F, A)) self.assertTrue(subclass_of(F, str)) self.assertTrue(subclass_of(F, A, str)) self.assertTrue(not subclass_of(F, A, str, int)) def test_subclass_of_union(self): self.assertTrue(subclass_of(F, Union[int, str])) self.assertTrue(subclass_of(F, Union[A, int])) self.assertTrue(subclass_of(F, Union[A, B])) self.assertTrue(not subclass_of(int, Union[A, B])) self.assertTrue(subclass_of(F, Optional[A])) self.assertTrue(subclass_of(NoneType, Optional[A])) def test_union_subclass_of_union(self): # Subclass holds if all elements of the first enum subclass any of # the right enum. self.assertTrue(subclass_of(Union[C, D], Union[A, B])) # int is no subclass of any of Union[A, B]. self.assertTrue(not subclass_of(Union[C, D, int], Union[A, B])) def test_union_subclass_of(self): if sys.version_info[1] in (5,): self.assertTrue(not subclass_of(Union[int, A, B, F], Union[C, D])) else: self.assertTrue(subclass_of(Union[B, F], A)) self.assertTrue(not subclass_of(Union[A, B], C)) self.assertTrue(not subclass_of(Union[A, B], Union[C, D])) def test_subclass_of_literal(self): self.assertTrue(subclass_of(int, Literal[int])) self.assertTrue(subclass_of(Any, Literal[Any])) self.assertTrue(not subclass_of(int, Literal[Any])) self.assertTrue(not subclass_of(int, Literal)) typish-1.9.3/tests/test_generic_collection_type.py000066400000000000000000000007561410304562500225170ustar00rootroot00000000000000from typing import List, Set, Tuple from unittest import TestCase from typish import TypingType class TestSomething(TestCase): def test_list_isinstance_generic_collection_type(self): self.assertTrue(isinstance(List[int], TypingType)) def test_set_isinstance_generic_collection_type(self): self.assertTrue(isinstance(Set[int], TypingType)) def test_tuple_isinstance_generic_collection_type(self): self.assertTrue(isinstance(Tuple[int, str], TypingType)) typish-1.9.3/tests/test_meta.py000066400000000000000000000003501410304562500165430ustar00rootroot00000000000000from unittest import TestCase from typish import __version__ class TestMeta(TestCase): def test_meta(self): # Test that __version__ is importable and is a string. self.assertTrue(isinstance(__version__, str)) typish-1.9.3/typish/000077500000000000000000000000001410304562500143645ustar00rootroot00000000000000typish-1.9.3/typish/__init__.py000066400000000000000000000026501410304562500165000ustar00rootroot00000000000000from typish._meta import __version__ from typish._types import ( T, KT, VT, Empty, Unknown, Module, NoneType, Ellipsis_, EllipsisType, ) from typish.classes._cls_dict import ClsDict from typish.classes._cls_function import ClsFunction from typish.classes._literal import Literal, LiteralAlias, is_literal_type from typish.classes._something import Something, TypingType from typish.classes._subscriptable_type import SubscriptableType from typish.classes._union_type import UnionType from typish.decorators._hintable import hintable from typish.functions._common_ancestor import ( common_ancestor, common_ancestor_of_types ) from typish.functions._get_alias import get_alias from typish.functions._get_args import get_args from typish.functions._get_mro import get_mro from typish.functions._get_origin import get_origin from typish.functions._get_simple_name import get_simple_name from typish.functions._get_type import get_type from typish.functions._get_type_hints_of_callable import ( get_args_and_return_type, get_type_hints_of_callable ) from typish.functions._instance_of import instance_of from typish.functions._is_type_annotation import is_type_annotation from typish.functions._is_optional_type import is_optional_type from typish.functions._subclass_of import subclass_of from typish.functions._is_from_typing import is_from_typing from typish._state import State, register_get_type typish-1.9.3/typish/_meta.py000066400000000000000000000003561410304562500160270ustar00rootroot00000000000000__title__ = 'typish' __version__ = '1.9.3' __author__ = 'Ramon Hagenaars' __author_email__ = 'ramon.hagenaars@gmail.com' __description__ = 'Functionality for types' __url__ = 'https://github.com/ramonhagenaars/typish' __license__ = 'MIT' typish-1.9.3/typish/_state.py000066400000000000000000000023501410304562500162150ustar00rootroot00000000000000from typing import Callable from typish import T class State: """ A class which instances hold any state that may be used by typish. """ def __init__(self) -> None: """ Constructor. """ self.get_type_per_cls = {} def register_get_type( self, cls: T, get_type_function: Callable[[T], type]) -> None: """ Register a callable for some type that is to be used when calling typish.get_type. :param cls: the type for which that given callable is to be called. :param get_type_function: the callable to call for that type. :return: None. """ self.get_type_per_cls[cls] = get_type_function DEFAULT = State() def register_get_type( cls: T, get_type_function: Callable[[T], type], state: State = DEFAULT) -> None: """ Register a callable for some type that is to be used when calling typish.get_type. :param cls: the type for which that given callable is to be called. :param get_type_function: the callable to call for that type. :param state: any state that is used by typish. :return: None. """ state.register_get_type(cls, get_type_function) typish-1.9.3/typish/_types.py000066400000000000000000000006541410304562500162460ustar00rootroot00000000000000""" PRIVATE MODULE: do not import (from) it directly. This module contains types that are not available by default. """ import typing from inspect import Parameter T = typing.TypeVar('T') KT = typing.TypeVar('KT') VT = typing.TypeVar('VT') Empty = Parameter.empty Unknown = type('Unknown', (Empty, ), {}) Module = type(typing) NoneType = type(None) Ellipsis_ = type(...) # Use EllipsisType instead. EllipsisType = type(...) typish-1.9.3/typish/classes/000077500000000000000000000000001410304562500160215ustar00rootroot00000000000000typish-1.9.3/typish/classes/__init__.py000066400000000000000000000000001410304562500201200ustar00rootroot00000000000000typish-1.9.3/typish/classes/_cls_dict.py000066400000000000000000000035401410304562500203200ustar00rootroot00000000000000from 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 typish-1.9.3/typish/classes/_cls_function.py000066400000000000000000000055511410304562500212260ustar00rootroot00000000000000import 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])) typish-1.9.3/typish/classes/_literal.py000066400000000000000000000047421410304562500201750ustar00rootroot00000000000000import 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) typish-1.9.3/typish/classes/_something.py000066400000000000000000000112371410304562500205330ustar00rootroot00000000000000import 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, ...]] typish-1.9.3/typish/classes/_subscriptable_type.py000066400000000000000000000030541410304562500224370ustar00rootroot00000000000000class _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 typish-1.9.3/typish/classes/_union_type.py000066400000000000000000000002621410304562500207230ustar00rootroot00000000000000class _UnionType(type): def __instancecheck__(self, instance): return str(instance).startswith('typing.Union') class UnionType(type, metaclass=_UnionType): ... typish-1.9.3/typish/decorators/000077500000000000000000000000001410304562500165315ustar00rootroot00000000000000typish-1.9.3/typish/decorators/__init__.py000066400000000000000000000000001410304562500206300ustar00rootroot00000000000000typish-1.9.3/typish/decorators/_hintable.py000066400000000000000000000074301410304562500210340ustar00rootroot00000000000000import inspect import re from functools import wraps from typing import Dict, Optional, Callable, List _DEFAULT_PARAM_NAME = 'hint' class _Hintable: _hints_per_frame = {} def __init__( self, decorated: Callable, param: str, stack_index: int) -> None: self._decorated = decorated self._param = param self._stack_index = stack_index def __call__(self, *args, **kwargs): stack = inspect.stack() previous_frame = stack[self._stack_index] frame_id = id(previous_frame.frame) if not self._hints_per_frame.get(frame_id): code_context = previous_frame.code_context[0].strip() hint_strs = self._extract_hints(code_context) globals_ = previous_frame.frame.f_globals # Store the type hint if any, otherwise the string, otherwise None. hints = [self._to_cls(hint_str, globals_) or hint_str or None for hint_str in hint_strs] self._hints_per_frame[frame_id] = hints hint = (self._hints_per_frame.get(frame_id) or [None]).pop() kwargs_ = {**kwargs, self._param: kwargs.get(self._param, hint)} return self._decorated(*args, **kwargs_) def _extract_hints(self, code_context: str) -> List[str]: result = [] regex = ( r'.+?(:(.+?))?=\s*' # e.g. 'x: int = ', $2 holds hint r'.*?{}\s*\(.*?\)\s*' # e.g. 'func(...) ' r'(#\s*type\s*:\s*(\w+))?\s*' # e.g. '# type: int ', $4 holds hint ).format(self._decorated.__name__) # Find all matches and store them (reversed) in the resulting list. for _, group2, _, group4 in re.findall(regex, code_context): raw_hint = (group2 or group4).strip() if self._is_between(raw_hint, '\'') or self._is_between(raw_hint, '"'): # Remove any quotes that surround the hint. raw_hint = raw_hint[1:-1].strip() result.insert(0, raw_hint) return result def _is_between(self, subject: str, character: str) -> bool: return subject.startswith(character) and subject.endswith(character) def _to_cls(self, hint: str, f_globals: Dict[str, type]) -> Optional[type]: return __builtins__.get(hint, f_globals.get(hint)) def _get_wrapper(decorated, param: str, stack_index: int): @wraps(decorated) def _wrapper(*args, **kwargs): return _Hintable(decorated, param, stack_index)(*args, **kwargs) if isinstance(decorated, type): raise TypeError('Only functions and methods should be decorated with ' '\'hintable\', not classes.') if param not in inspect.signature(decorated).parameters: raise TypeError('The decorated \'{}\' must accept a parameter with ' 'the name \'{}\'.' .format(decorated.__name__, param)) return _wrapper def hintable(decorated=None, *, param: str = _DEFAULT_PARAM_NAME) -> Callable: """ Allow a function or method to receive the type hint of a receiving variable. Example: >>> @hintable ... def cast(value, hint): ... return hint(value) >>> x: int = cast('42') 42 Use this decorator wisely. If a variable was hinted with a type (e.g. int in the above example), your function should return a value of that type (in the above example, that would be an int value). :param decorated: a function or method. :param param: the name of the parameter that receives the type hint. :return: the decorated function/method wrapped into a new function. """ if decorated is not None: wrapper = _get_wrapper(decorated, param, 2) else: wrapper = lambda decorated_: _get_wrapper(decorated_, param, 2) return wrapper typish-1.9.3/typish/functions/000077500000000000000000000000001410304562500163745ustar00rootroot00000000000000typish-1.9.3/typish/functions/__init__.py000066400000000000000000000000001410304562500204730ustar00rootroot00000000000000typish-1.9.3/typish/functions/_common_ancestor.py000066400000000000000000000021611410304562500222730ustar00rootroot00000000000000import 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 typish-1.9.3/typish/functions/_get_alias.py000066400000000000000000000022531410304562500210370ustar00rootroot00000000000000import 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, } typish-1.9.3/typish/functions/_get_args.py000066400000000000000000000006411410304562500207010ustar00rootroot00000000000000import 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 typish-1.9.3/typish/functions/_get_mro.py000066400000000000000000000016161410304562500205450ustar00rootroot00000000000000import 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) typish-1.9.3/typish/functions/_get_origin.py000066400000000000000000000021321410304562500212310ustar00rootroot00000000000000import 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, } typish-1.9.3/typish/functions/_get_simple_name.py000066400000000000000000000010301410304562500222270ustar00rootroot00000000000000from 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 typish-1.9.3/typish/functions/_get_type.py000066400000000000000000000105421410304562500207270ustar00rootroot00000000000000import 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 typish-1.9.3/typish/functions/_get_type_hints_of_callable.py000066400000000000000000000030571410304562500244420ustar00rootroot00000000000000import 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 typish-1.9.3/typish/functions/_instance_of.py000066400000000000000000000027321410304562500214010ustar00rootroot00000000000000from 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) typish-1.9.3/typish/functions/_is_from_typing.py000066400000000000000000000003631410304562500221370ustar00rootroot00000000000000import 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__ typish-1.9.3/typish/functions/_is_optional_type.py000066400000000000000000000011611410304562500224650ustar00rootroot00000000000000import 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 typish-1.9.3/typish/functions/_is_type_annotation.py000066400000000000000000000015241410304562500230150ustar00rootroot00000000000000import 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)) typish-1.9.3/typish/functions/_subclass_of.py000066400000000000000000000131151410304562500214110ustar00rootroot00000000000000import 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