Skip to content

Commit

Permalink
Added Sphinx documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Aug 26, 2019
1 parent c243180 commit 0813122
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 131 deletions.
9 changes: 9 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type: sphinx

build:
image: latest

python:
version: "3.6"
pip_install: true
extra_requirements: [doc]
142 changes: 14 additions & 128 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,146 +4,32 @@
.. 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

This library provides run-time type checking for functions defined with argument type annotations.
This library provides run-time type checking for functions defined with `PEP 484`_ argument
(and return) type annotations.

The ``typing`` module introduced in Python 3.5 (and available on PyPI for older versions of
Python 3) is supported. See below for details.
Three principal ways to do type checking are provided, each with its pros and cons:

There are three principal ways to use type checking, each with its pros and cons:

#. calling ``check_argument_types()`` from within the function body:
#. the ``check_argument_types()`` and ``check_return_type()`` functions:

* debugger friendly (except when running with the pydev debugger with the C extension installed)
* cannot check the type of the return value
* does not work reliably with dynamically defined type hints (e.g. in nested functions)
#. decorating the function with ``@typechecked``:
#. the ``@typechecked`` decorator:

* 100% reliable at finding the function object to be checked (does not need to check the garbage
collector)
* can check the type of the return value
* wraps returned generators (async or regular) and type checks yields, sends and returns
* automatically type checks yields and sends of returned generators (regular and async)
* adds an extra frame to the call stack for every call to a decorated function
#. using ``with TypeChecker('packagename'):``:
#. the stack profiler hook (``with TypeChecker('packagename'):``):

* emits warnings instead of raising ``TypeError``
* eliminates boilerplate
* requires very few modifications to the code
* multiple TypeCheckers can be stacked/nested
* noninvasive (only records type violations; does not raise exceptions)
* does not work reliably with dynamically defined type hints (e.g. in nested functions)
* may cause problems with badly behaving debuggers or profilers
* cannot distinguish between an exception being raised and a ``None`` being returned

If a function is called with incompatible argument types or a ``@typechecked`` decorated function
returns a value incompatible with the declared type, a descriptive ``TypeError`` exception is
raised.

Type checks can be fairly expensive so it is recommended to run Python in "optimized" mode
(``python -O`` or setting the ``PYTHONOPTIMIZE`` environment variable) when running code containing
type checks in production. The optimized mode will disable the type checks, by virtue of removing
all ``assert`` statements and setting the ``__debug__`` constant to ``False``.

Using ``check_argument_types()`` and ``check_return_value()``:

.. code-block:: python3
from typeguard import check_argument_types, check_return_value
def some_function(a: int, b: float, c: str, *args: str):
assert check_argument_types()
...
assert check_return_value(retval)
return retval
Using ``@typechecked``:

.. code-block:: python3
from typeguard import typechecked
@typechecked
def some_function(a: int, b: float, c: str, *args: str) -> bool:
...
@typechecked
class SomeClass:
# All type annotated methods (static, class methods included) are type checked
# Does not apply to inner classes!
def method(x: int) -> int:
...
To enable type checks even in optimized mode:

.. code-block:: python3
@typechecked(always=True)
def foo(a: str, b: int, c: Union[str, int]) -> bool:
...
Using ``TypeChecker``:

.. code-block:: python3
from warnings import filterwarnings
from typeguard import TypeChecker, TypeWarning
# Display all TypeWarnings, not just the first one
filterwarnings('always', category=TypeWarning)
# Run your entire application inside this context block
with TypeChecker(['mypackage', 'otherpackage']):
mypackage.run_app()
# Alternatively, manually start (and stop) the checker:
checker = TypeChecker('mypackage')
checker.start()
mypackage.start_app()
Some shortcomings in ``TypeChecker``:

* If a function returns ``None``, no return (or yield) value type checking is done because ``None``
is also returned to the profiler hook when an exception is raised
* Generator yield types are checked, send types are not
* Generator yields cannot be distinguished from returns
* Async generators are ignored

.. hint:: Some other things you can do with ``TypeChecker``:

* display all warnings from the start with ``python -W always::typeguard.TypeWarning``
* redirect them to logging using ``logging.captureWarnings()``
* record warnings in your pytest test suite and fail test(s) if you get any
(see the `pytest documentation <http://doc.pytest.org/en/latest/recwarn.html>`_ about that)

To directly check a value against the specified type:

.. code-block:: python3
from typeguard import check_type
check_type('variablename', [1234], List[int])
The following types from the ``typing`` package have specialized support:

============== ============================================================
Type Notes
============== ============================================================
``Callable`` Argument count is checked but types are not (yet)
``Dict`` Keys and values are typechecked
``List`` Contents are typechecked
``NamedTuple`` Field values are typechecked
``Set`` Contents are typechecked
``Tuple`` Contents are typechecked
``Type``
``TypeVar`` Constraints, bound types and co/contravariance are supported
but custom generic types are not (due to type erasure)
``Union``
============== ============================================================


Project links
-------------
See the documentation_ for further instructions.

