pax_global_header 0000666 0000000 0000000 00000000064 14545071262 0014520 g ustar 00root root 0000000 0000000 52 comment=c1cdf30c97ec89349a0a2cf8026626e3570f669a
python-typeguard-4.1.5/ 0000775 0000000 0000000 00000000000 14545071262 0015052 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/.github/ 0000775 0000000 0000000 00000000000 14545071262 0016412 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14545071262 0020575 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/.github/ISSUE_TEMPLATE/bug_report.yaml 0000664 0000000 0000000 00000003246 14545071262 0023636 0 ustar 00root root 0000000 0000000 name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: >
If you observed a crash in the library, or saw unexpected behavior in it, report
your findings here.
- type: checkboxes
attributes:
label: Things to check first
options:
- label: >
I have searched the existing issues and didn't find my bug already reported
there
required: true
- label: >
I have checked that my bug is still present in the latest release
required: true
- type: input
id: typeguard-version
attributes:
label: Typeguard version
description: What version of Typeguard were you running?
validations:
required: true
- type: input
id: python-version
attributes:
label: Python version
description: What version of Python were you running?
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: >
Unless you are reporting a crash, tell us what you expected to happen instead.
validations:
required: true
- type: textarea
id: mwe
attributes:
label: How can we reproduce the bug?
description: >
In order to investigate the bug, we need to be able to reproduce it on our own.
Please create a
[minimum workable example](https://stackoverflow.com/help/minimal-reproducible-example)
that demonstrates the problem. List any third party libraries required for this,
but avoid using them unless absolutely necessary.
validations:
required: true
python-typeguard-4.1.5/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000034 14545071262 0022562 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
python-typeguard-4.1.5/.github/ISSUE_TEMPLATE/features_request.yaml 0000664 0000000 0000000 00000002034 14545071262 0025046 0 ustar 00root root 0000000 0000000 name: Feature request
description: Suggest a new feature
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: >
If you have thought of a new feature that would increase the usefulness of this
project, please use this form to send us your idea.
- type: checkboxes
attributes:
label: Things to check first
options:
- label: >
I have searched the existing issues and didn't find my feature already
requested there
required: true
- type: textarea
id: feature
attributes:
label: Feature description
description: >
Describe the feature in detail. The more specific the description you can give,
the easier it should be to implement this feature.
validations:
required: true
- type: textarea
id: usecase
attributes:
label: Use case
description: >
Explain why you need this feature, and why you think it would be useful to
others too.
validations:
required: true
python-typeguard-4.1.5/.github/workflows/ 0000775 0000000 0000000 00000000000 14545071262 0020447 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/.github/workflows/publish.yml 0000664 0000000 0000000 00000002613 14545071262 0022642 0 ustar 00root root 0000000 0000000 name: Publish packages to PyPI
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.post[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
jobs:
build:
name: Build the source tarball and the wheel
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Install dependencies
run: pip install build
- name: Create packages
run: python -m build
- name: Archive packages
uses: actions/upload-artifact@v3
with:
name: dist
path: dist
publish:
name: Publish build artifacts to the PyPI
needs: build
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- name: Retrieve packages
uses: actions/download-artifact@v3
- name: Upload packages
uses: pypa/gh-action-pypi-publish@release/v1
release:
name: Create a GitHub release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- id: changelog
uses: agronholm/release-notes@v1
with:
path: docs/versionhistory.rst
- uses: ncipollo/release-action@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
python-typeguard-4.1.5/.github/workflows/test.yml 0000664 0000000 0000000 00000001664 14545071262 0022160 0 ustar 00root root 0000000 0000000 name: test suite
on:
push:
branches: [master]
pull_request:
jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", pypy-3.10]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: pip install -e .[test]
- name: Test with pytest
run: coverage run -m pytest
- name: Upload Coverage
uses: coverallsapp/github-action@v2
with:
parallel: true
coveralls:
name: Finish Coveralls
needs: test
runs-on: ubuntu-latest
steps:
- uses: coverallsapp/github-action@v2
with:
parallel-finished: true
python-typeguard-4.1.5/.gitignore 0000664 0000000 0000000 00000000235 14545071262 0017042 0 ustar 00root root 0000000 0000000 .project
.pydevproject
.idea
.tox
.coverage*
.cache
.eggs/
*.egg-info/
*.pyc
__pycache__/
docs/_build/
dist/
build/
.mypy_cache/
.pytest_cache/
.ruff_cache/
python-typeguard-4.1.5/.pre-commit-config.yaml 0000664 0000000 0000000 00000001757 14545071262 0021345 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
args: [ "--fix=lf" ]
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.287
hooks:
- id: ruff
args: [--fix, --show-fixes]
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
exclude: "^tests/mypy/negative.py"
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
hooks:
- id: mypy
additional_dependencies: [ "typing_extensions" ]
exclude: "^tests/"
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal
python-typeguard-4.1.5/.readthedocs.yml 0000664 0000000 0000000 00000000334 14545071262 0020140 0 ustar 00root root 0000000 0000000 version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
fail_on_warning: true
python:
install:
- method: pip
path: .
extra_requirements: [doc]
python-typeguard-4.1.5/LICENSE 0000664 0000000 0000000 00000002152 14545071262 0016057 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.
python-typeguard-4.1.5/README.rst 0000664 0000000 0000000 00000004123 14545071262 0016541 0 ustar 00root root 0000000 0000000 .. image:: https://github.com/agronholm/typeguard/actions/workflows/test.yml/badge.svg
:target: https://github.com/agronholm/typeguard/actions/workflows/test.yml
: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
.. image:: https://readthedocs.org/projects/typeguard/badge/?version=latest
:target: https://typeguard.readthedocs.io/en/latest/?badge=latest
:alt: Documentation
This library provides run-time type checking for functions defined with
`PEP 484 `_ argument (and return) type
annotations, and any arbitrary objects. It can be used together with static type
checkers as an additional layer of type safety, to catch type violations that could only
be detected at run time.
Two principal ways to do type checking are provided:
#. The ``check_type`` function:
* like ``isinstance()``, but supports arbitrary type annotations (within limits)
* can be used as a ``cast()`` replacement, but with actual checking of the value
#. Code instrumentation:
* entire modules, or individual functions (via ``@typechecked``) are recompiled, with
type checking code injected into them
* automatically checks function arguments, return values and assignments to annotated
local variables
* for generator functions (regular and async), checks yield and send values
* requires the original source code of the instrumented module(s) to be accessible
Two options are provided for code instrumentation:
#. the ``@typechecked`` function:
* can be applied to functions individually
#. the import hook (``typeguard.install_import_hook()``):
* automatically instruments targeted modules on import
* no manual code changes required in the target modules
* requires the import hook to be installed before the targeted modules are imported
* may clash with other import hooks
See the documentation_ for further information.
.. _documentation: https://typeguard.readthedocs.io/en/latest/
python-typeguard-4.1.5/docs/ 0000775 0000000 0000000 00000000000 14545071262 0016002 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/docs/api.rst 0000664 0000000 0000000 00000002665 14545071262 0017316 0 ustar 00root root 0000000 0000000 API reference
=============
.. module:: typeguard
Type checking
-------------
.. autofunction:: check_type
.. autodecorator:: typechecked
Import hook
-----------
.. autofunction:: install_import_hook
.. autoclass:: TypeguardFinder
:members:
.. autoclass:: ImportHookManager
:members:
Configuration
-------------
.. data:: config
:type: TypeCheckConfiguration
The global configuration object.
Used by :func:`@typechecked <.typechecked>` and :func:`.install_import_hook`, and
notably **not used** by :func:`.check_type`.
.. autoclass:: TypeCheckConfiguration
:members:
.. autoclass:: CollectionCheckStrategy
.. autoclass:: Unset
.. autoclass:: ForwardRefPolicy
.. autofunction:: warn_on_error
Custom checkers
---------------
.. autofunction:: check_type_internal
.. autofunction:: load_plugins
.. data:: checker_lookup_functions
:type: list[Callable[[Any, Tuple[Any, ...], Tuple[Any, ...]], Optional[Callable[[Any, Any, Tuple[Any, ...], TypeCheckMemo], Any]]]]
A list of callables that are used to look up a checker callable for an annotation.
.. autoclass:: TypeCheckMemo
:members:
Type check suppression
----------------------
.. autodecorator:: typeguard_ignore
.. autofunction:: suppress_type_checks
Exceptions and warnings
-----------------------
.. autoexception:: InstrumentationWarning
.. autoexception:: TypeCheckError
.. autoexception:: TypeCheckWarning
.. autoexception:: TypeHintWarning
python-typeguard-4.1.5/docs/conf.py 0000664 0000000 0000000 00000001666 14545071262 0017312 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
from importlib.metadata import version as get_version
from packaging.version import parse
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx_autodoc_typehints",
]
templates_path = ["_templates"]
source_suffix = ".rst"
master_doc = "index"
project = "Typeguard"
author = "Alex Grönholm"
copyright = "2015, " + author
v = parse(get_version("typeguard"))
version = v.base_version
release = v.public
language = "en"
exclude_patterns = ["_build"]
pygments_style = "sphinx"
autodoc_default_options = {"members": True}
autodoc_type_aliases = {
"TypeCheckerCallable": "typeguard.TypeCheckerCallable",
"TypeCheckFailCallback": "typeguard.TypeCheckFailCallback",
"TypeCheckLookupCallback": "typeguard.TypeCheckLookupCallback",
}
todo_include_todos = False
html_theme = "nature"
htmlhelp_basename = "typeguarddoc"
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
python-typeguard-4.1.5/docs/contributing.rst 0000664 0000000 0000000 00000006740 14545071262 0021252 0 ustar 00root root 0000000 0000000 Contributing to Typeguard
=========================
.. highlight:: bash
If you wish to contribute a fix or feature to Typeguard, please follow the following
guidelines.
When you make a pull request against the main Typeguard codebase, Github runs the test
suite against your modified code. Before making a pull request, you should ensure that
the modified code passes tests and code quality checks locally.
Running the test suite
----------------------
You can run the test suite two ways: either with tox_, or by running pytest_ directly.
To run tox_ against all supported (of those present on your system) Python versions::
tox
Tox will handle the installation of dependencies in separate virtual environments.
To pass arguments to the underlying pytest_ command, you can add them after ``--``, like
this::
tox -- -k somekeyword
To use pytest directly, you can set up a virtual environment and install the project in
development mode along with its test dependencies (virtualenv activation demonstrated
for Linux and macOS; on Windows you need ``venv\Scripts\activate`` instead)::
python -m venv venv
source venv/bin/activate
pip install -e .[test]
Now you can just run pytest_::
pytest
Building the documentation
--------------------------
To build the documentation, run ``tox -e docs``. This will place the documentation in
``build/sphinx/html`` where you can open ``index.html`` to view the formatted
documentation.
Typeguard uses ReadTheDocs_ to automatically build the documentation so the above
procedure is only necessary if you are modifying the documentation and wish to check the
results before committing.
Typeguard uses pre-commit_ to perform several code style/quality checks. It is
recommended to activate pre-commit_ on your local clone of the repository (using
``pre-commit install``) to ensure that your changes will pass the same checks on GitHub.
Making a pull request on Github
-------------------------------
To get your changes merged to the main codebase, you need a Github account.
#. Fork the repository (if you don't have your own fork of it yet) by navigating to the
`main Typeguard repository`_ and clicking on "Fork" near the top right corner.
#. Clone the forked repository to your local machine with
``git clone git@github.com/yourusername/typeguard``.
#. Create a branch for your pull request, like ``git checkout -b myfixname``
#. Make the desired changes to the code base.
#. Commit your changes locally. If your changes close an existing issue, add the text
``Fixes #XXX.`` or ``Closes #XXX.`` to the commit message (where XXX is the issue
number).
#. Push the changeset(s) to your forked repository (``git push``)
#. Navigate to Pull requests page on the original repository (not your fork) and click
"New pull request"
#. Click on the text "compare across forks".
#. Select your own fork as the head repository and then select the correct branch name.
#. Click on "Create pull request".
If you have trouble, consult the `pull request making guide`_ on opensource.com.
.. _Docker: https://docs.docker.com/desktop/#download-and-install
.. _docker compose: https://docs.docker.com/compose/
.. _tox: https://tox.readthedocs.io/en/latest/install.html
.. _pre-commit: https://pre-commit.com/#installation
.. _pytest: https://pypi.org/project/pytest/
.. _ReadTheDocs: https://readthedocs.org/
.. _main Typeguard repository: https://github.com/agronholm/typeguard
.. _pull request making guide: https://opensource.com/article/19/7/create-pull-request-github
python-typeguard-4.1.5/docs/extending.rst 0000664 0000000 0000000 00000010645 14545071262 0020527 0 ustar 00root root 0000000 0000000 Extending Typeguard
===================
.. py:currentmodule:: typeguard
Adding new type checkers
------------------------
The range of types supported by Typeguard can be extended by writing a
**type checker lookup funvtion** and one or more **type checker functions**. The former
will return one of the latter, or ``None`` if the given value does not match any of your
custom type checker functions.
The lookup function receives three arguments:
#. The origin type (the annotation with any arguments stripped from it)
#. The previously stripped out generic arguments, if any
#. Extra arguments from the :class:`~typing.Annotated` annotation, if any
For example, if the annotation was ``tuple``,, the lookup function would be called with
``tuple, (), ()``. If the type was parametrized, like ``tuple[str, int]``, it would be
called with ``tuple, (str, int), ()``. If the annotation was
``Annotated[tuple[str, int], "foo", "bar"]``, the arguments would instead be
``tuple, (str, int), ("foo", "bar")``.
The checker function receives four arguments:
#. The value to be type checked
#. The origin type
#. The generic arguments from the annotation (empty tuple when the annotation was not
parametrized)
#. The memo object (:class:`~.TypeCheckMemo`)
There are a couple of things to take into account when writing a type checker:
#. If your type checker function needs to do further type checks (such as type checking
items in a collection), you need to use :func:`~.check_type_internal` (and pass
along ``memo`` to it)
#. If you're type checking collections, your checker function should respect the
:attr:`~.TypeCheckConfiguration.collection_check_strategy` setting, available from
:attr:`~.TypeCheckMemo.config`
.. versionchanged:: 4.0
In Typeguard 4.0, checker functions **must** respect the settings in
``memo.config``, rather than the global configuration
The following example contains a lookup function and type checker for a custom class
(``MySpecialType``)::
from __future__ import annotations
from inspect import isclass
from typing import Any
from typeguard import TypeCheckError, TypeCheckerCallable, TypeCheckMemo
class MySpecialType:
pass
def check_my_special_type(
value: Any, origin_type: Any, args: tuple[Any, ...], memo: TypeCheckMemo
) -> None:
if not isinstance(value, MySpecialType):
raise TypeCheckError('is not my special type')
def my_checker_lookup(
origin_type: Any, args: tuple[Any, ...], extras: tuple[Any, ...]
) -> TypeCheckerCallable | None:
if isclass(origin_type) and issubclass(origin_type, MySpecialType):
return check_my_special_type
return None
Registering your type checker lookup function with Typeguard
------------------------------------------------------------
Just writing a type checker lookup function doesn't do anything by itself. You'll have
to advertise your type checker lookup function to Typeguard somehow. There are two ways
to do that (pick just one):
#. Append to :data:`typeguard.checker_lookup_functions`
#. Add an `entry point`_ to your project in the ``typeguard.checker_lookup`` group
If you're packaging your project with standard packaging tools, it may be better to add
an entry point instead of registering it manually, because manual registration requires
the registration code to run first before the lookup function can work.
To manually register the type checker lookup function with Typeguard::
from typeguard import checker_lookup_functions
checker_lookup_functions.append(my_checker_lookup)
For adding entry points to your project packaging metadata, the exact method may vary
depending on your packaging tool of choice, but the standard way (supported at least by
recent versions of ``setuptools``) is to add this to ``pyproject.toml``:
.. code-block:: toml
[project.entry-points]
typeguard.checker_lookup = {myplugin = "myapp.my_plugin_module:my_checker_lookup"}
The configuration above assumes that the **globally unique** (within the
``typeguard.checker_lookup`` namespace) entry point name for your lookup function is
``myplugin``, it lives in the ``myapp.my_plugin_module`` and the name of the function
there is ``my_checker_lookup``.
.. note:: After modifying your project configuration, you may have to reinstall it in
order for the entry point to become discoverable.
.. _entry point: https://docs.python.org/3/library/importlib.metadata.html#entry-points
python-typeguard-4.1.5/docs/features.rst 0000664 0000000 0000000 00000017703 14545071262 0020362 0 ustar 00root root 0000000 0000000 Features
=========
.. py:currentmodule:: typeguard
What does Typeguard check?
--------------------------
The following type checks are implemented in Typeguard:
* Types of arguments passed to instrumented functions
* Types of values returned from instrumented functions
* Types of values yielded from instrumented generator functions
* Types of values sent to instrumented generator functions
* Types of values assigned to local variables within instrumented functions
What does Typeguard NOT check?
------------------------------
The following type checks are not yet supported in Typeguard:
* Types of values assigned to class or instance variables
* Types of values assigned to global or nonlocal variables
* Stubs defined with :func:`@overload ` (the implementation is checked
if instrumented)
* ``yield_from`` statements in generator functions
* ``ParamSpec`` and ``Concatenate`` are currently ignored
* Types where they are shadowed by arguments with the same name (e.g.
``def foo(x: type, type: str): ...``)
Other limitations
-----------------
Local references to nested classes
++++++++++++++++++++++++++++++++++
Forward references from methods pointing to non-local nested classes cannot currently be
resolved::
class Outer:
class Inner:
pass
# Cannot be resolved as the name is no longer available
def method(self) -> "Inner":
return Outer.Inner()
This shortcoming may be resolved in a future release.
Using :func:`@typechecked ` on top of other decorators
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
As :func:`@typechecked ` works by recompiling the target function with
instrumentation added, it needs to replace all the references to the original function
with the new one. This could be impossible when it's placed on top of another decorator
that wraps the original function. It has no way of telling that other decorator that the
target function should be switched to a new one. To work around this limitation, either
place :func:`@typechecked ` at the bottom of the decorator stack, or use
the import hook instead.
Special considerations for ``if TYPE_CHECKING:``
------------------------------------------------
Both the import hook and :func:`@typechecked ` avoid checking against
anything imported in a module-level ``if TYPE_CHECKING:`` (or
``if typing.TYPE_CHECKING:``) block, since those types will not be available at run
time. Therefore, no errors or warnings are emitted for such annotations, even when they
would normally not be found.
Support for generator functions
-------------------------------
For generator functions, the checks applied depend on the function's return annotation.
For example, the following function gets its yield, send and return values type
checked::
from collections.abc import Generator
def my_generator() -> Generator[int, str, bool]:
a = yield 6
return True
In contrast, the following generator function only gets its yield value checked::
from collections.abc import Iterator
def my_generator() -> Iterator[int]:
a = yield 6
return True
Asynchronous generators work just the same way, except they don't support returning
values other than ``None``, so the annotation only has two items::
from collections.abc import AsyncGenerator
async def my_generator() -> AsyncGenerator[int, str]:
a = yield 6
Overall, the following type annotations will work for generator function type checking:
* :class:`typing.Generator`
* :class:`collections.abc.Generator`
* :class:`typing.Iterator`
* :class:`collections.abc.Iterator`
* :class:`typing.Iterable`
* :class:`collections.abc.Iterable`
* :class:`typing.AsyncIterator`
* :class:`collections.abc.AsyncIterator`
* :class:`typing.AsyncIterable`
* :class:`collections.abc.AsyncIterable`
* :class:`typing.AsyncGenerator`
* :class:`collections.abc.AsyncGenerator`
Support for PEP 604 unions on Pythons older than 3.10
-----------------------------------------------------
The :pep:`604` ``X | Y`` notation was introduced in Python 3.10, but it can be used with
older Python versions in modules where ``from __future__ import annotations`` is
present. Typeguard contains a special parser that lets it convert these to older
:class:`~typing.Union` annotations internally.
Support for generic built-in collection types on Pythons older than 3.9
-----------------------------------------------------------------------
The built-in collection types (:class:`list`, :class:`tuple`, :class:`dict`,
:class:`set` and :class:`frozenset`) gained support for generics in Python 3.9.
For earlier Python versions, Typeguard provides a way to work with such annotations by
substituting them with the equivalent :mod:`typing` types. The only requirement for this
to work is the use of ``from __future__ import annotations`` in all such modules.
Support for mock objects
------------------------
Typeguard handles the :class:`unittest.mock.Mock` class (and its subclasses) specially,
bypassing any type checks when encountering instances of these classes. Note that any
"spec" class passed to the mock object is currently not respected.
Supported standard library annotations
--------------------------------------
The following types from the standard library have specialized support:
.. list-table::
:header-rows: 1
* - Type(s)
- Notes
* - :class:`typing.Any`
- Any type passes type checks against this annotation. Inheriting from ``Any``
(:class:`typing.Any` on Python 3.11+, or ``typing.extensions.Any``) will pass any
type check
* - :class:`typing.Annotated`
- Original annotation is unwrapped and typechecked normally
* - :class:`BinaryIO`
- Specialized instance checks are performed
* - | :class:`typing.Callable`
| :class:`collections.abc.Callable`
- Argument count is checked but types are not (yet)
* - | :class:`dict`
| :class:`typing.Dict`
- Keys and values are typechecked
* - :class:`typing.IO`
- Specialized instance checks are performed
* - | :class:`list`
| :class:`typing.List`
- Contents are typechecked
* - :class:`typing.Literal`
-
* - :class:`typing.LiteralString`
- Checked as :class:`str`
* - | :class:`typing.Mapping`
| :class:`typing.MutableMapping`
| :class:`collections.abc.Mapping`
| :class:`collections.abc.MutableMapping`
- Keys and values are typechecked
* - :class:`typing.NamedTuple`
- Field values are typechecked
* - | :class:`typing.Never`
| :class:`typing.NoReturn`
- Supported in argument and return type annotations
* - :class:`typing.Protocol`
- Run-time protocols are checked with :func:`isinstance`, others are ignored
* - :class:`typing.Self`
-
* - | :class:`set`
| :class:`frozenset`
| :class:`typing.Set`
| :class:`typing.AbstractSet`
- Contents are typechecked
* - | :class:`typing.Sequence`
| :class:`collections.abc.Sequence`
- Contents are typechecked
* - :class:`typing.TextIO`
- Specialized instance checks are performed
* - | :class:`tuple`
| :class:`typing.Tuple`
- Contents are typechecked
* - | :class:`type`
| :class:`typing.Type`
-
* - :class:`typing.TypeGuard`
- Checked as :class:`bool`
* - :class:`typing.TypedDict`
- Contents are typechecked; On Python 3.8 and earlier, ``total`` from superclasses
is not respected (see `#101`_ for more information); On Python 3.9.0, false
positives can happen when constructing :class:`typing.TypedDict` classes using
old-style syntax (see `issue 42059`_)
* - :class:`typing.TypeVar`
- Constraints and bound types are typechecked
* - :class:`typing.Union`
- :pep:`604` unions are supported on all Python versions when
``from __future__ import annotations`` is used
.. _#101: https://github.com/agronholm/typeguard/issues/101
.. _issue 42059: https://bugs.python.org/issue42059
python-typeguard-4.1.5/docs/index.rst 0000664 0000000 0000000 00000000317 14545071262 0017644 0 ustar 00root root 0000000 0000000 Typeguard
=========
.. include:: ../README.rst
:end-before: See the
Quick links
-----------
.. toctree::
:maxdepth: 1
userguide
features
extending
contributing
api
versionhistory
python-typeguard-4.1.5/docs/userguide.rst 0000664 0000000 0000000 00000023720 14545071262 0020534 0 ustar 00root root 0000000 0000000 User guide
==========
.. py:currentmodule:: typeguard
Checking types directly
-----------------------
The most straightfoward way to do type checking with Typeguard is with
:func:`.check_type`. It can be used as as a beefed-up version of :func:`isinstance` that
also supports checking against annotations in the :mod:`typing` module::
from typeguard import check_type
# Raises TypeCheckError if there's a problem
check_type([1234], List[int])
It's also useful for safely casting the types of objects dynamically constructed from
external sources::
import json
from typing import List, TypedDict
from typeguard import check_type
# Example contents of "people.json":
# [
# {"name": "John Smith", "phone": "111-123123", "address": "123 Main Street"},
# {"name": "Jane Smith", "phone": "111-456456", "address": "123 Main Street"}
# ]
class Person(TypedDict):
name: str
phone: str
address: str
with open("people.json") as f:
people = check_type(json.load(f), List[Person])
With this code, static type checkers will recognize the type of ``people`` to be
``List[Person]``.
Using the decorator
-------------------
The :func:`@typechecked ` decorator is the simplest way to add type
checking on a case-by-case basis. It can be used on functions directly, or on entire
classes, in which case all the contained methods are instrumented::
from typeguard import typechecked
@typechecked
def some_function(a: int, b: float, c: str, *args: str) -> bool:
...
return retval
@typechecked
class SomeClass:
# All type annotated methods (including static and class methods and properties)
# are type checked.
# Does not apply to inner classes!
def method(x: int) -> int:
...
The decorator instruments functions by fetching the source code, parsing it to an
abstract syntax tree using :func:`ast.parse`, modifying it to add type checking, and
finally compiling the modified AST into byte code. This code is then used to make a new
function object that is used to replace the original one.
To explicitly set type checking options on a per-function basis, you can pass them as
keyword arguments to :func:`@typechecked `::
from typeguard import CollectionCheckStrategy, typechecked
@typechecked(collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
def some_function(a: int, b: float, c: str, *args: str) -> bool:
...
return retval
This also allows you to override the global options for specific functions when using
the import hook.
.. note:: You should always place this decorator closest to the original function,
as it will not work when there is another decorator wrapping the function.
For the same reason, when you use it on a class that has wrapping decorators on
its methods, such methods will not be instrumented. In contrast, the import hook
has no such restrictions.
Using the import hook
---------------------
The import hook, when active, automatically instruments all type annotated functions to
type check arguments, return values and values yielded by or sent to generator
functions. This allows for a non-invasive method of run time type checking. This method
does not modify the source code on disk, but instead modifies its AST (Abstract Syntax
Tree) when the module is loaded.
Using the import hook is as straightforward as installing it before you import any
modules you wish to be type checked. Give it the name of your top level package (or a
list of package names)::
from typeguard import install_import_hook
install_import_hook('myapp')
from myapp import some_module # import only AFTER installing the hook, or it won't take effect
If you wish, you can uninstall the import hook::
manager = install_import_hook('myapp')
from myapp import some_module
manager.uninstall()
or using the context manager approach::
with install_import_hook('myapp'):
from myapp import some_module
You can also customize the logic used to select which modules to instrument::
from typeguard import TypeguardFinder, install_import_hook
class CustomFinder(TypeguardFinder):
def should_instrument(self, module_name: str):
# disregard the module names list and instrument all loaded modules
return True
install_import_hook('', cls=CustomFinder)
.. _forwardrefs:
Notes on forward reference handling
-----------------------------------
The internal type checking functions, injected to instrumented code by either
:func:`@typechecked ` or the import hook, use the "naked" versions of any
annotations, undoing any quotations in them (and the effects of
``from __future__ import annotations``). As such, in instrumented code, the
:attr:`~.TypeCheckConfiguration.forward_ref_policy` only applies when using type
variables containing forward references, or type aliases likewise containing forward
references.
To facilitate the use of types only available to static type checkers, Typeguard
recognizes module-level imports guarded by ``if typing.TYPE_CHECKING:`` or
``if TYPE_CHECKING:`` (add the appropriate :mod:`typing` imports). Imports made within
such blocks on the module level will be replaced in calls to internal type checking
functions with :data:`~typing.Any`.
Using the pytest plugin
-----------------------
Typeguard comes with a pytest plugin that installs the import hook (explained in the
previous section). To use it, run ``pytest`` with the appropriate
``--typeguard-packages`` option. For example, if you wanted to instrument the
``foo.bar`` and ``xyz`` packages for type checking, you can do the following:
.. code-block:: bash
pytest --typeguard-packages=foo.bar,xyz
There is currently no support for specifying a customized module finder.
Setting configuration options
-----------------------------
There are several configuration options that can be set that influence how type checking
is done. The :data:`typeguard.config` (which is of type
:class:`~.TypeCheckConfiguration`) controls the options applied to code instrumented via
either :func:`@typechecked <.typechecked>` or the import hook. The
:func:`~.check_type`, function, however, uses the built-in defaults and is not affected
by the global configuration, so you must pass any configuration overrides explicitly
with each call.
You can also override specific configuration options in instrumented functions (or
entire classes) by passing keyword arguments to :func:`@typechecked <.typechecked>`.
You can do this even if you're using the import hook, as the import hook will remove the
decorator to ensure that no double instrumentation takes place. If you're using the
import hook to type check your code only during tests and don't want to include
``typeguard`` as a run-time dependency, you can use a dummy replacement for the
decorator.
For example, the following snippet will only import the decorator during a pytest_ run::
import sys
if "pytest" in sys.modules:
from typeguard import typechecked
else:
from typing import TypeVar
_T = TypeVar("_T")
def typechecked(target: _T, **kwargs) -> _T:
return target if target else typechecked
.. _pytest: https://docs.pytest.org/
Suppressing type checks
-----------------------
Temporarily disabling type checks
+++++++++++++++++++++++++++++++++
If you need to temporarily suppress type checking, you can use the
:func:`~.suppress_type_checks` function, either as a context manager or a decorator, to
skip the checks::
from typeguard import check_type, suppress_type_checks
with suppress_type_checks():
check_type(1, str) # would fail without the suppression
@suppress_type_checks
def my_suppressed_function(x: int) -> None:
...
Suppression state is tracked globally. Suppression ends only when all the context
managers have exited and all calls to decorated functions have returned.
Permanently suppressing type checks for selected functions
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
To exclude specific functions from run time type checking, you can use one of the
following decorators:
* :func:`@typeguard_ignore `: prevents the decorated
function from being instrumentated by the import hook
* :func:`@no_type_check `: as above, but disables static type
checking too
For example, calling the function defined below will not result in a type check error
when the containing module is instrumented by the import hook::
from typeguard import typeguard_ignore
@typeguard_ignore
def f(x: int) -> int:
return str(x)
.. warning:: The :func:`@no_type_check_decorator `
decorator is not currently recognized by Typeguard.
Suppressing the ``@typechecked`` decorator in production
--------------------------------------------------------
If you're using the :func:`@typechecked ` decorator to gradually introduce
run-time type checks to your code base, you can disable the checks in production by
running Python in optimized mode (as opposed to debug mode which is the default mode).
You can do this by either starting Python with the ``-O`` or ``-OO`` option, or by
setting the PYTHONOPTIMIZE_ environment variable. This will cause
:func:`@typechecked ` to become a no-op when the import hook is not being
used to instrument the code.
.. _PYTHONOPTIMIZE: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOPTIMIZE
Debugging instrumented code
---------------------------
If you find that your code behaves in an unexpected fashion with the Typeguard
instrumentation in place, you should set the ``typeguard.config.debug_instrumentation``
flag to ``True``. This will print all the instrumented code after the modifications,
which you can check to find the reason for the unexpected behavior.
If you're using the pytest plugin, you can also pass the
``--typeguard-debug-instrumentation`` and ``-s`` flags together for the same effect.
python-typeguard-4.1.5/docs/versionhistory.rst 0000664 0000000 0000000 00000053732 14545071262 0021655 0 ustar 00root root 0000000 0000000 Version history
===============
This library adheres to
`Semantic Versioning 2.0 `_.
**4.1.5** (2023-09-11)
- Fixed ``Callable`` erroneously rejecting a callable that has the requested amount of
positional arguments but they have defaults
(`#400 `_)
- Fixed a regression introduced in v4.1.4 where the elements of ``Literal`` got quotes
removed from them by the AST transformer
(`#399 `_)
**4.1.4** (2023-09-10)
- Fixed ``AttributeError`` where the transformer removed elements from a PEP 604 union
(`#384 `_)
- Fixed ``AttributeError: 'Subscript' object has no attribute 'slice'`` when
encountering an annotation with a subscript containing an ignored type (imported
within an ``if TYPE_CHECKING:`` block)
(`#397 `_)
- Fixed type checking not being skipped when the target is a union (PEP 604 or
``typing.Union``) where one of the elements is an ignored type (shadowed by an
argument, variable assignment or an ``if TYPE_CHECKING`` import)
(`#394 `_,
`#395 `_)
- Fixed type checking of class instances created in ``__new__()`` in cases such as enums
where this method is already invoked before the class has finished initializing
(`#398 `_)
**4.1.3** (2023-08-27)
- Dropped Python 3.7 support
- Fixed ``@typechecked`` optimization causing compilation of instrumented code to fail
when any block was left empty by the AST transformer (eg ``if`` or
``try`` / ``except`` blocks)
(`#352 `_)
- Fixed placement of injected typeguard imports with respect to ``__future__`` imports
and module docstrings (`#385 `_)
**4.1.2** (2023-08-18)
- Fixed ``Any`` being removed from a subscript that still contains other elements
(`#373 `_)
**4.1.1** (2023-08-16)
- Fixed ``suppress_type_checks()`` causing annotated variable assignments to always
assign ``None`` (`#380 `_)
**4.1.0** (2023-07-30)
- Added support for passing a tuple as ``expected_type`` to ``check_type()``, making it
more of a drop-in replacement for ``isinstance()``
(`#371 `_)
- Fixed regression where ``Literal`` inside a ``Union`` had quotes stripped from its
contents, thus typically causing ``NameError`` to be raised when run
(`#372 `_)
**4.0.1** (2023-07-27)
- Fixed handling of ``typing_extensions.Literal`` on Python 3.8 and 3.9 when
``typing_extensions>=4.6.0`` is installed
(`#363 `_; PR by Alex Waygood)
- Fixed ``NameError`` when generated type checking code references an imported name from
a method (`#362 `_)
- Fixed docstrings disappearing from instrumented functions
(`#359 `_)
- Fixed ``@typechecked`` failing to instrument functions when there are more than one
function within the same scope
(`#355 `_)
- Fixed ``frozenset`` not being checked
(`#367 `_)
**4.0.0** (2023-05-12)
- No changes
**4.0.0rc6** (2023-05-07)
- Fixed ``@typechecked`` optimization causing compilation of instrumented code to fail
when an ``if`` block was left empty by the AST transformer
(`#352 `_)
- Fixed the AST transformer trying to parse the second argument of ``typing.Annotated``
as a forward reference (`#353 `_)
**4.0.0rc5** (2023-05-01)
- Added ``InstrumentationWarning`` to the public API
- Changed ``@typechecked`` to skip instrumentation in optimized mode, as in typeguard
2.x
- Avoid type checks where the types in question are shadowed by local variables
- Fixed instrumentation using ``typing.Optional`` without a subscript when the subscript
value was erased due to being an ignored import
- Fixed ``TypeError: isinstance() arg 2 must be a type or tuple of types`` when
instrumented code tries to check a value against a naked (``str``, not ``ForwardRef``)
forward reference
- Fixed instrumentation using the wrong "self" type in the ``__new__()`` method
**4.0.0rc4** (2023-04-15)
- Fixed imports guarded by ``if TYPE_CHECKING:`` when used with subscripts
(``SomeType[...]``) being replaced with ``Any[...]`` instead of just ``Any``
- Fixed instrumentation inadvertently mutating a function's annotations on Python 3.7
and 3.8
- Fixed ``Concatenate[...]`` in ``Callable`` parameters causing ``TypeError`` to be
raised
- Fixed type checks for ``*args`` or ``**kwargs`` not being suppressed when their types
are unusable (guarded by ``if TYPE_CHECKING:`` or otherwise)
- Fixed ``TypeError`` when checking against a generic ``NewType``
- Don't try to check types shadowed by argument names (e.g.
``def foo(x: type, type: str): ...``)
- Don't check against unions where one of the elements is ``Any``
**4.0.0rc3** (2023-04-10)
- Fixed ``typing.Literal`` subscript contents being evaluated as forward references
- Fixed resolution of forward references in type aliases
**4.0.0rc2** (2023-04-08)
- The ``.pyc`` files now use a version-based optimization suffix in the file names so as
not to cause the interpreter to load potentially faulty/incompatible cached bytecode
generated by older versions
- Fixed typed variable positional and keyword arguments causing compilation errors on
Python 3.7 and 3.8
- Fixed compilation error when a type annotation contains a type guarded by
``if TYPE_CHECKING:``
**4.0.0rc1** (2023-04-02)
- **BACKWARD INCOMPATIBLE** ``check_type()`` no longer uses the global configuration.
It now uses the default configuration values, unless overridden with an explicit
``config`` argument.
- **BACKWARD INCOMPATIBLE** Removed ``CallMemo`` from the API
- **BACKWARD INCOMPATIBLE** Required checkers to use the configuration from
``memo.config``, rather than the global configuration
- Added keyword arguments to ``@typechecked``, allowing users to override settings on a
per-function basis
- Added support for using ``suppress_type_checks()`` as a decorator
- Added support for type checking against nonlocal classes defined within the same
parent function as the instrumented function
- Changed instrumentation to statically copy the function annotations to avoid having to
look up the function object at run time
- Improved support for avoiding type checks against imports declared in
``if TYPE_CHECKING:`` blocks
- Fixed ``check_type`` not returning the passed value when checking against ``Any``, or
when type checking is being suppressed
- Fixed ``suppress_type_checks()`` not ending the suppression if the context block
raises an exception
- Fixed checking non-dictionary objects against a ``TypedDict`` annotation
(PR by Tolker-KU)
**3.0.2** (2023-03-22)
- Improved warnings by ensuring that they target user code and not Typeguard internal
code
- Fixed ``warn_on_error()`` not showing where the type violation actually occurred
- Fixed local assignment to ``*args`` or ``**kwargs`` being type checked incorrectly
- Fixed ``TypeError`` on ``check_type(..., None)``
- Fixed unpacking assignment not working with a starred variable (``x, *y = ...``) in
the target tuple
- Fixed variable multi-assignment (``a = b = c = ...``) being type checked incorrectly
**3.0.1** (2023-03-16)
- Improved the documentation
- Fixed assignment unpacking (``a, b = ...``) being checked incorrectly
- Fixed ``@typechecked`` attempting to instrument wrapper decorators such as
``@contextmanager`` when applied to a class
- Fixed ``py.typed`` missing from the wheel when not building from a git checkout
**3.0.0** (2023-03-15)
- **BACKWARD INCOMPATIBLE** Dropped the ``argname``, ``memo``, ``globals`` and
``locals`` arguments from ``check_type()``
- **BACKWARD INCOMPATIBLE** Removed the ``check_argument_types()`` and
``check_return_type()`` functions (use ``@typechecked`` instead)
- **BACKWARD INCOMPATIBLE** Moved ``install_import_hook`` to be directly importable
from the ``typeguard`` module
- **BACKWARD INCOMPATIBLE** Changed the checking of collections (list, set, dict,
sequence, mapping) to only check the first item by default. To get the old behavior,
set ``typeguard.config.collection_check_strategy`` to
``CollectionCheckStrategy.ALL_ITEMS``
- **BACKWARD INCOMPATIBLE** Type checking failures now raise
``typeguard.TypeCheckError`` instead of ``TypeError``
- Dropped Python 3.5 and 3.6 support
- Dropped the deprecated profiler hook (``TypeChecker``)
- Added a configuration system
- Added support for custom type checking functions
- Added support for PEP 604 union types (``X | Y``) on all Python versions
- Added support for generic built-in collection types (``list[int]`` et al) on all
Python versions
- Added support for checking arbitrary ``Mapping`` types
- Added support for the ``Self`` type
- Added support for ``typing.Never`` (and ``typing_extensions.Never``)
- Added support for ``Never`` and ``NoReturn`` in argument annotations
- Added support for ``LiteralString``
- Added support for ``TypeGuard``
- Added support for the subclassable ``Any`` on Python 3.11 and ``typing_extensions``
- Added the possibility to have the import hook instrument all packages
- Added the ``suppress_type_checks()`` context manager function for temporarily
disabling type checks
- Much improved error messages showing where the type check failed
- Made it possible to apply ``@typechecked`` on top of ``@classmethod`` /
``@staticmethod`` (PR by jacobpbrugh)
- Changed ``check_type()`` to return the passed value, so it can be used (to an extent)
in place of ``typing.cast()``, but with run-time type checking
- Replaced custom implementation of ``is_typeddict()`` with the implementation from
``typing_extensions`` v4.1.0
- Emit ``InstrumentationWarning`` instead of raising ``RuntimeError`` from the pytest
plugin if modules in the target package have already been imported
- Fixed ``TypeError`` when checking against ``TypedDict`` when the value has mixed types
among the extra keys (PR by biolds)
- Fixed incompatibility with ``typing_extensions`` v4.1+ on Python 3.10 (PR by David C.)
- Fixed checking of ``Tuple[()]`` on Python 3.11 and ``tuple[()]`` on Python 3.9+
- Fixed integers 0 and 1 passing for ``Literal[False]`` and ``Literal[True]``,
respectively
- Fixed type checking of annotated variable positional and keyword arguments (``*args``
and ``**kwargs``)
- Fixed checks against ``unittest.Mock`` and derivatives being done in the wrong place
**2.13.3** (2021-12-10)
- Fixed ``TypeError`` when using typeguard within ``exec()`` (where ``__module__`` is ``None``)
(PR by Andy Jones)
- Fixed ``TypedDict`` causing ``TypeError: TypedDict does not support instance and class checks``
on Python 3.8 with standard library (not ``typing_extensions``) typed dicts
**2.13.2** (2021-11-23)
- Fixed ``typing_extensions`` being imported unconditionally on Python < 3.9
(bug introduced in 2.13.1)
**2.13.1** (2021-11-23)
- Fixed ``@typechecked`` replacing abstract properties with regular properties
- Fixed any generic type subclassing ``Dict`` being mistakenly checked as ``TypedDict`` on
Python 3.10
**2.13.0** (2021-10-11)
- Added support for returning ``NotImplemented`` from binary magic methods (``__eq__()`` et al)
- Added support for checking union types (e.g. ``Type[Union[X, Y]]``)
- Fixed error message when a check against a ``Literal`` fails in a union on Python 3.10
- Fixed ``NewType`` not being checked on Python 3.10
- Fixed unwarranted warning when ``@typechecked`` is applied to a class that contains unannotated
properties
- Fixed ``TypeError`` in the async generator wrapper due to changes in ``__aiter__()`` protocol
- Fixed broken ``TypeVar`` checks – variance is now (correctly) disregarded, and only bound types
and constraints are checked against (but type variable resolution is not done)
**2.12.1** (2021-06-04)
- Fixed ``AttributeError`` when ``__code__`` is missing from the checked callable (PR by epenet)
**2.12.0** (2021-04-01)
- Added ``@typeguard_ignore`` decorator to exclude specific functions and classes from
runtime type checking (PR by Claudio Jolowicz)
**2.11.1** (2021-02-16)
- Fixed compatibility with Python 3.10
**2.11.0** (2021-02-13)
- Added support for type checking class properties (PR by Ethan Pronovost)
- Fixed static type checking of ``@typechecked`` decorators (PR by Kenny Stauffer)
- Fixed wrong error message when type check against a ``bytes`` declaration fails
- Allowed ``memoryview`` objects to pass as ``bytes`` (like MyPy does)
- Shortened tracebacks (PR by prescod)
**2.10.0** (2020-10-17)
- Added support for Python 3.9 (PR by Csergő Bálint)
- Added support for nested ``Literal``
- Added support for ``TypedDict`` inheritance (with some caveats; see the user guide on that for
details)
- An appropriate ``TypeError`` is now raised when encountering an illegal ``Literal`` value
- Fixed checking ``NoReturn`` on Python < 3.8 when ``typing_extensions`` was not installed
- Fixed import hook matching unwanted modules (PR by Wouter Bolsterlee)
- Install the pytest plugin earlier in the test run to support more use cases
(PR by Wouter Bolsterlee)
**2.9.1** (2020-06-07)
- Fixed ``ImportError`` on Python < 3.8 when ``typing_extensions`` was not installed
**2.9.0** (2020-06-06)
- Upped the minimum Python version from 3.5.2 to 3.5.3
- Added support for ``typing.NoReturn``
- Added full support for ``typing_extensions`` (now equivalent to support of the ``typing`` module)
- Added the option of supplying ``check_type()`` with globals/locals for correct resolution of
forward references
- Fixed erroneous ``TypeError`` when trying to check against non-runtime ``typing.Protocol``
(skips the check for now until a proper compatibility check has been implemented)
- Fixed forward references in ``TypedDict`` not being resolved
- Fixed checking against recursive types
**2.8.0** (2020-06-02)
- Added support for the ``Mock`` and ``MagicMock`` types (PR by prescod)
- Added support for ``typing_extensions.Literal`` (PR by Ryan Rowe)
- Fixed unintended wrapping of untyped generators (PR by prescod)
- Fixed checking against bound type variables with ``check_type()`` without a call memo
- Fixed error message when checking against a ``Union`` containing a ``Literal``
**2.7.1** (2019-12-27)
- Fixed ``@typechecked`` returning ``None`` when called with ``always=True`` and Python runs in
optimized mode
- Fixed performance regression introduced in v2.7.0 (the ``getattr_static()`` call was causing a 3x
slowdown)
**2.7.0** (2019-12-10)
- Added support for ``typing.Protocol`` subclasses
- Added support for ``typing.AbstractSet``
- Fixed the handling of ``total=False`` in ``TypedDict``
- Fixed no error reported on unknown keys with ``TypedDict``
- Removed support of default values in ``TypedDict``, as they are not supported in the spec
**2.6.1** (2019-11-17)
- Fixed import errors when using the import hook and trying to import a module that has both a
module docstring and ``__future__`` imports in it
- Fixed ``AttributeError`` when using ``@typechecked`` on a metaclass
- Fixed ``@typechecked`` compatibility with built-in function wrappers
- Fixed type checking generator wrappers not being recognized as generators
- Fixed resolution of forward references in certain cases (inner classes, function-local classes)
- Fixed ``AttributeError`` when a class has contains a variable that is an instance of a class
that has a ``__call__()`` method
- Fixed class methods and static methods being wrapped incorrectly when ``@typechecked`` is applied
to the class
- Fixed ``AttributeError`` when ``@typechecked`` is applied to a function that has been decorated
with a decorator that does not properly wrap the original (PR by Joel Beach)
- Fixed collections with mixed value (or key) types raising ``TypeError`` on Python 3.7+ when
matched against unparametrized annotations from the ``typing`` module
- Fixed inadvertent ``TypeError`` when checking against a type variable that has constraints or
a bound type expressed as a forward reference
**2.6.0** (2019-11-06)
- Added a :pep:`302` import hook for annotating functions and classes with ``@typechecked``
- Added a pytest plugin that activates the import hook
- Added support for ``typing.TypedDict``
- Deprecated ``TypeChecker`` (will be removed in v3.0)
**2.5.1** (2019-09-26)
- Fixed incompatibility between annotated ``Iterable``, ``Iterator``, ``AsyncIterable`` or
``AsyncIterator`` return types and generator/async generator functions
- Fixed ``TypeError`` being wrapped inside another TypeError (PR by russok)
**2.5.0** (2019-08-26)
- Added yield type checking via ``TypeChecker`` for regular generators
- Added yield, send and return type checking via ``@typechecked`` for regular and async generators
- Silenced ``TypeChecker`` warnings about async generators
- Fixed bogus ``TypeError`` on ``Type[Any]``
- Fixed bogus ``TypeChecker`` warnings when an exception is raised from a type checked function
- Accept a ``bytearray`` where ``bytes`` are expected, as per `python/typing#552`_
- Added policies for dealing with unmatched forward references
- Added support for using ``@typechecked`` as a class decorator
- Added ``check_return_type()`` to accompany ``check_argument_types()``
- Added Sphinx documentation
.. _python/typing#552: https://github.com/python/typing/issues/552
**2.4.1** (2019-07-15)
- Fixed broken packaging configuration
**2.4.0** (2019-07-14)
- Added :pep:`561` support
- Added support for empty tuples (``Tuple[()]``)
- Added support for ``typing.Literal``
- Make getting the caller frame faster (PR by Nick Sweeting)
**2.3.1** (2019-04-12)
- Fixed thread safety issue with the type hints cache (PR by Kelsey Francis)
**2.3.0** (2019-03-27)
- Added support for ``typing.IO`` and derivatives
- Fixed return type checking for coroutine functions
- Dropped support for Python 3.4
**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
python-typeguard-4.1.5/pyproject.toml 0000664 0000000 0000000 00000005074 14545071262 0017774 0 ustar 00root root 0000000 0000000 [build-system]
requires = [
"setuptools >= 64",
"setuptools_scm[toml] >= 6.4"
]
build-backend = "setuptools.build_meta"
[project]
name = "typeguard"
description = "Run-time type checker for Python"
readme = "README.rst"
authors = [{name = "Alex Grönholm", email = "alex.gronholm@nextday.fi"}]
license = {text = "MIT"}
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.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
requires-python = ">= 3.8"
dependencies = [
"importlib_metadata >= 3.6; python_version < '3.10'",
"typing_extensions >= 4.7.0; python_version < '3.12'",
]
dynamic = ["version"]
[project.urls]
Documentation = "https://typeguard.readthedocs.io/en/latest/"
"Change log" = "https://typeguard.readthedocs.io/en/latest/versionhistory.html"
"Source code" = "https://github.com/agronholm/typeguard"
"Issue tracker" = "https://github.com/agronholm/typeguard/issues"
[project.optional-dependencies]
test = [
"coverage[toml] >= 7",
"pytest >= 7",
'mypy >= 1.2.0; python_implementation != "PyPy"',
]
doc = [
"packaging",
"Sphinx >= 7",
"sphinx-autodoc-typehints >= 1.2.0",
]
[project.entry-points]
pytest11 = {typeguard = "typeguard._pytest_plugin"}
[tool.setuptools.package-data]
typeguard = ["py.typed"]
[tool.setuptools_scm]
version_scheme = "post-release"
local_scheme = "dirty-tag"
[tool.pytest.ini_options]
addopts = "-rsx --tb=short"
testpaths = "tests"
xfail_strict = true
filterwarnings = ["error"]
[tool.coverage.run]
source = ["typeguard"]
[tool.coverage.report]
show_missing = true
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:"
]
[tool.ruff]
select = [
"E", "F", "W", # default flake-8
"I", # isort
"PGH", # pygrep-hooks
"UP", # pyupgrade
"B0", # flake8-bugbear
]
ignore = [
"PGH001",
"B008",
]
src = ["src"]
[tool.mypy]
python_version = "3.9"
strict = true
pretty = true
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = pypy3, py38, py39, py310, py311, py312
skip_missing_interpreters = true
minversion = 4.0
[testenv]
extras = test
commands = coverage run -m pytest {posargs}
package = editable
[testenv:docs]
extras = doc
package = editable
commands = sphinx-build -W -n docs build/sphinx
"""
python-typeguard-4.1.5/src/ 0000775 0000000 0000000 00000000000 14545071262 0015641 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/src/typeguard/ 0000775 0000000 0000000 00000000000 14545071262 0017645 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/src/typeguard/__init__.py 0000664 0000000 0000000 00000004027 14545071262 0021761 0 ustar 00root root 0000000 0000000 import os
from typing import Any
from ._checkers import TypeCheckerCallable as TypeCheckerCallable
from ._checkers import TypeCheckLookupCallback as TypeCheckLookupCallback
from ._checkers import check_type_internal as check_type_internal
from ._checkers import checker_lookup_functions as checker_lookup_functions
from ._checkers import load_plugins as load_plugins
from ._config import CollectionCheckStrategy as CollectionCheckStrategy
from ._config import ForwardRefPolicy as ForwardRefPolicy
from ._config import TypeCheckConfiguration as TypeCheckConfiguration
from ._decorators import typechecked as typechecked
from ._decorators import typeguard_ignore as typeguard_ignore
from ._exceptions import InstrumentationWarning as InstrumentationWarning
from ._exceptions import TypeCheckError as TypeCheckError
from ._exceptions import TypeCheckWarning as TypeCheckWarning
from ._exceptions import TypeHintWarning as TypeHintWarning
from ._functions import TypeCheckFailCallback as TypeCheckFailCallback
from ._functions import check_type as check_type
from ._functions import warn_on_error as warn_on_error
from ._importhook import ImportHookManager as ImportHookManager
from ._importhook import TypeguardFinder as TypeguardFinder
from ._importhook import install_import_hook as install_import_hook
from ._memo import TypeCheckMemo as TypeCheckMemo
from ._suppression import suppress_type_checks as suppress_type_checks
from ._utils import Unset as Unset
# Re-export imports so they look like they live directly in this package
for value in list(locals().values()):
if getattr(value, "__module__", "").startswith(f"{__name__}."):
value.__module__ = __name__
config: TypeCheckConfiguration
def __getattr__(name: str) -> Any:
if name == "config":
from ._config import global_config
return global_config
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# Automatically load checker lookup functions unless explicitly disabled
if "TYPEGUARD_DISABLE_PLUGIN_AUTOLOAD" not in os.environ:
load_plugins()
python-typeguard-4.1.5/src/typeguard/_checkers.py 0000664 0000000 0000000 00000064552 14545071262 0022161 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import collections.abc
import inspect
import sys
import types
import typing
import warnings
from enum import Enum
from inspect import Parameter, isclass, isfunction
from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
from textwrap import indent
from typing import (
IO,
AbstractSet,
Any,
BinaryIO,
Callable,
Dict,
ForwardRef,
List,
Mapping,
MutableMapping,
NewType,
Optional,
Sequence,
Set,
TextIO,
Tuple,
Type,
TypeVar,
Union,
)
from unittest.mock import Mock
try:
import typing_extensions
except ImportError:
typing_extensions = None # type: ignore[assignment]
from ._config import ForwardRefPolicy
from ._exceptions import TypeCheckError, TypeHintWarning
from ._memo import TypeCheckMemo
from ._utils import evaluate_forwardref, get_stacklevel, get_type_name, qualified_name
if sys.version_info >= (3, 11):
from typing import (
Annotated,
TypeAlias,
get_args,
get_origin,
get_type_hints,
is_typeddict,
)
SubclassableAny = Any
else:
from typing_extensions import (
Annotated,
TypeAlias,
get_args,
get_origin,
get_type_hints,
is_typeddict,
)
from typing_extensions import Any as SubclassableAny
if sys.version_info >= (3, 10):
from importlib.metadata import entry_points
from typing import ParamSpec
else:
from importlib_metadata import entry_points
from typing_extensions import ParamSpec
TypeCheckerCallable: TypeAlias = Callable[
[Any, Any, Tuple[Any, ...], TypeCheckMemo], Any
]
TypeCheckLookupCallback: TypeAlias = Callable[
[Any, Tuple[Any, ...], Tuple[Any, ...]], Optional[TypeCheckerCallable]
]
checker_lookup_functions: list[TypeCheckLookupCallback] = []
# Sentinel
_missing = object()
# Lifted from mypy.sharedparse
BINARY_MAGIC_METHODS = {
"__add__",
"__and__",
"__cmp__",
"__divmod__",
"__div__",
"__eq__",
"__floordiv__",
"__ge__",
"__gt__",
"__iadd__",
"__iand__",
"__idiv__",
"__ifloordiv__",
"__ilshift__",
"__imatmul__",
"__imod__",
"__imul__",
"__ior__",
"__ipow__",
"__irshift__",
"__isub__",
"__itruediv__",
"__ixor__",
"__le__",
"__lshift__",
"__lt__",
"__matmul__",
"__mod__",
"__mul__",
"__ne__",
"__or__",
"__pow__",
"__radd__",
"__rand__",
"__rdiv__",
"__rfloordiv__",
"__rlshift__",
"__rmatmul__",
"__rmod__",
"__rmul__",
"__ror__",
"__rpow__",
"__rrshift__",
"__rshift__",
"__rsub__",
"__rtruediv__",
"__rxor__",
"__sub__",
"__truediv__",
"__xor__",
}
def check_callable(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not callable(value):
raise TypeCheckError("is not callable")
if args:
try:
signature = inspect.signature(value)
except (TypeError, ValueError):
return
argument_types = args[0]
if isinstance(argument_types, list) and not any(
type(item) is ParamSpec for item in argument_types
):
# 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 TypeCheckError(
f"has mandatory keyword-only arguments in its declaration: "
f'{", ".join(unfulfilled_kwonlyargs)}'
)
num_positional_args = num_mandatory_pos_args = 0
has_varargs = False
for param in signature.parameters.values():
if param.kind in (
Parameter.POSITIONAL_ONLY,
Parameter.POSITIONAL_OR_KEYWORD,
):
num_positional_args += 1
if param.default is Parameter.empty:
num_mandatory_pos_args += 1
elif param.kind == Parameter.VAR_POSITIONAL:
has_varargs = True
if num_mandatory_pos_args > len(argument_types):
raise TypeCheckError(
f"has too many mandatory positional arguments in its declaration; "
f"expected {len(argument_types)} but {num_mandatory_pos_args} "
f"mandatory positional argument(s) declared"
)
elif not has_varargs and num_positional_args < len(argument_types):
raise TypeCheckError(
f"has too few arguments in its declaration; expected "
f"{len(argument_types)} but {num_positional_args} argument(s) "
f"declared"
)
def check_mapping(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if origin_type is Dict or origin_type is dict:
if not isinstance(value, dict):
raise TypeCheckError("is not a dict")
if origin_type is MutableMapping or origin_type is collections.abc.MutableMapping:
if not isinstance(value, collections.abc.MutableMapping):
raise TypeCheckError("is not a mutable mapping")
elif not isinstance(value, collections.abc.Mapping):
raise TypeCheckError("is not a mapping")
if args:
key_type, value_type = args
if key_type is not Any or value_type is not Any:
samples = memo.config.collection_check_strategy.iterate_samples(
value.items()
)
for k, v in samples:
try:
check_type_internal(k, key_type, memo)
except TypeCheckError as exc:
exc.append_path_element(f"key {k!r}")
raise
try:
check_type_internal(v, value_type, memo)
except TypeCheckError as exc:
exc.append_path_element(f"value of key {k!r}")
raise
def check_typed_dict(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, dict):
raise TypeCheckError("is not a dict")
declared_keys = frozenset(origin_type.__annotations__)
if hasattr(origin_type, "__required_keys__"):
required_keys = origin_type.__required_keys__
else: # py3.8 and lower
required_keys = declared_keys if origin_type.__total__ else frozenset()
existing_keys = frozenset(value)
extra_keys = existing_keys - declared_keys
if extra_keys:
keys_formatted = ", ".join(f'"{key}"' for key in sorted(extra_keys, key=repr))
raise TypeCheckError(f"has unexpected extra key(s): {keys_formatted}")
missing_keys = required_keys - existing_keys
if missing_keys:
keys_formatted = ", ".join(f'"{key}"' for key in sorted(missing_keys, key=repr))
raise TypeCheckError(f"is missing required key(s): {keys_formatted}")
for key, argtype in get_type_hints(origin_type).items():
argvalue = value.get(key, _missing)
if argvalue is not _missing:
try:
check_type_internal(argvalue, argtype, memo)
except TypeCheckError as exc:
exc.append_path_element(f"value of key {key!r}")
raise
def check_list(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, list):
raise TypeCheckError("is not a list")
if args and args != (Any,):
samples = memo.config.collection_check_strategy.iterate_samples(value)
for i, v in enumerate(samples):
try:
check_type_internal(v, args[0], memo)
except TypeCheckError as exc:
exc.append_path_element(f"item {i}")
raise
def check_sequence(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, collections.abc.Sequence):
raise TypeCheckError("is not a sequence")
if args and args != (Any,):
samples = memo.config.collection_check_strategy.iterate_samples(value)
for i, v in enumerate(samples):
try:
check_type_internal(v, args[0], memo)
except TypeCheckError as exc:
exc.append_path_element(f"item {i}")
raise
def check_set(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if origin_type is frozenset:
if not isinstance(value, frozenset):
raise TypeCheckError("is not a frozenset")
elif not isinstance(value, AbstractSet):
raise TypeCheckError("is not a set")
if args and args != (Any,):
samples = memo.config.collection_check_strategy.iterate_samples(value)
for v in samples:
try:
check_type_internal(v, args[0], memo)
except TypeCheckError as exc:
exc.append_path_element(f"[{v}]")
raise
def check_tuple(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
# Specialized check for NamedTuples
if field_types := getattr(origin_type, "__annotations__", None):
if not isinstance(value, origin_type):
raise TypeCheckError(
f"is not a named tuple of type {qualified_name(origin_type)}"
)
for name, field_type in field_types.items():
try:
check_type_internal(getattr(value, name), field_type, memo)
except TypeCheckError as exc:
exc.append_path_element(f"attribute {name!r}")
raise
return
elif not isinstance(value, tuple):
raise TypeCheckError("is not a tuple")
if args:
use_ellipsis = args[-1] is Ellipsis
tuple_params = args[: -1 if use_ellipsis else None]
else:
# Unparametrized Tuple or plain tuple
return
if use_ellipsis:
element_type = tuple_params[0]
samples = memo.config.collection_check_strategy.iterate_samples(value)
for i, element in enumerate(samples):
try:
check_type_internal(element, element_type, memo)
except TypeCheckError as exc:
exc.append_path_element(f"item {i}")
raise
elif tuple_params == ((),):
if value != ():
raise TypeCheckError("is not an empty tuple")
else:
if len(value) != len(tuple_params):
raise TypeCheckError(
f"has wrong number of elements (expected {len(tuple_params)}, got "
f"{len(value)} instead)"
)
for i, (element, element_type) in enumerate(zip(value, tuple_params)):
try:
check_type_internal(element, element_type, memo)
except TypeCheckError as exc:
exc.append_path_element(f"item {i}")
raise
def check_union(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
errors: dict[str, TypeCheckError] = {}
for type_ in args:
try:
check_type_internal(value, type_, memo)
return
except TypeCheckError as exc:
errors[get_type_name(type_)] = exc
formatted_errors = indent(
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
)
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
def check_uniontype(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
errors: dict[str, TypeCheckError] = {}
for type_ in args:
try:
check_type_internal(value, type_, memo)
return
except TypeCheckError as exc:
errors[get_type_name(type_)] = exc
formatted_errors = indent(
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
)
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
def check_class(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isclass(value):
raise TypeCheckError("is not a class")
if not args:
return
if isinstance(args[0], ForwardRef):
expected_class = evaluate_forwardref(args[0], memo)
else:
expected_class = args[0]
if expected_class is Any:
return
elif getattr(expected_class, "_is_protocol", False):
check_protocol(value, expected_class, (), memo)
elif isinstance(expected_class, TypeVar):
check_typevar(value, expected_class, (), memo, subclass_check=True)
elif get_origin(expected_class) is Union:
errors: dict[str, TypeCheckError] = {}
for arg in get_args(expected_class):
if arg is Any:
return
try:
check_class(value, type, (arg,), memo)
return
except TypeCheckError as exc:
errors[get_type_name(arg)] = exc
else:
formatted_errors = indent(
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
)
raise TypeCheckError(
f"did not match any element in the union:\n{formatted_errors}"
)
elif not issubclass(value, expected_class):
raise TypeCheckError(f"is not a subclass of {qualified_name(expected_class)}")
def check_newtype(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
check_type_internal(value, origin_type.__supertype__, memo)
def check_instance(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, origin_type):
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
def check_typevar(
value: Any,
origin_type: TypeVar,
args: tuple[Any, ...],
memo: TypeCheckMemo,
*,
subclass_check: bool = False,
) -> None:
if origin_type.__bound__ is not None:
annotation = (
Type[origin_type.__bound__] if subclass_check else origin_type.__bound__
)
check_type_internal(value, annotation, memo)
elif origin_type.__constraints__:
for constraint in origin_type.__constraints__:
annotation = Type[constraint] if subclass_check else constraint
try:
check_type_internal(value, annotation, memo)
except TypeCheckError:
pass
else:
break
else:
formatted_constraints = ", ".join(
get_type_name(constraint) for constraint in origin_type.__constraints__
)
raise TypeCheckError(
f"does not match any of the constraints " f"({formatted_constraints})"
)
if typing_extensions is None:
def _is_literal_type(typ: object) -> bool:
return typ is typing.Literal
else:
def _is_literal_type(typ: object) -> bool:
return typ is typing.Literal or typ is typing_extensions.Literal
def check_literal(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
def get_literal_args(literal_args: tuple[Any, ...]) -> tuple[Any, ...]:
retval: list[Any] = []
for arg in literal_args:
if _is_literal_type(get_origin(arg)):
retval.extend(get_literal_args(arg.__args__))
elif arg is None or isinstance(arg, (int, str, bytes, bool, Enum)):
retval.append(arg)
else:
raise TypeError(
f"Illegal literal value: {arg}"
) # TypeError here is deliberate
return tuple(retval)
final_args = tuple(get_literal_args(args))
try:
index = final_args.index(value)
except ValueError:
pass
else:
if type(final_args[index]) is type(value):
return
formatted_args = ", ".join(repr(arg) for arg in final_args)
raise TypeCheckError(f"is not any of ({formatted_args})") from None
def check_literal_string(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
check_type_internal(value, str, memo)
def check_typeguard(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
check_type_internal(value, bool, memo)
def check_none(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if value is not None:
raise TypeCheckError("is not None")
def check_number(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if origin_type is complex and not isinstance(value, (complex, float, int)):
raise TypeCheckError("is neither complex, float or int")
elif origin_type is float and not isinstance(value, (float, int)):
raise TypeCheckError("is neither float or int")
def check_io(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if origin_type is TextIO or (origin_type is IO and args == (str,)):
if not isinstance(value, TextIOBase):
raise TypeCheckError("is not a text based I/O object")
elif origin_type is BinaryIO or (origin_type is IO and args == (bytes,)):
if not isinstance(value, (RawIOBase, BufferedIOBase)):
raise TypeCheckError("is not a binary I/O object")
elif not isinstance(value, IOBase):
raise TypeCheckError("is not an I/O object")
def check_protocol(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
# TODO: implement proper compatibility checking and support non-runtime protocols
if getattr(origin_type, "_is_runtime_protocol", False):
if not isinstance(value, origin_type):
raise TypeCheckError(
f"is not compatible with the {origin_type.__qualname__} protocol"
)
else:
warnings.warn(
f"Typeguard cannot check the {origin_type.__qualname__} protocol because "
f"it is a non-runtime protocol. If you would like to type check this "
f"protocol, please use @typing.runtime_checkable",
stacklevel=get_stacklevel(),
)
def check_byteslike(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, (bytearray, bytes, memoryview)):
raise TypeCheckError("is not bytes-like")
def check_self(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if memo.self_type is None:
raise TypeCheckError("cannot be checked against Self outside of a method call")
if isclass(value):
if not issubclass(value, memo.self_type):
raise TypeCheckError(
f"is not an instance of the self type "
f"({qualified_name(memo.self_type)})"
)
elif not isinstance(value, memo.self_type):
raise TypeCheckError(
f"is not an instance of the self type ({qualified_name(memo.self_type)})"
)
def check_paramspec(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
pass # No-op for now
def check_instanceof(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, origin_type):
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
def check_type_internal(
value: Any,
annotation: Any,
memo: TypeCheckMemo,
) -> None:
"""
Check that the given object is compatible with the given type annotation.
This function should only be used by type checker callables. Applications should use
:func:`~.check_type` instead.
:param value: the value to check
:param annotation: the type annotation to check against
:param memo: a memo object containing configuration and information necessary for
looking up forward references
"""
if isinstance(annotation, ForwardRef):
try:
annotation = evaluate_forwardref(annotation, memo)
except NameError:
if memo.config.forward_ref_policy is ForwardRefPolicy.ERROR:
raise
elif memo.config.forward_ref_policy is ForwardRefPolicy.WARN:
warnings.warn(
f"Cannot resolve forward reference {annotation.__forward_arg__!r}",
TypeHintWarning,
stacklevel=get_stacklevel(),
)
return
if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
return
# Skip type checks if value is an instance of a class that inherits from Any
if not isclass(value) and SubclassableAny in type(value).__bases__:
return
extras: tuple[Any, ...]
origin_type = get_origin(annotation)
if origin_type is Annotated:
annotation, *extras_ = get_args(annotation)
extras = tuple(extras_)
origin_type = get_origin(annotation)
else:
extras = ()
if origin_type is not None:
args = get_args(annotation)
# Compatibility hack to distinguish between unparametrized and empty tuple
# (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
args = ((),)
else:
origin_type = annotation
args = ()
for lookup_func in checker_lookup_functions:
checker = lookup_func(origin_type, args, extras)
if checker:
checker(value, origin_type, args, memo)
return
if isclass(origin_type):
if not isinstance(value, origin_type):
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
elif type(origin_type) is str: # noqa: E721
warnings.warn(
f"Skipping type check against {origin_type!r}; this looks like a "
f"string-form forward reference imported from another module",
TypeHintWarning,
stacklevel=get_stacklevel(),
)
# Equality checks are applied to these
origin_type_checkers = {
bytes: check_byteslike,
AbstractSet: check_set,
BinaryIO: check_io,
Callable: check_callable,
collections.abc.Callable: check_callable,
complex: check_number,
dict: check_mapping,
Dict: check_mapping,
float: check_number,
frozenset: check_set,
IO: check_io,
list: check_list,
List: check_list,
typing.Literal: check_literal,
Mapping: check_mapping,
MutableMapping: check_mapping,
None: check_none,
collections.abc.Mapping: check_mapping,
collections.abc.MutableMapping: check_mapping,
Sequence: check_sequence,
collections.abc.Sequence: check_sequence,
collections.abc.Set: check_set,
set: check_set,
Set: check_set,
TextIO: check_io,
tuple: check_tuple,
Tuple: check_tuple,
type: check_class,
Type: check_class,
Union: check_union,
}
if sys.version_info >= (3, 10):
origin_type_checkers[types.UnionType] = check_uniontype
origin_type_checkers[typing.TypeGuard] = check_typeguard
if sys.version_info >= (3, 11):
origin_type_checkers.update(
{typing.LiteralString: check_literal_string, typing.Self: check_self}
)
if typing_extensions is not None:
# On some Python versions, these may simply be re-exports from typing,
# but exactly which Python versions is subject to change,
# so it's best to err on the safe side
# and update the dictionary on all Python versions
# if typing_extensions is installed
origin_type_checkers[typing_extensions.Literal] = check_literal
origin_type_checkers[typing_extensions.LiteralString] = check_literal_string
origin_type_checkers[typing_extensions.Self] = check_self
origin_type_checkers[typing_extensions.TypeGuard] = check_typeguard
def builtin_checker_lookup(
origin_type: Any, args: tuple[Any, ...], extras: tuple[Any, ...]
) -> TypeCheckerCallable | None:
checker = origin_type_checkers.get(origin_type)
if checker is not None:
return checker
elif is_typeddict(origin_type):
return check_typed_dict
elif isclass(origin_type) and issubclass(
origin_type, Tuple # type: ignore[arg-type]
):
# NamedTuple
return check_tuple
elif getattr(origin_type, "_is_protocol", False):
return check_protocol
elif isinstance(origin_type, ParamSpec):
return check_paramspec
elif isinstance(origin_type, TypeVar):
return check_typevar
elif origin_type.__class__ is NewType:
# typing.NewType on Python 3.10+
return check_newtype
elif (
isfunction(origin_type)
and getattr(origin_type, "__module__", None) == "typing"
and getattr(origin_type, "__qualname__", "").startswith("NewType.")
and hasattr(origin_type, "__supertype__")
):
# typing.NewType on Python 3.9 and below
return check_newtype
return None
checker_lookup_functions.append(builtin_checker_lookup)
def load_plugins() -> None:
"""
Load all type checker lookup functions from entry points.
All entry points from the ``typeguard.checker_lookup`` group are loaded, and the
returned lookup functions are added to :data:`typeguard.checker_lookup_functions`.
.. note:: This function is called implicitly on import, unless the
``TYPEGUARD_DISABLE_PLUGIN_AUTOLOAD`` environment variable is present.
"""
for ep in entry_points(group="typeguard.checker_lookup"):
try:
plugin = ep.load()
except Exception as exc:
warnings.warn(
f"Failed to load plugin {ep.name!r}: " f"{qualified_name(exc)}: {exc}",
stacklevel=2,
)
continue
if not callable(plugin):
warnings.warn(
f"Plugin {ep} returned a non-callable object: {plugin!r}", stacklevel=2
)
continue
checker_lookup_functions.insert(0, plugin)
python-typeguard-4.1.5/src/typeguard/_config.py 0000664 0000000 0000000 00000005443 14545071262 0021631 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from collections.abc import Collection
from dataclasses import dataclass
from enum import Enum, auto
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from ._functions import TypeCheckFailCallback
T = TypeVar("T")
class ForwardRefPolicy(Enum):
"""
Defines how unresolved forward references are handled.
Members:
* ``ERROR``: propagate the :exc:`NameError` when the forward reference lookup fails
* ``WARN``: emit a :class:`~.TypeHintWarning` if the forward reference lookup fails
* ``IGNORE``: silently skip checks for unresolveable forward references
"""
ERROR = auto()
WARN = auto()
IGNORE = auto()
class CollectionCheckStrategy(Enum):
"""
Specifies how thoroughly the contents of collections are type checked.
This has an effect on the following built-in checkers:
* ``AbstractSet``
* ``Dict``
* ``List``
* ``Mapping``
* ``Set``
* ``Tuple[, ...]`` (arbitrarily sized tuples)
Members:
* ``FIRST_ITEM``: check only the first item
* ``ALL_ITEMS``: check all items
"""
FIRST_ITEM = auto()
ALL_ITEMS = auto()
def iterate_samples(self, collection: Collection[T]) -> Collection[T]:
if self is CollectionCheckStrategy.FIRST_ITEM:
if len(collection):
return [next(iter(collection))]
else:
return ()
else:
return collection
@dataclass
class TypeCheckConfiguration:
"""
You can change Typeguard's behavior with these settings.
.. attribute:: typecheck_fail_callback
:type: Callable[[TypeCheckError, TypeCheckMemo], Any]
Callable that is called when type checking fails.
Default: ``None`` (the :exc:`~.TypeCheckError` is raised directly)
.. attribute:: forward_ref_policy
:type: ForwardRefPolicy
Specifies what to do when a forward reference fails to resolve.
Default: ``WARN``
.. attribute:: collection_check_strategy
:type: CollectionCheckStrategy
Specifies how thoroughly the contents of collections (list, dict, etc.) are
type checked.
Default: ``FIRST_ITEM``
.. attribute:: debug_instrumentation
:type: bool
If set to ``True``, the code of modules or functions instrumented by typeguard
is printed to ``sys.stderr`` after the instrumentation is done
Requires Python 3.9 or newer.
Default: ``False``
"""
forward_ref_policy: ForwardRefPolicy = ForwardRefPolicy.WARN
typecheck_fail_callback: TypeCheckFailCallback | None = None
collection_check_strategy: CollectionCheckStrategy = (
CollectionCheckStrategy.FIRST_ITEM
)
debug_instrumentation: bool = False
global_config = TypeCheckConfiguration()
python-typeguard-4.1.5/src/typeguard/_decorators.py 0000664 0000000 0000000 00000021517 14545071262 0022531 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import ast
import inspect
import sys
from collections.abc import Sequence
from functools import partial
from inspect import isclass, isfunction
from types import CodeType, FrameType, FunctionType
from typing import TYPE_CHECKING, Any, Callable, ForwardRef, TypeVar, cast, overload
from warnings import warn
from ._config import CollectionCheckStrategy, ForwardRefPolicy, global_config
from ._exceptions import InstrumentationWarning
from ._functions import TypeCheckFailCallback
from ._transformer import TypeguardTransformer
from ._utils import Unset, function_name, get_stacklevel, is_method_of, unset
if TYPE_CHECKING:
from typeshed.stdlib.types import _Cell
_F = TypeVar("_F")
def typeguard_ignore(f: _F) -> _F:
"""This decorator is a noop during static type-checking."""
return f
else:
from typing import no_type_check as typeguard_ignore # noqa: F401
T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
def make_cell(value: object) -> _Cell:
return (lambda: value).__closure__[0] # type: ignore[index]
def find_target_function(
new_code: CodeType, target_path: Sequence[str], firstlineno: int
) -> CodeType | None:
target_name = target_path[0]
for const in new_code.co_consts:
if isinstance(const, CodeType):
if const.co_name == target_name:
if const.co_firstlineno == firstlineno:
return const
elif len(target_path) > 1:
target_code = find_target_function(
const, target_path[1:], firstlineno
)
if target_code:
return target_code
return None
def instrument(f: T_CallableOrType) -> FunctionType | str:
if not getattr(f, "__code__", None):
return "no code associated"
elif not getattr(f, "__module__", None):
return "__module__ attribute is not set"
elif f.__code__.co_filename == "":
return "cannot instrument functions defined in a REPL"
elif hasattr(f, "__wrapped__"):
return (
"@typechecked only supports instrumenting functions wrapped with "
"@classmethod, @staticmethod or @property"
)
target_path = [item for item in f.__qualname__.split(".") if item != ""]
module_source = inspect.getsource(sys.modules[f.__module__])
module_ast = ast.parse(module_source)
instrumentor = TypeguardTransformer(target_path, f.__code__.co_firstlineno)
instrumentor.visit(module_ast)
if not instrumentor.target_node or instrumentor.target_lineno is None:
return "instrumentor did not find the target function"
module_code = compile(module_ast, f.__code__.co_filename, "exec", dont_inherit=True)
new_code = find_target_function(
module_code, target_path, instrumentor.target_lineno
)
if not new_code:
return "cannot find the target function in the AST"
if global_config.debug_instrumentation and sys.version_info >= (3, 9):
# Find the matching AST node, then unparse it to source and print to stdout
print(
f"Source code of {f.__qualname__}() after instrumentation:"
"\n----------------------------------------------",
file=sys.stderr,
)
print(ast.unparse(instrumentor.target_node), file=sys.stderr)
print(
"----------------------------------------------",
file=sys.stderr,
)
closure = f.__closure__
if new_code.co_freevars != f.__code__.co_freevars:
# Create a new closure and find values for the new free variables
frame = cast(FrameType, inspect.currentframe())
frame = cast(FrameType, frame.f_back)
frame_locals = cast(FrameType, frame.f_back).f_locals
cells: list[_Cell] = []
for key in new_code.co_freevars:
if key in instrumentor.names_used_in_annotations:
# Find the value and make a new cell from it
value = frame_locals.get(key) or ForwardRef(key)
cells.append(make_cell(value))
else:
# Reuse the cell from the existing closure
assert f.__closure__
cells.append(f.__closure__[f.__code__.co_freevars.index(key)])
closure = tuple(cells)
new_function = FunctionType(new_code, f.__globals__, f.__name__, closure=closure)
new_function.__module__ = f.__module__
new_function.__name__ = f.__name__
new_function.__qualname__ = f.__qualname__
new_function.__annotations__ = f.__annotations__
new_function.__doc__ = f.__doc__
new_function.__defaults__ = f.__defaults__
new_function.__kwdefaults__ = f.__kwdefaults__
return new_function
@overload
def typechecked(
*,
forward_ref_policy: ForwardRefPolicy | Unset = unset,
typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
collection_check_strategy: CollectionCheckStrategy | Unset = unset,
debug_instrumentation: bool | Unset = unset,
) -> Callable[[T_CallableOrType], T_CallableOrType]:
...
@overload
def typechecked(target: T_CallableOrType) -> T_CallableOrType:
...
def typechecked(
target: T_CallableOrType | None = None,
*,
forward_ref_policy: ForwardRefPolicy | Unset = unset,
typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
collection_check_strategy: CollectionCheckStrategy | Unset = unset,
debug_instrumentation: bool | Unset = unset,
) -> Any:
"""
Instrument the target function to perform run-time type checking.
This decorator recompiles the target function, injecting code to type check
arguments, return values, yield values (excluding ``yield from``) and assignments to
annotated local variables.
This can also be used as a class decorator. This will instrument all type annotated
methods, including :func:`@classmethod `,
:func:`@staticmethod `, and :class:`@property ` decorated
methods in the class.
.. note:: When Python is run in optimized mode (``-O`` or ``-OO``, this decorator
is a no-op). This is a feature meant for selectively introducing type checking
into a code base where the checks aren't meant to be run in production.
:param target: the function or class to enable type checking for
:param forward_ref_policy: override for
:attr:`.TypeCheckConfiguration.forward_ref_policy`
:param typecheck_fail_callback: override for
:attr:`.TypeCheckConfiguration.typecheck_fail_callback`
:param collection_check_strategy: override for
:attr:`.TypeCheckConfiguration.collection_check_strategy`
:param debug_instrumentation: override for
:attr:`.TypeCheckConfiguration.debug_instrumentation`
"""
if target is None:
return partial(
typechecked,
forward_ref_policy=forward_ref_policy,
typecheck_fail_callback=typecheck_fail_callback,
collection_check_strategy=collection_check_strategy,
debug_instrumentation=debug_instrumentation,
)
if not __debug__:
return target
if isclass(target):
for key, attr in target.__dict__.items():
if is_method_of(attr, target):
retval = instrument(attr)
if isfunction(retval):
setattr(target, key, retval)
elif isinstance(attr, (classmethod, staticmethod)):
if is_method_of(attr.__func__, target):
retval = instrument(attr.__func__)
if isfunction(retval):
wrapper = attr.__class__(retval)
setattr(target, key, wrapper)
elif isinstance(attr, property):
kwargs: dict[str, Any] = dict(doc=attr.__doc__)
for name in ("fset", "fget", "fdel"):
property_func = kwargs[name] = getattr(attr, name)
if is_method_of(property_func, target):
retval = instrument(property_func)
if isfunction(retval):
kwargs[name] = retval
setattr(target, key, attr.__class__(**kwargs))
return target
# Find either the first Python wrapper or the actual function
wrapper_class: type[classmethod[Any, Any, Any]] | type[
staticmethod[Any, Any]
] | None = None
if isinstance(target, (classmethod, staticmethod)):
wrapper_class = target.__class__
target = target.__func__
retval = instrument(target)
if isinstance(retval, str):
warn(
f"{retval} -- not typechecking {function_name(target)}",
InstrumentationWarning,
stacklevel=get_stacklevel(),
)
return target
if wrapper_class is None:
return retval
else:
return wrapper_class(retval)
python-typeguard-4.1.5/src/typeguard/_exceptions.py 0000664 0000000 0000000 00000002141 14545071262 0022535 0 ustar 00root root 0000000 0000000 from collections import deque
from typing import Deque
class TypeHintWarning(UserWarning):
"""
A warning that is emitted when a type hint in string form could not be resolved to
an actual type.
"""
class TypeCheckWarning(UserWarning):
"""Emitted by typeguard's type checkers when a type mismatch is detected."""
def __init__(self, message: str):
super().__init__(message)
class InstrumentationWarning(UserWarning):
"""Emitted when there's a problem with instrumenting a function for type checks."""
def __init__(self, message: str):
super().__init__(message)
class TypeCheckError(Exception):
"""
Raised by typeguard's type checkers when a type mismatch is detected.
"""
def __init__(self, message: str):
super().__init__(message)
self._path: Deque[str] = deque()
def append_path_element(self, element: str) -> None:
self._path.append(element)
def __str__(self) -> str:
if self._path:
return " of ".join(self._path) + " " + str(self.args[0])
else:
return str(self.args[0])
python-typeguard-4.1.5/src/typeguard/_functions.py 0000664 0000000 0000000 00000024243 14545071262 0022373 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
import warnings
from typing import Any, Callable, NoReturn, TypeVar, Union, overload
from . import _suppression
from ._checkers import BINARY_MAGIC_METHODS, check_type_internal
from ._config import (
CollectionCheckStrategy,
ForwardRefPolicy,
TypeCheckConfiguration,
)
from ._exceptions import TypeCheckError, TypeCheckWarning
from ._memo import TypeCheckMemo
from ._utils import get_stacklevel, qualified_name
if sys.version_info >= (3, 11):
from typing import Literal, Never, TypeAlias
else:
from typing_extensions import Literal, Never, TypeAlias
T = TypeVar("T")
TypeCheckFailCallback: TypeAlias = Callable[[TypeCheckError, TypeCheckMemo], Any]
@overload
def check_type(
value: object,
expected_type: type[T],
*,
forward_ref_policy: ForwardRefPolicy = ...,
typecheck_fail_callback: TypeCheckFailCallback | None = ...,
collection_check_strategy: CollectionCheckStrategy = ...,
) -> T:
...
@overload
def check_type(
value: object,
expected_type: Any,
*,
forward_ref_policy: ForwardRefPolicy = ...,
typecheck_fail_callback: TypeCheckFailCallback | None = ...,
collection_check_strategy: CollectionCheckStrategy = ...,
) -> Any:
...
def check_type(
value: object,
expected_type: Any,
*,
forward_ref_policy: ForwardRefPolicy = TypeCheckConfiguration().forward_ref_policy,
typecheck_fail_callback: (TypeCheckFailCallback | None) = (
TypeCheckConfiguration().typecheck_fail_callback
),
collection_check_strategy: CollectionCheckStrategy = (
TypeCheckConfiguration().collection_check_strategy
),
) -> Any:
"""
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.
This function wraps :func:`~.check_type_internal` in the following ways:
* Respects type checking suppression (:func:`~.suppress_type_checks`)
* Forms a :class:`~.TypeCheckMemo` from the current stack frame
* Calls the configured type check fail callback if the check fails
Note that this function is independent of the globally shared configuration in
:data:`typeguard.config`. This means that usage within libraries is safe from being
affected configuration changes made by other libraries or by the integrating
application. Instead, configuration options have the same default values as their
corresponding fields in :class:`TypeCheckConfiguration`.
:param value: value to be checked against ``expected_type``
:param expected_type: a class or generic type instance, or a tuple of such things
:param forward_ref_policy: see :attr:`TypeCheckConfiguration.forward_ref_policy`
:param typecheck_fail_callback:
see :attr`TypeCheckConfiguration.typecheck_fail_callback`
:param collection_check_strategy:
see :attr:`TypeCheckConfiguration.collection_check_strategy`
:return: ``value``, unmodified
:raises TypeCheckError: if there is a type mismatch
"""
if type(expected_type) is tuple:
expected_type = Union[expected_type]
config = TypeCheckConfiguration(
forward_ref_policy=forward_ref_policy,
typecheck_fail_callback=typecheck_fail_callback,
collection_check_strategy=collection_check_strategy,
)
if _suppression.type_checks_suppressed or expected_type is Any:
return value
frame = sys._getframe(1)
memo = TypeCheckMemo(frame.f_globals, frame.f_locals, config=config)
try:
check_type_internal(value, expected_type, memo)
except TypeCheckError as exc:
exc.append_path_element(qualified_name(value, add_class_prefix=True))
if config.typecheck_fail_callback:
config.typecheck_fail_callback(exc, memo)
else:
raise
return value
def check_argument_types(
func_name: str,
arguments: dict[str, tuple[Any, Any]],
memo: TypeCheckMemo,
) -> Literal[True]:
if _suppression.type_checks_suppressed:
return True
for argname, (value, annotation) in arguments.items():
if annotation is NoReturn or annotation is Never:
exc = TypeCheckError(
f"{func_name}() was declared never to be called but it was"
)
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise exc
try:
check_type_internal(value, annotation, memo)
except TypeCheckError as exc:
qualname = qualified_name(value, add_class_prefix=True)
exc.append_path_element(f'argument "{argname}" ({qualname})')
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return True
def check_return_type(
func_name: str,
retval: T,
annotation: Any,
memo: TypeCheckMemo,
) -> T:
if _suppression.type_checks_suppressed:
return retval
if annotation is NoReturn or annotation is Never:
exc = TypeCheckError(f"{func_name}() was declared never to return but it did")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise exc
try:
check_type_internal(retval, annotation, memo)
except TypeCheckError as exc:
# Allow NotImplemented if this is a binary magic method (__eq__() et al)
if retval is NotImplemented and annotation is bool:
# This does (and cannot) not check if it's actually a method
func_name = func_name.rsplit(".", 1)[-1]
if func_name in BINARY_MAGIC_METHODS:
return retval
qualname = qualified_name(retval, add_class_prefix=True)
exc.append_path_element(f"the return value ({qualname})")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return retval
def check_send_type(
func_name: str,
sendval: T,
annotation: Any,
memo: TypeCheckMemo,
) -> T:
if _suppression.type_checks_suppressed:
return sendval
if annotation is NoReturn or annotation is Never:
exc = TypeCheckError(
f"{func_name}() was declared never to be sent a value to but it was"
)
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise exc
try:
check_type_internal(sendval, annotation, memo)
except TypeCheckError as exc:
qualname = qualified_name(sendval, add_class_prefix=True)
exc.append_path_element(f"the value sent to generator ({qualname})")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return sendval
def check_yield_type(
func_name: str,
yieldval: T,
annotation: Any,
memo: TypeCheckMemo,
) -> T:
if _suppression.type_checks_suppressed:
return yieldval
if annotation is NoReturn or annotation is Never:
exc = TypeCheckError(f"{func_name}() was declared never to yield but it did")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise exc
try:
check_type_internal(yieldval, annotation, memo)
except TypeCheckError as exc:
qualname = qualified_name(yieldval, add_class_prefix=True)
exc.append_path_element(f"the yielded value ({qualname})")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return yieldval
def check_variable_assignment(
value: object, varname: str, annotation: Any, memo: TypeCheckMemo
) -> Any:
if _suppression.type_checks_suppressed:
return value
try:
check_type_internal(value, annotation, memo)
except TypeCheckError as exc:
qualname = qualified_name(value, add_class_prefix=True)
exc.append_path_element(f"value assigned to {varname} ({qualname})")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return value
def check_multi_variable_assignment(
value: Any, targets: list[dict[str, Any]], memo: TypeCheckMemo
) -> Any:
if max(len(target) for target in targets) == 1:
iterated_values = [value]
else:
iterated_values = list(value)
if not _suppression.type_checks_suppressed:
for expected_types in targets:
value_index = 0
for ann_index, (varname, expected_type) in enumerate(
expected_types.items()
):
if varname.startswith("*"):
varname = varname[1:]
keys_left = len(expected_types) - 1 - ann_index
next_value_index = len(iterated_values) - keys_left
obj: object = iterated_values[value_index:next_value_index]
value_index = next_value_index
else:
obj = iterated_values[value_index]
value_index += 1
try:
check_type_internal(obj, expected_type, memo)
except TypeCheckError as exc:
qualname = qualified_name(obj, add_class_prefix=True)
exc.append_path_element(f"value assigned to {varname} ({qualname})")
if memo.config.typecheck_fail_callback:
memo.config.typecheck_fail_callback(exc, memo)
else:
raise
return iterated_values[0] if len(iterated_values) == 1 else iterated_values
def warn_on_error(exc: TypeCheckError, memo: TypeCheckMemo) -> None:
"""
Emit a warning on a type mismatch.
This is intended to be used as an error handler in
:attr:`TypeCheckConfiguration.typecheck_fail_callback`.
"""
warnings.warn(TypeCheckWarning(str(exc)), stacklevel=get_stacklevel())
python-typeguard-4.1.5/src/typeguard/_importhook.py 0000664 0000000 0000000 00000014365 14545071262 0022562 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import ast
import sys
import types
from collections.abc import Callable, Iterable
from importlib.abc import MetaPathFinder
from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import cache_from_source, decode_source
from inspect import isclass
from os import PathLike
from types import CodeType, ModuleType, TracebackType
from typing import Sequence, TypeVar
from unittest.mock import patch
from ._config import global_config
from ._transformer import TypeguardTransformer
if sys.version_info >= (3, 12):
from collections.abc import Buffer
else:
from typing_extensions import Buffer
if sys.version_info >= (3, 11):
from typing import ParamSpec
else:
from typing_extensions import ParamSpec
if sys.version_info >= (3, 10):
from importlib.metadata import PackageNotFoundError, version
else:
from importlib_metadata import PackageNotFoundError, version
try:
OPTIMIZATION = "typeguard" + "".join(version("typeguard").split(".")[:3])
except PackageNotFoundError:
OPTIMIZATION = "typeguard"
P = ParamSpec("P")
T = TypeVar("T")
# The name of this function is magical
def _call_with_frames_removed(
f: Callable[P, T], *args: P.args, **kwargs: P.kwargs
) -> T:
return f(*args, **kwargs)
def optimized_cache_from_source(path: str, debug_override: bool | None = None) -> str:
return cache_from_source(path, debug_override, optimization=OPTIMIZATION)
class TypeguardLoader(SourceFileLoader):
@staticmethod
def source_to_code(
data: Buffer | str | ast.Module | ast.Expression | ast.Interactive,
path: Buffer | str | PathLike[str] = "",
) -> CodeType:
if isinstance(data, (ast.Module, ast.Expression, ast.Interactive)):
tree = data
else:
if isinstance(data, str):
source = data
else:
source = decode_source(data)
tree = _call_with_frames_removed(
ast.parse,
source,
path,
"exec",
)
tree = TypeguardTransformer().visit(tree)
ast.fix_missing_locations(tree)
if global_config.debug_instrumentation and sys.version_info >= (3, 9):
print(
f"Source code of {path!r} after instrumentation:\n"
"----------------------------------------------",
file=sys.stderr,
)
print(ast.unparse(tree), file=sys.stderr)
print("----------------------------------------------", file=sys.stderr)
return _call_with_frames_removed(
compile, tree, path, "exec", 0, dont_inherit=True
)
def exec_module(self, module: ModuleType) -> None:
# Use a custom optimization marker – the import lock should make this monkey
# patch safe
with patch(
"importlib._bootstrap_external.cache_from_source",
optimized_cache_from_source,
):
super().exec_module(module)
class TypeguardFinder(MetaPathFinder):
"""
Wraps another path finder and instruments the module with
:func:`@typechecked ` if :meth:`should_instrument` returns
``True``.
Should not be used directly, but rather via :func:`~.install_import_hook`.
.. versionadded:: 2.6
"""
def __init__(self, packages: list[str] | None, original_pathfinder: MetaPathFinder):
self.packages = packages
self._original_pathfinder = original_pathfinder
def find_spec(
self,
fullname: str,
path: Sequence[str] | None,
target: types.ModuleType | None = None,
) -> ModuleSpec | None:
if self.should_instrument(fullname):
spec = self._original_pathfinder.find_spec(fullname, path, target)
if spec is not None and isinstance(spec.loader, SourceFileLoader):
spec.loader = TypeguardLoader(spec.loader.name, spec.loader.path)
return spec
return None
def should_instrument(self, module_name: str) -> bool:
"""
Determine whether the module with the given name should be instrumented.
:param module_name: full name of the module that is about to be imported (e.g.
``xyz.abc``)
"""
if self.packages is None:
return True
for package in self.packages:
if module_name == package or module_name.startswith(package + "."):
return True
return False
class ImportHookManager:
"""
A handle that can be used to uninstall the Typeguard import hook.
"""
def __init__(self, hook: MetaPathFinder):
self.hook = hook
def __enter__(self) -> None:
pass
def __exit__(
self,
exc_type: type[BaseException],
exc_val: BaseException,
exc_tb: TracebackType,
) -> None:
self.uninstall()
def uninstall(self) -> None:
"""Uninstall the import hook."""
try:
sys.meta_path.remove(self.hook)
except ValueError:
pass # already removed
def install_import_hook(
packages: Iterable[str] | None = None,
*,
cls: type[TypeguardFinder] = TypeguardFinder,
) -> ImportHookManager:
"""
Install an import hook that instruments functions for automatic type checking.
This only affects modules loaded **after** this hook has been installed.
:param packages: an iterable of package names to instrument, or ``None`` to
instrument all packages
:param cls: a custom meta path finder class
:return: a context manager that uninstalls the hook on exit (or when you call
``.uninstall()``)
.. versionadded:: 2.6
"""
if packages is None:
target_packages: list[str] | None = None
elif isinstance(packages, str):
target_packages = [packages]
else:
target_packages = list(packages)
for finder in sys.meta_path:
if (
isclass(finder)
and finder.__name__ == "PathFinder"
and hasattr(finder, "find_spec")
):
break
else:
raise RuntimeError("Cannot find a PathFinder in sys.meta_path")
hook = cls(target_packages, finder)
sys.meta_path.insert(0, hook)
return ImportHookManager(hook)
python-typeguard-4.1.5/src/typeguard/_memo.py 0000664 0000000 0000000 00000002427 14545071262 0021320 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from typing import Any
from typeguard._config import TypeCheckConfiguration, global_config
class TypeCheckMemo:
"""
Contains information necessary for type checkers to do their work.
.. attribute:: globals
:type: dict[str, Any]
Dictionary of global variables to use for resolving forward references.
.. attribute:: locals
:type: dict[str, Any]
Dictionary of local variables to use for resolving forward references.
.. attribute:: self_type
:type: type | None
When running type checks within an instance method or class method, this is the
class object that the first argument (usually named ``self`` or ``cls``) refers
to.
.. attribute:: config
:type: TypeCheckConfiguration
Contains the configuration for a particular set of type checking operations.
"""
__slots__ = "globals", "locals", "self_type", "config"
def __init__(
self,
globals: dict[str, Any],
locals: dict[str, Any],
*,
self_type: type | None = None,
config: TypeCheckConfiguration = global_config,
):
self.globals = globals
self.locals = locals
self.self_type = self_type
self.config = config
python-typeguard-4.1.5/src/typeguard/_pytest_plugin.py 0000664 0000000 0000000 00000007204 14545071262 0023267 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
import warnings
from pytest import Config, Parser
from typeguard._config import CollectionCheckStrategy, ForwardRefPolicy, global_config
from typeguard._exceptions import InstrumentationWarning
from typeguard._importhook import install_import_hook
from typeguard._utils import qualified_name, resolve_reference
def pytest_addoption(parser: Parser) -> None:
group = parser.getgroup("typeguard")
group.addoption(
"--typeguard-packages",
action="store",
help="comma separated name list of packages and modules to instrument for "
"type checking, or :all: to instrument all modules loaded after typeguard",
)
group.addoption(
"--typeguard-debug-instrumentation",
action="store_true",
help="print all instrumented code to stderr",
)
group.addoption(
"--typeguard-typecheck-fail-callback",
action="store",
help=(
"a module:varname (e.g. typeguard:warn_on_error) reference to a function "
"that is called (with the exception, and memo object as arguments) to "
"handle a TypeCheckError"
),
)
group.addoption(
"--typeguard-forward-ref-policy",
action="store",
choices=list(ForwardRefPolicy.__members__),
help=(
"determines how to deal with unresolveable forward references in type "
"annotations"
),
)
group.addoption(
"--typeguard-collection-check-strategy",
action="store",
choices=list(CollectionCheckStrategy.__members__),
help="determines how thoroughly to check collections (list, dict, etc)",
)
def pytest_configure(config: Config) -> None:
packages_option = config.getoption("typeguard_packages")
if packages_option:
if packages_option == ":all:":
packages: list[str] | None = None
else:
packages = [pkg.strip() for pkg in packages_option.split(",")]
already_imported_packages = sorted(
package for package in packages if package in sys.modules
)
if already_imported_packages:
warnings.warn(
f"typeguard cannot check these packages because they are already "
f"imported: {', '.join(already_imported_packages)}",
InstrumentationWarning,
stacklevel=1,
)
install_import_hook(packages=packages)
debug_option = config.getoption("typeguard_debug_instrumentation")
if debug_option:
global_config.debug_instrumentation = True
fail_callback_option = config.getoption("typeguard_typecheck_fail_callback")
if fail_callback_option:
callback = resolve_reference(fail_callback_option)
if not callable(callback):
raise TypeError(
f"{fail_callback_option} ({qualified_name(callback.__class__)}) is not "
f"a callable"
)
global_config.typecheck_fail_callback = callback
forward_ref_policy_option = config.getoption("typeguard_forward_ref_policy")
if forward_ref_policy_option:
forward_ref_policy = ForwardRefPolicy.__members__[forward_ref_policy_option]
global_config.forward_ref_policy = forward_ref_policy
collection_check_strategy_option = config.getoption(
"typeguard_collection_check_strategy"
)
if collection_check_strategy_option:
collection_check_strategy = CollectionCheckStrategy.__members__[
collection_check_strategy_option
]
global_config.collection_check_strategy = collection_check_strategy
python-typeguard-4.1.5/src/typeguard/_suppression.py 0000664 0000000 0000000 00000004341 14545071262 0022752 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from collections.abc import Callable, Generator
from contextlib import contextmanager
from functools import update_wrapper
from threading import Lock
from typing import ContextManager, TypeVar, overload
if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
from typing_extensions import ParamSpec
P = ParamSpec("P")
T = TypeVar("T")
type_checks_suppressed = 0
type_checks_suppress_lock = Lock()
@overload
def suppress_type_checks(func: Callable[P, T]) -> Callable[P, T]:
...
@overload
def suppress_type_checks() -> ContextManager[None]:
...
def suppress_type_checks(
func: Callable[P, T] | None = None
) -> Callable[P, T] | ContextManager[None]:
"""
Temporarily suppress all type checking.
This function has two operating modes, based on how it's used:
#. as a context manager (``with suppress_type_checks(): ...``)
#. as a decorator (``@suppress_type_checks``)
When used as a context manager, :func:`check_type` and any automatically
instrumented functions skip the actual type checking. These context managers can be
nested.
When used as a decorator, all type checking is suppressed while the function is
running.
Type checking will resume once no more context managers are active and no decorated
functions are running.
Both operating modes are thread-safe.
"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
global type_checks_suppressed
with type_checks_suppress_lock:
type_checks_suppressed += 1
assert func is not None
try:
return func(*args, **kwargs)
finally:
with type_checks_suppress_lock:
type_checks_suppressed -= 1
def cm() -> Generator[None, None, None]:
global type_checks_suppressed
with type_checks_suppress_lock:
type_checks_suppressed += 1
try:
yield
finally:
with type_checks_suppress_lock:
type_checks_suppressed -= 1
if func is None:
# Context manager mode
return contextmanager(cm)()
else:
# Decorator mode
update_wrapper(wrapper, func)
return wrapper
python-typeguard-4.1.5/src/typeguard/_transformer.py 0000664 0000000 0000000 00000127526 14545071262 0022735 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import ast
import builtins
import sys
import typing
from ast import (
AST,
Add,
AnnAssign,
Assign,
AsyncFunctionDef,
Attribute,
AugAssign,
BinOp,
BitAnd,
BitOr,
BitXor,
Call,
ClassDef,
Constant,
Dict,
Div,
Expr,
Expression,
FloorDiv,
FunctionDef,
If,
Import,
ImportFrom,
Index,
List,
Load,
LShift,
MatMult,
Mod,
Module,
Mult,
Name,
NamedExpr,
NodeTransformer,
NodeVisitor,
Pass,
Pow,
Return,
RShift,
Starred,
Store,
Sub,
Subscript,
Tuple,
Yield,
YieldFrom,
alias,
copy_location,
expr,
fix_missing_locations,
keyword,
walk,
)
from collections import defaultdict
from collections.abc import Generator, Sequence
from contextlib import contextmanager
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any, ClassVar, cast, overload
generator_names = (
"typing.Generator",
"collections.abc.Generator",
"typing.Iterator",
"collections.abc.Iterator",
"typing.Iterable",
"collections.abc.Iterable",
"typing.AsyncIterator",
"collections.abc.AsyncIterator",
"typing.AsyncIterable",
"collections.abc.AsyncIterable",
"typing.AsyncGenerator",
"collections.abc.AsyncGenerator",
)
anytype_names = (
"typing.Any",
"typing_extensions.Any",
)
literal_names = (
"typing.Literal",
"typing_extensions.Literal",
)
annotated_names = (
"typing.Annotated",
"typing_extensions.Annotated",
)
ignore_decorators = (
"typing.no_type_check",
"typeguard.typeguard_ignore",
)
aug_assign_functions = {
Add: "iadd",
Sub: "isub",
Mult: "imul",
MatMult: "imatmul",
Div: "itruediv",
FloorDiv: "ifloordiv",
Mod: "imod",
Pow: "ipow",
LShift: "ilshift",
RShift: "irshift",
BitAnd: "iand",
BitXor: "ixor",
BitOr: "ior",
}
@dataclass
class TransformMemo:
node: Module | ClassDef | FunctionDef | AsyncFunctionDef | None
parent: TransformMemo | None
path: tuple[str, ...]
joined_path: Constant = field(init=False)
return_annotation: expr | None = None
yield_annotation: expr | None = None
send_annotation: expr | None = None
is_async: bool = False
local_names: set[str] = field(init=False, default_factory=set)
imported_names: dict[str, str] = field(init=False, default_factory=dict)
ignored_names: set[str] = field(init=False, default_factory=set)
load_names: defaultdict[str, dict[str, Name]] = field(
init=False, default_factory=lambda: defaultdict(dict)
)
has_yield_expressions: bool = field(init=False, default=False)
has_return_expressions: bool = field(init=False, default=False)
memo_var_name: Name | None = field(init=False, default=None)
should_instrument: bool = field(init=False, default=True)
variable_annotations: dict[str, expr] = field(init=False, default_factory=dict)
configuration_overrides: dict[str, Any] = field(init=False, default_factory=dict)
code_inject_index: int = field(init=False, default=0)
def __post_init__(self) -> None:
elements: list[str] = []
memo = self
while isinstance(memo.node, (ClassDef, FunctionDef, AsyncFunctionDef)):
elements.insert(0, memo.node.name)
if not memo.parent:
break
memo = memo.parent
if isinstance(memo.node, (FunctionDef, AsyncFunctionDef)):
elements.insert(0, "")
self.joined_path = Constant(".".join(elements))
# Figure out where to insert instrumentation code
if self.node:
for index, child in enumerate(self.node.body):
if isinstance(child, ImportFrom) and child.module == "__future__":
# (module only) __future__ imports must come first
continue
elif (
isinstance(child, Expr)
and isinstance(child.value, Constant)
and isinstance(child.value.value, str)
):
continue # docstring
self.code_inject_index = index
break
def get_unused_name(self, name: str) -> str:
memo: TransformMemo | None = self
while memo is not None:
if name in memo.local_names:
memo = self
name += "_"
else:
memo = memo.parent
self.local_names.add(name)
return name
def is_ignored_name(self, expression: expr | Expr | None) -> bool:
top_expression = (
expression.value if isinstance(expression, Expr) else expression
)
if isinstance(top_expression, Attribute) and isinstance(
top_expression.value, Name
):
name = top_expression.value.id
elif isinstance(top_expression, Name):
name = top_expression.id
else:
return False
memo: TransformMemo | None = self
while memo is not None:
if name in memo.ignored_names:
return True
memo = memo.parent
return False
def get_memo_name(self) -> Name:
if not self.memo_var_name:
self.memo_var_name = Name(id="memo", ctx=Load())
return self.memo_var_name
def get_import(self, module: str, name: str) -> Name:
if module in self.load_names and name in self.load_names[module]:
return self.load_names[module][name]
qualified_name = f"{module}.{name}"
if name in self.imported_names and self.imported_names[name] == qualified_name:
return Name(id=name, ctx=Load())
alias = self.get_unused_name(name)
node = self.load_names[module][name] = Name(id=alias, ctx=Load())
self.imported_names[name] = qualified_name
return node
def insert_imports(self, node: Module | FunctionDef | AsyncFunctionDef) -> None:
"""Insert imports needed by injected code."""
if not self.load_names:
return
# Insert imports after any "from __future__ ..." imports and any docstring
for modulename, names in self.load_names.items():
aliases = [
alias(orig_name, new_name.id if orig_name != new_name.id else None)
for orig_name, new_name in sorted(names.items())
]
node.body.insert(self.code_inject_index, ImportFrom(modulename, aliases, 0))
def name_matches(self, expression: expr | Expr | None, *names: str) -> bool:
if expression is None:
return False
path: list[str] = []
top_expression = (
expression.value if isinstance(expression, Expr) else expression
)
if isinstance(top_expression, Subscript):
top_expression = top_expression.value
elif isinstance(top_expression, Call):
top_expression = top_expression.func
while isinstance(top_expression, Attribute):
path.insert(0, top_expression.attr)
top_expression = top_expression.value
if not isinstance(top_expression, Name):
return False
if top_expression.id in self.imported_names:
translated = self.imported_names[top_expression.id]
elif hasattr(builtins, top_expression.id):
translated = "builtins." + top_expression.id
else:
translated = top_expression.id
path.insert(0, translated)
joined_path = ".".join(path)
if joined_path in names:
return True
elif self.parent:
return self.parent.name_matches(expression, *names)
else:
return False
def get_config_keywords(self) -> list[keyword]:
if self.parent and isinstance(self.parent.node, ClassDef):
overrides = self.parent.configuration_overrides.copy()
else:
overrides = {}
overrides.update(self.configuration_overrides)
return [keyword(key, value) for key, value in overrides.items()]
class NameCollector(NodeVisitor):
def __init__(self) -> None:
self.names: set[str] = set()
def visit_Import(self, node: Import) -> None:
for name in node.names:
self.names.add(name.asname or name.name)
def visit_ImportFrom(self, node: ImportFrom) -> None:
for name in node.names:
self.names.add(name.asname or name.name)
def visit_Assign(self, node: Assign) -> None:
for target in node.targets:
if isinstance(target, Name):
self.names.add(target.id)
def visit_NamedExpr(self, node: NamedExpr) -> Any:
if isinstance(node.target, Name):
self.names.add(node.target.id)
def visit_FunctionDef(self, node: FunctionDef) -> None:
pass
def visit_ClassDef(self, node: ClassDef) -> None:
pass
class GeneratorDetector(NodeVisitor):
"""Detects if a function node is a generator function."""
contains_yields: bool = False
in_root_function: bool = False
def visit_Yield(self, node: Yield) -> Any:
self.contains_yields = True
def visit_YieldFrom(self, node: YieldFrom) -> Any:
self.contains_yields = True
def visit_ClassDef(self, node: ClassDef) -> Any:
pass
def visit_FunctionDef(self, node: FunctionDef | AsyncFunctionDef) -> Any:
if not self.in_root_function:
self.in_root_function = True
self.generic_visit(node)
self.in_root_function = False
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> Any:
self.visit_FunctionDef(node)
class AnnotationTransformer(NodeTransformer):
type_substitutions: ClassVar[dict[str, tuple[str, str]]] = {
"builtins.dict": ("typing", "Dict"),
"builtins.list": ("typing", "List"),
"builtins.tuple": ("typing", "Tuple"),
"builtins.set": ("typing", "Set"),
"builtins.frozenset": ("typing", "FrozenSet"),
}
def __init__(self, transformer: TypeguardTransformer):
self.transformer = transformer
self._memo = transformer._memo
self._level = 0
def visit(self, node: AST) -> Any:
# Don't process Literals
if isinstance(node, expr) and self._memo.name_matches(node, *literal_names):
return node
self._level += 1
new_node = super().visit(node)
self._level -= 1
if isinstance(new_node, Expression) and not hasattr(new_node, "body"):
return None
# Return None if this new node matches a variation of typing.Any
if (
self._level == 0
and isinstance(new_node, expr)
and self._memo.name_matches(new_node, *anytype_names)
):
return None
return new_node
def visit_BinOp(self, node: BinOp) -> Any:
self.generic_visit(node)
if isinstance(node.op, BitOr):
# If either branch of the BinOp has been transformed to `None`, it means
# that a type in the union was ignored, so the entire annotation should e
# ignored
if not hasattr(node, "left") or not hasattr(node, "right"):
return None
# Return Any if either side is Any
if self._memo.name_matches(node.left, *anytype_names):
return node.left
elif self._memo.name_matches(node.right, *anytype_names):
return node.right
if sys.version_info < (3, 10):
union_name = self.transformer._get_import("typing", "Union")
return Subscript(
value=union_name,
slice=Index(
Tuple(elts=[node.left, node.right], ctx=Load()), ctx=Load()
),
ctx=Load(),
)
return node
def visit_Attribute(self, node: Attribute) -> Any:
if self._memo.is_ignored_name(node):
return None
return node
def visit_Subscript(self, node: Subscript) -> Any:
if self._memo.is_ignored_name(node.value):
return None
# The subscript of typing(_extensions).Literal can be any arbitrary string, so
# don't try to evaluate it as code
if node.slice:
if isinstance(node.slice, Index):
# Python 3.8
slice_value = node.slice.value # type: ignore[attr-defined]
else:
slice_value = node.slice
if isinstance(slice_value, Tuple):
if self._memo.name_matches(node.value, *annotated_names):
# Only treat the first argument to typing.Annotated as a potential
# forward reference
items = cast(
typing.List[expr],
[self.visit(slice_value.elts[0])] + slice_value.elts[1:],
)
else:
items = cast(
typing.List[expr],
[self.visit(item) for item in slice_value.elts],
)
# If this is a Union and any of the items is Any, erase the entire
# annotation
if self._memo.name_matches(node.value, "typing.Union") and any(
item is None
or (
isinstance(item, expr)
and self._memo.name_matches(item, *anytype_names)
)
for item in items
):
return None
# If all items in the subscript were Any, erase the subscript entirely
if all(item is None for item in items):
return node.value
for index, item in enumerate(items):
if item is None:
items[index] = self.transformer._get_import("typing", "Any")
slice_value.elts = items
else:
self.generic_visit(node)
# If the transformer erased the slice entirely, just return the node
# value without the subscript (unless it's Optional, in which case erase
# the node entirely
if self._memo.name_matches(node.value, "typing.Optional"):
return None
elif sys.version_info >= (3, 9) and not hasattr(node, "slice"):
return node.value
elif sys.version_info < (3, 9) and not hasattr(node.slice, "value"):
return node.value
return node
def visit_Name(self, node: Name) -> Any:
if self._memo.is_ignored_name(node):
return None
if sys.version_info < (3, 9):
for typename, substitute in self.type_substitutions.items():
if self._memo.name_matches(node, typename):
new_node = self.transformer._get_import(*substitute)
return copy_location(new_node, node)
return node
def visit_Call(self, node: Call) -> Any:
# Don't recurse into calls
return node
def visit_Constant(self, node: Constant) -> Any:
if isinstance(node.value, str):
expression = ast.parse(node.value, mode="eval")
new_node = self.visit(expression)
if new_node:
return copy_location(new_node.body, node)
else:
return None
return node
class TypeguardTransformer(NodeTransformer):
def __init__(
self, target_path: Sequence[str] | None = None, target_lineno: int | None = None
) -> None:
self._target_path = tuple(target_path) if target_path else None
self._memo = self._module_memo = TransformMemo(None, None, ())
self.names_used_in_annotations: set[str] = set()
self.target_node: FunctionDef | AsyncFunctionDef | None = None
self.target_lineno = target_lineno
def generic_visit(self, node: AST) -> AST:
has_non_empty_body_initially = bool(getattr(node, "body", None))
initial_type = type(node)
node = super().generic_visit(node)
if (
type(node) is initial_type
and has_non_empty_body_initially
and hasattr(node, "body")
and not node.body
):
# If we have still the same node type after transformation
# but we've optimised it's body away, we add a `pass` statement.
node.body = [Pass()]
return node
@contextmanager
def _use_memo(
self, node: ClassDef | FunctionDef | AsyncFunctionDef
) -> Generator[None, Any, None]:
new_memo = TransformMemo(node, self._memo, self._memo.path + (node.name,))
old_memo = self._memo
self._memo = new_memo
if isinstance(node, (FunctionDef, AsyncFunctionDef)):
new_memo.should_instrument = (
self._target_path is None or new_memo.path == self._target_path
)
if new_memo.should_instrument:
# Check if the function is a generator function
detector = GeneratorDetector()
detector.visit(node)
# Extract yield, send and return types where possible from a subscripted
# annotation like Generator[int, str, bool]
return_annotation = deepcopy(node.returns)
if detector.contains_yields and new_memo.name_matches(
return_annotation, *generator_names
):
if isinstance(return_annotation, Subscript):
annotation_slice = return_annotation.slice
# Python < 3.9
if isinstance(annotation_slice, Index):
annotation_slice = (
annotation_slice.value # type: ignore[attr-defined]
)
if isinstance(annotation_slice, Tuple):
items = annotation_slice.elts
else:
items = [annotation_slice]
if len(items) > 0:
new_memo.yield_annotation = self._convert_annotation(
items[0]
)
if len(items) > 1:
new_memo.send_annotation = self._convert_annotation(
items[1]
)
if len(items) > 2:
new_memo.return_annotation = self._convert_annotation(
items[2]
)
else:
new_memo.return_annotation = self._convert_annotation(
return_annotation
)
if isinstance(node, AsyncFunctionDef):
new_memo.is_async = True
yield
self._memo = old_memo
def _get_import(self, module: str, name: str) -> Name:
memo = self._memo if self._target_path else self._module_memo
return memo.get_import(module, name)
@overload
def _convert_annotation(self, annotation: None) -> None:
...
@overload
def _convert_annotation(self, annotation: expr) -> expr:
...
def _convert_annotation(self, annotation: expr | None) -> expr | None:
if annotation is None:
return None
# Convert PEP 604 unions (x | y) and generic built-in collections where
# necessary, and undo forward references
new_annotation = cast(expr, AnnotationTransformer(self).visit(annotation))
if isinstance(new_annotation, expr):
new_annotation = ast.copy_location(new_annotation, annotation)
# Store names used in the annotation
names = {node.id for node in walk(new_annotation) if isinstance(node, Name)}
self.names_used_in_annotations.update(names)
return new_annotation
def visit_Name(self, node: Name) -> Name:
self._memo.local_names.add(node.id)
return node
def visit_Module(self, node: Module) -> Module:
self._module_memo = self._memo = TransformMemo(node, None, ())
self.generic_visit(node)
self._module_memo.insert_imports(node)
fix_missing_locations(node)
return node
def visit_Import(self, node: Import) -> Import:
for name in node.names:
self._memo.local_names.add(name.asname or name.name)
self._memo.imported_names[name.asname or name.name] = name.name
return node
def visit_ImportFrom(self, node: ImportFrom) -> ImportFrom:
for name in node.names:
if name.name != "*":
alias = name.asname or name.name
self._memo.local_names.add(alias)
self._memo.imported_names[alias] = f"{node.module}.{name.name}"
return node
def visit_ClassDef(self, node: ClassDef) -> ClassDef | None:
self._memo.local_names.add(node.name)
# Eliminate top level classes not belonging to the target path
if (
self._target_path is not None
and not self._memo.path
and node.name != self._target_path[0]
):
return None
with self._use_memo(node):
for decorator in node.decorator_list.copy():
if self._memo.name_matches(decorator, "typeguard.typechecked"):
# Remove the decorator to prevent duplicate instrumentation
node.decorator_list.remove(decorator)
# Store any configuration overrides
if isinstance(decorator, Call) and decorator.keywords:
self._memo.configuration_overrides.update(
{kw.arg: kw.value for kw in decorator.keywords if kw.arg}
)
self.generic_visit(node)
return node
def visit_FunctionDef(
self, node: FunctionDef | AsyncFunctionDef
) -> FunctionDef | AsyncFunctionDef | None:
"""
Injects type checks for function arguments, and for a return of None if the
function is annotated to return something else than Any or None, and the body
ends without an explicit "return".
"""
self._memo.local_names.add(node.name)
# Eliminate top level functions not belonging to the target path
if (
self._target_path is not None
and not self._memo.path
and node.name != self._target_path[0]
):
return None
# Skip instrumentation if we're instrumenting the whole module and the function
# contains either @no_type_check or @typeguard_ignore
if self._target_path is None:
for decorator in node.decorator_list:
if self._memo.name_matches(decorator, *ignore_decorators):
return node
with self._use_memo(node):
arg_annotations: dict[str, Any] = {}
if self._target_path is None or self._memo.path == self._target_path:
# Find line number we're supposed to match against
if node.decorator_list:
first_lineno = node.decorator_list[0].lineno
else:
first_lineno = node.lineno
for decorator in node.decorator_list.copy():
if self._memo.name_matches(decorator, "typing.overload"):
# Remove overloads entirely
return None
elif self._memo.name_matches(decorator, "typeguard.typechecked"):
# Remove the decorator to prevent duplicate instrumentation
node.decorator_list.remove(decorator)
# Store any configuration overrides
if isinstance(decorator, Call) and decorator.keywords:
self._memo.configuration_overrides = {
kw.arg: kw.value for kw in decorator.keywords if kw.arg
}
if self.target_lineno == first_lineno:
assert self.target_node is None
self.target_node = node
if node.decorator_list:
self.target_lineno = node.decorator_list[0].lineno
else:
self.target_lineno = node.lineno
all_args = node.args.args + node.args.kwonlyargs + node.args.posonlyargs
# Ensure that any type shadowed by the positional or keyword-only
# argument names are ignored in this function
for arg in all_args:
self._memo.ignored_names.add(arg.arg)
# Ensure that any type shadowed by the variable positional argument name
# (e.g. "args" in *args) is ignored this function
if node.args.vararg:
self._memo.ignored_names.add(node.args.vararg.arg)
# Ensure that any type shadowed by the variable keywrod argument name
# (e.g. "kwargs" in *kwargs) is ignored this function
if node.args.kwarg:
self._memo.ignored_names.add(node.args.kwarg.arg)
for arg in all_args:
annotation = self._convert_annotation(deepcopy(arg.annotation))
if annotation:
arg_annotations[arg.arg] = annotation
if node.args.vararg:
annotation_ = self._convert_annotation(node.args.vararg.annotation)
if annotation_:
if sys.version_info >= (3, 9):
container = Name("tuple", ctx=Load())
else:
container = self._get_import("typing", "Tuple")
subscript_slice: Tuple | Index = Tuple(
[
annotation_,
Constant(Ellipsis),
],
ctx=Load(),
)
if sys.version_info < (3, 9):
subscript_slice = Index(subscript_slice, ctx=Load())
arg_annotations[node.args.vararg.arg] = Subscript(
container, subscript_slice, ctx=Load()
)
if node.args.kwarg:
annotation_ = self._convert_annotation(node.args.kwarg.annotation)
if annotation_:
if sys.version_info >= (3, 9):
container = Name("dict", ctx=Load())
else:
container = self._get_import("typing", "Dict")
subscript_slice = Tuple(
[
Name("str", ctx=Load()),
annotation_,
],
ctx=Load(),
)
if sys.version_info < (3, 9):
subscript_slice = Index(subscript_slice, ctx=Load())
arg_annotations[node.args.kwarg.arg] = Subscript(
container, subscript_slice, ctx=Load()
)
if arg_annotations:
self._memo.variable_annotations.update(arg_annotations)
self.generic_visit(node)
if arg_annotations:
annotations_dict = Dict(
keys=[Constant(key) for key in arg_annotations.keys()],
values=[
Tuple([Name(key, ctx=Load()), annotation], ctx=Load())
for key, annotation in arg_annotations.items()
],
)
func_name = self._get_import(
"typeguard._functions", "check_argument_types"
)
args = [
self._memo.joined_path,
annotations_dict,
self._memo.get_memo_name(),
]
node.body.insert(
self._memo.code_inject_index, Expr(Call(func_name, args, []))
)
# Add a checked "return None" to the end if there's no explicit return
# Skip if the return annotation is None or Any
if (
self._memo.return_annotation
and (not self._memo.is_async or not self._memo.has_yield_expressions)
and not isinstance(node.body[-1], Return)
and (
not isinstance(self._memo.return_annotation, Constant)
or self._memo.return_annotation.value is not None
)
):
func_name = self._get_import(
"typeguard._functions", "check_return_type"
)
return_node = Return(
Call(
func_name,
[
self._memo.joined_path,
Constant(None),
self._memo.return_annotation,
self._memo.get_memo_name(),
],
[],
)
)
# Replace a placeholder "pass" at the end
if isinstance(node.body[-1], Pass):
copy_location(return_node, node.body[-1])
del node.body[-1]
node.body.append(return_node)
# Insert code to create the call memo, if it was ever needed for this
# function
if self._memo.memo_var_name:
memo_kwargs: dict[str, Any] = {}
if self._memo.parent and isinstance(self._memo.parent.node, ClassDef):
for decorator in node.decorator_list:
if (
isinstance(decorator, Name)
and decorator.id == "staticmethod"
):
break
elif (
isinstance(decorator, Name)
and decorator.id == "classmethod"
):
memo_kwargs["self_type"] = Name(
id=node.args.args[0].arg, ctx=Load()
)
break
else:
if node.args.args:
if node.name == "__new__":
memo_kwargs["self_type"] = Name(
id=node.args.args[0].arg, ctx=Load()
)
else:
memo_kwargs["self_type"] = Attribute(
Name(id=node.args.args[0].arg, ctx=Load()),
"__class__",
ctx=Load(),
)
# Construct the function reference
# Nested functions get special treatment: the function name is added
# to free variables (and the closure of the resulting function)
names: list[str] = [node.name]
memo = self._memo.parent
while memo:
if isinstance(memo.node, (FunctionDef, AsyncFunctionDef)):
# This is a nested function. Use the function name as-is.
del names[:-1]
break
elif not isinstance(memo.node, ClassDef):
break
names.insert(0, memo.node.name)
memo = memo.parent
config_keywords = self._memo.get_config_keywords()
if config_keywords:
memo_kwargs["config"] = Call(
self._get_import("dataclasses", "replace"),
[self._get_import("typeguard._config", "global_config")],
config_keywords,
)
self._memo.memo_var_name.id = self._memo.get_unused_name("memo")
memo_store_name = Name(id=self._memo.memo_var_name.id, ctx=Store())
globals_call = Call(Name(id="globals", ctx=Load()), [], [])
locals_call = Call(Name(id="locals", ctx=Load()), [], [])
memo_expr = Call(
self._get_import("typeguard", "TypeCheckMemo"),
[globals_call, locals_call],
[keyword(key, value) for key, value in memo_kwargs.items()],
)
node.body.insert(
self._memo.code_inject_index,
Assign([memo_store_name], memo_expr),
)
self._memo.insert_imports(node)
# Special case the __new__() method to create a local alias from the
# class name to the first argument (usually "cls")
if (
isinstance(node, FunctionDef)
and node.args
and self._memo.parent is not None
and isinstance(self._memo.parent.node, ClassDef)
and node.name == "__new__"
):
first_args_expr = Name(node.args.args[0].arg, ctx=Load())
cls_name = Name(self._memo.parent.node.name, ctx=Store())
node.body.insert(
self._memo.code_inject_index,
Assign([cls_name], first_args_expr),
)
# Rmove any placeholder "pass" at the end
if isinstance(node.body[-1], Pass):
del node.body[-1]
return node
def visit_AsyncFunctionDef(
self, node: AsyncFunctionDef
) -> FunctionDef | AsyncFunctionDef | None:
return self.visit_FunctionDef(node)
def visit_Return(self, node: Return) -> Return:
"""This injects type checks into "return" statements."""
self.generic_visit(node)
if (
self._memo.return_annotation
and self._memo.should_instrument
and not self._memo.is_ignored_name(self._memo.return_annotation)
):
func_name = self._get_import("typeguard._functions", "check_return_type")
old_node = node
retval = old_node.value or Constant(None)
node = Return(
Call(
func_name,
[
self._memo.joined_path,
retval,
self._memo.return_annotation,
self._memo.get_memo_name(),
],
[],
)
)
copy_location(node, old_node)
return node
def visit_Yield(self, node: Yield) -> Yield | Call:
"""
This injects type checks into "yield" expressions, checking both the yielded
value and the value sent back to the generator, when appropriate.
"""
self._memo.has_yield_expressions = True
self.generic_visit(node)
if (
self._memo.yield_annotation
and self._memo.should_instrument
and not self._memo.is_ignored_name(self._memo.yield_annotation)
):
func_name = self._get_import("typeguard._functions", "check_yield_type")
yieldval = node.value or Constant(None)
node.value = Call(
func_name,
[
self._memo.joined_path,
yieldval,
self._memo.yield_annotation,
self._memo.get_memo_name(),
],
[],
)
if (
self._memo.send_annotation
and self._memo.should_instrument
and not self._memo.is_ignored_name(self._memo.send_annotation)
):
func_name = self._get_import("typeguard._functions", "check_send_type")
old_node = node
call_node = Call(
func_name,
[
self._memo.joined_path,
old_node,
self._memo.send_annotation,
self._memo.get_memo_name(),
],
[],
)
copy_location(call_node, old_node)
return call_node
return node
def visit_AnnAssign(self, node: AnnAssign) -> Any:
"""
This injects a type check into a local variable annotation-assignment within a
function body.
"""
self.generic_visit(node)
if (
isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef))
and node.annotation
and isinstance(node.target, Name)
):
self._memo.ignored_names.add(node.target.id)
annotation = self._convert_annotation(deepcopy(node.annotation))
if annotation:
self._memo.variable_annotations[node.target.id] = annotation
if node.value:
func_name = self._get_import(
"typeguard._functions", "check_variable_assignment"
)
node.value = Call(
func_name,
[
node.value,
Constant(node.target.id),
annotation,
self._memo.get_memo_name(),
],
[],
)
return node
def visit_Assign(self, node: Assign) -> Any:
"""
This injects a type check into a local variable assignment within a function
body. The variable must have been annotated earlier in the function body.
"""
self.generic_visit(node)
# Only instrument function-local assignments
if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)):
targets: list[dict[Constant, expr | None]] = []
check_required = False
for target in node.targets:
elts: Sequence[expr]
if isinstance(target, Name):
elts = [target]
elif isinstance(target, Tuple):
elts = target.elts
else:
continue
annotations_: dict[Constant, expr | None] = {}
for exp in elts:
prefix = ""
if isinstance(exp, Starred):
exp = exp.value
prefix = "*"
if isinstance(exp, Name):
self._memo.ignored_names.add(exp.id)
name = prefix + exp.id
annotation = self._memo.variable_annotations.get(exp.id)
if annotation:
annotations_[Constant(name)] = annotation
check_required = True
else:
annotations_[Constant(name)] = None
targets.append(annotations_)
if check_required:
# Replace missing annotations with typing.Any
for item in targets:
for key, expression in item.items():
if expression is None:
item[key] = self._get_import("typing", "Any")
if len(targets) == 1 and len(targets[0]) == 1:
func_name = self._get_import(
"typeguard._functions", "check_variable_assignment"
)
target_varname = next(iter(targets[0]))
node.value = Call(
func_name,
[
node.value,
target_varname,
targets[0][target_varname],
self._memo.get_memo_name(),
],
[],
)
elif targets:
func_name = self._get_import(
"typeguard._functions", "check_multi_variable_assignment"
)
targets_arg = List(
[
Dict(keys=list(target), values=list(target.values()))
for target in targets
],
ctx=Load(),
)
node.value = Call(
func_name,
[node.value, targets_arg, self._memo.get_memo_name()],
[],
)
return node
def visit_NamedExpr(self, node: NamedExpr) -> Any:
"""This injects a type check into an assignment expression (a := foo())."""
self.generic_visit(node)
# Only instrument function-local assignments
if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)) and isinstance(
node.target, Name
):
self._memo.ignored_names.add(node.target.id)
# Bail out if no matching annotation is found
annotation = self._memo.variable_annotations.get(node.target.id)
if annotation is None:
return node
func_name = self._get_import(
"typeguard._functions", "check_variable_assignment"
)
node.value = Call(
func_name,
[
node.value,
Constant(node.target.id),
annotation,
self._memo.get_memo_name(),
],
[],
)
return node
def visit_AugAssign(self, node: AugAssign) -> Any:
"""
This injects a type check into an augmented assignment expression (a += 1).
"""
self.generic_visit(node)
# Only instrument function-local assignments
if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)) and isinstance(
node.target, Name
):
# Bail out if no matching annotation is found
annotation = self._memo.variable_annotations.get(node.target.id)
if annotation is None:
return node
# Bail out if the operator is not found (newer Python version?)
try:
operator_func_name = aug_assign_functions[node.op.__class__]
except KeyError:
return node
operator_func = self._get_import("operator", operator_func_name)
operator_call = Call(
operator_func, [Name(node.target.id, ctx=Load()), node.value], []
)
check_call = Call(
self._get_import("typeguard._functions", "check_variable_assignment"),
[
operator_call,
Constant(node.target.id),
annotation,
self._memo.get_memo_name(),
],
[],
)
return Assign(targets=[node.target], value=check_call)
return node
def visit_If(self, node: If) -> Any:
"""
This blocks names from being collected from a module-level
"if typing.TYPE_CHECKING:" block, so that they won't be type checked.
"""
self.generic_visit(node)
if (
self._memo is self._module_memo
and isinstance(node.test, Name)
and self._memo.name_matches(node.test, "typing.TYPE_CHECKING")
):
collector = NameCollector()
collector.visit(node)
self._memo.ignored_names.update(collector.names)
return node
python-typeguard-4.1.5/src/typeguard/_union_transformer.py 0000664 0000000 0000000 00000002511 14545071262 0024127 0 ustar 00root root 0000000 0000000 """
Transforms lazily evaluated PEP 604 unions into typing.Unions, for compatibility with
Python versions older than 3.10.
"""
from __future__ import annotations
from ast import (
BinOp,
BitOr,
Index,
Load,
Name,
NodeTransformer,
Subscript,
fix_missing_locations,
parse,
)
from ast import Tuple as ASTTuple
from types import CodeType
from typing import Any, Dict, FrozenSet, List, Set, Tuple, Union
type_substitutions = {
"dict": Dict,
"list": List,
"tuple": Tuple,
"set": Set,
"frozenset": FrozenSet,
"Union": Union,
}
class UnionTransformer(NodeTransformer):
def __init__(self, union_name: Name | None = None):
self.union_name = union_name or Name(id="Union", ctx=Load())
def visit_BinOp(self, node: BinOp) -> Any:
self.generic_visit(node)
if isinstance(node.op, BitOr):
return Subscript(
value=self.union_name,
slice=Index(
ASTTuple(elts=[node.left, node.right], ctx=Load()), ctx=Load()
),
ctx=Load(),
)
return node
def compile_type_hint(hint: str) -> CodeType:
parsed = parse(hint, "", "eval")
UnionTransformer().visit(parsed)
fix_missing_locations(parsed)
return compile(parsed, "", "eval", flags=0)
python-typeguard-4.1.5/src/typeguard/_utils.py 0000664 0000000 0000000 00000011515 14545071262 0021521 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import inspect
import sys
from importlib import import_module
from inspect import currentframe
from types import CodeType, FrameType, FunctionType
from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union, cast, final
from weakref import WeakValueDictionary
if TYPE_CHECKING:
from ._memo import TypeCheckMemo
if sys.version_info >= (3, 10):
from typing import get_args, get_origin
def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
return forwardref._evaluate(memo.globals, memo.locals, frozenset())
else:
from typing_extensions import get_args, get_origin
evaluate_extra_args: tuple[frozenset[Any], ...] = (
(frozenset(),) if sys.version_info >= (3, 9) else ()
)
def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
from ._union_transformer import compile_type_hint, type_substitutions
if not forwardref.__forward_evaluated__:
forwardref.__forward_code__ = compile_type_hint(forwardref.__forward_arg__)
try:
return forwardref._evaluate(memo.globals, memo.locals, *evaluate_extra_args)
except NameError:
if sys.version_info < (3, 10):
# Try again, with the type substitutions (list -> List etc.) in place
new_globals = memo.globals.copy()
new_globals.setdefault("Union", Union)
if sys.version_info < (3, 9):
new_globals.update(type_substitutions)
return forwardref._evaluate(
new_globals, memo.locals or new_globals, *evaluate_extra_args
)
raise
_functions_map: WeakValueDictionary[CodeType, FunctionType] = WeakValueDictionary()
def get_type_name(type_: Any) -> str:
name: str
for attrname in "__name__", "_name", "__forward_arg__":
candidate = getattr(type_, attrname, None)
if isinstance(candidate, str):
name = candidate
break
else:
origin = get_origin(type_)
candidate = getattr(origin, "_name", None)
if candidate is None:
candidate = type_.__class__.__name__.strip("_")
if isinstance(candidate, str):
name = candidate
else:
return "(unknown)"
args = get_args(type_)
if args:
if name == "Literal":
formatted_args = ", ".join(repr(arg) for arg in args)
else:
formatted_args = ", ".join(get_type_name(arg) for arg in args)
name += f"[{formatted_args}]"
module = getattr(type_, "__module__", None)
if module and module not in (None, "typing", "typing_extensions", "builtins"):
name = module + "." + name
return name
def qualified_name(obj: Any, *, add_class_prefix: bool = False) -> 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.
"""
if obj is None:
return "None"
elif inspect.isclass(obj):
prefix = "class " if add_class_prefix else ""
type_ = obj
else:
prefix = ""
type_ = type(obj)
module = type_.__module__
qualname = type_.__qualname__
name = qualname if module in ("typing", "builtins") else f"{module}.{qualname}"
return prefix + name
def function_name(func: Callable[..., Any]) -> 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.
"""
# For partial functions and objects with __call__ defined, __qualname__ does not
# exist
module = getattr(func, "__module__", "")
qualname = (module + ".") if module not in ("builtins", "") else ""
return qualname + getattr(func, "__qualname__", repr(func))
def resolve_reference(reference: str) -> Any:
modulename, varname = reference.partition(":")[::2]
if not modulename or not varname:
raise ValueError(f"{reference!r} is not a module:varname reference")
obj = import_module(modulename)
for attr in varname.split("."):
obj = getattr(obj, attr)
return obj
def is_method_of(obj: object, cls: type) -> bool:
return (
inspect.isfunction(obj)
and obj.__module__ == cls.__module__
and obj.__qualname__.startswith(cls.__qualname__ + ".")
)
def get_stacklevel() -> int:
level = 1
frame = cast(FrameType, currentframe()).f_back
while frame and frame.f_globals.get("__name__", "").startswith("typeguard."):
level += 1
frame = frame.f_back
return level
@final
class Unset:
__slots__ = ()
def __repr__(self) -> str:
return ""
unset = Unset()
python-typeguard-4.1.5/src/typeguard/py.typed 0000664 0000000 0000000 00000000000 14545071262 0021332 0 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/tests/ 0000775 0000000 0000000 00000000000 14545071262 0016214 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/tests/__init__.py 0000664 0000000 0000000 00000002127 14545071262 0020327 0 ustar 00root root 0000000 0000000 from typing import (
AbstractSet,
Collection,
Dict,
Generic,
List,
NamedTuple,
NewType,
Protocol,
TypeVar,
Union,
runtime_checkable,
)
T_Foo = TypeVar("T_Foo")
TBound = TypeVar("TBound", bound="Parent")
TConstrained = TypeVar("TConstrained", "Parent", int)
TTypingConstrained = TypeVar("TTypingConstrained", List[int], AbstractSet[str])
TIntStr = TypeVar("TIntStr", int, str)
TIntCollection = TypeVar("TIntCollection", int, Collection[int])
TParent = TypeVar("TParent", bound="Parent")
TChild = TypeVar("TChild", bound="Child")
class Employee(NamedTuple):
name: str
id: int
JSONType = Union[str, float, bool, None, List["JSONType"], Dict[str, "JSONType"]]
myint = NewType("myint", int)
mylist = NewType("mylist", List[int])
class FooGeneric(Generic[T_Foo]):
pass
class Parent:
pass
class Child(Parent):
def method(self, a: int) -> None:
pass
class StaticProtocol(Protocol):
def meth(self) -> None:
...
@runtime_checkable
class RuntimeProtocol(Protocol):
member: int
def meth(self) -> None:
...
python-typeguard-4.1.5/tests/conftest.py 0000664 0000000 0000000 00000001224 14545071262 0020412 0 ustar 00root root 0000000 0000000 import random
import re
import string
import sys
from itertools import count
import pytest
version_re = re.compile(r"_py(\d)(\d)\.py$")
def pytest_ignore_collect(path, config):
match = version_re.search(path.basename)
if match:
version = tuple(int(x) for x in match.groups())
if sys.version_info < version:
return True
@pytest.fixture
def sample_set() -> set:
# Create a set which, when iterated, returns "bb" as the first item
for num in count():
letter = random.choice(string.ascii_lowercase)
dummy_set = {letter, num}
if next(iter(dummy_set)) == letter:
return dummy_set
python-typeguard-4.1.5/tests/dummymodule.py 0000664 0000000 0000000 00000014617 14545071262 0021140 0 ustar 00root root 0000000 0000000 """Module docstring."""
import sys
from contextlib import contextmanager
from typing import (
TYPE_CHECKING,
Any,
AsyncGenerator,
Callable,
Dict,
Generator,
List,
Literal,
Sequence,
Tuple,
Type,
TypeVar,
Union,
no_type_check,
no_type_check_decorator,
overload,
)
from typeguard import (
CollectionCheckStrategy,
ForwardRefPolicy,
typechecked,
typeguard_ignore,
)
if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
from typing_extensions import ParamSpec
if TYPE_CHECKING:
from nonexistent import Imaginary
T = TypeVar("T", bound="DummyClass")
P = ParamSpec("P")
@no_type_check_decorator
def dummy_decorator(func):
return func
@typechecked
def type_checked_func(x: int, y: int) -> int:
return x * y
@no_type_check
def non_type_checked_func(x: int, y: str) -> 6:
return "foo"
@dummy_decorator
def non_type_checked_decorated_func(x: int, y: str) -> 6:
# This is to ensure that we avoid using a local variable that's already in use
_call_memo = "foo" # noqa: F841
return "foo"
@typeguard_ignore
def non_typeguard_checked_func(x: int, y: str) -> 6:
return "foo"
class Metaclass(type):
pass
@typechecked
class DummyClass(metaclass=Metaclass):
def type_checked_method(self, x: int, y: int) -> int:
return x * y
@classmethod
def type_checked_classmethod(cls, x: int, y: int) -> int:
return x * y
@staticmethod
def type_checked_staticmethod(x: int, y: int) -> int:
return x * y
@classmethod
def undocumented_classmethod(cls, x, y):
pass
@staticmethod
def undocumented_staticmethod(x, y):
pass
@property
def unannotated_property(self):
return None
def outer():
@typechecked
class Inner:
def get_self(self) -> "Inner":
return self
def create_inner() -> "Inner":
return Inner()
return create_inner
@typechecked
class Outer:
class Inner:
pass
def create_inner(self) -> "Inner":
return Outer.Inner()
@classmethod
def create_inner_classmethod(cls) -> "Inner":
return Outer.Inner()
@staticmethod
def create_inner_staticmethod() -> "Inner":
return Outer.Inner()
@contextmanager
@typechecked
def dummy_context_manager() -> Generator[int, None, None]:
yield 1
@overload
def overloaded_func(a: int) -> int:
...
@overload
def overloaded_func(a: str) -> str:
...
@typechecked
def overloaded_func(a: Union[str, int]) -> Union[str, int]:
return a
@typechecked
def missing_return() -> int:
pass
def get_inner_class() -> type:
@typechecked
class InnerClass:
def get_self(self) -> "InnerClass":
return self
return InnerClass
def create_local_class_instance() -> object:
class Inner:
pass
@typechecked
def get_instance() -> "Inner":
return instance
instance = Inner()
return get_instance()
@typechecked
async def async_func(a: int) -> str:
return str(a)
@typechecked
def generator_func(yield_value: Any, return_value: Any) -> Generator[int, Any, str]:
yield yield_value
return return_value
@typechecked
async def asyncgen_func(yield_value: Any) -> AsyncGenerator[int, Any]:
yield yield_value
@typechecked
def pep_604_union_args(
x: "Callable[[], Literal[-1]] | Callable[..., Union[int, str]]",
) -> None:
pass
@typechecked
def pep_604_union_retval(x: Any) -> "str | int":
return x
@typechecked
def builtin_generic_collections(x: "list[set[int]]") -> Any:
return x
@typechecked
def paramspec_function(func: P, args: P.args, kwargs: P.kwargs) -> None:
pass
@typechecked
def aug_assign() -> int:
x: int = 1
x += 1
return x
@typechecked
def multi_assign_single_value() -> Tuple[int, float, complex]:
x: int
y: float
z: complex
x = y = z = 6
return x, y, z
@typechecked
def multi_assign_iterable() -> Tuple[Sequence[int], Sequence[float], Sequence[complex]]:
x: Sequence[int]
y: Sequence[float]
z: Sequence[complex]
x = y = z = [6, 7]
return x, y, z
@typechecked
def unpacking_assign() -> Tuple[int, str]:
x: int
x, y = (1, "foo")
return x, y
@typechecked
def unpacking_assign_generator() -> Tuple[int, str]:
def genfunc():
yield 1
yield "foo"
x: int
x, y = genfunc()
return x, y
@typechecked
def unpacking_assign_star_with_annotation() -> Tuple[int, List[bytes], str]:
x: int
z: str
x, *y, z = (1, b"abc", b"bah", "foo")
return x, y, z
@typechecked
def unpacking_assign_star_no_annotation(value: Any) -> Tuple[int, List[bytes], str]:
x: int
y: List[bytes]
z: str
x, *y, z = value
return x, y, z
@typechecked(forward_ref_policy=ForwardRefPolicy.ERROR)
def override_forward_ref_policy(value: "NonexistentType") -> None: # noqa: F821
pass
@typechecked(typecheck_fail_callback=lambda exc, memo: print(exc))
def override_typecheck_fail_callback(value: int) -> None:
pass
@typechecked(collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
def override_collection_check_strategy(value: List[int]) -> None:
pass
@typechecked(typecheck_fail_callback=lambda exc, memo: print(exc))
class OverrideClass:
def override_typecheck_fail_callback(self, value: int) -> None:
pass
class Inner:
@typechecked
def override_typecheck_fail_callback(self, value: int) -> None:
pass
@typechecked
def typed_variable_args(
*args: str, **kwargs: int
) -> Tuple[Tuple[str, ...], Dict[str, int]]:
return args, kwargs
@typechecked
def guarded_type_hint_plain(x: "Imaginary") -> "Imaginary":
y: Imaginary = x
return y
@typechecked
def guarded_type_hint_subscript_toplevel(x: "Imaginary[int]") -> "Imaginary[int]":
y: Imaginary[int] = x
return y
@typechecked
def guarded_type_hint_subscript_nested(
x: List["Imaginary[int]"],
) -> List["Imaginary[int]"]:
y: List[Imaginary[int]] = x
return y
@typechecked
def literal(x: Literal["foo"]) -> Literal["foo"]:
y: Literal["foo"] = x
return y
@typechecked
def literal_in_union(x: Union[Literal["foo"],]) -> Literal["foo"]:
y: Literal["foo"] = x
return y
@typechecked
def typevar_forwardref(x: Type[T]) -> T:
return x()
def never_called(x: List["NonExistentType"]) -> List["NonExistentType"]: # noqa: F821
"""Regression test for #335."""
return x
python-typeguard-4.1.5/tests/mypy/ 0000775 0000000 0000000 00000000000 14545071262 0017212 5 ustar 00root root 0000000 0000000 python-typeguard-4.1.5/tests/mypy/negative.py 0000664 0000000 0000000 00000003252 14545071262 0021370 0 ustar 00root root 0000000 0000000 from typeguard import typechecked, typeguard_ignore
@typechecked
def foo(x: int) -> int:
return x + 1
@typechecked
def bar(x: int) -> int:
return str(x) # noqa: E501 # error: Incompatible return value type (got "str", expected "int") [return-value]
@typeguard_ignore
def non_typeguard_checked_func(x: int) -> int:
return str(x) # noqa: E501 # error: Incompatible return value type (got "str", expected "int") [return-value]
@typechecked
def returns_str() -> str:
return bar(0) # noqa: E501 # error: Incompatible return value type (got "int", expected "str") [return-value]
@typechecked
def arg_type(x: int) -> str:
return True # noqa: E501 # error: Incompatible return value type (got "bool", expected "str") [return-value]
@typechecked
def ret_type() -> str:
return True # noqa: E501 # error: Incompatible return value type (got "bool", expected "str") [return-value]
_ = arg_type(foo) # noqa: E501 # error: Argument 1 to "arg_type" has incompatible type "Callable[[int], int]"; expected "int" [arg-type]
_ = foo("typeguard") # noqa: E501 # error: Argument 1 to "foo" has incompatible type "str"; expected "int" [arg-type]
@typechecked
class MyClass:
def __init__(self, x: int = 0) -> None:
self.x = x
def add(self, y: int) -> int:
return self.x + y
def get_value(c: MyClass) -> int:
return c.x
def create_myclass(x: int) -> MyClass:
return MyClass(x)
_ = get_value("foo") # noqa: E501 # error: Argument 1 to "get_value" has incompatible type "str"; expected "MyClass" [arg-type]
_ = MyClass(returns_str()) # noqa: E501 # error: Argument 1 to "MyClass" has incompatible type "str"; expected "int" [arg-type]
python-typeguard-4.1.5/tests/mypy/positive.py 0000664 0000000 0000000 00000001526 14545071262 0021432 0 ustar 00root root 0000000 0000000 from typing import Callable
from typeguard import typechecked
@typechecked
def foo(x: str) -> str:
return "hello " + x
def takes_callable(f: Callable[[str], str]) -> str:
return f("typeguard")
takes_callable(foo)
@typechecked
def has_valid_arguments(x: int, y: str) -> None:
pass
def has_valid_return_type(y: str) -> str:
return y
@typechecked
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
def add(self, y: int) -> int:
return self.x + y
def get_value(c: MyClass) -> int:
return c.x
@typechecked
def get_value_checked(c: MyClass) -> int:
return c.x
def create_myclass(x: int) -> MyClass:
return MyClass(x)
@typechecked
def create_myclass_checked(x: int) -> MyClass:
return MyClass(x)
get_value(create_myclass(3))
get_value_checked(create_myclass_checked(1))
python-typeguard-4.1.5/tests/mypy/test_type_annotations.py 0000664 0000000 0000000 00000005666 14545071262 0024236 0 ustar 00root root 0000000 0000000 import os
import platform
import re
import subprocess
from typing import Dict, List
import pytest
POSITIVE_FILE = "positive.py"
NEGATIVE_FILE = "negative.py"
LINE_PATTERN = NEGATIVE_FILE + ":([0-9]+):"
pytestmark = [
pytest.mark.skipif(
platform.python_implementation() == "PyPy",
reason="MyPy does not work with PyPy yet",
)
]
def get_mypy_cmd(filename: str) -> List[str]:
return ["mypy", "--strict", filename]
def get_negative_mypy_output() -> str:
"""
Get the output from running mypy on the negative examples file.
"""
process = subprocess.run(
get_mypy_cmd(NEGATIVE_FILE), stdout=subprocess.PIPE, check=False
)
output = process.stdout.decode()
assert output
return output
def get_expected_errors() -> Dict[int, str]:
"""
Extract the expected errors from comments in the negative examples file.
"""
with open(NEGATIVE_FILE) as f:
lines = f.readlines()
expected = {}
for idx, line in enumerate(lines):
line = line.rstrip()
if "# error" in line:
expected[idx + 1] = line[line.index("# error") + 2 :]
# Sanity check. Should update if negative.py changes.
assert len(expected) == 9
return expected
def get_mypy_errors() -> Dict[int, str]:
"""
Extract the errors from running mypy on the negative examples file.
"""
mypy_output = get_negative_mypy_output()
got = {}
for line in mypy_output.splitlines():
m = re.match(LINE_PATTERN, line)
if m is None:
continue
got[int(m.group(1))] = line[len(m.group(0)) + 1 :]
return got
@pytest.fixture
def chdir_local() -> None:
"""
Change to the local directory. This is so that mypy treats imports from
typeguard as external imports instead of source code (which is handled
differently by mypy).
"""
os.chdir(os.path.dirname(__file__))
@pytest.mark.usefixtures("chdir_local")
def test_positive() -> None:
"""
Run mypy on the positive test file. There should be no errors.
"""
subprocess.check_call(get_mypy_cmd(POSITIVE_FILE))
@pytest.mark.usefixtures("chdir_local")
def test_negative() -> None:
"""
Run mypy on the negative test file. This should fail. The errors from mypy
should match the comments in the file.
"""
got_errors = get_mypy_errors()
expected_errors = get_expected_errors()
if set(got_errors) != set(expected_errors):
raise RuntimeError(
f"Expected error lines {set(expected_errors)} does not "
+ f"match mypy error lines {set(got_errors)}."
)
mismatches = [
(idx, expected_errors[idx], got_errors[idx])
for idx in expected_errors
if expected_errors[idx] != got_errors[idx]
]
for idx, expected, got in mismatches:
print(f"Line {idx}", f"Expected: {expected}", f"Got: {got}", sep="\n\t")
if mismatches:
raise RuntimeError("Error messages changed")
python-typeguard-4.1.5/tests/test_checkers.py 0000664 0000000 0000000 00000100633 14545071262 0021417 0 ustar 00root root 0000000 0000000 import collections.abc
import sys
from contextlib import nullcontext
from functools import partial
from io import BytesIO, StringIO
from pathlib import Path
from typing import (
IO,
AbstractSet,
Any,
AnyStr,
BinaryIO,
Callable,
Collection,
ContextManager,
Dict,
ForwardRef,
FrozenSet,
Iterator,
List,
Literal,
Mapping,
MutableMapping,
Optional,
Sequence,
Set,
TextIO,
Tuple,
Type,
TypedDict,
TypeVar,
Union,
)
import pytest
from typeguard import (
CollectionCheckStrategy,
ForwardRefPolicy,
TypeCheckError,
TypeCheckMemo,
TypeHintWarning,
check_type,
check_type_internal,
suppress_type_checks,
)
from typeguard._utils import qualified_name
from . import (
Child,
Employee,
JSONType,
Parent,
RuntimeProtocol,
StaticProtocol,
TChild,
TIntStr,
TParent,
TTypingConstrained,
myint,
mylist,
)
if sys.version_info >= (3, 11):
from typing import LiteralString
SubclassableAny = Any
else:
from typing_extensions import Any as SubclassableAny
from typing_extensions import LiteralString
if sys.version_info >= (3, 10):
from typing import Concatenate, ParamSpec, TypeGuard
else:
from typing_extensions import Concatenate, ParamSpec, TypeGuard
if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated
P = ParamSpec("P")
class TestAnyStr:
@pytest.mark.parametrize(
"value", [pytest.param("bar", id="str"), pytest.param(b"bar", id="bytes")]
)
def test_valid(self, value):
check_type(value, AnyStr)
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 4, AnyStr).match(
r"does not match any of the constraints \(bytes, str\)"
)
class TestBytesLike:
@pytest.mark.parametrize(
"value",
[
pytest.param(b"test", id="bytes"),
pytest.param(bytearray(b"test"), id="bytearray"),
pytest.param(memoryview(b"test"), id="memoryview"),
],
)
def test_valid(self, value):
check_type(value, bytes)
def test_fail(self):
pytest.raises(TypeCheckError, check_type, "test", bytes).match(
r"str is not bytes-like"
)
class TestFloat:
@pytest.mark.parametrize(
"value", [pytest.param(3, id="int"), pytest.param(3.87, id="float")]
)
def test_valid(self, value):
check_type(value, float)
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, "foo", float).match(
r"str is neither float or int"
)
class TestComplexNumber:
@pytest.mark.parametrize(
"value",
[
pytest.param(3, id="int"),
pytest.param(3.87, id="float"),
pytest.param(3.87 + 8j, id="complex"),
],
)
def test_valid(self, value):
check_type(value, complex)
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, "foo", complex).match(
"str is neither complex, float or int"
)
class TestCallable:
def test_any_args(self):
def some_callable(x: int, y: str) -> int:
pass
check_type(some_callable, Callable[..., int])
def test_exact_arg_count(self):
def some_callable(x: int, y: str) -> int:
pass
check_type(some_callable, Callable[[int, str], int])
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Callable[..., int]).match(
"is not callable"
)
def test_too_few_arguments(self):
def some_callable(x: int) -> int:
pass
pytest.raises(
TypeCheckError, check_type, some_callable, Callable[[int, str], int]
).match(
r"has too few arguments in its declaration; expected 2 but 1 argument\(s\) "
r"declared"
)
def test_too_many_arguments(self):
def some_callable(x: int, y: str, z: float) -> int:
pass
pytest.raises(
TypeCheckError, check_type, some_callable, Callable[[int, str], int]
).match(
r"has too many mandatory positional arguments in its declaration; expected "
r"2 but 3 mandatory positional argument\(s\) declared"
)
def test_mandatory_kwonlyargs(self):
def some_callable(x: int, y: str, *, z: float, bar: str) -> int:
pass
pytest.raises(
TypeCheckError, check_type, some_callable, Callable[[int, str], int]
).match(r"has mandatory keyword-only arguments in its declaration: z, bar")
def test_class(self):
"""
Test that passing a class as a callable does not count the "self" argument
against the ones declared in the Callable specification.
"""
class SomeClass:
def __init__(self, x: int, y: str):
pass
check_type(SomeClass, Callable[[int, str], Any])
def test_plain(self):
def callback(a):
pass
check_type(callback, Callable)
def test_partial_class(self):
"""
Test that passing a bound method as a callable does not count the "self"
argument against the ones declared in the Callable specification.
"""
class SomeClass:
def __init__(self, x: int, y: str):
pass
check_type(partial(SomeClass, y="foo"), Callable[[int], Any])
def test_bound_method(self):
"""
Test that passing a bound method as a callable does not count the "self"
argument against the ones declared in the Callable specification.
"""
check_type(Child().method, Callable[[int], Any])
def test_partial_bound_method(self):
"""
Test that passing a bound method as a callable does not count the "self"
argument against the ones declared in the Callable specification.
"""
check_type(partial(Child().method, 1), Callable[[], Any])
def test_defaults(self):
"""
Test that a callable having "too many" arguments don't raise an error if the
extra arguments have default values.
"""
def some_callable(x: int, y: str, z: float = 1.2) -> int:
pass
check_type(some_callable, Callable[[int, str], Any])
def test_builtin(self):
"""
Test that checking a Callable annotation against a builtin callable does not
raise an error.
"""
check_type([].append, Callable[[int], Any])
def test_concatenate(self):
"""Test that ``Concatenate`` in the arglist is ignored."""
check_type([].append, Callable[Concatenate[object, P], Any])
def test_positional_only_arg_with_default(self):
def some_callable(x: int = 1, /) -> None:
pass
check_type(some_callable, Callable[[int], Any])
class TestLiteral:
def test_literal_union(self):
annotation = Union[str, Literal[1, 6, 8]]
check_type(6, annotation)
pytest.raises(TypeCheckError, check_type, 4, annotation).match(
r"int did not match any element in the union:\n"
r" str: is not an instance of str\n"
r" Literal\[1, 6, 8\]: is not any of \(1, 6, 8\)$"
)
def test_literal_nested(self):
annotation = Literal[1, Literal["x", "a", Literal["z"]], 6, 8]
check_type("z", annotation)
pytest.raises(TypeCheckError, check_type, 4, annotation).match(
r"int is not any of \(1, 'x', 'a', 'z', 6, 8\)$"
)
def test_literal_int_as_bool(self):
pytest.raises(TypeCheckError, check_type, 0, Literal[False])
pytest.raises(TypeCheckError, check_type, 1, Literal[True])
def test_literal_illegal_value(self):
pytest.raises(TypeError, check_type, 4, Literal[1, 1.1]).match(
r"Illegal literal value: 1.1$"
)
class TestMapping:
class DummyMapping(collections.abc.Mapping):
_values = {"a": 1, "b": 10, "c": 100}
def __getitem__(self, index: str):
return self._values[index]
def __iter__(self):
return iter(self._values)
def __len__(self) -> int:
return len(self._values)
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Mapping[str, int]).match(
"is not a mapping"
)
def test_bad_key_type(self):
pytest.raises(
TypeCheckError, check_type, TestMapping.DummyMapping(), Mapping[int, int]
).match(
f"key 'a' of {__name__}.TestMapping.DummyMapping is not an instance of int"
)
def test_bad_value_type(self):
pytest.raises(
TypeCheckError, check_type, TestMapping.DummyMapping(), Mapping[str, str]
).match(
f"value of key 'a' of {__name__}.TestMapping.DummyMapping is not an "
f"instance of str"
)
def test_bad_key_type_full_check(self):
pytest.raises(
TypeCheckError,
check_type,
{"x": 1, 3: 2},
Mapping[str, int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("key 3 of dict is not an instance of str")
def test_bad_value_type_full_check(self):
pytest.raises(
TypeCheckError,
check_type,
{"x": 1, "y": "a"},
Mapping[str, int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("value of key 'y' of dict is not an instance of int")
def test_any_value_type(self):
check_type(TestMapping.DummyMapping(), Mapping[str, Any])
class TestMutableMapping:
class DummyMutableMapping(collections.abc.MutableMapping):
_values = {"a": 1, "b": 10, "c": 100}
def __getitem__(self, index: str):
return self._values[index]
def __setitem__(self, key, value):
self._values[key] = value
def __delitem__(self, key):
del self._values[key]
def __iter__(self):
return iter(self._values)
def __len__(self) -> int:
return len(self._values)
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, MutableMapping[str, int]).match(
"is not a mutable mapping"
)
def test_bad_key_type(self):
pytest.raises(
TypeCheckError,
check_type,
TestMutableMapping.DummyMutableMapping(),
MutableMapping[int, int],
).match(
f"key 'a' of {__name__}.TestMutableMapping.DummyMutableMapping is not an "
f"instance of int"
)
def test_bad_value_type(self):
pytest.raises(
TypeCheckError,
check_type,
TestMutableMapping.DummyMutableMapping(),
MutableMapping[str, str],
).match(
f"value of key 'a' of {__name__}.TestMutableMapping.DummyMutableMapping "
f"is not an instance of str"
)
class TestDict:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Dict[str, int]).match(
"int is not a dict"
)
def test_bad_key_type(self):
pytest.raises(TypeCheckError, check_type, {1: 2}, Dict[str, int]).match(
"key 1 of dict is not an instance of str"
)
def test_bad_value_type(self):
pytest.raises(TypeCheckError, check_type, {"x": "a"}, Dict[str, int]).match(
"value of key 'x' of dict is not an instance of int"
)
def test_bad_key_type_full_check(self):
pytest.raises(
TypeCheckError,
check_type,
{"x": 1, 3: 2},
Dict[str, int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("key 3 of dict is not an instance of str")
def test_bad_value_type_full_check(self):
pytest.raises(
TypeCheckError,
check_type,
{"x": 1, "y": "a"},
Dict[str, int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("value of key 'y' of dict is not an instance of int")
class TestTypedDict:
@pytest.mark.parametrize(
"value, total, error_re",
[
pytest.param({"x": 6, "y": "foo"}, True, None, id="correct"),
pytest.param(
{"y": "foo"},
True,
r'dict is missing required key\(s\): "x"',
id="missing_x",
),
pytest.param(
{"x": 6, "y": 3}, True, "dict is not an instance of str", id="wrong_y"
),
pytest.param(
{"x": 6},
True,
r'is missing required key\(s\): "y"',
id="missing_y_error",
),
pytest.param({"x": 6}, False, None, id="missing_y_ok"),
pytest.param(
{"x": "abc"}, False, "dict is not an instance of int", id="wrong_x"
),
pytest.param(
{"x": 6, "foo": "abc"},
False,
r'dict has unexpected extra key\(s\): "foo"',
id="unknown_key",
),
pytest.param(
None,
True,
"is not a dict",
id="not_dict",
),
],
)
def test_typed_dict(self, value, total: bool, error_re: Optional[str]):
class DummyDict(TypedDict, total=total):
x: int
y: str
if error_re:
pytest.raises(TypeCheckError, check_type, value, DummyDict).match(error_re)
else:
check_type(value, DummyDict)
def test_inconsistent_keys_invalid(self):
class DummyDict(TypedDict):
x: int
pytest.raises(
TypeCheckError, check_type, {"x": 1, "y": 2, b"z": 3}, DummyDict
).match(r'dict has unexpected extra key\(s\): "y", "b\'z\'"')
class TestList:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, List[int]).match(
"int is not a list"
)
def test_first_check_success(self):
check_type(["aa", "bb", 1], List[str])
def test_first_check_empty(self):
check_type([], List[str])
def test_first_check_fail(self):
pytest.raises(TypeCheckError, check_type, ["bb"], List[int]).match(
"list is not an instance of int"
)
def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
[1, 2, "bb"],
List[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("list is not an instance of int")
class TestSequence:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Sequence[int]).match(
"int is not a sequence"
)
@pytest.mark.parametrize(
"value",
[pytest.param([1, "bb"], id="list"), pytest.param((1, "bb"), id="tuple")],
)
def test_first_check_success(self, value):
check_type(value, Sequence[int])
def test_first_check_empty(self):
check_type([], Sequence[int])
def test_first_check_fail(self):
pytest.raises(TypeCheckError, check_type, ["bb"], Sequence[int]).match(
"list is not an instance of int"
)
def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
[1, 2, "bb"],
Sequence[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("list is not an instance of int")
class TestAbstractSet:
def test_custom_type(self):
class DummySet(AbstractSet[int]):
def __contains__(self, x: object) -> bool:
return x == 1
def __len__(self) -> int:
return 1
def __iter__(self) -> Iterator[int]:
yield 1
check_type(DummySet(), AbstractSet[int])
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, AbstractSet[int]).match(
"int is not a set"
)
def test_first_check_fail(self, sample_set):
# Create a set which, when iterated, returns "bb" as the first item
pytest.raises(TypeCheckError, check_type, sample_set, AbstractSet[int]).match(
"set is not an instance of int"
)
def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
{1, 2, "bb"},
AbstractSet[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("set is not an instance of int")
class TestSet:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Set[int]).match("int is not a set")
def test_valid(self):
check_type({1, 2}, Set[int])
def test_first_check_empty(self):
check_type(set(), Set[int])
def test_first_check_fail(self, sample_set: set):
pytest.raises(TypeCheckError, check_type, sample_set, Set[int]).match(
"set is not an instance of int"
)
def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
{1, 2, "bb"},
Set[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("set is not an instance of int")
class TestFrozenSet:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, FrozenSet[int]).match(
"int is not a frozenset"
)
def test_valid(self):
check_type(frozenset({1, 2}), FrozenSet[int])
def test_first_check_empty(self):
check_type(frozenset(), FrozenSet[int])
def test_first_check_fail(self, sample_set: set):
pytest.raises(
TypeCheckError, check_type, frozenset(sample_set), FrozenSet[int]
).match("set is not an instance of int")
def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
frozenset({1, 2, "bb"}),
FrozenSet[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("set is not an instance of int")
def test_set_against_frozenset(self, sample_set: set):
pytest.raises(TypeCheckError, check_type, sample_set, FrozenSet[int]).match(
"set is not a frozenset"
)
@pytest.mark.parametrize(
"annotated_type",
[
pytest.param(Tuple, id="typing"),
pytest.param(
tuple,
id="builtin",
marks=[
pytest.mark.skipif(
sys.version_info < (3, 9),
reason="builtins.tuple is not parametrizable before Python 3.9",
)
],
),
],
)
class TestTuple:
def test_bad_type(self, annotated_type: Any):
pytest.raises(TypeCheckError, check_type, 5, annotated_type[int]).match(
"int is not a tuple"
)
def test_first_check_empty(self, annotated_type: Any):
check_type((), annotated_type[int, ...])
def test_unparametrized_tuple(self, annotated_type: Any):
check_type((5, "foo"), annotated_type)
def test_unparametrized_tuple_fail(self, annotated_type: Any):
pytest.raises(TypeCheckError, check_type, 5, annotated_type).match(
"int is not a tuple"
)
def test_too_many_elements(self, annotated_type: Any):
pytest.raises(
TypeCheckError, check_type, (1, "aa", 2), annotated_type[int, str]
).match(r"tuple has wrong number of elements \(expected 2, got 3 instead\)")
def test_too_few_elements(self, annotated_type: Any):
pytest.raises(TypeCheckError, check_type, (1,), annotated_type[int, str]).match(
r"tuple has wrong number of elements \(expected 2, got 1 instead\)"
)
def test_bad_element(self, annotated_type: Any):
pytest.raises(
TypeCheckError, check_type, (1, 2), annotated_type[int, str]
).match("tuple is not an instance of str")
def test_ellipsis_bad_element(self, annotated_type: Any):
pytest.raises(
TypeCheckError, check_type, ("blah",), annotated_type[int, ...]
).match("tuple is not an instance of int")
def test_ellipsis_bad_element_full_check(self, annotated_type: Any):
pytest.raises(
TypeCheckError,
check_type,
(1, 2, "blah"),
annotated_type[int, ...],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("tuple is not an instance of int")
def test_empty_tuple(self, annotated_type: Any):
check_type((), annotated_type[()])
def test_empty_tuple_fail(self, annotated_type: Any):
pytest.raises(TypeCheckError, check_type, (1,), annotated_type[()]).match(
"tuple is not an empty tuple"
)
class TestNamedTuple:
def test_valid(self):
check_type(Employee("bob", 1), Employee)
def test_type_mismatch(self):
pytest.raises(TypeCheckError, check_type, ("bob", 1), Employee).match(
r"tuple is not a named tuple of type tests.Employee"
)
def test_wrong_field_type(self):
pytest.raises(TypeCheckError, check_type, Employee(2, 1), Employee).match(
r"Employee is not an instance of str"
)
class TestUnion:
@pytest.mark.parametrize(
"value", [pytest.param(6, id="int"), pytest.param("aa", id="str")]
)
def test_valid(self, value):
check_type(value, Union[str, int])
def test_typing_type_fail(self):
pytest.raises(TypeCheckError, check_type, 1, Union[str, Collection]).match(
"int did not match any element in the union:\n"
" str: is not an instance of str\n"
" Collection: is not an instance of collections.abc.Collection"
)
@pytest.mark.parametrize(
"annotation",
[
pytest.param(Union[str, int], id="pep484"),
pytest.param(
ForwardRef("str | int"),
id="pep604",
marks=[
pytest.mark.skipif(
sys.version_info < (3, 10), reason="Requires Python 3.10+"
)
],
),
],
)
@pytest.mark.parametrize(
"value", [pytest.param(6.5, id="float"), pytest.param(b"aa", id="bytes")]
)
def test_union_fail(self, annotation, value):
qualname = qualified_name(value)
pytest.raises(TypeCheckError, check_type, value, annotation).match(
f"{qualname} did not match any element in the union:\n"
f" str: is not an instance of str\n"
f" int: is not an instance of int"
)
class TestTypevar:
def test_bound(self):
check_type(Child(), TParent)
def test_bound_fail(self):
with pytest.raises(TypeCheckError, match="is not an instance of tests.Child"):
check_type(Parent(), TChild)
@pytest.mark.parametrize(
"value", [pytest.param([6, 7], id="int"), pytest.param({"aa", "bb"}, id="str")]
)
def test_collection_constraints(self, value):
check_type(value, TTypingConstrained)
def test_collection_constraints_fail(self):
pytest.raises(TypeCheckError, check_type, {1, 2}, TTypingConstrained).match(
r"set does not match any of the constraints \(List\[int\], "
r"AbstractSet\[str\]\)"
)
def test_constraints_fail(self):
pytest.raises(TypeCheckError, check_type, 2.5, TIntStr).match(
r"float does not match any of the constraints \(int, str\)"
)
class TestNewType:
def test_simple_valid(self):
check_type(1, myint)
def test_simple_bad_value(self):
pytest.raises(TypeCheckError, check_type, "a", myint).match(
r"str is not an instance of int"
)
def test_generic_valid(self):
check_type([1], mylist)
def test_generic_bad_value(self):
pytest.raises(TypeCheckError, check_type, ["a"], mylist).match(
r"item 0 of list is not an instance of int"
)
class TestType:
@pytest.mark.parametrize("annotation", [pytest.param(Type), pytest.param(type)])
def test_unparametrized(self, annotation: Any):
check_type(TestNewType, annotation)
@pytest.mark.parametrize("annotation", [pytest.param(Type), pytest.param(type)])
def test_unparametrized_fail(self, annotation: Any):
pytest.raises(TypeCheckError, check_type, 1, annotation).match(
"int is not a class"
)
@pytest.mark.parametrize(
"value", [pytest.param(Parent, id="exact"), pytest.param(Child, id="subclass")]
)
def test_parametrized(self, value):
check_type(value, Type[Parent])
def test_parametrized_fail(self):
pytest.raises(TypeCheckError, check_type, int, Type[str]).match(
"class int is not a subclass of str"
)
@pytest.mark.parametrize(
"value", [pytest.param(str, id="str"), pytest.param(int, id="int")]
)
def test_union(self, value):
check_type(value, Type[Union[str, int, list]])
def test_union_any(self):
check_type(list, Type[Union[str, int, Any]])
def test_any(self):
check_type(list, Type[Any])
def test_union_fail(self):
pytest.raises(
TypeCheckError, check_type, dict, Type[Union[str, int, list]]
).match(
"class dict did not match any element in the union:\n"
" str: is not a subclass of str\n"
" int: is not a subclass of int\n"
" list: is not a subclass of list"
)
def test_union_typevar(self):
T = TypeVar("T", bound=Parent)
check_type(Child, Type[T])
class TestIO:
@pytest.mark.parametrize(
"annotation",
[
pytest.param(BinaryIO, id="direct"),
pytest.param(IO[bytes], id="parametrized"),
],
)
def test_binary_valid(self, annotation):
check_type(BytesIO(), annotation)
@pytest.mark.parametrize(
"annotation",
[
pytest.param(BinaryIO, id="direct"),
pytest.param(IO[bytes], id="parametrized"),
],
)
def test_binary_fail(self, annotation):
pytest.raises(TypeCheckError, check_type, StringIO(), annotation).match(
"_io.StringIO is not a binary I/O object"
)
def test_binary_real_file(self, tmp_path: Path):
with tmp_path.joinpath("testfile").open("wb") as f:
check_type(f, BinaryIO)
@pytest.mark.parametrize(
"annotation",
[pytest.param(TextIO, id="direct"), pytest.param(IO[str], id="parametrized")],
)
def test_text_valid(self, annotation):
check_type(StringIO(), annotation)
@pytest.mark.parametrize(
"annotation",
[pytest.param(TextIO, id="direct"), pytest.param(IO[str], id="parametrized")],
)
def test_text_fail(self, annotation):
pytest.raises(TypeCheckError, check_type, BytesIO(), annotation).match(
"_io.BytesIO is not a text based I/O object"
)
def test_text_real_file(self, tmp_path: Path):
with tmp_path.joinpath("testfile").open("w") as f:
check_type(f, TextIO)
class TestProtocol:
def test_protocol(self):
class Foo:
member = 1
def meth(self) -> None:
pass
check_type(Foo(), RuntimeProtocol)
check_type(Foo, Type[RuntimeProtocol])
def test_protocol_warns_on_static(self):
class Foo:
member = 1
def meth(self) -> None:
pass
with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
) as warning:
check_type(Foo(), StaticProtocol)
assert warning.list[0].filename == __file__
with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
) as warning:
check_type(Foo, Type[StaticProtocol])
assert warning.list[0].filename == __file__
def test_fail_non_method_members(self):
class Foo:
val = 1
def meth(self) -> None:
pass
clsname = f"{__name__}.TestProtocol.test_fail_non_method_members..Foo"
pytest.raises(TypeCheckError, check_type, Foo(), RuntimeProtocol).match(
f"{clsname} is not compatible with the RuntimeProtocol protocol"
)
pytest.raises(TypeCheckError, check_type, Foo, Type[RuntimeProtocol]).match(
f"class {clsname} is not compatible with the RuntimeProtocol protocol"
)
def test_fail(self):
class Foo:
def meth2(self) -> None:
pass
pattern = (
f"{__name__}.TestProtocol.test_fail..Foo is not compatible with "
f"the RuntimeProtocol protocol"
)
pytest.raises(TypeCheckError, check_type, Foo(), RuntimeProtocol).match(pattern)
pytest.raises(TypeCheckError, check_type, Foo, Type[RuntimeProtocol]).match(
pattern
)
class TestRecursiveType:
def test_valid(self):
check_type({"a": [1, 2, 3]}, JSONType)
def test_fail(self):
with pytest.raises(
TypeCheckError,
match=(
"dict did not match any element in the union:\n"
" str: is not an instance of str\n"
" float: is neither float or int\n"
" bool: is not an instance of bool\n"
" NoneType: is not an instance of NoneType\n"
" List\\[JSONType\\]: is not a list\n"
" Dict\\[str, JSONType\\]: value of key 'a' did not match any element "
"in the union:\n"
" str: is not an instance of str\n"
" float: is neither float or int\n"
" bool: is not an instance of bool\n"
" NoneType: is not an instance of NoneType\n"
" List\\[JSONType\\]: is not a list\n"
" Dict\\[str, JSONType\\]: is not a dict"
),
):
check_type({"a": (1, 2, 3)}, JSONType)
class TestAnnotated:
def test_valid(self):
check_type("aa", Annotated[str, "blah"])
def test_fail(self):
pytest.raises(TypeCheckError, check_type, 1, Annotated[str, "blah"]).match(
"int is not an instance of str"
)
class TestLiteralString:
def test_valid(self):
check_type("aa", LiteralString)
def test_fail(self):
pytest.raises(TypeCheckError, check_type, 1, LiteralString).match(
"int is not an instance of str"
)
class TestTypeGuard:
def test_valid(self):
check_type(True, TypeGuard)
def test_fail(self):
pytest.raises(TypeCheckError, check_type, 1, TypeGuard).match(
"int is not an instance of bool"
)
@pytest.mark.parametrize(
"policy, contextmanager",
[
pytest.param(ForwardRefPolicy.ERROR, pytest.raises(NameError), id="error"),
pytest.param(ForwardRefPolicy.WARN, pytest.warns(TypeHintWarning), id="warn"),
pytest.param(ForwardRefPolicy.IGNORE, nullcontext(), id="ignore"),
],
)
def test_forward_reference_policy(
policy: ForwardRefPolicy, contextmanager: ContextManager
):
with contextmanager:
check_type(1, ForwardRef("Foo"), forward_ref_policy=policy) # noqa: F821
def test_any():
assert check_type("aa", Any) == "aa"
def test_suppressed_checking():
with suppress_type_checks():
assert check_type("aa", int) == "aa"
def test_suppressed_checking_exception():
with pytest.raises(RuntimeError), suppress_type_checks():
assert check_type("aa", int) == "aa"
raise RuntimeError
pytest.raises(TypeCheckError, check_type, "aa", int)
def test_any_subclass():
class Foo(SubclassableAny):
pass
check_type(Foo(), int)
def test_none():
check_type(None, None)
def test_return_checked_value():
value = {"foo": 1}
assert check_type(value, Dict[str, int]) is value
def test_imported_str_forward_ref():
value = {"foo": 1}
memo = TypeCheckMemo(globals(), locals())
pattern = r"Skipping type check against 'Dict\[str, int\]'"
with pytest.warns(TypeHintWarning, match=pattern):
check_type_internal(value, "Dict[str, int]", memo)
def test_check_against_tuple_success():
check_type(1, (float, Union[str, int]))
def test_check_against_tuple_failure():
pytest.raises(TypeCheckError, check_type, "aa", (int, bytes))
python-typeguard-4.1.5/tests/test_importhook.py 0000664 0000000 0000000 00000004215 14545071262 0022022 0 ustar 00root root 0000000 0000000 import sys
import warnings
from importlib import import_module
from importlib.util import cache_from_source
from pathlib import Path
import pytest
from typeguard import TypeCheckError, TypeguardFinder, install_import_hook
from typeguard._importhook import OPTIMIZATION
pytestmark = pytest.mark.filterwarnings("error:no type annotations present")
this_dir = Path(__file__).parent
dummy_module_path = this_dir / "dummymodule.py"
cached_module_path = Path(
cache_from_source(str(dummy_module_path), optimization=OPTIMIZATION)
)
def import_dummymodule():
if cached_module_path.exists():
cached_module_path.unlink()
sys.path.insert(0, str(this_dir))
try:
with install_import_hook(["dummymodule"]):
with warnings.catch_warnings():
warnings.filterwarnings("error", module="typeguard")
module = import_module("dummymodule")
return module
finally:
sys.path.remove(str(this_dir))
def test_blanket_import():
dummymodule = import_dummymodule()
try:
pytest.raises(TypeCheckError, dummymodule.type_checked_func, 2, "3").match(
r'argument "y" \(str\) is not an instance of int'
)
finally:
del sys.modules["dummymodule"]
def test_package_name_matching():
"""
The path finder only matches configured (sub)packages.
"""
packages = ["ham", "spam.eggs"]
dummy_original_pathfinder = None
finder = TypeguardFinder(packages, dummy_original_pathfinder)
assert finder.should_instrument("ham")
assert finder.should_instrument("ham.eggs")
assert finder.should_instrument("spam.eggs")
assert not finder.should_instrument("spam")
assert not finder.should_instrument("ha")
assert not finder.should_instrument("spam_eggs")
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires ast.unparse()")
def test_debug_instrumentation(monkeypatch, capsys):
monkeypatch.setattr("typeguard.config.debug_instrumentation", True)
import_dummymodule()
out, err = capsys.readouterr()
assert f"Source code of '{dummy_module_path}' after instrumentation:" in err
assert "class DummyClass" in err
python-typeguard-4.1.5/tests/test_instrumentation.py 0000664 0000000 0000000 00000025263 14545071262 0023100 0 ustar 00root root 0000000 0000000 import asyncio
import sys
import warnings
from importlib import import_module
from importlib.util import cache_from_source
from pathlib import Path
import pytest
from pytest import FixtureRequest
from typeguard import TypeCheckError, config, install_import_hook, suppress_type_checks
from typeguard._importhook import OPTIMIZATION
pytestmark = pytest.mark.filterwarnings("error:no type annotations present")
this_dir = Path(__file__).parent
dummy_module_path = this_dir / "dummymodule.py"
cached_module_path = Path(
cache_from_source(str(dummy_module_path), optimization=OPTIMIZATION)
)
# This block here is to test the recipe mentioned in the user guide
if "pytest" in sys.modules:
from typeguard import typechecked
else:
from typing import TypeVar
_T = TypeVar("_T")
def typechecked(target: _T, **kwargs) -> _T:
return target if target else typechecked
@pytest.fixture(scope="module", params=["typechecked", "importhook"])
def method(request: FixtureRequest) -> str:
return request.param
@pytest.fixture(scope="module")
def dummymodule(method: str):
config.debug_instrumentation = True
sys.path.insert(0, str(this_dir))
try:
sys.modules.pop("dummymodule", None)
if cached_module_path.exists():
cached_module_path.unlink()
if method == "typechecked":
return import_module("dummymodule")
with install_import_hook(["dummymodule"]):
with warnings.catch_warnings():
warnings.filterwarnings("error", module="typeguard")
module = import_module("dummymodule")
return module
finally:
sys.path.remove(str(this_dir))
def test_type_checked_func(dummymodule):
assert dummymodule.type_checked_func(2, 3) == 6
def test_type_checked_func_error(dummymodule):
pytest.raises(TypeCheckError, dummymodule.type_checked_func, 2, "3").match(
r'argument "y" \(str\) is not an instance of int'
)
def test_non_type_checked_func(dummymodule):
assert dummymodule.non_type_checked_func("bah", 9) == "foo"
def test_non_type_checked_decorated_func(dummymodule):
assert dummymodule.non_type_checked_func("bah", 9) == "foo"
def test_typeguard_ignored_func(dummymodule):
assert dummymodule.non_type_checked_func("bah", 9) == "foo"
def test_type_checked_method(dummymodule):
instance = dummymodule.DummyClass()
pytest.raises(TypeCheckError, instance.type_checked_method, "bah", 9).match(
r'argument "x" \(str\) is not an instance of int'
)
def test_type_checked_classmethod(dummymodule):
pytest.raises(
TypeCheckError, dummymodule.DummyClass.type_checked_classmethod, "bah", 9
).match(r'argument "x" \(str\) is not an instance of int')
def test_type_checked_staticmethod(dummymodule):
pytest.raises(
TypeCheckError, dummymodule.DummyClass.type_checked_staticmethod, "bah", 9
).match(r'argument "x" \(str\) is not an instance of int')
@pytest.mark.xfail(reason="No workaround for this has been implemented yet")
def test_inner_class_method(dummymodule):
retval = dummymodule.Outer().create_inner()
assert retval.__class__.__qualname__ == "Outer.Inner"
@pytest.mark.xfail(reason="No workaround for this has been implemented yet")
def test_inner_class_classmethod(dummymodule):
retval = dummymodule.Outer.create_inner_classmethod()
assert retval.__class__.__qualname__ == "Outer.Inner"
@pytest.mark.xfail(reason="No workaround for this has been implemented yet")
def test_inner_class_staticmethod(dummymodule):
retval = dummymodule.Outer.create_inner_staticmethod()
assert retval.__class__.__qualname__ == "Outer.Inner"
def test_local_class_instance(dummymodule):
instance = dummymodule.create_local_class_instance()
assert (
instance.__class__.__qualname__ == "create_local_class_instance..Inner"
)
def test_contextmanager(dummymodule):
with dummymodule.dummy_context_manager() as value:
assert value == 1
def test_overload(dummymodule):
dummymodule.overloaded_func(1)
dummymodule.overloaded_func("x")
pytest.raises(TypeCheckError, dummymodule.overloaded_func, b"foo")
def test_async_func(dummymodule):
pytest.raises(TypeCheckError, asyncio.run, dummymodule.async_func(b"foo"))
def test_generator_valid(dummymodule):
gen = dummymodule.generator_func(6, "foo")
assert gen.send(None) == 6
try:
gen.send(None)
except StopIteration as exc:
assert exc.value == "foo"
else:
pytest.fail("Generator did not exit")
def test_generator_bad_yield_type(dummymodule):
gen = dummymodule.generator_func("foo", "foo")
pytest.raises(TypeCheckError, gen.send, None).match(
r"yielded value \(str\) is not an instance of int"
)
gen.close()
def test_generator_bad_return_type(dummymodule):
gen = dummymodule.generator_func(6, 6)
assert gen.send(None) == 6
pytest.raises(TypeCheckError, gen.send, None).match(
r"return value \(int\) is not an instance of str"
)
gen.close()
def test_asyncgen_valid(dummymodule):
gen = dummymodule.asyncgen_func(6)
assert asyncio.run(gen.asend(None)) == 6
def test_asyncgen_bad_yield_type(dummymodule):
gen = dummymodule.asyncgen_func("foo")
pytest.raises(TypeCheckError, asyncio.run, gen.asend(None)).match(
r"yielded value \(str\) is not an instance of int"
)
def test_missing_return(dummymodule):
pytest.raises(TypeCheckError, dummymodule.missing_return).match(
r"the return value \(None\) is not an instance of int"
)
def test_pep_604_union_args(dummymodule):
pytest.raises(TypeCheckError, dummymodule.pep_604_union_args, 1.1).match(
r'argument "x" \(float\) did not match any element in the union:'
r"\n Callable\[list, Literal\[-1\]\]: is not callable"
r"\n Callable\[ellipsis, Union\[int, str\]\]: is not callable"
)
def test_pep_604_union_retval(dummymodule):
pytest.raises(TypeCheckError, dummymodule.pep_604_union_retval, 1.1).match(
r"the return value \(float\) did not match any element in the union:"
r"\n str: is not an instance of str"
r"\n int: is not an instance of int"
)
def test_builtin_generic_collections(dummymodule):
pytest.raises(TypeCheckError, dummymodule.builtin_generic_collections, 1.1).match(
r'argument "x" \(float\) is not a list'
)
def test_paramspec(dummymodule):
def foo(a: int, b: str, *, c: bytes) -> None:
pass
dummymodule.paramspec_function(foo, (1, "bar"), {"c": b"abc"})
def test_augmented_assign(dummymodule):
assert dummymodule.aug_assign() == 2
def test_multi_assign_single_value(dummymodule):
assert dummymodule.multi_assign_single_value() == (6, 6, 6)
def test_multi_assign_iterable(dummymodule):
assert dummymodule.multi_assign_iterable() == ([6, 7], [6, 7], [6, 7])
def test_unpacking_assign(dummymodule):
assert dummymodule.unpacking_assign() == (1, "foo")
def test_unpacking_assign_from_generator(dummymodule):
assert dummymodule.unpacking_assign_generator() == (1, "foo")
def test_unpacking_assign_star_with_annotation(dummymodule):
assert dummymodule.unpacking_assign_star_with_annotation() == (
1,
[b"abc", b"bah"],
"foo",
)
def test_unpacking_assign_star_no_annotation_success(dummymodule):
assert dummymodule.unpacking_assign_star_no_annotation(
(1, b"abc", b"bah", "foo")
) == (
1,
[b"abc", b"bah"],
"foo",
)
def test_unpacking_assign_star_no_annotation_fail(dummymodule):
with pytest.raises(
TypeCheckError, match=r"value assigned to z \(bytes\) is not an instance of str"
):
dummymodule.unpacking_assign_star_no_annotation((1, b"abc", b"bah", b"foo"))
class TestOptionsOverride:
def test_forward_ref_policy(self, dummymodule):
with pytest.raises(NameError, match="name 'NonexistentType' is not defined"):
dummymodule.override_forward_ref_policy(6)
def test_typecheck_fail_callback(self, dummymodule, capsys):
dummymodule.override_typecheck_fail_callback("foo")
assert capsys.readouterr().out == (
'argument "value" (str) is not an instance of int\n'
)
def test_override_collection_check_strategy(self, dummymodule):
with pytest.raises(
TypeCheckError,
match=r'item 1 of argument "value" \(list\) is not an instance of int',
):
dummymodule.override_collection_check_strategy([1, "foo"])
def test_outer_class_typecheck_fail_callback(self, dummymodule, capsys):
dummymodule.OverrideClass().override_typecheck_fail_callback("foo")
assert capsys.readouterr().out == (
'argument "value" (str) is not an instance of int\n'
)
def test_inner_class_no_overrides(self, dummymodule):
with pytest.raises(TypeCheckError):
dummymodule.OverrideClass.Inner().override_typecheck_fail_callback("foo")
class TestVariableArguments:
def test_success(self, dummymodule):
assert dummymodule.typed_variable_args("foo", "bar", a=1, b=8) == (
("foo", "bar"),
{"a": 1, "b": 8},
)
def test_args_fail(self, dummymodule):
with pytest.raises(
TypeCheckError,
match=r'item 0 of argument "args" \(tuple\) is not an instance of str',
):
dummymodule.typed_variable_args(1, a=1, b=8)
def test_kwargs_fail(self, dummymodule):
with pytest.raises(
TypeCheckError,
match=r'value of key \'a\' of argument "kwargs" \(dict\) is not an '
r"instance of int",
):
dummymodule.typed_variable_args("foo", "bar", a="baz")
class TestGuardedType:
def test_plain(self, dummymodule):
assert dummymodule.guarded_type_hint_plain("foo") == "foo"
def test_subscript_toplevel(self, dummymodule):
assert dummymodule.guarded_type_hint_subscript_toplevel("foo") == "foo"
def test_subscript_nested(self, dummymodule):
assert dummymodule.guarded_type_hint_subscript_nested(["foo"]) == ["foo"]
def test_literal(dummymodule):
assert dummymodule.literal("foo") == "foo"
def test_literal_in_union(dummymodule):
"""Regression test for #372."""
assert dummymodule.literal_in_union("foo") == "foo"
def test_typevar_forwardref(dummymodule):
instance = dummymodule.typevar_forwardref(dummymodule.DummyClass)
assert isinstance(instance, dummymodule.DummyClass)
def test_suppress_annotated_assignment(dummymodule):
with suppress_type_checks():
assert dummymodule.literal_in_union("foo") == "foo"
def test_suppress_annotated_multi_assignment(dummymodule):
with suppress_type_checks():
assert dummymodule.multi_assign_single_value() == (6, 6, 6)
python-typeguard-4.1.5/tests/test_plugins.py 0000664 0000000 0000000 00000001316 14545071262 0021307 0 ustar 00root root 0000000 0000000 from pytest import MonkeyPatch
from typeguard import load_plugins
def test_custom_type_checker(monkeypatch: MonkeyPatch) -> None:
def lookup_func(origin_type, args, extras):
pass
class FakeEntryPoint:
name = "test"
def load(self):
return lookup_func
def fake_entry_points(group):
assert group == "typeguard.checker_lookup"
return [FakeEntryPoint()]
checker_lookup_functions = []
monkeypatch.setattr("typeguard._checkers.entry_points", fake_entry_points)
monkeypatch.setattr(
"typeguard._checkers.checker_lookup_functions", checker_lookup_functions
)
load_plugins()
assert checker_lookup_functions[0] is lookup_func
python-typeguard-4.1.5/tests/test_suppression.py 0000664 0000000 0000000 00000002563 14545071262 0022225 0 ustar 00root root 0000000 0000000 import pytest
from typeguard import TypeCheckError, check_type, suppress_type_checks, typechecked
def test_contextmanager_typechecked():
@typechecked
def foo(x: str) -> None:
pass
with suppress_type_checks():
foo(1)
def test_contextmanager_check_type():
with suppress_type_checks():
check_type(1, str)
def test_contextmanager_nesting():
with suppress_type_checks(), suppress_type_checks():
check_type(1, str)
pytest.raises(TypeCheckError, check_type, 1, str)
def test_contextmanager_exception():
"""
Test that type check suppression stops even if an exception is raised within the
context manager block.
"""
with pytest.raises(RuntimeError):
with suppress_type_checks():
raise RuntimeError
pytest.raises(TypeCheckError, check_type, 1, str)
@suppress_type_checks
def test_decorator_typechecked():
@typechecked
def foo(x: str) -> None:
pass
foo(1)
@suppress_type_checks
def test_decorator_check_type():
check_type(1, str)
def test_decorator_exception():
"""
Test that type check suppression stops even if an exception is raised from a
decorated function.
"""
@suppress_type_checks
def foo():
raise RuntimeError
with pytest.raises(RuntimeError):
foo()
pytest.raises(TypeCheckError, check_type, 1, str)
python-typeguard-4.1.5/tests/test_transformer.py 0000664 0000000 0000000 00000144734 14545071262 0022204 0 ustar 00root root 0000000 0000000 import sys
from ast import parse
from textwrap import dedent
import pytest
from typeguard._transformer import TypeguardTransformer
if sys.version_info >= (3, 9):
from ast import unparse
else:
pytest.skip("Requires Python 3.9 or newer", allow_module_level=True)
def test_arguments_only() -> None:
node = parse(
dedent(
"""
def foo(x: int) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
def foo(x: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
"""
).strip()
)
def test_return_only() -> None:
node = parse(
dedent(
"""
def foo(x) -> int:
return 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type
def foo(x) -> int:
memo = TypeCheckMemo(globals(), locals())
return check_return_type('foo', 6, int, memo)
"""
).strip()
)
class TestGenerator:
def test_yield(self) -> None:
node = parse(
dedent(
"""
from collections.abc import Generator
from typing import Any
def foo(x) -> Generator[int, Any, str]:
yield 2
yield 6
return 'test'
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type, check_yield_type
from collections.abc import Generator
from typing import Any
def foo(x) -> Generator[int, Any, str]:
memo = TypeCheckMemo(globals(), locals())
yield check_yield_type('foo', 2, int, memo)
yield check_yield_type('foo', 6, int, memo)
return check_return_type('foo', 'test', str, memo)
"""
).strip()
)
def test_no_return_type_check(self) -> None:
node = parse(
dedent(
"""
from collections.abc import Generator
def foo(x) -> Generator[int, None, None]:
yield 2
yield 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_send_type, check_yield_type
from collections.abc import Generator
def foo(x) -> Generator[int, None, None]:
memo = TypeCheckMemo(globals(), locals())
check_send_type('foo', (yield check_yield_type('foo', 2, int, \
memo)), None, memo)
check_send_type('foo', (yield check_yield_type('foo', 6, int, \
memo)), None, memo)
"""
).strip()
)
def test_no_send_type_check(self) -> None:
node = parse(
dedent(
"""
from typing import Any
from collections.abc import Generator
def foo(x) -> Generator[int, Any, Any]:
yield 2
yield 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_yield_type
from typing import Any
from collections.abc import Generator
def foo(x) -> Generator[int, Any, Any]:
memo = TypeCheckMemo(globals(), locals())
yield check_yield_type('foo', 2, int, memo)
yield check_yield_type('foo', 6, int, memo)
"""
).strip()
)
class TestAsyncGenerator:
def test_full(self) -> None:
node = parse(
dedent(
"""
from collections.abc import AsyncGenerator
async def foo(x) -> AsyncGenerator[int, None]:
yield 2
yield 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_send_type, check_yield_type
from collections.abc import AsyncGenerator
async def foo(x) -> AsyncGenerator[int, None]:
memo = TypeCheckMemo(globals(), locals())
check_send_type('foo', (yield check_yield_type('foo', 2, int, \
memo)), None, memo)
check_send_type('foo', (yield check_yield_type('foo', 6, int, \
memo)), None, memo)
"""
).strip()
)
def test_no_yield_type_check(self) -> None:
node = parse(
dedent(
"""
from typing import Any
from collections.abc import AsyncGenerator
async def foo() -> AsyncGenerator[Any, None]:
yield 2
yield 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_send_type
from typing import Any
from collections.abc import AsyncGenerator
async def foo() -> AsyncGenerator[Any, None]:
memo = TypeCheckMemo(globals(), locals())
check_send_type('foo', (yield 2), None, memo)
check_send_type('foo', (yield 6), None, memo)
"""
).strip()
)
def test_no_send_type_check(self) -> None:
node = parse(
dedent(
"""
from typing import Any
from collections.abc import AsyncGenerator
async def foo() -> AsyncGenerator[int, Any]:
yield 2
yield 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_yield_type
from typing import Any
from collections.abc import AsyncGenerator
async def foo() -> AsyncGenerator[int, Any]:
memo = TypeCheckMemo(globals(), locals())
yield check_yield_type('foo', 2, int, memo)
yield check_yield_type('foo', 6, int, memo)
"""
).strip()
)
def test_pass_only() -> None:
node = parse(
dedent(
"""
def foo(x) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
def foo(x) -> None:
pass
"""
).strip()
)
@pytest.mark.parametrize(
"import_line, decorator",
[
pytest.param("from typing import no_type_check", "@no_type_check"),
pytest.param("from typeguard import typeguard_ignore", "@typeguard_ignore"),
pytest.param("import typing", "@typing.no_type_check"),
pytest.param("import typeguard", "@typeguard.typeguard_ignore"),
],
)
def test_no_type_check_decorator(import_line: str, decorator: str) -> None:
node = parse(
dedent(
f"""
{import_line}
{decorator}
def foo(x: int) -> int:
return x
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
f"""
{import_line}
{decorator}
def foo(x: int) -> int:
return x
"""
).strip()
)
@pytest.mark.parametrize(
"import_line, annotation",
[
pytest.param("from typing import Any", "Any"),
pytest.param("from typing import Any as AlterAny", "AlterAny"),
pytest.param("from typing_extensions import Any", "Any"),
pytest.param("from typing_extensions import Any as AlterAny", "AlterAny"),
pytest.param("import typing", "typing.Any"),
pytest.param("import typing as typing_alter", "typing_alter.Any"),
pytest.param("import typing_extensions as typing_alter", "typing_alter.Any"),
],
)
def test_any_only(import_line: str, annotation: str) -> None:
node = parse(
dedent(
f"""
{import_line}
def foo(x, y: {annotation}) -> {annotation}:
return 1
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
f"""
{import_line}
def foo(x, y: {annotation}) -> {annotation}:
return 1
"""
).strip()
)
def test_any_in_union() -> None:
node = parse(
dedent(
"""
from typing import Any, Union
def foo(x, y: Union[Any, None]) -> Union[Any, None]:
return 1
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any, Union
def foo(x, y: Union[Any, None]) -> Union[Any, None]:
return 1
"""
).strip()
)
def test_any_in_pep_604_union() -> None:
node = parse(
dedent(
"""
from typing import Any
def foo(x, y: Any | None) -> Any | None:
return 1
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any
def foo(x, y: Any | None) -> Any | None:
return 1
"""
).strip()
)
def test_any_in_nested_dict() -> None:
# Regression test for #373
node = parse(
dedent(
"""
from typing import Any
def foo(x: dict[str, dict[str, Any]]) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
from typing import Any
def foo(x: dict[str, dict[str, Any]]) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, dict[str, dict[str, Any]])}, memo)
"""
).strip()
)
def test_avoid_global_names() -> None:
node = parse(
dedent(
"""
memo = TypeCheckMemo = check_argument_types = check_return_type = None
def func1(x: int) -> int:
dummy = (memo,)
return x
def func2(x: int) -> int:
return x
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo as TypeCheckMemo_
from typeguard._functions import \
check_argument_types as check_argument_types_, check_return_type as check_return_type_
memo = TypeCheckMemo = check_argument_types = check_return_type = None
def func1(x: int) -> int:
memo_ = TypeCheckMemo_(globals(), locals())
check_argument_types_('func1', {'x': (x, int)}, memo_)
dummy = (memo,)
return check_return_type_('func1', x, int, memo_)
def func2(x: int) -> int:
memo_ = TypeCheckMemo_(globals(), locals())
check_argument_types_('func2', {'x': (x, int)}, memo_)
return check_return_type_('func2', x, int, memo_)
"""
).strip()
)
def test_avoid_local_names() -> None:
node = parse(
dedent(
"""
def foo(x: int) -> int:
memo = TypeCheckMemo = check_argument_types = check_return_type = None
return x
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
def foo(x: int) -> int:
from typeguard import TypeCheckMemo as TypeCheckMemo_
from typeguard._functions import \
check_argument_types as check_argument_types_, check_return_type as check_return_type_
memo_ = TypeCheckMemo_(globals(), locals())
check_argument_types_('foo', {'x': (x, int)}, memo_)
memo = TypeCheckMemo = check_argument_types = check_return_type = None
return check_return_type_('foo', x, int, memo_)
"""
).strip()
)
def test_avoid_nonlocal_names() -> None:
node = parse(
dedent(
"""
def outer():
memo = TypeCheckMemo = check_argument_types = check_return_type = None
def foo(x: int) -> int:
return x
return foo
"""
)
)
TypeguardTransformer(["outer", "foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
def outer():
memo = TypeCheckMemo = check_argument_types = check_return_type = None
def foo(x: int) -> int:
from typeguard import TypeCheckMemo as TypeCheckMemo_
from typeguard._functions import \
check_argument_types as check_argument_types_, check_return_type as check_return_type_
memo_ = TypeCheckMemo_(globals(), locals())
check_argument_types_('outer..foo', {'x': (x, int)}, memo_)
return check_return_type_('outer..foo', x, int, memo_)
return foo
"""
).strip()
)
def test_method() -> None:
node = parse(
dedent(
"""
class Foo:
def foo(self, x: int) -> int:
return x
"""
)
)
TypeguardTransformer(["Foo", "foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
class Foo:
def foo(self, x: int) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_return_type
memo = TypeCheckMemo(globals(), locals(), self_type=self.__class__)
check_argument_types('Foo.foo', {'x': (x, int)}, memo)
return check_return_type('Foo.foo', x, int, memo)
"""
).strip()
)
def test_classmethod() -> None:
node = parse(
dedent(
"""
class Foo:
@classmethod
def foo(cls, x: int) -> int:
return x
"""
)
)
TypeguardTransformer(["Foo", "foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
class Foo:
@classmethod
def foo(cls, x: int) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_return_type
memo = TypeCheckMemo(globals(), locals(), self_type=cls)
check_argument_types('Foo.foo', {'x': (x, int)}, memo)
return check_return_type('Foo.foo', x, int, memo)
"""
).strip()
)
def test_staticmethod() -> None:
node = parse(
dedent(
"""
class Foo:
@staticmethod
def foo(x: int) -> int:
return x
"""
)
)
TypeguardTransformer(["Foo", "foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
class Foo:
@staticmethod
def foo(x: int) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_return_type
memo = TypeCheckMemo(globals(), locals())
check_argument_types('Foo.foo', {'x': (x, int)}, memo)
return check_return_type('Foo.foo', x, int, memo)
"""
).strip()
)
def test_new_with_self() -> None:
node = parse(
dedent(
"""
from typing import Self
class Foo:
def __new__(cls) -> Self:
return super().__new__(cls)
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type
from typing import Self
class Foo:
def __new__(cls) -> Self:
Foo = cls
memo = TypeCheckMemo(globals(), locals(), self_type=cls)
return check_return_type('Foo.__new__', super().__new__(cls), \
Self, memo)
"""
).strip()
)
def test_new_with_explicit_class_name() -> None:
# Regression test for #398
node = parse(
dedent(
"""
class A:
def __new__(cls) -> 'A':
return object.__new__(cls)
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type
class A:
def __new__(cls) -> 'A':
A = cls
memo = TypeCheckMemo(globals(), locals(), self_type=cls)
return check_return_type('A.__new__', object.__new__(cls), A, memo)
"""
).strip()
)
def test_local_function() -> None:
node = parse(
dedent(
"""
def wrapper():
def foo(x: int) -> int:
return x
def foo2(x: int) -> int:
return x
return foo
"""
)
)
TypeguardTransformer(["wrapper", "foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
def wrapper():
def foo(x: int) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_return_type
memo = TypeCheckMemo(globals(), locals())
check_argument_types('wrapper..foo', {'x': (x, int)}, memo)
return check_return_type('wrapper..foo', x, int, memo)
def foo2(x: int) -> int:
return x
return foo
"""
).strip()
)
def test_function_local_class_method() -> None:
node = parse(
dedent(
"""
def wrapper():
class Foo:
class Bar:
def method(self, x: int) -> int:
return x
def method2(self, x: int) -> int:
return x
"""
)
)
TypeguardTransformer(["wrapper", "Foo", "Bar", "method"]).visit(node)
assert (
unparse(node)
== dedent(
"""
def wrapper():
class Foo:
class Bar:
def method(self, x: int) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_return_type
memo = TypeCheckMemo(globals(), locals(), \
self_type=self.__class__)
check_argument_types('wrapper..Foo.Bar.method', \
{'x': (x, int)}, memo)
return check_return_type(\
'wrapper..Foo.Bar.method', x, int, memo)
def method2(self, x: int) -> int:
return x
"""
).strip()
)
def test_keyword_only_argument() -> None:
node = parse(
dedent(
"""
def foo(*, x: int) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
def foo(*, x: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
"""
).strip()
)
def test_positional_only_argument() -> None:
node = parse(
dedent(
"""
def foo(x: int, /) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
def foo(x: int, /) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
"""
).strip()
)
def test_variable_positional_argument() -> None:
node = parse(
dedent(
"""
def foo(*args: int) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
def foo(*args: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'args': (args, tuple[int, ...])}, memo)
"""
).strip()
)
def test_variable_keyword_argument() -> None:
node = parse(
dedent(
"""
def foo(**kwargs: int) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
def foo(**kwargs: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'kwargs': (kwargs, dict[str, int])}, memo)
"""
).strip()
)
class TestTypecheckingImport:
"""
Test that annotations imported conditionally on typing.TYPE_CHECKING are not used in
run-time checks.
"""
def test_direct_references(self) -> None:
node = parse(
dedent(
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import typing
from typing import Hashable, Sequence
def foo(x: Hashable, y: typing.Collection, *args: Hashable, \
**kwargs: typing.Collection) -> Sequence:
bar: typing.Collection
baz: Hashable = 1
return (1, 2)
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import typing
from typing import Hashable, Sequence
def foo(x: Hashable, y: typing.Collection, *args: Hashable, \
**kwargs: typing.Collection) -> Sequence:
bar: typing.Collection
baz: Hashable = 1
return (1, 2)
"""
).strip()
)
def test_collection_parameter(self) -> None:
node = parse(
dedent(
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from nonexistent import FooBar
def foo(x: list[FooBar]) -> list[FooBar]:
return x
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, check_return_type
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from nonexistent import FooBar
def foo(x: list[FooBar]) -> list[FooBar]:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, list)}, memo)
return check_return_type('foo', x, list, memo)
"""
).strip()
)
def test_variable_annotations(self) -> None:
node = parse(
dedent(
"""
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from nonexistent import FooBar
def foo(x: Any) -> None:
y: FooBar = x
z: list[FooBar] = [y]
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_variable_assignment
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from nonexistent import FooBar
def foo(x: Any) -> None:
memo = TypeCheckMemo(globals(), locals())
y: FooBar = x
z: list[FooBar] = check_variable_assignment([y], 'z', list, \
memo)
"""
).strip()
)
def test_generator_function(self) -> None:
node = parse(
dedent(
"""
from typing import Any, TYPE_CHECKING
from collections.abc import Generator
if TYPE_CHECKING:
import typing
from typing import Hashable, Sequence
def foo(x: Hashable, y: typing.Collection) -> Generator[Hashable, \
typing.Collection, Sequence]:
yield 'foo'
return (1, 2)
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any, TYPE_CHECKING
from collections.abc import Generator
if TYPE_CHECKING:
import typing
from typing import Hashable, Sequence
def foo(x: Hashable, y: typing.Collection) -> Generator[Hashable, \
typing.Collection, Sequence]:
yield 'foo'
return (1, 2)
"""
).strip()
)
def test_optional(self) -> None:
node = parse(
dedent(
"""
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Optional[Hashable]) -> Optional[Hashable]:
return x
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Optional[Hashable]) -> Optional[Hashable]:
return x
"""
).strip()
)
def test_subscript_within_union(self) -> None:
# Regression test for #397
node = parse(
dedent(
"""
from typing import Any, Iterable, Union, TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Union[Iterable[Hashable], str]) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
from typing import Any, Iterable, Union, TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Union[Iterable[Hashable], str]) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, Union[Iterable, str])}, memo)
"""
).strip()
)
def test_pep604_union(self) -> None:
node = parse(
dedent(
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Hashable | str) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Hashable
def foo(x: Hashable | str) -> None:
pass
"""
).strip()
)
class TestAssign:
def test_annotated_assign(self) -> None:
node = parse(
dedent(
"""
def foo() -> None:
x: int = otherfunc()
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_variable_assignment
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int = check_variable_assignment(otherfunc(), 'x', int, memo)
"""
).strip()
)
def test_varargs_assign(self) -> None:
node = parse(
dedent(
"""
def foo(*args: int) -> None:
args = (5,)
"""
)
)
TypeguardTransformer().visit(node)
if sys.version_info < (3, 9):
extra_import = "from typing import Tuple\n"
tuple_type = "Tuple"
else:
extra_import = ""
tuple_type = "tuple"
assert (
unparse(node)
== dedent(
f"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_variable_assignment
{extra_import}
def foo(*args: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {{'args': (args, \
{tuple_type}[int, ...])}}, memo)
args = check_variable_assignment((5,), 'args', \
{tuple_type}[int, ...], memo)
"""
).strip()
)
def test_kwargs_assign(self) -> None:
node = parse(
dedent(
"""
def foo(**kwargs: int) -> None:
kwargs = {'a': 5}
"""
)
)
TypeguardTransformer().visit(node)
if sys.version_info < (3, 9):
extra_import = "from typing import Dict\n"
dict_type = "Dict"
else:
extra_import = ""
dict_type = "dict"
assert (
unparse(node)
== dedent(
f"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_variable_assignment
{extra_import}
def foo(**kwargs: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {{'kwargs': (kwargs, \
{dict_type}[str, int])}}, memo)
kwargs = check_variable_assignment({{'a': 5}}, 'kwargs', \
{dict_type}[str, int], memo)
"""
).strip()
)
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="Requires Python < 3.10")
def test_pep604_assign(self) -> None:
node = parse(
dedent(
"""
Union = None
def foo() -> None:
x: int | str = otherfunc()
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_variable_assignment
from typing import Union as Union_
Union = None
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int | str = check_variable_assignment(otherfunc(), 'x', \
Union_[int, str], memo)
"""
).strip()
)
def test_multi_assign(self) -> None:
node = parse(
dedent(
"""
def foo() -> None:
x: int
z: bytes
x, y, z = otherfunc()
"""
)
)
TypeguardTransformer().visit(node)
target = "x, y, z" if sys.version_info >= (3, 11) else "(x, y, z)"
assert (
unparse(node)
== dedent(
f"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_multi_variable_assignment
from typing import Any
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int
z: bytes
{target} = check_multi_variable_assignment(otherfunc(), \
[{{'x': int, 'y': Any, 'z': bytes}}], memo)
"""
).strip()
)
def test_star_multi_assign(self) -> None:
node = parse(
dedent(
"""
def foo() -> None:
x: int
z: bytes
x, *y, z = otherfunc()
"""
)
)
TypeguardTransformer().visit(node)
target = "x, *y, z" if sys.version_info >= (3, 11) else "(x, *y, z)"
assert (
unparse(node)
== dedent(
f"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_multi_variable_assignment
from typing import Any
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int
z: bytes
{target} = check_multi_variable_assignment(otherfunc(), \
[{{'x': int, '*y': Any, 'z': bytes}}], memo)
"""
).strip()
)
def test_assignment_annotated_argument(self) -> None:
node = parse(
dedent(
"""
def foo(x: int) -> None:
x = 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_variable_assignment
def foo(x: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
x = check_variable_assignment(6, 'x', int, memo)
"""
).strip()
)
def test_assignment_expr(self) -> None:
node = parse(
dedent(
"""
def foo() -> None:
x: int
if x := otherfunc():
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_variable_assignment
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int
if (x := check_variable_assignment(otherfunc(), 'x', int, \
memo)):
pass
"""
).strip()
)
def test_assignment_expr_annotated_argument(self) -> None:
node = parse(
dedent(
"""
def foo(x: int) -> None:
if x := otherfunc():
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_variable_assignment
def foo(x: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
if (x := check_variable_assignment(otherfunc(), 'x', int, memo)):
pass
"""
).strip()
)
@pytest.mark.parametrize(
"operator, function",
[
pytest.param("+=", "iadd", id="add"),
pytest.param("-=", "isub", id="subtract"),
pytest.param("*=", "imul", id="multiply"),
pytest.param("@=", "imatmul", id="matrix_multiply"),
pytest.param("/=", "itruediv", id="div"),
pytest.param("//=", "ifloordiv", id="floordiv"),
pytest.param("**=", "ipow", id="power"),
pytest.param("<<=", "ilshift", id="left_bitshift"),
pytest.param(">>=", "irshift", id="right_bitshift"),
pytest.param("&=", "iand", id="and"),
pytest.param("^=", "ixor", id="xor"),
pytest.param("|=", "ior", id="or"),
],
)
def test_augmented_assignment(self, operator: str, function: str) -> None:
node = parse(
dedent(
f"""
def foo() -> None:
x: int
x {operator} 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
f"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_variable_assignment
from operator import {function}
def foo() -> None:
memo = TypeCheckMemo(globals(), locals())
x: int
x = check_variable_assignment({function}(x, 6), 'x', int, memo)
"""
).strip()
)
def test_augmented_assignment_non_annotated(self) -> None:
node = parse(
dedent(
"""
def foo() -> None:
x = 1
x += 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
def foo() -> None:
x = 1
x += 6
"""
).strip()
)
def test_augmented_assignment_annotated_argument(self) -> None:
node = parse(
dedent(
"""
def foo(x: int) -> None:
x += 6
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, \
check_variable_assignment
from operator import iadd
def foo(x: int) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, int)}, memo)
x = check_variable_assignment(iadd(x, 6), 'x', int, memo)
"""
).strip()
)
def test_argname_typename_conflicts() -> None:
node = parse(
dedent(
"""
from collections.abc import Generator
def foo(x: kwargs, /, y: args, *args: x, baz: x, **kwargs: y) -> \
Generator[args, x, kwargs]:
yield y
return x
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from collections.abc import Generator
def foo(x: kwargs, /, y: args, *args: x, baz: x, **kwargs: y) -> \
Generator[args, x, kwargs]:
yield y
return x
"""
).strip()
)
def test_local_assignment_typename_conflicts() -> None:
node = parse(
dedent(
"""
def foo() -> int:
int = 6
return int
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
def foo() -> int:
int = 6
return int
"""
).strip()
)
def test_local_ann_assignment_typename_conflicts() -> None:
node = parse(
dedent(
"""
from typing import Any
def foo() -> int:
int: Any = 6
return int
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any
def foo() -> int:
int: Any = 6
return int
"""
).strip()
)
def test_local_named_expr_typename_conflicts() -> None:
node = parse(
dedent(
"""
from typing import Any
def foo() -> int:
if (int := 6):
pass
return int
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Any
def foo() -> int:
if (int := 6):
pass
return int
"""
).strip()
)
def test_dont_leave_empty_ast_container_nodes() -> None:
# Regression test for #352
node = parse(
dedent(
"""
if True:
class A:
...
def func():
...
def foo(x: str) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
if True:
pass
def foo(x: str) -> None:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, str)}, memo)
"""
).strip()
)
def test_dont_leave_empty_ast_container_nodes_2() -> None:
# Regression test for #352
node = parse(
dedent(
"""
try:
class A:
...
def func():
...
except:
class A:
...
def func():
...
def foo(x: str) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
try:
pass
except:
pass
def foo(x: str) -> None:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, str)}, memo)
"""
).strip()
)
class TestTypeShadowedByArgument:
def test_typing_union(self) -> None:
# Regression test for #394
node = parse(
dedent(
"""
from __future__ import annotations
from typing import Union
class A:
...
def foo(A: Union[A, None]) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
from __future__ import annotations
from typing import Union
def foo(A: Union[A, None]) -> None:
pass
"""
).strip()
)
def test_pep604_union(self) -> None:
# Regression test for #395
node = parse(
dedent(
"""
from __future__ import annotations
class A:
...
def foo(A: A | None) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
from __future__ import annotations
def foo(A: A | None) -> None:
pass
"""
).strip()
)
def test_dont_parse_annotated_2nd_arg() -> None:
# Regression test for #352
node = parse(
dedent(
"""
from typing import Annotated
def foo(x: Annotated[str, 'foo bar']) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
from typing import Annotated
def foo(x: Annotated[str, 'foo bar']) -> None:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, Annotated[str, 'foo bar'])}, memo)
"""
).strip()
)
def test_respect_docstring() -> None:
# Regression test for #359
node = parse(
dedent(
'''
def foo() -> int:
"""This is a docstring."""
return 1
'''
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
'''
def foo() -> int:
"""This is a docstring."""
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type
memo = TypeCheckMemo(globals(), locals())
return check_return_type('foo', 1, int, memo)
'''
).strip()
)
def test_respect_future_import() -> None:
# Regression test for #385
node = parse(
dedent(
'''
"""module docstring"""
from __future__ import annotations
def foo() -> int:
return 1
'''
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
'''
"""module docstring"""
from __future__ import annotations
from typeguard import TypeCheckMemo
from typeguard._functions import check_return_type
def foo() -> int:
memo = TypeCheckMemo(globals(), locals())
return check_return_type('foo', 1, int, memo)
'''
).strip()
)
def test_literal() -> None:
# Regression test for #399
node = parse(
dedent(
"""
from typing import Literal
def foo(x: Literal['a', 'b']) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
from typing import Literal
def foo(x: Literal['a', 'b']) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, Literal['a', 'b'])}, memo)
"""
).strip()
)
python-typeguard-4.1.5/tests/test_typechecked.py 0000664 0000000 0000000 00000042263 14545071262 0022124 0 ustar 00root root 0000000 0000000 import asyncio
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from textwrap import dedent
from typing import (
Any,
AsyncGenerator,
AsyncIterable,
AsyncIterator,
Dict,
Generator,
Iterable,
Iterator,
List,
)
from unittest.mock import Mock
import pytest
from typeguard import TypeCheckError, typechecked
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self
class TestCoroutineFunction:
def test_success(self):
@typechecked
async def foo(a: int) -> str:
return "test"
assert asyncio.run(foo(1)) == "test"
def test_bad_arg(self):
@typechecked
async def foo(a: int) -> str:
return "test"
with pytest.raises(
TypeCheckError, match=r'argument "a" \(str\) is not an instance of int'
):
asyncio.run(foo("foo"))
def test_bad_return(self):
@typechecked
async def foo(a: int) -> str:
return 1
with pytest.raises(
TypeCheckError, match=r"return value \(int\) is not an instance of str"
):
asyncio.run(foo(1))
def test_any_return(self):
@typechecked
async def foo() -> Any:
return 1
assert asyncio.run(foo()) == 1
class TestGenerator:
def test_generator_bare(self):
@typechecked
def genfunc() -> Generator:
val1 = yield 2
val2 = yield 3
val3 = yield 4
return [val1, val2, val3]
gen = genfunc()
with pytest.raises(StopIteration) as exc:
value = next(gen)
while True:
value = gen.send(str(value))
assert isinstance(value, int)
assert exc.value.value == ["2", "3", "4"]
def test_generator_annotated(self):
@typechecked
def genfunc() -> Generator[int, str, List[str]]:
val1 = yield 2
val2 = yield 3
val3 = yield 4
return [val1, val2, val3]
gen = genfunc()
with pytest.raises(StopIteration) as exc:
value = next(gen)
while True:
value = gen.send(str(value))
assert isinstance(value, int)
assert exc.value.value == ["2", "3", "4"]
def test_generator_iterable_bare(self):
@typechecked
def genfunc() -> Iterable:
yield 2
yield 3
yield 4
values = list(genfunc())
assert values == [2, 3, 4]
def test_generator_iterable_annotated(self):
@typechecked
def genfunc() -> Iterable[int]:
yield 2
yield 3
yield 4
values = list(genfunc())
assert values == [2, 3, 4]
def test_generator_iterator_bare(self):
@typechecked
def genfunc() -> Iterator:
yield 2
yield 3
yield 4
values = list(genfunc())
assert values == [2, 3, 4]
def test_generator_iterator_annotated(self):
@typechecked
def genfunc() -> Iterator[int]:
yield 2
yield 3
yield 4
values = list(genfunc())
assert values == [2, 3, 4]
def test_bad_yield_as_generator(self):
@typechecked
def genfunc() -> Generator[int, str, None]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen)
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_bad_yield_as_iterable(self):
@typechecked
def genfunc() -> Iterable[int]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen)
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_bad_yield_as_iterator(self):
@typechecked
def genfunc() -> Iterator[int]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen)
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_generator_bad_send(self):
@typechecked
def genfunc() -> Generator[int, str, None]:
yield 1
yield 2
pass
gen = genfunc()
next(gen)
with pytest.raises(TypeCheckError) as exc:
gen.send(2)
exc.match(r"value sent to generator \(int\) is not an instance of str")
def test_generator_bad_return(self):
@typechecked
def genfunc() -> Generator[int, str, str]:
yield 1
return 6
gen = genfunc()
next(gen)
with pytest.raises(TypeCheckError) as exc:
gen.send("foo")
exc.match(r"return value \(int\) is not an instance of str")
def test_return_generator(self):
@typechecked
def genfunc() -> Generator[int, None, None]:
yield 1
@typechecked
def foo() -> Generator[int, None, None]:
return genfunc()
foo()
class TestAsyncGenerator:
def test_async_generator_bare(self):
@typechecked
async def genfunc() -> AsyncGenerator:
values.append((yield 2))
values.append((yield 3))
values.append((yield 4))
async def run_generator():
gen = genfunc()
value = await gen.asend(None)
with pytest.raises(StopAsyncIteration):
while True:
value = await gen.asend(str(value))
assert isinstance(value, int)
values = []
asyncio.run(run_generator())
assert values == ["2", "3", "4"]
def test_async_generator_annotated(self):
@typechecked
async def genfunc() -> AsyncGenerator[int, str]:
values.append((yield 2))
values.append((yield 3))
values.append((yield 4))
async def run_generator():
gen = genfunc()
value = await gen.asend(None)
with pytest.raises(StopAsyncIteration):
while True:
value = await gen.asend(str(value))
assert isinstance(value, int)
values = []
asyncio.run(run_generator())
assert values == ["2", "3", "4"]
def test_generator_iterable_bare(self):
@typechecked
async def genfunc() -> AsyncIterable:
yield 2
yield 3
yield 4
async def run_generator():
return [value async for value in genfunc()]
assert asyncio.run(run_generator()) == [2, 3, 4]
def test_generator_iterable_annotated(self):
@typechecked
async def genfunc() -> AsyncIterable[int]:
yield 2
yield 3
yield 4
async def run_generator():
return [value async for value in genfunc()]
assert asyncio.run(run_generator()) == [2, 3, 4]
def test_generator_iterator_bare(self):
@typechecked
async def genfunc() -> AsyncIterator:
yield 2
yield 3
yield 4
async def run_generator():
return [value async for value in genfunc()]
assert asyncio.run(run_generator()) == [2, 3, 4]
def test_generator_iterator_annotated(self):
@typechecked
async def genfunc() -> AsyncIterator[int]:
yield 2
yield 3
yield 4
async def run_generator():
return [value async for value in genfunc()]
assert asyncio.run(run_generator()) == [2, 3, 4]
def test_async_bad_yield_as_generator(self):
@typechecked
async def genfunc() -> AsyncGenerator[int, str]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen.__anext__().__await__())
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_async_bad_yield_as_iterable(self):
@typechecked
async def genfunc() -> AsyncIterable[int]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen.__anext__().__await__())
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_async_bad_yield_as_iterator(self):
@typechecked
async def genfunc() -> AsyncIterator[int]:
yield "foo"
gen = genfunc()
with pytest.raises(TypeCheckError) as exc:
next(gen.__anext__().__await__())
exc.match(r"the yielded value \(str\) is not an instance of int")
def test_async_generator_bad_send(self):
@typechecked
async def genfunc() -> AsyncGenerator[int, str]:
yield 1
yield 2
gen = genfunc()
pytest.raises(StopIteration, next, gen.__anext__().__await__())
with pytest.raises(TypeCheckError) as exc:
next(gen.asend(2).__await__())
exc.match(r"the value sent to generator \(int\) is not an instance of str")
def test_return_async_generator(self):
@typechecked
async def genfunc() -> AsyncGenerator[int, None]:
yield 1
@typechecked
def foo() -> AsyncGenerator[int, None]:
return genfunc()
foo()
def test_async_generator_iterate(self):
@typechecked
async def asyncgenfunc() -> AsyncGenerator[int, None]:
yield 1
asyncgen = asyncgenfunc()
aiterator = asyncgen.__aiter__()
exc = pytest.raises(StopIteration, aiterator.__anext__().send, None)
assert exc.value.value == 1
class TestSelf:
def test_return_valid(self):
class Foo:
@typechecked
def method(self) -> Self:
return self
Foo().method()
def test_return_invalid(self):
class Foo:
@typechecked
def method(self) -> Self:
return 1
foo = Foo()
pytest.raises(TypeCheckError, foo.method).match(
rf"the return value \(int\) is not an instance of the self type "
rf"\({__name__}\.{self.__class__.__name__}\.test_return_invalid\."
rf"\.Foo\)"
)
def test_classmethod_return_valid(self):
class Foo:
@classmethod
@typechecked
def method(cls) -> Self:
return Foo()
Foo.method()
def test_classmethod_return_invalid(self):
class Foo:
@classmethod
@typechecked
def method(cls) -> Self:
return 1
pytest.raises(TypeCheckError, Foo.method).match(
rf"the return value \(int\) is not an instance of the self type "
rf"\({__name__}\.{self.__class__.__name__}\."
rf"test_classmethod_return_invalid\.\.Foo\)"
)
def test_arg_valid(self):
class Foo:
@typechecked
def method(self, another: Self) -> None:
pass
foo = Foo()
foo2 = Foo()
foo.method(foo2)
def test_arg_invalid(self):
class Foo:
@typechecked
def method(self, another: Self) -> None:
pass
foo = Foo()
pytest.raises(TypeCheckError, foo.method, 1).match(
rf'argument "another" \(int\) is not an instance of the self type '
rf"\({__name__}\.{self.__class__.__name__}\.test_arg_invalid\."
rf"\.Foo\)"
)
def test_classmethod_arg_valid(self):
class Foo:
@classmethod
@typechecked
def method(cls, another: Self) -> None:
pass
foo = Foo()
Foo.method(foo)
def test_classmethod_arg_invalid(self):
class Foo:
@classmethod
@typechecked
def method(cls, another: Self) -> None:
pass
foo = Foo()
pytest.raises(TypeCheckError, foo.method, 1).match(
rf'argument "another" \(int\) is not an instance of the self type '
rf"\({__name__}\.{self.__class__.__name__}\."
rf"test_classmethod_arg_invalid\.\.Foo\)"
)
class TestMock:
def test_mock_argument(self):
@typechecked
def foo(x: int) -> None:
pass
foo(Mock())
def test_return_mock(self):
@typechecked
def foo() -> int:
return Mock()
foo()
def test_decorator_before_classmethod():
class Foo:
@typechecked
@classmethod
def method(cls, x: int) -> None:
pass
pytest.raises(TypeCheckError, Foo().method, "bar").match(
r'argument "x" \(str\) is not an instance of int'
)
def test_classmethod():
@typechecked
class Foo:
@classmethod
def method(cls, x: int) -> None:
pass
pytest.raises(TypeCheckError, Foo().method, "bar").match(
r'argument "x" \(str\) is not an instance of int'
)
def test_decorator_before_staticmethod():
class Foo:
@typechecked
@staticmethod
def method(x: int) -> None:
pass
pytest.raises(TypeCheckError, Foo().method, "bar").match(
r'argument "x" \(str\) is not an instance of int'
)
def test_staticmethod():
@typechecked
class Foo:
@staticmethod
def method(x: int) -> None:
pass
pytest.raises(TypeCheckError, Foo().method, "bar").match(
r'argument "x" \(str\) is not an instance of int'
)
def test_retain_dunder_attributes():
@typechecked
def foo(x: int, y: str = "foo") -> None:
"""This is a docstring."""
assert foo.__module__ == __name__
assert foo.__name__ == "foo"
assert foo.__qualname__ == "test_retain_dunder_attributes..foo"
assert foo.__doc__ == "This is a docstring."
assert foo.__defaults__ == ("foo",)
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires ast.unparse()")
def test_debug_instrumentation(monkeypatch, capsys):
monkeypatch.setattr("typeguard.config.debug_instrumentation", True)
@typechecked
def foo(a: str) -> int:
return 6
out, err = capsys.readouterr()
assert err == dedent(
"""\
Source code of test_debug_instrumentation..foo() after instrumentation:
----------------------------------------------
def foo(a: str) -> int:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types, check_return_type
memo = TypeCheckMemo(globals(), locals())
check_argument_types('test_debug_instrumentation..foo', \
{'a': (a, str)}, memo)
return check_return_type('test_debug_instrumentation..foo', 6, \
int, memo)
----------------------------------------------
"""
)
def test_keyword_argument_default():
# Regression test for #305
@typechecked
def foo(*args, x: "int | None" = None):
pass
foo()
def test_return_type_annotation_refers_to_nonlocal():
class Internal:
pass
@typechecked
def foo() -> Internal:
return Internal()
assert isinstance(foo(), Internal)
def test_existing_method_decorator():
@typechecked
class Foo:
@contextmanager
def method(self, x: int) -> None:
yield x + 1
with Foo().method(6) as value:
assert value == 7
@pytest.mark.parametrize(
"flags, expected_return_code",
[
pytest.param([], 1, id="debug"),
pytest.param(["-O"], 0, id="O"),
pytest.param(["-OO"], 0, id="OO"),
],
)
def test_typechecked_disabled_in_optimized_mode(
tmp_path: Path, flags: List[str], expected_return_code: int
):
code = dedent(
"""
from typeguard import typechecked
@typechecked
def foo(x: int) -> None:
pass
foo("a")
"""
)
script_path = tmp_path / "code.py"
script_path.write_text(code)
process = subprocess.run(
[sys.executable, *flags, str(script_path)], capture_output=True
)
assert process.returncode == expected_return_code
if process.returncode == 1:
assert process.stderr.endswith(
b'typeguard.TypeCheckError: argument "x" (str) is not an instance of '
b"int\n"
)
def test_reference_imported_name_from_method() -> None:
# Regression test for #362
@typechecked
class A:
def foo(self) -> Dict[str, Any]:
return {}
A().foo()
def test_getter_setter():
"""Regression test for #355."""
@typechecked
class Foo:
def __init__(self, x: int):
self._x = x
@property
def x(self) -> int:
return self._x
@x.setter
def x(self, value: int) -> None:
self._x = value
f = Foo(1)
f.x = 2
assert f.x == 2
with pytest.raises(TypeCheckError):
f.x = "foo"
def test_duplicate_method():
class Foo:
def x(self) -> str:
return "first"
@typechecked()
def x(self, value: int) -> str: # noqa: F811
return "second"
assert Foo().x(1) == "second"
with pytest.raises(TypeCheckError):
Foo().x("wrong")
python-typeguard-4.1.5/tests/test_union_transformer.py 0000664 0000000 0000000 00000003274 14545071262 0023405 0 ustar 00root root 0000000 0000000 import typing
from typing import Callable
import pytest
from typing_extensions import Literal
from typeguard._union_transformer import compile_type_hint, type_substitutions
eval_globals = {"Callable": Callable, "Literal": Literal, "typing": typing}
eval_globals.update(type_substitutions)
@pytest.mark.parametrize(
"inputval, expected",
[
["str | int", "Union[str, int]"],
["str | int | bytes", "Union[str, int, bytes]"],
["str | Union[int | bytes, set]", "Union[str, int, bytes, Set]"],
["str | int | Callable[..., bytes]", "Union[str, int, Callable[..., bytes]]"],
["str | int | Callable[[], bytes]", "Union[str, int, Callable[[], bytes]]"],
[
"str | int | Callable[[], bytes | set]",
"Union[str, int, Callable[[], Union[bytes, Set]]]",
],
["str | int | Literal['foo']", "Union[str, int, Literal['foo']]"],
["str | int | Literal[-1]", "Union[str, int, Literal[-1]]"],
["str | int | Literal[-1]", "Union[str, int, Literal[-1]]"],
[
'str | int | Literal["It\'s a string \'\\""]',
"Union[str, int, Literal['It\\'s a string \\'\"']]",
],
[
"typing.Tuple | typing.List | Literal[-1]",
"Union[Tuple, List, Literal[-1]]",
],
["tuple[int, ...]", "Tuple[int, ...]"],
],
)
def test_union_transformer(inputval: str, expected: str) -> None:
code = compile_type_hint(inputval)
evaluated = eval(code, eval_globals)
evaluated_repr = repr(evaluated)
evaluated_repr = evaluated_repr.replace("typing.", "")
evaluated_repr = evaluated_repr.replace("typing_extensions.", "")
assert evaluated_repr == expected
python-typeguard-4.1.5/tests/test_utils.py 0000664 0000000 0000000 00000001277 14545071262 0020774 0 ustar 00root root 0000000 0000000 import pytest
from typeguard._utils import function_name, qualified_name
from . import Child
@pytest.mark.parametrize(
"inputval, add_class_prefix, expected",
[
pytest.param(qualified_name, False, "function", id="func"),
pytest.param(Child(), False, "tests.Child", id="instance"),
pytest.param(int, False, "int", id="builtintype"),
pytest.param(int, True, "class int", id="builtintype_classprefix"),
],
)
def test_qualified_name(inputval, add_class_prefix, expected):
assert qualified_name(inputval, add_class_prefix=add_class_prefix) == expected
def test_function_name():
assert function_name(function_name) == "typeguard._utils.function_name"
python-typeguard-4.1.5/tests/test_warn_on_error.py 0000664 0000000 0000000 00000001567 14545071262 0022512 0 ustar 00root root 0000000 0000000 from typing import List
import pytest
from typeguard import TypeCheckWarning, check_type, config, typechecked, warn_on_error
def test_check_type(recwarn):
with pytest.warns(TypeCheckWarning) as warning:
check_type(1, str, typecheck_fail_callback=warn_on_error)
assert len(warning.list) == 1
assert warning.list[0].filename == __file__
assert warning.list[0].lineno == test_check_type.__code__.co_firstlineno + 2
def test_typechecked(monkeypatch, recwarn):
@typechecked
def foo() -> List[int]:
return ["aa"] # type: ignore[list-item]
monkeypatch.setattr(config, "typecheck_fail_callback", warn_on_error)
with pytest.warns(TypeCheckWarning) as warning:
foo()
assert len(warning.list) == 1
assert warning.list[0].filename == __file__
assert warning.list[0].lineno == test_typechecked.__code__.co_firstlineno + 3