pax_global_header 0000666 0000000 0000000 00000000064 13334266740 0014522 g ustar 00root root 0000000 0000000 52 comment=4f5a591a112c06645f28d0e9ebf9244b5e773cae
typeguard-2.2.2/ 0000775 0000000 0000000 00000000000 13334266740 0013531 5 ustar 00root root 0000000 0000000 typeguard-2.2.2/.gitignore 0000664 0000000 0000000 00000000163 13334266740 0015521 0 ustar 00root root 0000000 0000000 .project
.pydevproject
.idea
.tox
.coverage
.cache
.eggs/
*.egg-info/
*.pyc
__pycache__/
docs/_build/
dist/
build/
typeguard-2.2.2/.travis.yml 0000664 0000000 0000000 00000003273 13334266740 0015647 0 ustar 00root root 0000000 0000000 language: 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.rst 0000664 0000000 0000000 00000007550 13334266740 0015561 0 ustar 00root root 0000000 0000000 Version 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/LICENSE 0000664 0000000 0000000 00000002152 13334266740 0014536 0 ustar 00root root 0000000 0000000 This 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.rst 0000664 0000000 0000000 00000011510 13334266740 0015216 0 ustar 00root root 0000000 0000000 .. 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.toml 0000664 0000000 0000000 00000000127 13334266740 0016445 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools >= 36.2.7", "wheel", "setuptools_scm >= 1.7.0"]
typeguard-2.2.2/setup.cfg 0000664 0000000 0000000 00000002135 13334266740 0015353 0 ustar 00root root 0000000 0000000 [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.py 0000664 0000000 0000000 00000000317 13334266740 0015244 0 ustar 00root root 0000000 0000000 from 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/ 0000775 0000000 0000000 00000000000 13334266740 0014673 5 ustar 00root root 0000000 0000000 typeguard-2.2.2/tests/test_typeguard.py 0000664 0000000 0000000 00000063677 13334266740 0020333 0 ustar 00root root 0000000 0000000 import 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.ini 0000664 0000000 0000000 00000000454 13334266740 0015047 0 ustar 00root root 0000000 0000000 [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.py 0000664 0000000 0000000 00000063672 13334266740 0016125 0 ustar 00root root 0000000 0000000 __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)