* `Change log <https://github.com/agronholm/typeguard/blob/master/CHANGELOG.rst>`_
* `Source repository <https://github.com/agronholm/typeguard>`_
* `Issue tracker <https://github.com/agronholm/typeguard/issues>`_
.. _PEP 484: https://www.python.org/dev/peps/pep-0484/
.. _documentation: https://typeguard.readthedocs.io/en/latest/
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 changes: 21 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
API reference
=============

.. module:: typeguard

.. autofunction:: check_type

.. autofunction:: check_argument_types

.. autofunction:: check_return_type

.. autodecorator:: typechecked

.. autoclass:: TypeChecker

.. autoexception:: TypeHintWarning

.. autoexception:: TypeWarning

.. autoclass:: ForwardRefPolicy
:members:
33 changes: 33 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
import pkg_resources

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 = pkg_resources.get_distribution('typeguard').parsed_version
version = v.base_version
release = v.public

language = None

exclude_patterns = ['_build']
pygments_style = 'sphinx'
highlight_language = 'python3'
todo_include_todos = False
add_module_names = False

html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
htmlhelp_basename = 'typeguarddoc'

intersphinx_mapping = {'python': ('http://docs.python.org/3/', None)}
130 changes: 130 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
Typeguard
=========

.. toctree::
:maxdepth: 2
:caption: Quick links:

api
versionhistory

Using type checker functions
----------------------------

Two functions are provided, potentially for use with the ``assert`` statement:

* :func:`~typeguard.check_argument_types`
* :func:`~typeguard.check_return_type`

These can be used to implement fine grained type checking for select functions.
If the function is called with incompatible types, or :func:`~typeguard.check_return_type` is used
and the return value does not match the return type annotation, then a :exc:`TypeError` is raised.

For example::

from typeguard import check_argument_types, check_return_value

def some_function(a: int, b: float, c: str, *args: str) -> bool:
assert check_argument_types()
...
assert check_return_value(retval)
return retval

When combined with the ``assert`` statement, these checks are automatically removed from the code
by the compiler when Python is executed in optimized mode (by passing the ``-O`` switch to the
interpreter, or by setting the ``PYTHONOPTIMIZE`` environment variable to ``1`` (or higher).

.. note:: This method is not reliable when used in nested functions (i.e. functions defined inside
other functions). This is because this operating mode relies on finding the correct function
object using the garbage collector, and when a nested function is running, its function object
may no longer be around anymore, as it is only bound to the closure of the enclosing function.
For this reason, it is recommended to use ``@typechecked`` instead for nested functions.

Using the decorator
-------------------

The simplest way to type checking of both argument values and the return value for a single
function is to use the ``@typechecked`` decorator::

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) are type checked.
# Does not apply to inner classes!
def method(x: int) -> int:
...

The decorator works just like the two previously mentioned checker functions except that it has no
issues with nested functions. The drawback, however, is that it adds one stack frame per wrapped
function which may make debugging harder.

When a generator function is wrapped with ``@typechecked``, the yields, sends and the return value
are also type checked against the :class:`~typing.Generator` annotation. The same applies to the
yields and sends of an async generator (annotated with :class:`~typing.AsyncGenerator`).

.. note::
The decorator also respects the optimized mode setting so it does nothing when the interpreter
is running in optimized mode.

Using the profiler hook
-----------------------

This type checking approach requires no code changes, but does come with a number of drawbacks.
It relies on setting a profiler hook in the interpreter which gets called every time a new Python
stack frame is entered or exited.

The easiest way to use this approach is to use a :class:`~typeguard.TypeChecker` as a context
manager::

from warnings import filterwarnings

from typeguard import TypeChecker, TypeWarning

# Display all TypeWarnings, not just the first one
filterwarnings('always', category=TypeWarning)

# Run your entire application inside this context block
with TypeChecker(['mypackage', 'otherpackage']):
mypackage.run_app()

Alternatively, manually start (and stop) the checker::

checker = TypeChecker(['mypackage', 'otherpackage'])
checker.start()
mypackage.start_app()

The profiler hook approach has the following drawbacks:

* Return values of ``None`` are not type checked, as they cannot be distinguished from exceptions
being raised
* The hook relies on finding the target function using the garbage collector which may make it
miss some type violations, especially with nested functions
* Generator yield types are checked, send types are not
* Generator yields cannot be distinguished from returns
* Async generators are not type checked at all

.. hint:: Some other things you can do with :class:`~typeguard.TypeChecker`:

* Display all warnings from the start with ``python -W always::typeguard.TypeWarning``
* Redirect them to logging using :func:`logging.captureWarnings`
* Record warnings in your pytest test suite and fail test(s) if you get any
(see the `pytest documentation`_ about that)

.. _pytest documentation: http://doc.pytest.org/en/latest/warnings.html#assertwarnings

Checking types directly
-----------------------

Typeguard can also be used 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 TypeError if there's a problem
check_type('variablename', [1234], List[int])

0 comments on commit 0813122

Please sign in to comment.