pax_global_header00006660000000000000000000000064133342667400014522gustar00rootroot0000000000000052 comment=4f5a591a112c06645f28d0e9ebf9244b5e773cae typeguard-2.2.2/000077500000000000000000000000001333426674000135315ustar00rootroot00000000000000typeguard-2.2.2/.gitignore000066400000000000000000000001631333426674000155210ustar00rootroot00000000000000.project .pydevproject .idea .tox .coverage .cache .eggs/ *.egg-info/ *.pyc __pycache__/ docs/_build/ dist/ build/ typeguard-2.2.2/.travis.yml000066400000000000000000000032731333426674000156470ustar00rootroot00000000000000language: python sudo: false stages: - name: test - name: deploy to pypi if: type = push AND tag =~ ^\d+\.\d+\.\d+ jobs: fast_finish: true include: - env: TOXENV=flake8 - env: TOXENV=pypy3 cache: pip python: pypy3 - env: TOXENV=py34 python: "3.4" after_success: &after_success - pip install coveralls - coveralls - env: TOXENV=py35 python: "3.5.0" after_success: *after_success - env: TOXENV=py35 python: "3.5.2" after_success: *after_success - env: TOXENV=py36 python: "3.6" after_success: *after_success - env: TOXENV=py37 python: "3.7" dist: xenial sudo: required after_success: *after_success - stage: deploy to pypi install: skip script: skip deploy: provider: pypi user: agronholm password: secure: gRSVobMY46ku8LMU/CkbhoawxDKZK0bQge2jBZwMt6UNK8X/Cu/mYHS7pihWRqGWacURLp7WZGeUB9ouHfGVIqlc8KQvdS4IgTHgo/CyZVG6AytyRPj+by9tmmYGh58J6DBstTD8c3h6pVytV4f0GcPJh+Cqfgfa6TKLF9dstxZELl5U4W46Po1Rk6Jk0GmhA7qKUd6/Y9fNRPntuEABFNGca8zTDinYTBzhQ6FbbuXfaF4FQkx3EvPm72ruagNkjCBkKXeqSr80Zxl0pPK5imW9VxhumnCm+DwStZ1dISQhEoJzK3b9GIllcFFWF4vUkTlEv9T+yZMhVyrJ+BBqPfKq1eNOALyWSVBTwWmjTez1AD0nNC4s5HegvKf2PF8F7y3EGUm+TLyKN3gC3LX14MHDJz4GJXY7n9gPW8syXU3npc1+bmaf1yfnR1BxncJQmru8nlmrpjG86w9qBH4BBSlkpTF7M82vcFKQ2w9BGZwoQ9vvzduMGRXCwgONfor+UPaRVarLyc+0j6HLXsC+EI9JN1PcDWF7WTj+PizYERB+U9PpjgniAKffGvhUUxjJvVnD9f/6CIKq4qjlTLv8C7PMwx1MVbE+p1JTxq530rQ55RMXicJCvhkn9mM0ytz/JUrDryOGDqmKiWVp53F+yLJHsUy8sN/zT3s6n8tf/Zg= distributions: sdist bdist_wheel on: tags: true python: "3.4" install: pip install tox script: tox typeguard-2.2.2/CHANGELOG.rst000066400000000000000000000075501333426674000155610ustar00rootroot00000000000000Version history =============== This library adheres to `Semantic Versioning `_. **2.2.2** (2018-08-13) - Fixed false positive when checking a callable against the plain ``typing.Callable`` on Python 3.7 **2.2.1** (2018-08-12) - Argument type annotations are no longer unioned with the types of their default values, except in the case of ``None`` as the default value (although PEP 484 still recommends against this) - Fixed some generic types (``typing.Collection`` among others) producing false negatives on Python 3.7 - Shortened unnecessarily long tracebacks by raising a new ``TypeError`` based on the old one - Allowed type checking against arbitrary types by removing the requirement to supply a call memo to ``check_type()`` - Fixed ``AttributeError`` when running with the pydev debugger extension installed - Fixed getting type names on ``typing.*`` on Python 3.7 (fix by Dale Jung) **2.2.0** (2018-07-08) - Fixed compatibility with Python 3.7 - Removed support for Python 3.3 - Added support for ``typing.NewType`` (contributed by reinhrst) **2.1.4** (2018-01-07) - Removed support for backports.typing, as it has been removed from PyPI - Fixed checking of the numeric tower (complex -> float -> int) according to PEP 484 **2.1.3** (2017-03-13) - Fixed type checks against generic classes **2.1.2** (2017-03-12) - Fixed leak of function objects (should've used a ``WeakValueDictionary`` instead of ``WeakKeyDictionary``) - Fixed obscure failure of TypeChecker when it's unable to find the function object - Fixed parametrized ``Type`` not working with type variables - Fixed type checks against variable positional and keyword arguments **2.1.1** (2016-12-20) - Fixed formatting of README.rst so it renders properly on PyPI **2.1.0** (2016-12-17) - Added support for ``typings.Type`` (available in Python 3.5.2+) - Added a third, ``sys.setprofile()`` based type checking approach (``typeguard.TypeChecker``) - Changed certain type error messages to display "function" instead of the function's qualified name **2.0.2** (2016-12-17) - More Python 3.6 compatibility fixes (along with a broader test suite) **2.0.1** (2016-12-10) - Fixed additional Python 3.6 compatibility issues **2.0.0** (2016-12-10) - **BACKWARD INCOMPATIBLE** Dropped Python 3.2 support - Fixed incompatibility with Python 3.6 - Use ``inspect.signature()`` in place of ``inspect.getfullargspec`` - Added support for ``typing.NamedTuple`` **1.2.3** (2016-09-13) - Fixed ``@typechecked`` skipping the check of return value type when the type annotation was ``None`` **1.2.2** (2016-08-23) - Fixed checking of homogenous Tuple declarations (``Tuple[bool, ...]``) **1.2.1** (2016-06-29) - Use ``backports.typing`` when possible to get new features on older Pythons - Fixed incompatibility with Python 3.5.2 **1.2.0** (2016-05-21) - Fixed argument counting when a class is checked against a Callable specification - Fixed argument counting when a functools.partial object is checked against a Callable specification - Added checks against mandatory keyword-only arguments when checking against a Callable specification **1.1.3** (2016-05-09) - Gracefully exit if ``check_type_arguments`` can't find a reference to the current function **1.1.2** (2016-05-08) - Fixed TypeError when checking a builtin function against a parametrized Callable **1.1.1** (2016-01-03) - Fixed improper argument counting with bound methods when typechecking callables **1.1.0** (2016-01-02) - Eliminated the need to pass a reference to the currently executing function to ``check_argument_types()`` **1.0.2** (2016-01-02) - Fixed types of default argument values not being considered as valid for the argument **1.0.1** (2016-01-01) - Fixed type hints retrieval being done for the wrong callable in cases where the callable was wrapped with one or more decorators **1.0.0** (2015-12-28) - Initial release typeguard-2.2.2/LICENSE000066400000000000000000000021521333426674000145360ustar00rootroot00000000000000This is the MIT license: http://www.opensource.org/licenses/mit-license.php Copyright (c) Alex Grönholm 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. typeguard-2.2.2/README.rst000066400000000000000000000115101333426674000152160ustar00rootroot00000000000000.. image:: https://travis-ci.org/agronholm/typeguard.svg?branch=master :target: https://travis-ci.org/agronholm/typeguard :alt: Build Status .. image:: https://coveralls.io/repos/agronholm/typeguard/badge.svg?branch=master&service=github :target: https://coveralls.io/github/agronholm/typeguard?branch=master :alt: Code Coverage This library provides run-time type checking for functions defined with argument type annotations. The ``typing`` module introduced in Python 3.5 (and available on PyPI for older versions of Python 3) is supported. See below for details. There are three principal ways to use type checking, each with its pros and cons: #. calling ``check_argument_types()`` from within the function body: * debugger friendly (except when running with the pydev debugger with the C extension installed) * cannot check the type of the return value * does not work reliably with dynamically defined type hints (e.g. in nested functions) #. decorating the function with ``@typechecked``: * 100% reliable at finding the function object to be checked (does not need to check the garbage collector) * can check the type of the return value * adds an extra frame to the call stack for every call to a decorated function #. using ``with TypeChecker('packagename'):``: * emits warnings instead of raising ``TypeError`` * eliminates boilerplate * multiple TypeCheckers can be stacked/nested * noninvasive (only records type violations; does not raise exceptions) * does not work reliably with dynamically defined type hints (e.g. in nested functions) * may cause problems with badly behaving debuggers or profilers If a function is called with incompatible argument types or a ``@typechecked`` decorated function returns a value incompatible with the declared type, a descriptive ``TypeError`` exception is raised. Type checks can be fairly expensive so it is recommended to run Python in "optimized" mode (``python -O`` or setting the ``PYTHONOPTIMIZE`` environment variable) when running code containing type checks in production. The optimized mode will disable the type checks, by virtue of removing all ``assert`` statements and setting the ``__debug__`` constant to ``False``. Using ``check_argument_types()``: .. code-block:: python3 from typeguard import check_argument_types def some_function(a: int, b: float, c: str, *args: str): assert check_argument_types() ... Using ``@typechecked``: .. code-block:: python3 from typeguard import typechecked @typechecked def some_function(a: int, b: float, c: str, *args: str) -> bool: ... To enable type checks even in optimized mode: .. code-block:: python3 @typechecked(always=True) def foo(a: str, b: int, c: Union[str, int]) -> bool: ... Using ``TypeChecker``: .. code-block:: python3 from warnings import filterwarnings from typeguard import TypeChecker, TypeWarning # Display all TypeWarnings, not just the first one filterwarnings('always', category=TypeWarning) # Run your entire application inside this context block with TypeChecker(['mypackage', 'otherpackage']): mypackage.run_app() # Alternatively, manually start (and stop) the checker: checker = TypeChecker('mypackage') checker.start() mypackage.start_app() .. hint:: Some other things you can do with ``TypeChecker``: * display all warnings from the start with ``python -W always::typeguard.TypeWarning`` * redirect them to logging using ``logging.captureWarnings()`` * record warnings in your pytest test suite and fail test(s) if you get any (see the `pytest documentation `_ about that) To directly check a value against the specified type: .. code-block:: python3 from typeguard import check_type check_type('variablename', [1234], List[int]) The following types from the ``typing`` package have specialized support: ============== ============================================================ Type Notes ============== ============================================================ ``Callable`` Argument count is checked but types are not (yet) ``Dict`` Keys and values are typechecked ``List`` Contents are typechecked ``NamedTuple`` Field values are typechecked ``Set`` Contents are typechecked ``Tuple`` Contents are typechecked ``Type`` ``TypeVar`` Constraints, bound types and co/contravariance are supported but custom generic types are not (due to type erasure) ``Union`` ============== ============================================================ Project links ------------- * `Change log `_ * `Source repository `_ * `Issue tracker `_ typeguard-2.2.2/pyproject.toml000066400000000000000000000001271333426674000164450ustar00rootroot00000000000000[build-system] requires = ["setuptools >= 36.2.7", "wheel", "setuptools_scm >= 1.7.0"] typeguard-2.2.2/setup.cfg000066400000000000000000000021351333426674000153530ustar00rootroot00000000000000[metadata] name = typeguard description = Run-time type checker for Python long_description = file: README.rst author = Alex Grönholm author_email = alex.gronholm@nextday.fi project_urls = Source code = https://github.com/agronholm/typeguard Issue tracker = https://github.com/agronholm/typeguard/issues license = MIT license_file = LICENSE classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [options] py_modules = typeguard python_requires = >= 3.4 zip_safe = True install_requires = typing >= 3.5; python_version == "3.4" [options.extras_require] testing = pytest pytest-cov [tool:pytest] addopts = -rsx --tb=short --cov testpaths = tests [coverage:run] source = typeguard [coverage:report] show_missing = true [flake8] max-line-length = 99 ignore = E251 typeguard-2.2.2/setup.py000066400000000000000000000003171333426674000152440ustar00rootroot00000000000000from setuptools import setup setup( use_scm_version={ 'version_scheme': 'post-release', 'local_scheme': 'dirty-tag' }, setup_requires=[ 'setuptools_scm >= 1.7.0' ] ) typeguard-2.2.2/tests/000077500000000000000000000000001333426674000146735ustar00rootroot00000000000000typeguard-2.2.2/tests/test_typeguard.py000066400000000000000000000636771333426674000203330ustar00rootroot00000000000000import sys from concurrent.futures import ThreadPoolExecutor from functools import wraps, partial from io import StringIO from typing import ( Any, Callable, Dict, List, Set, Tuple, Union, TypeVar, Sequence, NamedTuple, Iterable, Container, Generic) import pytest from typeguard import ( typechecked, check_argument_types, qualified_name, TypeChecker, TypeWarning, function_name, check_type) try: from typing import Type except ImportError: Type = List # don't worry, Type is not actually used if this happens! try: from typing import Collection except ImportError: Collection = None class Parent: pass class Child(Parent): def method(self, a: int): pass @pytest.mark.parametrize('inputval, expected', [ (qualified_name, 'function'), (Child(), 'test_typeguard.Child'), (int, 'int') ], ids=['func', 'instance', 'builtintype']) def test_qualified_name(inputval, expected): assert qualified_name(inputval) == expected def test_function_name(): assert function_name(function_name) == 'typeguard.function_name' def test_check_type_no_memo(): check_type('foo', [1], List[int]) def test_check_type_no_memo_fail(): pytest.raises(TypeError, check_type, 'foo', ['a'], List[int]).\ match('type of foo\[0\] must be int; got str instead') class TestCheckArgumentTypes: def test_any_type(self): def foo(a: Any): assert check_argument_types() foo('aa') def test_callable_exact_arg_count(self): def foo(a: Callable[[int, str], int]): assert check_argument_types() def some_callable(x: int, y: str) -> int: pass foo(some_callable) def test_callable_bad_type(self): def foo(a: Callable[..., int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == 'argument "a" must be a callable' def test_callable_too_few_arguments(self): def foo(a: Callable[[int, str], int]): assert check_argument_types() def some_callable(x: int) -> int: pass exc = pytest.raises(TypeError, foo, some_callable) assert str(exc.value) == ( 'callable passed as argument "a" has too few arguments in its declaration; expected 2 ' 'but 1 argument(s) declared') def test_callable_too_many_arguments(self): def foo(a: Callable[[int, str], int]): assert check_argument_types() def some_callable(x: int, y: str, z: float) -> int: pass exc = pytest.raises(TypeError, foo, some_callable) assert str(exc.value) == ( 'callable passed as argument "a" has too many arguments in its declaration; expected ' '2 but 3 argument(s) declared') def test_callable_mandatory_kwonlyargs(self): def foo(a: Callable[[int, str], int]): assert check_argument_types() def some_callable(x: int, y: str, *, z: float, bar: str) -> int: pass exc = pytest.raises(TypeError, foo, some_callable) assert str(exc.value) == ( 'callable passed as argument "a" has mandatory keyword-only arguments in its ' 'declaration: z, bar') def test_callable_class(self): """ Test that passing a class as a callable does not count the "self" argument "a"gainst the ones declared in the Callable specification. """ def foo(a: Callable[[int, str], Any]): assert check_argument_types() class SomeClass: def __init__(self, x: int, y: str): pass foo(SomeClass) def test_callable_plain(self): def foo(a: Callable): assert check_argument_types() def callback(a): pass foo(callback) def test_callable_partial_class(self): """ Test that passing a bound method as a callable does not count the "self" argument "a"gainst the ones declared in the Callable specification. """ def foo(a: Callable[[int], Any]): assert check_argument_types() class SomeClass: def __init__(self, x: int, y: str): pass foo(partial(SomeClass, y='foo')) def test_callable_bound_method(self): """ Test that passing a bound method as a callable does not count the "self" argument "a"gainst the ones declared in the Callable specification. """ def foo(callback: Callable[[int], Any]): assert check_argument_types() foo(Child().method) def test_callable_partial_bound_method(self): """ Test that passing a bound method as a callable does not count the "self" argument "a"gainst the ones declared in the Callable specification. """ def foo(callback: Callable[[], Any]): assert check_argument_types() foo(partial(Child().method, 1)) def test_callable_defaults(self): """ Test that a callable having "too many" arguments don't raise an error if the extra arguments have default values. """ def foo(callback: Callable[[int, str], Any]): assert check_argument_types() def some_callable(x: int, y: str, z: float = 1.2) -> int: pass foo(some_callable) def test_callable_builtin(self): """ Test that checking a Callable annotation against a builtin callable does not raise an error. """ def foo(callback: Callable[[int], Any]): assert check_argument_types() foo([].append) def test_dict_bad_type(self): def foo(a: Dict[str, int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == ( 'type of argument "a" must be a dict; got int instead') def test_dict_bad_key_type(self): def foo(a: Dict[str, int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, {1: 2}) assert str(exc.value) == 'type of keys of argument "a" must be str; got int instead' def test_dict_bad_value_type(self): def foo(a: Dict[str, int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, {'x': 'a'}) assert str(exc.value) == "type of argument \"a\"['x'] must be int; got str instead" def test_list_bad_type(self): def foo(a: List[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == ( 'type of argument "a" must be a list; got int instead') def test_list_bad_element(self): def foo(a: List[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, [1, 2, 'bb']) assert str(exc.value) == ( 'type of argument "a"[2] must be int; got str instead') def test_sequence_bad_type(self): def foo(a: Sequence[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == ( 'type of argument "a" must be a sequence; got int instead') def test_sequence_bad_element(self): def foo(a: Sequence[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, [1, 2, 'bb']) assert str(exc.value) == ( 'type of argument "a"[2] must be int; got str instead') def test_set_bad_type(self): def foo(a: Set[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == 'type of argument "a" must be a set; got int instead' def test_set_bad_element(self): def foo(a: Set[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, {1, 2, 'bb'}) assert str(exc.value) == ( 'type of elements of argument "a" must be int; got str instead') def test_tuple_bad_type(self): def foo(a: Tuple[int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, 5) assert str(exc.value) == ( 'type of argument "a" must be a tuple; got int instead') def test_tuple_too_many_elements(self): def foo(a: Tuple[int, str]): assert check_argument_types() exc = pytest.raises(TypeError, foo, (1, 'aa', 2)) assert str(exc.value) == ('argument "a" has wrong number of elements (expected 2, got 3 ' 'instead)') def test_tuple_too_few_elements(self): def foo(a: Tuple[int, str]): assert check_argument_types() exc = pytest.raises(TypeError, foo, (1,)) assert str(exc.value) == ('argument "a" has wrong number of elements (expected 2, got 1 ' 'instead)') def test_tuple_bad_element(self): def foo(a: Tuple[int, str]): assert check_argument_types() exc = pytest.raises(TypeError, foo, (1, 2)) assert str(exc.value) == ( 'type of argument "a"[1] must be str; got int instead') def test_tuple_ellipsis_bad_element(self): def foo(a: Tuple[int, ...]): assert check_argument_types() exc = pytest.raises(TypeError, foo, (1, 2, 'blah')) assert str(exc.value) == ( 'type of argument "a"[2] must be int; got str instead') def test_namedtuple(self): Employee = NamedTuple('Employee', [('name', str), ('id', int)]) def foo(bar: Employee): assert check_argument_types() foo(Employee('bob', 1)) def test_namedtuple_type_mismatch(self): Employee = NamedTuple('Employee', [('name', str), ('id', int)]) def foo(bar: Employee): assert check_argument_types() pytest.raises(TypeError, foo, ('bob', 1)).\ match('type of argument "bar" must be a named tuple of type ' '(test_typeguard\.)?Employee; got tuple instead') def test_namedtuple_wrong_field_type(self): Employee = NamedTuple('Employee', [('name', str), ('id', int)]) def foo(bar: Employee): assert check_argument_types() pytest.raises(TypeError, foo, Employee(2, 1)).\ match('type of argument "bar".name must be str; got int instead') @pytest.mark.parametrize('value', [6, 'aa']) def test_union(self, value): def foo(a: Union[str, int]): assert check_argument_types() foo(value) def test_union_typing_type(self): def foo(a: Union[str, Collection]): assert check_argument_types() with pytest.raises(TypeError): foo(1) @pytest.mark.parametrize('value', [6.5, b'aa']) def test_union_fail(self, value): def foo(a: Union[str, int]): assert check_argument_types() exc = pytest.raises(TypeError, foo, value) assert str(exc.value) == ( 'type of argument "a" must be one of (str, int); got {} instead'. format(value.__class__.__name__)) @pytest.mark.parametrize('values', [ (6, 7), ('aa', 'bb') ], ids=['int', 'str']) def test_typevar_constraints(self, values): T = TypeVar('T', int, str) def foo(a: T, b: T): assert check_argument_types() foo(*values) def test_typevar_constraints_fail_typing_type(self): T = TypeVar('T', int, Collection) def foo(a: T, b: T): assert check_argument_types() with pytest.raises(TypeError): foo('aa', 'bb') def test_typevar_constraints_fail(self): T = TypeVar('T', int, str) def foo(a: T, b: T): assert check_argument_types() exc = pytest.raises(TypeError, foo, 2.5, 'aa') assert str(exc.value) == ('type of argument "a" must be one of (int, str); got float ' 'instead') def test_typevar_bound(self): T = TypeVar('T', bound=Parent) def foo(a: T, b: T): assert check_argument_types() foo(Child(), Child()) def test_typevar_bound_fail(self): T = TypeVar('T', bound=Child) def foo(a: T, b: T): assert check_argument_types() exc = pytest.raises(TypeError, foo, Parent(), Parent()) assert str(exc.value) == ('type of argument "a" must be test_typeguard.Child or one of ' 'its subclasses; got test_typeguard.Parent instead') def test_typevar_invariant_fail(self): T = TypeVar('T', int, str) def foo(a: T, b: T): assert check_argument_types() exc = pytest.raises(TypeError, foo, 2, 3.6) assert str(exc.value) == 'type of argument "b" must be exactly int; got float instead' def test_typevar_covariant(self): T = TypeVar('T', covariant=True) def foo(a: T, b: T): assert check_argument_types() foo(Parent(), Child()) def test_typevar_covariant_fail(self): T = TypeVar('T', covariant=True) def foo(a: T, b: T): assert check_argument_types() exc = pytest.raises(TypeError, foo, Child(), Parent()) assert str(exc.value) == ('type of argument "b" must be test_typeguard.Child or one of ' 'its subclasses; got test_typeguard.Parent instead') def test_typevar_contravariant(self): T = TypeVar('T', contravariant=True) def foo(a: T, b: T): assert check_argument_types() foo(Child(), Parent()) def test_typevar_contravariant_fail(self): T = TypeVar('T', contravariant=True) def foo(a: T, b: T): assert check_argument_types() exc = pytest.raises(TypeError, foo, Parent(), Child()) assert str(exc.value) == ('type of argument "b" must be test_typeguard.Parent or one of ' 'its superclasses; got test_typeguard.Child instead') @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported') def test_class_bad_subclass(self): def foo(a: Type[Child]): assert check_argument_types() pytest.raises(TypeError, foo, Parent).match( '"a" must be a subclass of test_typeguard.Child; got test_typeguard.Parent instead') def test_wrapped_function(self): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @decorator def foo(a: 'Child'): assert check_argument_types() exc = pytest.raises(TypeError, foo, Parent()) assert str(exc.value) == ('type of argument "a" must be test_typeguard.Child; ' 'got test_typeguard.Parent instead') def test_mismatching_default_type(self): def foo(a: str = 1): assert check_argument_types() pytest.raises(TypeError, foo).match('type of argument "a" must be str; got int instead') def test_implicit_default_none(self): """ Test that if the default value is ``None``, a ``None`` argument can be passed. """ def foo(a: str=None): assert check_argument_types() foo() def test_generator(self): """Test that argument type checking works in a generator function too.""" def generate(a: int): assert check_argument_types() yield a yield a + 1 gen = generate(1) next(gen) def test_varargs(self): def foo(*args: int): assert check_argument_types() foo(1, 2) def test_varargs_fail(self): def foo(*args: int): assert check_argument_types() exc = pytest.raises(TypeError, foo, 1, 'a') exc.match('type of argument "args"\[1\] must be int; got str instead') def test_kwargs(self): def foo(**kwargs: int): assert check_argument_types() foo(a=1, b=2) def test_kwargs_fail(self): def foo(**kwargs: int): assert check_argument_types() exc = pytest.raises(TypeError, foo, a=1, b='a') exc.match('type of argument "kwargs"\[\'b\'\] must be int; got str instead') def test_generic(self): T_Foo = TypeVar('T_Foo') class FooGeneric(Generic[T_Foo]): pass def foo(a: FooGeneric[str]): assert check_argument_types() foo(FooGeneric[str]()) def test_newtype(self): try: from typing import NewType except ImportError: pytest.skip('Skipping newtype test since no NewType in current typing library') myint = NewType("myint", int) def foo(a: myint) -> int: assert check_argument_types() return 42 assert foo(1) == 42 exc = pytest.raises(TypeError, foo, "a") assert str(exc.value) == 'type of argument "a" must be int; got str instead' @pytest.mark.skipif(Collection is None, reason='typing.Collection is not available') def test_collection(self): def foo(a: Collection): assert check_argument_types() pytest.raises(TypeError, foo, True).match( 'type of argument "a" must be collections.abc.Collection; got bool instead') class TestTypeChecked: def test_typechecked(self): @typechecked def foo(a: int, b: str) -> str: return 'abc' assert foo(4, 'abc') == 'abc' def test_typechecked_always(self): @typechecked(always=True) def foo(a: int, b: str) -> str: return 'abc' assert foo(4, 'abc') == 'abc' def test_typechecked_arguments_fail(self): @typechecked def foo(a: int, b: str) -> str: return 'abc' exc = pytest.raises(TypeError, foo, 4, 5) assert str(exc.value) == 'type of argument "b" must be str; got int instead' def test_typechecked_return_type_fail(self): @typechecked def foo(a: int, b: str) -> str: return 6 exc = pytest.raises(TypeError, foo, 4, 'abc') assert str(exc.value) == 'type of the return value must be str; got int instead' def test_typechecked_return_typevar_fail(self): T = TypeVar('T', int, float) @typechecked def foo(a: T, b: T) -> T: return 'a' exc = pytest.raises(TypeError, foo, 4, 2) assert str(exc.value) == 'type of the return value must be exactly int; got str instead' def test_typechecked_no_annotations(self, recwarn): def foo(a, b): pass typechecked(foo) func_name = function_name(foo) assert len(recwarn) == 1 assert str(recwarn[0].message) == ( 'no type annotations present -- not typechecking {}'.format(func_name)) def test_return_type_none(self): """Check that a declared return type of None is respected.""" @typechecked def foo() -> None: return 'a' exc = pytest.raises(TypeError, foo) assert str(exc.value) == 'type of the return value must be NoneType; got str instead' @pytest.mark.parametrize('typehint', [ Callable[..., int], Callable ], ids=['parametrized', 'unparametrized']) def test_callable(self, typehint): @typechecked def foo(a: typehint): pass def some_callable() -> int: pass foo(some_callable) @pytest.mark.parametrize('typehint', [ List[int], List, list, ], ids=['parametrized', 'unparametrized', 'plain']) def test_list(self, typehint): @typechecked def foo(a: typehint): pass foo([1, 2]) @pytest.mark.parametrize('typehint', [ Dict[str, int], Dict, dict ], ids=['parametrized', 'unparametrized', 'plain']) def test_dict(self, typehint): @typechecked def foo(a: typehint): pass foo({'x': 2}) @pytest.mark.parametrize('typehint', [ Sequence[str], Sequence ], ids=['parametrized', 'unparametrized']) @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'], ids=['tuple', 'list', 'str']) def test_sequence(self, typehint, value): @typechecked def foo(a: typehint): pass foo(value) @pytest.mark.parametrize('typehint', [ Iterable[str], Iterable ], ids=['parametrized', 'unparametrized']) @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'], ids=['tuple', 'list', 'str']) def test_iterable(self, typehint, value): @typechecked def foo(a: typehint): pass foo(value) @pytest.mark.parametrize('typehint', [ Container[str], Container ], ids=['parametrized', 'unparametrized']) @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'], ids=['tuple', 'list', 'str']) def test_container(self, typehint, value): @typechecked def foo(a: typehint): pass foo(value) @pytest.mark.parametrize('typehint', [ Set[int], Set, set ], ids=['parametrized', 'unparametrized', 'plain']) @pytest.mark.parametrize('value', [set(), {6}]) def test_set(self, typehint, value): @typechecked def foo(a: typehint): pass foo(value) @pytest.mark.parametrize('typehint', [ Tuple[int, int], Tuple[int, ...], Tuple, tuple ], ids=['parametrized', 'ellipsis', 'unparametrized', 'plain']) def test_tuple(self, typehint): @typechecked def foo(a: typehint): pass foo((1, 2)) @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported') @pytest.mark.parametrize('typehint', [ Type[Parent], Type[TypeVar('UnboundType')], Type[TypeVar('BoundType', bound=Parent)], Type, type ], ids=['parametrized', 'unbound-typevar', 'bound-typevar', 'unparametrized', 'plain']) def test_class(self, typehint): @typechecked def foo(a: typehint): pass foo(Child) @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported') def test_class_not_a_class(self): @typechecked def foo(a: Type[dict]): pass exc = pytest.raises(TypeError, foo, 1) exc.match('type of argument "a" must be a type; got int instead') @pytest.mark.parametrize('typehint, value', [ (complex, complex(1, 5)), (complex, 1.0), (complex, 1), (float, 1.0), (float, 1) ], ids=['complex-complex', 'complex-float', 'complex-int', 'float-float', 'float-int']) def test_numbers(self, typehint, value): @typechecked def foo(a: typehint): pass foo(value) class TestTypeChecker: @pytest.fixture def executor(self): executor = ThreadPoolExecutor(1) yield executor executor.shutdown() @pytest.fixture def checker(self): return TypeChecker(__name__) def test_check_call_args(self, checker: TypeChecker): def foo(a: int): pass with checker, pytest.warns(TypeWarning) as record: assert checker.active foo(1) foo('x') assert not checker.active foo('x') assert len(record) == 1 warning = record[0].message assert warning.error == 'type of argument "a" must be int; got str instead' assert warning.func is foo assert isinstance(warning.stack, list) buffer = StringIO() warning.print_stack(buffer) assert len(buffer.getvalue()) > 100 def test_check_return_value(self, checker: TypeChecker): def foo() -> int: return 'x' with checker, pytest.warns(TypeWarning) as record: foo() assert len(record) == 1 assert record[0].message.error == 'type of the return value must be int; got str instead' def test_threaded_check_call_args(self, checker: TypeChecker, executor): def foo(a: int): pass with checker, pytest.warns(TypeWarning) as record: executor.submit(foo, 1).result() executor.submit(foo, 'x').result() executor.submit(foo, 'x').result() assert len(record) == 1 warning = record[0].message assert warning.error == 'type of argument "a" must be int; got str instead' assert warning.func is foo def test_double_start(self, checker: TypeChecker): """Test that the same type checker can't be started twice while running.""" with checker: pytest.raises(RuntimeError, checker.start).match('type checker already running') def test_nested(self): """Test that nesting of type checker context managers works as expected.""" def foo(a: int): pass with TypeChecker(__name__), pytest.warns(TypeWarning) as record: foo('x') with TypeChecker(__name__): foo('x') assert len(record) == 3 def test_existing_profiler(self, checker: TypeChecker): """ Test that an existing profiler function is chained with the type checker and restored after the block is exited. """ def foo(a: int): pass def profiler(frame, event, arg): nonlocal profiler_run_count if event in ('call', 'return'): profiler_run_count += 1 if old_profiler: old_profiler(frame, event, arg) profiler_run_count = 0 old_profiler = sys.getprofile() sys.setprofile(profiler) try: with checker, pytest.warns(TypeWarning) as record: foo(1) foo('x') assert sys.getprofile() is profiler finally: sys.setprofile(old_profiler) assert profiler_run_count assert len(record) == 1 typeguard-2.2.2/tox.ini000066400000000000000000000004541333426674000150470ustar00rootroot00000000000000[tox] minversion = 2.5.0 envlist = pypy3, py34, py35, py36, py37, flake8 skip_missing_interpreters = true [testenv] extras = testing commands = python -m pytest {posargs} [testenv:pypy3] ignore_outcome = true [testenv:flake8] deps = flake8 commands = flake8 typeguard.py tests skip_install = true typeguard-2.2.2/typeguard.py000066400000000000000000000636721333426674000161250ustar00rootroot00000000000000__all__ = ('typechecked', 'check_argument_types', 'check_type', 'TypeWarning', 'TypeChecker') import collections.abc import gc import inspect import sys import threading from collections import OrderedDict from functools import wraps, partial from inspect import Parameter, isclass, isfunction from traceback import extract_stack, print_stack from types import CodeType, FunctionType # noqa from typing import (Callable, Any, Union, Dict, List, TypeVar, Tuple, Set, Sequence, get_type_hints, TextIO, Optional) from warnings import warn from weakref import WeakKeyDictionary, WeakValueDictionary try: from typing import Type except ImportError: Type = None _type_hints_map = WeakKeyDictionary() # type: Dict[FunctionType, Dict[str, Any]] _functions_map = WeakValueDictionary() # type: Dict[CodeType, FunctionType] class _CallMemo: __slots__ = ('func', 'func_name', 'signature', 'typevars', 'arguments', 'type_hints') def __init__(self, func: Callable, frame=None, args: tuple = None, kwargs: Dict[str, Any] = None): self.func = func self.func_name = function_name(func) self.signature = inspect.signature(func) self.typevars = {} # type: Dict[Any, type] if args is not None and kwargs is not None: self.arguments = self.signature.bind(*args, **kwargs).arguments else: assert frame, 'frame must be specified if args or kwargs is None' self.arguments = frame.f_locals.copy() self.type_hints = _type_hints_map.get(func) if self.type_hints is None: hints = get_type_hints(func) self.type_hints = _type_hints_map[func] = OrderedDict() for name, parameter in self.signature.parameters.items(): if name in hints: annotated_type = hints[name] # PEP 428 discourages it by MyPy does not complain if parameter.default is None: annotated_type = Optional[annotated_type] if parameter.kind == Parameter.VAR_POSITIONAL: self.type_hints[name] = Tuple[annotated_type, ...] elif parameter.kind == Parameter.VAR_KEYWORD: self.type_hints[name] = Dict[str, annotated_type] else: self.type_hints[name] = annotated_type if 'return' in hints: self.type_hints['return'] = hints['return'] def get_type_name(type_): # typing.* types don't have a __name__ on Python 3.7+ return getattr(type_, '__name__', None) or type_._name def find_function(frame) -> Optional[Callable]: """ Return a function object from the garbage collector that matches the frame's code object. This process is unreliable as several function objects could use the same code object. Fortunately the likelihood of this happening with the combination of the function objects having different type annotations is a very rare occurrence. :param frame: a frame object :return: a function object if one was found, ``None`` if not """ func = _functions_map.get(frame.f_code) if func is None: for obj in gc.get_referrers(frame.f_code): if inspect.isfunction(obj): if func is None: # The first match was found func = obj else: # A second match was found return None # Cache the result for future lookups if func is not None: _functions_map[frame.f_code] = func else: raise LookupError('target function not found') return func def qualified_name(obj) -> str: """ Return the qualified name (e.g. package.module.Type) for the given object. Builtins and types from the :mod:`typing` package get special treatment by having the module name stripped from the generated name. """ type_ = obj if inspect.isclass(obj) else type(obj) module = type_.__module__ qualname = type_.__qualname__ return qualname if module in ('typing', 'builtins') else '{}.{}'.format(module, qualname) def function_name(func: FunctionType) -> str: """ Return the qualified name of the given function. Builtins and types from the :mod:`typing` package get special treatment by having the module name stripped from the generated name. """ module = func.__module__ qualname = func.__qualname__ return qualname if module == 'builtins' else '{}.{}'.format(module, qualname) def check_callable(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not callable(value): raise TypeError('{} must be a callable'.format(argname)) if expected_type.__args__: try: signature = inspect.signature(value) except (TypeError, ValueError): return if hasattr(expected_type, '__result__'): # Python 3.5 argument_types = expected_type.__args__ check_args = argument_types is not Ellipsis else: # Python 3.6 argument_types = expected_type.__args__[:-1] check_args = argument_types != (Ellipsis,) if check_args: # The callable must not have keyword-only arguments without defaults unfulfilled_kwonlyargs = [ param.name for param in signature.parameters.values() if param.kind == Parameter.KEYWORD_ONLY and param.default == Parameter.empty] if unfulfilled_kwonlyargs: raise TypeError( 'callable passed as {} has mandatory keyword-only arguments in its ' 'declaration: {}'.format(argname, ', '.join(unfulfilled_kwonlyargs))) num_mandatory_args = len([ param.name for param in signature.parameters.values() if param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) and param.default is Parameter.empty]) has_varargs = any(param for param in signature.parameters.values() if param.kind == Parameter.VAR_POSITIONAL) if num_mandatory_args > len(argument_types): raise TypeError( 'callable passed as {} has too many arguments in its declaration; expected {} ' 'but {} argument(s) declared'.format(argname, len(argument_types), num_mandatory_args)) elif not has_varargs and num_mandatory_args < len(argument_types): raise TypeError( 'callable passed as {} has too few arguments in its declaration; expected {} ' 'but {} argument(s) declared'.format(argname, len(argument_types), num_mandatory_args)) def check_dict(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not isinstance(value, dict): raise TypeError('type of {} must be a dict; got {} instead'. format(argname, qualified_name(value))) key_type, value_type = getattr(expected_type, '__args__', expected_type.__parameters__) for k, v in value.items(): check_type('keys of {}'.format(argname), k, key_type, memo) check_type('{}[{!r}]'.format(argname, k), v, value_type, memo) def check_list(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not isinstance(value, list): raise TypeError('type of {} must be a list; got {} instead'. format(argname, qualified_name(value))) value_type = getattr(expected_type, '__args__', expected_type.__parameters__)[0] if value_type: for i, v in enumerate(value): check_type('{}[{}]'.format(argname, i), v, value_type, memo) def check_sequence(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not isinstance(value, collections.Sequence): raise TypeError('type of {} must be a sequence; got {} instead'. format(argname, qualified_name(value))) value_type = getattr(expected_type, '__args__', expected_type.__parameters__)[0] if value_type: for i, v in enumerate(value): check_type('{}[{}]'.format(argname, i), v, value_type, memo) def check_set(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not isinstance(value, collections.Set): raise TypeError('type of {} must be a set; got {} instead'. format(argname, qualified_name(value))) value_type = getattr(expected_type, '__args__', expected_type.__parameters__)[0] if value_type: for v in value: check_type('elements of {}'.format(argname), v, value_type, memo) def check_tuple(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: # Specialized check for NamedTuples if hasattr(expected_type, '_field_types'): if not isinstance(value, expected_type): raise TypeError('type of {} must be a named tuple of type {}; got {} instead'. format(argname, qualified_name(expected_type), qualified_name(value))) for name, field_type in expected_type._field_types.items(): check_type('{}.{}'.format(argname, name), getattr(value, name), field_type, memo) return elif not isinstance(value, tuple): raise TypeError('type of {} must be a tuple; got {} instead'. format(argname, qualified_name(value))) if getattr(expected_type, '__tuple_params__', None): # Python 3.5 use_ellipsis = expected_type.__tuple_use_ellipsis__ tuple_params = expected_type.__tuple_params__ elif getattr(expected_type, '__args__', None): # Python 3.6+ use_ellipsis = expected_type.__args__[-1] is Ellipsis tuple_params = expected_type.__args__[:-1 if use_ellipsis else None] else: # Unparametrized Tuple or plain tuple return if use_ellipsis: element_type = tuple_params[0] for i, element in enumerate(value): check_type('{}[{}]'.format(argname, i), element, element_type, memo) else: if len(value) != len(tuple_params): raise TypeError('{} has wrong number of elements (expected {}, got {} instead)' .format(argname, len(tuple_params), len(value))) for i, (element, element_type) in enumerate(zip(value, tuple_params)): check_type('{}[{}]'.format(argname, i), element, element_type, memo) def check_union(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if hasattr(expected_type, '__union_params__'): # Python 3.5 union_params = expected_type.__union_params__ else: # Python 3.6+ union_params = expected_type.__args__ for type_ in union_params: try: check_type(argname, value, type_, memo) return except TypeError: pass typelist = ', '.join(get_type_name(t) for t in union_params) raise TypeError('type of {} must be one of ({}); got {} instead'. format(argname, typelist, qualified_name(value))) def check_class(argname: str, value, expected_type, memo: Optional[_CallMemo]) -> None: if not isclass(value): raise TypeError('type of {} must be a type; got {} instead'.format( argname, qualified_name(value))) # Needed on Python 3.7+ if expected_type is Type: return expected_class = expected_type.__args__[0] if expected_type.__args__ else None if expected_class: if isinstance(expected_class, TypeVar): check_typevar(argname, value, expected_class, memo, True) elif not issubclass(value, expected_class): raise TypeError('{} must be a subclass of {}; got {} instead'.format( argname, qualified_name(expected_class), qualified_name(value))) def check_typevar(argname: str, value, typevar: TypeVar, memo: Optional[_CallMemo], subclass_check: bool = False) -> None: if memo is None: raise TypeError('encountered a TypeVar but a call memo was not provided') bound_type = memo.typevars.get(typevar, typevar.__bound__) value_type = value if subclass_check else type(value) subject = argname if subclass_check else 'type of ' + argname if bound_type is None: # The type variable hasn't been bound yet -- check that the given value matches the # constraints of the type variable, if any if typevar.__constraints__ and value_type not in typevar.__constraints__: typelist = ', '.join(get_type_name(t) for t in typevar.__constraints__ if t is not object) raise TypeError('{} must be one of ({}); got {} instead'. format(subject, typelist, qualified_name(value_type))) elif typevar.__covariant__ or typevar.__bound__: if not issubclass(value_type, bound_type): raise TypeError( '{} must be {} or one of its subclasses; got {} instead'. format(subject, qualified_name(bound_type), qualified_name(value_type))) elif typevar.__contravariant__: if not issubclass(bound_type, value_type): raise TypeError( '{} must be {} or one of its superclasses; got {} instead'. format(subject, qualified_name(bound_type), qualified_name(value_type))) else: # invariant if value_type is not bound_type: raise TypeError( '{} must be exactly {}; got {} instead'. format(subject, qualified_name(bound_type), qualified_name(value_type))) if typevar not in memo.typevars: # Bind the type variable to a concrete type memo.typevars[typevar] = value_type def check_number(argname: str, value, expected_type): if expected_type is complex and not isinstance(value, (complex, float, int)): raise TypeError('type of {} must be either complex, float or int; got {} instead'. format(argname, qualified_name(value.__class__))) elif expected_type is float and not isinstance(value, (float, int)): raise TypeError('type of {} must be either float or int; got {} instead'. format(argname, qualified_name(value.__class__))) # Equality checks are applied to these origin_type_checkers = { Callable: check_callable, collections.abc.Callable: check_callable, dict: check_dict, Dict: check_dict, list: check_list, List: check_list, Sequence: check_sequence, collections.abc.Sequence: check_sequence, set: check_set, Set: check_set, tuple: check_tuple, Tuple: check_tuple, type: check_class, Union: check_union } _subclass_check_unions = hasattr(Union, '__union_set_params__') if Type is not None: origin_type_checkers[Type] = check_class def check_type(argname: str, value, expected_type, memo: Optional[_CallMemo] = None) -> None: """ Ensure that ``value`` matches ``expected_type``. The types from the :mod:`typing` module do not support :func:`isinstance` or :func:`issubclass` so a number of type specific checks are required. This function knows which checker to call for which type. :param argname: name of the argument to check; used for error messages :param value: value to be checked against ``expected_type`` :param expected_type: a class or generic type instance """ if expected_type is Any: return if expected_type is None: # Only happens on < 3.6 expected_type = type(None) origin_type = getattr(expected_type, '__origin__', None) if origin_type is not None: checker_func = origin_type_checkers.get(origin_type) if checker_func: checker_func(argname, value, expected_type, memo) else: check_type(argname, value, origin_type, memo) elif isclass(expected_type): if issubclass(expected_type, Tuple): check_tuple(argname, value, expected_type, memo) elif issubclass(expected_type, Callable) and hasattr(expected_type, '__args__'): # Needed on Python 3.5.0 to 3.5.2 check_callable(argname, value, expected_type, memo) elif issubclass(expected_type, (float, complex)): check_number(argname, value, expected_type) elif _subclass_check_unions and issubclass(expected_type, Union): check_union(argname, value, expected_type, memo) elif isinstance(expected_type, TypeVar): check_typevar(argname, value, expected_type, memo) else: expected_type = (getattr(expected_type, '__extra__', None) or origin_type or expected_type) if not isinstance(value, expected_type): raise TypeError( 'type of {} must be {}; got {} instead'. format(argname, qualified_name(expected_type), qualified_name(value))) elif isinstance(expected_type, TypeVar): # Only happens on < 3.6 check_typevar(argname, value, expected_type, memo) elif (isfunction(expected_type) and getattr(expected_type, "__module__", None) == "typing" and getattr(expected_type, "__qualname__", None).startswith("NewType.") and hasattr(expected_type, "__supertype__")): # typing.NewType, should check against supertype (recursively) return check_type(argname, value, expected_type.__supertype__, memo) def check_return_type(retval, memo: Optional[_CallMemo]) -> bool: if 'return' in memo.type_hints: try: check_type('the return value', retval, memo.type_hints['return'], memo) except TypeError as exc: # suppress unnecessarily long tracebacks raise TypeError(exc) from None return True def check_argument_types(memo: Optional[_CallMemo] = None) -> bool: """ Check that the argument values match the annotated types. Unless both ``args`` and ``kwargs`` are provided, the information will be retrieved from the previous stack frame (ie. from the function that called this). :return: ``True`` :raises TypeError: if there is an argument type mismatch """ if memo is None: frame = inspect.currentframe().f_back try: func = find_function(frame) except LookupError: return True # This can happen with the Pydev/PyCharm debugger extension installed memo = _CallMemo(func, frame) for argname, expected_type in memo.type_hints.items(): if argname != 'return' and argname in memo.arguments: value = memo.arguments[argname] description = 'argument "{}"'.format(argname) try: check_type(description, value, expected_type, memo) except TypeError as exc: # suppress unnecessarily long tracebacks raise TypeError(exc) from None return True def typechecked(func: Callable = None, *, always: bool = False): """ Perform runtime type checking on the arguments that are passed to the wrapped function. The return value is also checked against the return annotation if any. If the ``__debug__`` global variable is set to ``False``, no wrapping and therefore no type checking is done, unless ``always`` is ``True``. :param func: the function to enable type checking for :param always: ``True`` to enable type checks even in optimized mode """ if not __debug__ and not always: # pragma: no cover return func if func is None: return partial(typechecked, always=always) if not getattr(func, '__annotations__', None): warn('no type annotations present -- not typechecking {}'.format(function_name(func))) return func @wraps(func) def wrapper(*args, **kwargs): memo = _CallMemo(func, args=args, kwargs=kwargs) check_argument_types(memo) retval = func(*args, **kwargs) check_return_type(retval, memo) return retval return wrapper class TypeWarning(UserWarning): """ A warning that is emitted when a type check fails. :ivar str event: ``call`` or ``return`` :ivar Callable func: the function in which the violation occurred (the called function if event is ``call``, or the function where a value of the wrong type was returned from if event is ``return``) :ivar str error: the error message contained by the caught :cls:`TypeError` :ivar frame: the frame in which the violation occurred """ __slots__ = ('func', 'event', 'message', 'frame') def __init__(self, memo: Optional[_CallMemo], event: str, frame, exception: TypeError): # pragma: no cover self.func = memo.func self.event = event self.error = str(exception) self.frame = frame if self.event == 'call': caller_frame = self.frame.f_back event = 'call to {}() from {}:{}'.format( function_name(self.func), caller_frame.f_code.co_filename, caller_frame.f_lineno) else: event = 'return from {}() at {}:{}'.format( function_name(self.func), self.frame.f_code.co_filename, self.frame.f_lineno) super().__init__('[{thread_name}] {event}: {self.error}'.format( thread_name=threading.current_thread().name, event=event, self=self)) @property def stack(self): """Return the stack where the last frame is from the target function.""" return extract_stack(self.frame) def print_stack(self, file: TextIO = None, limit: int = None) -> None: """ Print the traceback from the stack frame where the target function was run. :param file: an open file to print to (prints to stdout if omitted) :param limit: the maximum number of stack frames to print """ print_stack(self.frame, limit, file) class TypeChecker: """ A type checker that collects type violations by hooking into ``sys.setprofile()``. :param all_threads: ``True`` to check types in all threads created while the checker is running, ``False`` to only check in the current one """ def __init__(self, packages: Union[str, Sequence[str]], *, all_threads: bool = True): assert check_argument_types() self.all_threads = all_threads self._call_memos = {} # type: Dict[Any, _CallMemo] self._previous_profiler = None self._previous_thread_profiler = None self._active = False if isinstance(packages, str): self._packages = (packages,) else: self._packages = tuple(packages) @property def active(self) -> bool: """Return ``True`` if currently collecting type violations.""" return self._active def should_check_type(self, func: Callable) -> bool: if not func.__annotations__: # No point in checking if there are no type hints return False else: # Check types if the module matches any of the package prefixes return any(func.__module__ == package or func.__module__.startswith(package + '.') for package in self._packages) def start(self): if self._active: raise RuntimeError('type checker already running') self._active = True # Install this instance as the current profiler self._previous_profiler = sys.getprofile() sys.setprofile(self) # If requested, set this instance as the default profiler for all future threads # (does not affect existing threads) if self.all_threads: self._previous_thread_profiler = threading._profile_hook threading.setprofile(self) def stop(self): if self._active: if sys.getprofile() is self: sys.setprofile(self._previous_profiler) else: # pragma: no cover warn('the system profiling hook has changed unexpectedly') if self.all_threads: if threading._profile_hook is self: threading.setprofile(self._previous_thread_profiler) else: # pragma: no cover warn('the threading profiling hook has changed unexpectedly') self._active = False def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop() def __call__(self, frame, event: str, arg) -> None: # pragma: no cover if not self._active: # This happens if all_threads was enabled and a thread was created when the checker was # running but was then stopped. The thread's profiler callback can't be reset any other # way but this. sys.setprofile(self._previous_thread_profiler) return # If an actual profiler is running, don't include the type checking times in its results if event == 'call': try: func = find_function(frame) except Exception: func = None if func is not None and self.should_check_type(func): memo = self._call_memos[frame] = _CallMemo(func, frame) try: check_argument_types(memo) except TypeError as exc: warn(TypeWarning(memo, event, frame, exc)) if self._previous_profiler is not None: self._previous_profiler(frame, event, arg) elif event == 'return': if self._previous_profiler is not None: self._previous_profiler(frame, event, arg) memo = self._call_memos.pop(frame, None) if memo is not None: try: check_return_type(arg, memo) except TypeError as exc: warn(TypeWarning(memo, event, frame, exc)) elif self._previous_profiler is not None: self._previous_profiler(frame, event, arg)