pax_global_header00006660000000000000000000000064146250530060014513gustar00rootroot0000000000000052 comment=016f8139f5a0a63147d68df9558cc5584cd2c49a typeguard-4.3.0/000077500000000000000000000000001462505300600135235ustar00rootroot00000000000000typeguard-4.3.0/.github/000077500000000000000000000000001462505300600150635ustar00rootroot00000000000000typeguard-4.3.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001462505300600172465ustar00rootroot00000000000000typeguard-4.3.0/.github/ISSUE_TEMPLATE/bug_report.yaml000066400000000000000000000032461462505300600223070ustar00rootroot00000000000000name: 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 typeguard-4.3.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341462505300600212330ustar00rootroot00000000000000blank_issues_enabled: false typeguard-4.3.0/.github/ISSUE_TEMPLATE/features_request.yaml000066400000000000000000000020341462505300600235170ustar00rootroot00000000000000name: 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 typeguard-4.3.0/.github/pull_request_template.md000066400000000000000000000021501462505300600220220ustar00rootroot00000000000000 ## Changes Fixes #. ## Checklist If this is a user-facing code change, like a bugfix or a new feature, please ensure that you've fulfilled the following conditions (where applicable): - [ ] You've added tests (in `tests/`) added which would fail without your patch - [ ] You've updated the documentation (in `docs/`, in case of behavior changes or new features) - [ ] You've added a new changelog entry (in `docs/versionhistory.rst`). If this is a trivial change, like a typo fix or a code reformatting, then you can ignore these instructions. ### Updating the changelog If there are no entries after the last release, use `**UNRELEASED**` as the version. If, say, your patch fixes issue #999, the entry should look like this: `* Fix big bad boo-boo in the pytest plugin (#999 _; PR by @yourgithubaccount)` If there's no issue linked, just link to your pull request instead by updating the changelog after you've created the PR. typeguard-4.3.0/.github/workflows/000077500000000000000000000000001462505300600171205ustar00rootroot00000000000000typeguard-4.3.0/.github/workflows/publish.yml000066400000000000000000000026131462505300600213130ustar00rootroot00000000000000name: 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@v4 - name: Set up Python uses: actions/setup-python@v5 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@v4 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@v4 - 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@v4 - id: changelog uses: agronholm/release-notes@v1 with: path: docs/versionhistory.rst - uses: ncipollo/release-action@v1 with: body: ${{ steps.changelog.outputs.changelog }} typeguard-4.3.0/.github/workflows/test.yml000066400000000000000000000016741462505300600206320ustar00rootroot00000000000000name: 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", "3.13", pypy-3.10] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 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 typeguard-4.3.0/.gitignore000066400000000000000000000002351462505300600155130ustar00rootroot00000000000000.project .pydevproject .idea .tox .coverage* .cache .eggs/ *.egg-info/ *.pyc __pycache__/ docs/_build/ dist/ build/ .mypy_cache/ .pytest_cache/ .ruff_cache/ typeguard-4.3.0/.pre-commit-config.yaml000066400000000000000000000016531462505300600200110ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.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.4.5 hooks: - id: ruff args: [--fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.10.0 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 ci: autoupdate_schedule: quarterly typeguard-4.3.0/.readthedocs.yml000066400000000000000000000003341462505300600166110ustar00rootroot00000000000000version: 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] typeguard-4.3.0/LICENSE000066400000000000000000000021521462505300600145300ustar00rootroot00000000000000This is the MIT license: http://www.opensource.org/licenses/mit-license.php Copyright (c) Alex Grönholm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. typeguard-4.3.0/README.rst000066400000000000000000000041231462505300600152120ustar00rootroot00000000000000.. 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/ typeguard-4.3.0/docs/000077500000000000000000000000001462505300600144535ustar00rootroot00000000000000typeguard-4.3.0/docs/api.rst000066400000000000000000000026651462505300600157670ustar00rootroot00000000000000API 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 typeguard-4.3.0/docs/conf.py000066400000000000000000000017001462505300600157500ustar00rootroot00000000000000#!/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 = "sphinx_rtd_theme" htmlhelp_basename = "typeguarddoc" intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} typeguard-4.3.0/docs/contributing.rst000066400000000000000000000067401462505300600177230ustar00rootroot00000000000000Contributing 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 typeguard-4.3.0/docs/extending.rst000066400000000000000000000106451462505300600172000ustar00rootroot00000000000000Extending Typeguard =================== .. py:currentmodule:: typeguard Adding new type checkers ------------------------ The range of types supported by Typeguard can be extended by writing a **type checker lookup function** 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 typeguard-4.3.0/docs/features.rst000066400000000000000000000210331462505300600170220ustar00rootroot00000000000000Features ========= .. 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. Protocol checking +++++++++++++++++ As of version 4.3.0, Typeguard can check instances and classes against Protocols, regardless of whether they were annotated with :decorator:`typing.runtime_checkable`. There are several limitations on the checks performed, however: * For non-callable members, only presence is checked for; no type compatibility checks are performed * For methods, only the number of positional arguments are checked against, so any added keyword-only arguments without defaults don't currently trip the checker * Likewise, argument types are not checked for compatibility 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 typeguard-4.3.0/docs/index.rst000066400000000000000000000003171462505300600163150ustar00rootroot00000000000000Typeguard ========= .. include:: ../README.rst :end-before: See the Quick links ----------- .. toctree:: :maxdepth: 1 userguide features extending contributing api versionhistory typeguard-4.3.0/docs/userguide.rst000066400000000000000000000250261462505300600172060ustar00rootroot00000000000000User 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 plugin for pytest (v7.0 or newer) 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 It is also possible to set option for the pytest plugin using pytest's own configuration. For example, here's how you might specify several options in ``pyproject.toml``: .. code-block:: toml [tool.pytest.ini_options] typeguard-packages = """ foo.bar xyz""" typeguard-debug-instrumentation = true typeguard-typecheck-fail-callback = "mypackage:failcallback" typeguard-forward-ref-policy = "ERROR" typeguard-collection-check-strategy = "ALL_ITEMS" See the next section for details on how the individual options work. .. note:: 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. typeguard-4.3.0/docs/versionhistory.rst000066400000000000000000000573711462505300600203310ustar00rootroot00000000000000Version history =============== This library adheres to `Semantic Versioning 2.0 `_. **4.3.0** (2024-05-27) - Added support for checking against static protocols - Fixed some compatibility problems when running on Python 3.13 (`#460 `_; PR by @JelleZijlstra) - Fixed test suite incompatibility with pytest 8.2 (`#461 `_) - Fixed pytest plugin crashing on pytest version older than v7.0.0 (even if it's just present) (`#343 `_) **4.2.1** (2023-03-24) - Fixed missing ``typing_extensions`` dependency for Python 3.12 (`#444 `_) - Fixed deprecation warning in the test suite on Python 3.13 (`#444 `_) **4.2.0** (2023-03-23) - Added support for specifying options for the pytest plugin via pytest config files (`#440 `_) - Avoid creating reference cycles when type checking unions (PR by Shantanu) - Fixed ``Optional[...]`` being removed from the AST if it was located within a subscript (`#442 `_) - Fixed ``TypedDict`` from ``typing_extensions`` not being recognized as one (`#443 `_) - Fixed ``typing`` types (``dict[str, int]``, ``List[str]``, etc.) not passing checks against ``type`` or ``Type`` (`#432 `_, PR by Yongxin Wang) - Fixed detection of optional fields (``NotRequired[...]``) in ``TypedDict`` when using forward references (`#424 `_) - Fixed mapping checks against Django's ``MultiValueDict`` (`#419 `_) **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 typeguard-4.3.0/pyproject.toml000066400000000000000000000051431462505300600164420ustar00rootroot00000000000000[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.10.0", ] 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", "sphinx-rtd-theme >= 1.3.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 = "--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] src = ["src"] [tool.ruff.lint] extend-select = [ "W", # pycodestyle warnings "I", # isort "PGH", # pygrep-hooks "UP", # pyupgrade "B0", # flake8-bugbear ] ignore = [ "S307", "B008", ] [tool.mypy] python_version = "3.9" strict = true pretty = true [tool.tox] legacy_tox_ini = """ [tox] envlist = pypy3, py38, py39, py310, py311, py312, py313 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 """ typeguard-4.3.0/src/000077500000000000000000000000001462505300600143125ustar00rootroot00000000000000typeguard-4.3.0/src/typeguard/000077500000000000000000000000001462505300600163165ustar00rootroot00000000000000typeguard-4.3.0/src/typeguard/__init__.py000066400000000000000000000040271462505300600204320ustar00rootroot00000000000000import 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() typeguard-4.3.0/src/typeguard/_checkers.py000066400000000000000000000752001462505300600206220ustar00rootroot00000000000000from __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 from weakref import WeakKeyDictionary try: import typing_extensions except ImportError: typing_extensions = None # type: ignore[assignment] # Must use this because typing.is_typeddict does not recognize # TypedDict from typing_extensions, and as of version 4.12.0 # typing_extensions.TypedDict is different from typing.TypedDict # on all versions. from typing_extensions import is_typeddict 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, NotRequired, TypeAlias, get_args, get_origin, ) SubclassableAny = Any else: from typing_extensions import ( Annotated, NotRequired, TypeAlias, get_args, get_origin, ) 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] = [] generic_alias_types: tuple[type, ...] = (type(List), type(List[Any])) if sys.version_info >= (3, 9): generic_alias_types += (types.GenericAlias,) protocol_check_cache: WeakKeyDictionary[ type[Any], dict[type[Any], TypeCheckError | None] ] = WeakKeyDictionary() # 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 = set(origin_type.__required_keys__) else: # py3.8 and lower required_keys = set(declared_keys) if origin_type.__total__ else set() existing_keys = set(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}") # Detect NotRequired fields which are hidden by get_type_hints() type_hints: dict[str, type] = {} for key, annotation in origin_type.__annotations__.items(): if isinstance(annotation, ForwardRef): annotation = evaluate_forwardref(annotation, memo) if get_origin(annotation) is NotRequired: required_keys.discard(key) annotation = get_args(annotation)[0] type_hints[key] = annotation 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 type_hints.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] = {} try: 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()), " " ) finally: del errors # avoid creating ref cycle 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) and not isinstance(value, generic_alias_types): 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): # type: ignore[arg-type] 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: subject: type[Any] = value if isclass(value) else type(value) if subject in protocol_check_cache: result_map = protocol_check_cache[subject] if origin_type in result_map: if exc := result_map[origin_type]: raise exc else: return # Collect a set of methods and non-method attributes present in the protocol ignored_attrs = set(dir(typing.Protocol)) | { "__annotations__", "__non_callable_proto_members__", } expected_methods: dict[str, tuple[Any, Any]] = {} expected_noncallable_members: dict[str, Any] = {} for attrname in dir(origin_type): # Skip attributes present in typing.Protocol if attrname in ignored_attrs: continue member = getattr(origin_type, attrname) if callable(member): signature = inspect.signature(member) argtypes = [ (p.annotation if p.annotation is not Parameter.empty else Any) for p in signature.parameters.values() if p.kind is not Parameter.KEYWORD_ONLY ] or Ellipsis return_annotation = ( signature.return_annotation if signature.return_annotation is not Parameter.empty else Any ) expected_methods[attrname] = argtypes, return_annotation else: expected_noncallable_members[attrname] = member for attrname, annotation in typing.get_type_hints(origin_type).items(): expected_noncallable_members[attrname] = annotation subject_annotations = typing.get_type_hints(subject) # Check that all required methods are present and their signatures are compatible result_map = protocol_check_cache.setdefault(subject, {}) try: for attrname, callable_args in expected_methods.items(): try: method = getattr(subject, attrname) except AttributeError: if attrname in subject_annotations: raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol " f"because its {attrname!r} attribute is not a method" ) from None else: raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol " f"because it has no method named {attrname!r}" ) from None if not callable(method): raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol " f"because its {attrname!r} attribute is not a callable" ) # TODO: raise exception on added keyword-only arguments without defaults try: check_callable(method, Callable, callable_args, memo) except TypeCheckError as exc: raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol " f"because its {attrname!r} method {exc}" ) from None # Check that all required non-callable members are present for attrname in expected_noncallable_members: # TODO: implement assignability checks for non-callable members if attrname not in subject_annotations and not hasattr(subject, attrname): raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol " f"because it has no attribute named {attrname!r}" ) except TypeCheckError as exc: result_map[origin_type] = exc raise else: result_map[origin_type] = None 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) typeguard-4.3.0/src/typeguard/_config.py000066400000000000000000000054361462505300600203040ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Iterable 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: Iterable[T]) -> Iterable[T]: if self is CollectionCheckStrategy.FIRST_ITEM: try: return [next(iter(collection))] except StopIteration: 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() typeguard-4.3.0/src/typeguard/_decorators.py000066400000000000000000000215111462505300600211740ustar00rootroot00000000000000from __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) typeguard-4.3.0/src/typeguard/_exceptions.py000066400000000000000000000021411462505300600212060ustar00rootroot00000000000000from 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]) typeguard-4.3.0/src/typeguard/_functions.py000066400000000000000000000242311462505300600210410ustar00rootroot00000000000000from __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()) typeguard-4.3.0/src/typeguard/_importhook.py000066400000000000000000000143651462505300600212330ustar00rootroot00000000000000from __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) typeguard-4.3.0/src/typeguard/_memo.py000066400000000000000000000024271462505300600177710ustar00rootroot00000000000000from __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 typeguard-4.3.0/src/typeguard/_pytest_plugin.py000066400000000000000000000105001462505300600217310ustar00rootroot00000000000000from __future__ import annotations import sys import warnings from typing import TYPE_CHECKING, Any, Literal 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 if TYPE_CHECKING: from pytest import Config, Parser def pytest_addoption(parser: Parser) -> None: def add_ini_option( opt_type: ( Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None ), ) -> None: parser.addini( group.options[-1].names()[0][2:], group.options[-1].attrs()["help"], opt_type, ) 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", ) add_ini_option("linelist") group.addoption( "--typeguard-debug-instrumentation", action="store_true", help="print all instrumented code to stderr", ) add_ini_option("bool") 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" ), ) add_ini_option("string") group.addoption( "--typeguard-forward-ref-policy", action="store", choices=list(ForwardRefPolicy.__members__), help=( "determines how to deal with unresolveable forward references in type " "annotations" ), ) add_ini_option("string") group.addoption( "--typeguard-collection-check-strategy", action="store", choices=list(CollectionCheckStrategy.__members__), help="determines how thoroughly to check collections (list, dict, etc)", ) add_ini_option("string") def pytest_configure(config: Config) -> None: def getoption(name: str) -> Any: return config.getoption(name.replace("-", "_")) or config.getini(name) packages: list[str] | None = [] if packages_option := config.getoption("typeguard_packages"): packages = [pkg.strip() for pkg in packages_option.split(",")] elif packages_ini := config.getini("typeguard-packages"): packages = packages_ini if packages: if packages == [":all:"]: packages = None else: 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 = getoption("typeguard-debug-instrumentation") if debug_option: global_config.debug_instrumentation = True fail_callback_option = 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 = 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 = 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 typeguard-4.3.0/src/typeguard/_suppression.py000066400000000000000000000043321462505300600214230ustar00rootroot00000000000000from __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 typeguard-4.3.0/src/typeguard/_transformer.py000066400000000000000000001276111462505300600214010ustar00rootroot00000000000000from __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" ) and not hasattr(node, "slice"): return None if 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 typeguard-4.3.0/src/typeguard/_union_transformer.py000066400000000000000000000025121462505300600226010ustar00rootroot00000000000000""" 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) typeguard-4.3.0/src/typeguard/_utils.py000066400000000000000000000122261462505300600201720ustar00rootroot00000000000000from __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, 13): from typing import get_args, get_origin def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any: return forwardref._evaluate( memo.globals, memo.locals, type_params=(), recursive_guard=frozenset() ) elif 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, recursive_guard=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() typeguard-4.3.0/src/typeguard/py.typed000066400000000000000000000000001462505300600200030ustar00rootroot00000000000000typeguard-4.3.0/tests/000077500000000000000000000000001462505300600146655ustar00rootroot00000000000000typeguard-4.3.0/tests/__init__.py000066400000000000000000000021501462505300600167740ustar00rootroot00000000000000from 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): member: int def meth(self, x: str) -> None: ... @runtime_checkable class RuntimeProtocol(Protocol): member: int def meth(self, x: str) -> None: ... typeguard-4.3.0/tests/conftest.py000066400000000000000000000017761462505300600170770ustar00rootroot00000000000000import random import re import string import sys import typing from itertools import count from pathlib import Path import pytest import typing_extensions version_re = re.compile(r"_py(\d)(\d)\.py$") pytest_plugins = ["pytester"] def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool: match = version_re.search(collection_path.name) if match: version = tuple(int(x) for x in match.groups()) if sys.version_info < version: return True return False @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 @pytest.fixture( params=[ pytest.param(typing, id="typing"), pytest.param(typing_extensions, id="typing_extensions"), ] ) def typing_provider(request): return request.param typeguard-4.3.0/tests/dummymodule.py000066400000000000000000000147101462505300600176030ustar00rootroot00000000000000"""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") if sys.version_info <= (3, 13): @no_type_check_decorator def dummy_decorator(func): return func @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" @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" @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 typeguard-4.3.0/tests/mypy/000077500000000000000000000000001462505300600156635ustar00rootroot00000000000000typeguard-4.3.0/tests/mypy/negative.py000066400000000000000000000032521462505300600200410ustar00rootroot00000000000000from 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] typeguard-4.3.0/tests/mypy/positive.py000066400000000000000000000015261462505300600201030ustar00rootroot00000000000000from 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)) typeguard-4.3.0/tests/mypy/test_type_annotations.py000066400000000000000000000056661462505300600227070ustar00rootroot00000000000000import 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") typeguard-4.3.0/tests/test_checkers.py000066400000000000000000001122211462505300600200640ustar00rootroot00000000000000import 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, TypeVar, Union, ) import pytest from typeguard import ( CollectionCheckStrategy, ForwardRefPolicy, TypeCheckError, TypeCheckMemo, TypeHintWarning, check_type, check_type_internal, suppress_type_checks, ) from typeguard._checkers import is_typeddict 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") @pytest.mark.skipif( sys.version_info >= (3, 13), reason="AnyStr is deprecated on Python 3.13" ) 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") def test_custom_dict_generator_items(self): class CustomDict(dict): def items(self): for key in self: yield key, self[key] check_type(CustomDict(a=1), Dict[str, 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], typing_provider ): class DummyDict(typing_provider.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, typing_provider): class DummyDict(typing_provider.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\'"') def test_notrequired_pass(self, typing_provider): try: NotRequired = typing_provider.NotRequired except AttributeError: pytest.skip(f"'NotRequired' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict): x: int y: "NotRequired[int]" check_type({"x": 8}, DummyDict) def test_notrequired_fail(self, typing_provider): try: NotRequired = typing_provider.NotRequired except AttributeError: pytest.skip(f"'NotRequired' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict): x: int y: "NotRequired[int]" with pytest.raises( TypeCheckError, match=r"value of key 'y' of dict is not an instance of int" ): check_type({"x": 1, "y": "foo"}, DummyDict) def test_is_typeddict(self, typing_provider): # Ensure both typing.TypedDict and typing_extensions.TypedDict are recognized class DummyDict(typing_provider.TypedDict): x: int assert is_typeddict(DummyDict) assert not is_typeddict(dict) 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" ) @pytest.mark.skipif( sys.implementation.name != "cpython", reason="Test relies on CPython's reference counting behavior", ) def test_union_reference_leak(self): leaked = True class Leak: def __del__(self): nonlocal leaked leaked = False def inner1(): leak = Leak() # noqa: F841 check_type(b"asdf", Union[str, bytes]) inner1() assert not leaked leaked = True def inner2(): leak = Leak() # noqa: F841 with pytest.raises(TypeCheckError, match="any element in the union:"): check_type(1, Union[str, bytes]) inner2() assert not leaked 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]) @pytest.mark.parametrize("check_against", [type, Type[Any]]) def test_generic_aliase(self, check_against): if sys.version_info >= (3, 9): check_type(dict[str, str], check_against) check_type(Dict, check_against) check_type(Dict[str, str], check_against) 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) @pytest.mark.parametrize( "instantiate, annotation", [ pytest.param(True, RuntimeProtocol, id="instance_runtime"), pytest.param(False, Type[RuntimeProtocol], id="class_runtime"), pytest.param(True, StaticProtocol, id="instance_static"), pytest.param(False, Type[StaticProtocol], id="class_static"), ], ) class TestProtocol: def test_member_defaultval(self, instantiate, annotation): class Foo: member = 1 def meth(self, x: str) -> None: pass subject = Foo() if instantiate else Foo for _ in range(2): # Makes sure that the cache is also exercised check_type(subject, annotation) def test_member_annotation(self, instantiate, annotation): class Foo: member: int def meth(self, x: str) -> None: pass subject = Foo() if instantiate else Foo for _ in range(2): check_type(subject, annotation) def test_attribute_missing(self, instantiate, annotation): class Foo: val = 1 def meth(self, x: str) -> None: pass clsname = f"{__name__}.TestProtocol.test_attribute_missing..Foo" subject = Foo() if instantiate else Foo for _ in range(2): pytest.raises(TypeCheckError, check_type, subject, annotation).match( f"{clsname} is not compatible with the (Runtime|Static)Protocol " f"protocol because it has no attribute named 'member'" ) def test_method_missing(self, instantiate, annotation): class Foo: member: int pattern = ( f"{__name__}.TestProtocol.test_method_missing..Foo is not " f"compatible with the (Runtime|Static)Protocol protocol because it has no " f"method named 'meth'" ) subject = Foo() if instantiate else Foo for _ in range(2): pytest.raises(TypeCheckError, check_type, subject, annotation).match( pattern ) def test_attribute_is_not_method_1(self, instantiate, annotation): class Foo: member: int meth: str pattern = ( f"{__name__}.TestProtocol.test_attribute_is_not_method_1..Foo is " f"not compatible with the (Runtime|Static)Protocol protocol because its " f"'meth' attribute is not a method" ) subject = Foo() if instantiate else Foo for _ in range(2): pytest.raises(TypeCheckError, check_type, subject, annotation).match( pattern ) def test_attribute_is_not_method_2(self, instantiate, annotation): class Foo: member: int meth = "foo" pattern = ( f"{__name__}.TestProtocol.test_attribute_is_not_method_2..Foo is " f"not compatible with the (Runtime|Static)Protocol protocol because its " f"'meth' attribute is not a callable" ) subject = Foo() if instantiate else Foo for _ in range(2): pytest.raises(TypeCheckError, check_type, subject, annotation).match( pattern ) def test_method_signature_mismatch(self, instantiate, annotation): class Foo: member: int def meth(self, x: str, y: int) -> None: pass pattern = ( rf"(class )?{__name__}.TestProtocol.test_method_signature_mismatch." rf".Foo is not compatible with the (Runtime|Static)Protocol " rf"protocol because its 'meth' method has too many mandatory positional " rf"arguments in its declaration; expected 2 but 3 mandatory positional " rf"argument\(s\) declared" ) subject = Foo() if instantiate else Foo for _ in range(2): pytest.raises(TypeCheckError, check_type, subject, annotation).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)) typeguard-4.3.0/tests/test_importhook.py000066400000000000000000000042151462505300600204730ustar00rootroot00000000000000import 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 typeguard-4.3.0/tests/test_instrumentation.py000066400000000000000000000252631462505300600215510ustar00rootroot00000000000000import 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) typeguard-4.3.0/tests/test_plugins.py000066400000000000000000000013161462505300600177600ustar00rootroot00000000000000from 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 typeguard-4.3.0/tests/test_pytest_plugin.py000066400000000000000000000047401462505300600212110ustar00rootroot00000000000000from textwrap import dedent import pytest from pytest import MonkeyPatch, Pytester from typeguard import CollectionCheckStrategy, ForwardRefPolicy, TypeCheckConfiguration @pytest.fixture def config(monkeypatch: MonkeyPatch) -> TypeCheckConfiguration: config = TypeCheckConfiguration() monkeypatch.setattr("typeguard._pytest_plugin.global_config", config) return config def test_config_options(pytester: Pytester, config: TypeCheckConfiguration) -> None: pytester.makepyprojecttoml( ''' [tool.pytest.ini_options] typeguard-packages = """ mypackage otherpackage""" typeguard-debug-instrumentation = true typeguard-typecheck-fail-callback = "mypackage:failcallback" typeguard-forward-ref-policy = "ERROR" typeguard-collection-check-strategy = "ALL_ITEMS" ''' ) pytester.makepyfile( mypackage=( dedent( """ def failcallback(): pass """ ) ) ) pytester.plugins = ["typeguard"] pytester.syspathinsert() pytestconfig = pytester.parseconfigure() assert pytestconfig.getini("typeguard-packages") == ["mypackage", "otherpackage"] assert config.typecheck_fail_callback.__name__ == "failcallback" assert config.debug_instrumentation is True assert config.forward_ref_policy is ForwardRefPolicy.ERROR assert config.collection_check_strategy is CollectionCheckStrategy.ALL_ITEMS def test_commandline_options( pytester: Pytester, config: TypeCheckConfiguration ) -> None: pytester.makepyfile( mypackage=( dedent( """ def failcallback(): pass """ ) ) ) pytester.plugins = ["typeguard"] pytester.syspathinsert() pytestconfig = pytester.parseconfigure( "--typeguard-packages=mypackage,otherpackage", "--typeguard-typecheck-fail-callback=mypackage:failcallback", "--typeguard-debug-instrumentation", "--typeguard-forward-ref-policy=ERROR", "--typeguard-collection-check-strategy=ALL_ITEMS", ) assert pytestconfig.getoption("typeguard_packages") == "mypackage,otherpackage" assert config.typecheck_fail_callback.__name__ == "failcallback" assert config.debug_instrumentation is True assert config.forward_ref_policy is ForwardRefPolicy.ERROR assert config.collection_check_strategy is CollectionCheckStrategy.ALL_ITEMS typeguard-4.3.0/tests/test_suppression.py000066400000000000000000000025631462505300600206760ustar00rootroot00000000000000import 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) typeguard-4.3.0/tests/test_transformer.py000066400000000000000000001464621462505300600206550ustar00rootroot00000000000000import 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_optional_nested(self) -> None: node = parse( dedent( """ from typing import Any, List, Optional def foo(x: List[Optional[int]]) -> None: pass """ ) ) TypeguardTransformer().visit(node) assert ( unparse(node) == dedent( """ from typeguard import TypeCheckMemo from typeguard._functions import check_argument_types from typing import Any, List, Optional def foo(x: List[Optional[int]]) -> None: memo = TypeCheckMemo(globals(), locals()) check_argument_types('foo', {'x': (x, List[Optional[int]])}, memo) """ ).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() ) typeguard-4.3.0/tests/test_typechecked.py000066400000000000000000000422631462505300600205750ustar00rootroot00000000000000import 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") typeguard-4.3.0/tests/test_union_transformer.py000066400000000000000000000032741462505300600220560ustar00rootroot00000000000000import 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 typeguard-4.3.0/tests/test_utils.py000066400000000000000000000012771462505300600174450ustar00rootroot00000000000000import 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" typeguard-4.3.0/tests/test_warn_on_error.py000066400000000000000000000015671462505300600211630ustar00rootroot00000000000000from 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