PyPI version License Python versions supported Format

Continuous integration test status Continuous integration test coverage Documentation status

Description

This package provides a light framework that can be used to automatically generate exceptions documentation marked up in reStructuredText.

The exdoc module details how to register exceptions, how to traced them and how to generate their documentation

Interpreter

The package has been developed and tested with Python 2.7, 3.5, 3.6 and 3.7 under Linux (Debian, Ubuntu), Apple macOS and Microsoft Windows

Installing

$ pip install pexdoc

Documentation

Available at Read the Docs

Contributing

  1. Abide by the adopted code of conduct

  2. Fork the repository from GitHub and then clone personal copy [1]:

    $ github_user=myname
    $ git clone --recurse-submodules \
          https://github.com/"${github_user}"/pexdoc.git
    Cloning into 'pexdoc'...
    ...
    $ cd pexdoc || exit 1
    $ export PEXDOC_DIR=${PWD}
    $
    
  3. The package uses two sub-modules: a set of custom Pylint plugins to help with some areas of code quality and consistency (under the pylint_plugins directory), and a lightweight package management framework (under the pypkg directory). Additionally, the pre-commit framework is used to perform various pre-commit code quality and consistency checks. To enable the pre-commit hooks:

    $ cd "${PEXDOC_DIR}" || exit 1
    $ pre-commit install
    pre-commit installed at .../pexdoc/.git/hooks/pre-commit
    $
    
  4. Ensure that the Python interpreter can find the package modules (update the $PYTHONPATH environment variable, or use sys.paths(), etc.)

    $ export PYTHONPATH=${PYTHONPATH}:${PEXDOC_DIR}
    $
    
  5. Install the dependencies (if needed, done automatically by pip):

  6. Implement a new feature or fix a bug

  7. Write a unit test which shows that the contributed code works as expected. Run the package tests to ensure that the bug fix or new feature does not have adverse side effects. If possible achieve 100% code and branch coverage of the contribution. Thorough package validation can be done via Tox and Pytest:

    $ PKG_NAME=pexdoc tox
    GLOB sdist-make: .../pexdoc/setup.py
    py27-pkg create: .../pexdoc/.tox/py27
    py27-pkg installdeps: -r.../pexdoc/requirements/tests_py27.pip, -r.../pexdoc/requirements/docs_py27.pip
    ...
      py27-pkg: commands succeeded
      py35-pkg: commands succeeded
      py36-pkg: commands succeeded
      py37-pkg: commands succeeded
      congratulations :)
    $
    

    Setuptools can also be used (Tox is configured as its virtual environment manager):

    $ PKG_NAME=pexdoc python setup.py tests
    running tests
    running egg_info
    writing pexdoc.egg-info/PKG-INFO
    writing dependency_links to pexdoc.egg-info/dependency_links.txt
    writing requirements to pexdoc.egg-info/requires.txt
    ...
      py27-pkg: commands succeeded
      py35-pkg: commands succeeded
      py36-pkg: commands succeeded
      py37-pkg: commands succeeded
      congratulations :)
    $
    

    Tox (or Setuptools via Tox) runs with the following default environments: py27-pkg, py35-pkg, py36-pkg and py37-pkg [3]. These use the 2.7, 3.5, 3.6 and 3.7 interpreters, respectively, to test all code in the documentation (both in Sphinx *.rst source files and in docstrings), run all unit tests, measure test coverage and re-build the exceptions documentation. To pass arguments to Pytest (the test runner) use a double dash (--) after all the Tox arguments, for example:

    $ PKG_NAME=pexdoc tox -e py27-pkg -- -n 4
    GLOB sdist-make: .../pexdoc/setup.py
    py27-pkg inst-nodeps: .../pexdoc/.tox/.tmp/package/1/pexdoc-1.1.4.zip
    ...
      py27-pkg: commands succeeded
      congratulations :)
    $
    

    Or use the -a Setuptools optional argument followed by a quoted string with the arguments for Pytest. For example:

    $ PKG_NAME=pexdoc python setup.py tests -a "-e py27-pkg -- -n 4"
    running tests
    ...
      py27-pkg: commands succeeded
      congratulations :)
    $
    

    There are other convenience environments defined for Tox [3]:

    • py27-repl, py35-repl, py36-repl and py37-repl run the Python 2.7, 3.5, 3.6 and 3.7 REPL, respectively, in the appropriate virtual environment. The pexdoc package is pip-installed by Tox when the environments are created. Arguments to the interpreter can be passed in the command line after a double dash (--).

    • py27-test, py35-test, py36-test and py37-test run Pytest using the Python 2.7, 3.5, 3.6 and 3.7 interpreter, respectively, in the appropriate virtual environment. Arguments to pytest can be passed in the command line after a double dash (--) , for example:

      $ PKG_NAME=pexdoc tox -e py27-test -- -x test_pexdoc.py
      GLOB sdist-make: .../pexdoc/setup.py
      py27-pkg inst-nodeps: .../pexdoc/.tox/.tmp/package/1/pexdoc-1.1.4.zip
      ...
        py27-pkg: commands succeeded
        congratulations :)
      $
      
    • py27-test, py35-test, py36-test and py37-test test code and branch coverage using the 2.7, 3.5, 3.6 and 3.7 interpreter, respectively, in the appropriate virtual environment. Arguments to pytest can be passed in the command line after a double dash (--). The report can be found in ${PEXDOC_DIR}/.tox/py[PV]/usr/share/pe xdoc/tests/htmlcov/index.html where [PV] stands for 2.7, 3.5, 3.6 or 3.7 depending on the interpreter used.

  8. Verify that continuous integration tests pass. The package has continuous integration configured for Linux, Apple macOS and Microsoft Windows (all via Azure DevOps).

  9. Document the new feature or bug fix (if needed). The script ${PEXDOC_DIR}/pypkg/build_docs.py re-builds the whole package documentation (re-generates images, cogs source files, etc.):

    $ "${PEXDOC_DIR}"/pypkg/build_docs.py -h
    usage: build_docs.py [-h] [-d DIRECTORY] [-r]
                         [-n NUM_CPUS] [-t]
    
    Build pexdoc package documentation
    
    optional arguments:
      -h, --help            show this help message and exit
      -d DIRECTORY, --directory DIRECTORY
                            specify source file directory
                            (default ../pexdoc)
      -r, --rebuild         rebuild exceptions documentation.
                            If no module name is given all
                            modules with auto-generated
                            exceptions documentation are
                            rebuilt
      -n NUM_CPUS, --num-cpus NUM_CPUS
                            number of CPUs to use (default: 1)
      -t, --test            diff original and rebuilt file(s)
                            (exit code 0 indicates file(s) are
                            identical, exit code 1 indicates
                            file(s) are different)
    

Footnotes

[1]All examples are for the bash shell
[2]It is assumed that all the Python interpreters are in the executables path. Source code for the interpreters can be downloaded from Python’s main site
[3](1, 2) Tox configuration largely inspired by Ionel’s codelog

Changelog

  • 1.1.4 [2019-03-21]: Dependency fix
  • 1.1.3 [2019-03-21]: Dependency update
  • 1.1.2 [2019-03-21]: Documentation and dependency update
  • 1.1.1 [2019-03-04]: Updated package management framework
  • 1.1.0 [2018-01-19]: Dropped support for Python interpreter versions 2.6, 3.3 and 3.4. Updated dependencies versions to their current versions
  • 1.0.9 [2017-02-10]: Package build enhancements and fixes
  • 1.0.8 [2017-02-07]: Python 3.6 support
  • 1.0.7 [2017-01-02]: Suppressed deprecation warnings for Python 2.6
  • 1.0.6 [2016-06-13]: Fixed Windows continuous integration failure
  • 1.0.5 [2016-06-11]: Minor documentation build bug fix
  • 1.0.4 [2016-05-13]: Documentation update
  • 1.0.3 [2016-05-11]: Documentation update
  • 1.0.2 [2016-05-11]: Documentation update
  • 1.0.1 [2016-05-11]: Documentation update
  • 1.0.0 [2016-05-01]: Final release of 1.0.0 branch
  • 1.0.0rc1 [2016-05-01]: Initial commit, forked a subset from putil PyPI package

License

The MIT License (MIT)

Copyright (c) 2013-2019 Pablo Acosta-Serafini

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.

Contents

PyPI version License Python versions supported Format

Continuous integration test status Continuous integration test coverage Documentation status

Description

This package provides a light framework that can be used to automatically generate exceptions documentation marked up in reStructuredText.

The exdoc module details how to register exceptions, how to traced them and how to generate their documentation

Interpreter

The package has been developed and tested with Python 2.7, 3.5, 3.6 and 3.7 under Linux (Debian, Ubuntu), Apple macOS and Microsoft Windows

Installing

$ pip install pexdoc

Documentation

Available at Read the Docs

Contributing

  1. Abide by the adopted code of conduct

  2. Fork the repository from GitHub and then clone personal copy [1]:

    $ github_user=myname
    $ git clone --recurse-submodules \
          https://github.com/"${github_user}"/pexdoc.git
    Cloning into 'pexdoc'...
    ...
    $ cd pexdoc || exit 1
    $ export PEXDOC_DIR=${PWD}
    $
    
  3. The package uses two sub-modules: a set of custom Pylint plugins to help with some areas of code quality and consistency (under the pylint_plugins directory), and a lightweight package management framework (under the pypkg directory). Additionally, the pre-commit framework is used to perform various pre-commit code quality and consistency checks. To enable the pre-commit hooks:

    $ cd "${PEXDOC_DIR}" || exit 1
    $ pre-commit install
    pre-commit installed at .../pexdoc/.git/hooks/pre-commit
    $
    
  4. Ensure that the Python interpreter can find the package modules (update the $PYTHONPATH environment variable, or use sys.paths(), etc.)

    $ export PYTHONPATH=${PYTHONPATH}:${PEXDOC_DIR}
    $
    
  5. Install the dependencies (if needed, done automatically by pip):

  6. Implement a new feature or fix a bug

  7. Write a unit test which shows that the contributed code works as expected. Run the package tests to ensure that the bug fix or new feature does not have adverse side effects. If possible achieve 100% code and branch coverage of the contribution. Thorough package validation can be done via Tox and Pytest:

    $ PKG_NAME=pexdoc tox
    GLOB sdist-make: .../pexdoc/setup.py
    py27-pkg create: .../pexdoc/.tox/py27
    py27-pkg installdeps: -r.../pexdoc/requirements/tests_py27.pip, -r.../pexdoc/requirements/docs_py27.pip
    ...
      py27-pkg: commands succeeded
      py35-pkg: commands succeeded
      py36-pkg: commands succeeded
      py37-pkg: commands succeeded
      congratulations :)
    $
    

    Setuptools can also be used (Tox is configured as its virtual environment manager):

    $ PKG_NAME=pexdoc python setup.py tests
    running tests
    running egg_info
    writing pexdoc.egg-info/PKG-INFO
    writing dependency_links to pexdoc.egg-info/dependency_links.txt
    writing requirements to pexdoc.egg-info/requires.txt
    ...
      py27-pkg: commands succeeded
      py35-pkg: commands succeeded
      py36-pkg: commands succeeded
      py37-pkg: commands succeeded
      congratulations :)
    $
    

    Tox (or Setuptools via Tox) runs with the following default environments: py27-pkg, py35-pkg, py36-pkg and py37-pkg [3]. These use the 2.7, 3.5, 3.6 and 3.7 interpreters, respectively, to test all code in the documentation (both in Sphinx *.rst source files and in docstrings), run all unit tests, measure test coverage and re-build the exceptions documentation. To pass arguments to Pytest (the test runner) use a double dash (--) after all the Tox arguments, for example:

    $ PKG_NAME=pexdoc tox -e py27-pkg -- -n 4
    GLOB sdist-make: .../pexdoc/setup.py
    py27-pkg inst-nodeps: .../pexdoc/.tox/.tmp/package/1/pexdoc-1.1.4.zip
    ...
      py27-pkg: commands succeeded
      congratulations :)
    $
    

    Or use the -a Setuptools optional argument followed by a quoted string with the arguments for Pytest. For example:

    $ PKG_NAME=pexdoc python setup.py tests -a "-e py27-pkg -- -n 4"
    running tests
    ...
      py27-pkg: commands succeeded
      congratulations :)
    $
    

    There are other convenience environments defined for Tox [3]:

    • py27-repl, py35-repl, py36-repl and py37-repl run the Python 2.7, 3.5, 3.6 and 3.7 REPL, respectively, in the appropriate virtual environment. The pexdoc package is pip-installed by Tox when the environments are created. Arguments to the interpreter can be passed in the command line after a double dash (--).

    • py27-test, py35-test, py36-test and py37-test run Pytest using the Python 2.7, 3.5, 3.6 and 3.7 interpreter, respectively, in the appropriate virtual environment. Arguments to pytest can be passed in the command line after a double dash (--) , for example:

      $ PKG_NAME=pexdoc tox -e py27-test -- -x test_pexdoc.py
      GLOB sdist-make: .../pexdoc/setup.py
      py27-pkg inst-nodeps: .../pexdoc/.tox/.tmp/package/1/pexdoc-1.1.4.zip
      ...
        py27-pkg: commands succeeded
        congratulations :)
      $
      
    • py27-test, py35-test, py36-test and py37-test test code and branch coverage using the 2.7, 3.5, 3.6 and 3.7 interpreter, respectively, in the appropriate virtual environment. Arguments to pytest can be passed in the command line after a double dash (--). The report can be found in ${PEXDOC_DIR}/.tox/py[PV]/usr/share/pe xdoc/tests/htmlcov/index.html where [PV] stands for 2.7, 3.5, 3.6 or 3.7 depending on the interpreter used.

  8. Verify that continuous integration tests pass. The package has continuous integration configured for Linux, Apple macOS and Microsoft Windows (all via Azure DevOps).

  9. Document the new feature or bug fix (if needed). The script ${PEXDOC_DIR}/pypkg/build_docs.py re-builds the whole package documentation (re-generates images, cogs source files, etc.):

    $ "${PEXDOC_DIR}"/pypkg/build_docs.py -h
    usage: build_docs.py [-h] [-d DIRECTORY] [-r]
                         [-n NUM_CPUS] [-t]
    
    Build pexdoc package documentation
    
    optional arguments:
      -h, --help            show this help message and exit
      -d DIRECTORY, --directory DIRECTORY
                            specify source file directory
                            (default ../pexdoc)
      -r, --rebuild         rebuild exceptions documentation.
                            If no module name is given all
                            modules with auto-generated
                            exceptions documentation are
                            rebuilt
      -n NUM_CPUS, --num-cpus NUM_CPUS
                            number of CPUs to use (default: 1)
      -t, --test            diff original and rebuilt file(s)
                            (exit code 0 indicates file(s) are
                            identical, exit code 1 indicates
                            file(s) are different)
    

Footnotes

[1]All examples are for the bash shell
[2]It is assumed that all the Python interpreters are in the executables path. Source code for the interpreters can be downloaded from Python’s main site
[3](1, 2) Tox configuration largely inspired by Ionel’s codelog

Changelog

  • 1.1.4 [2019-03-21]: Dependency fix
  • 1.1.3 [2019-03-21]: Dependency update
  • 1.1.2 [2019-03-21]: Documentation and dependency update
  • 1.1.1 [2019-03-04]: Updated package management framework
  • 1.1.0 [2018-01-19]: Dropped support for Python interpreter versions 2.6, 3.3 and 3.4. Updated dependencies versions to their current versions
  • 1.0.9 [2017-02-10]: Package build enhancements and fixes
  • 1.0.8 [2017-02-07]: Python 3.6 support
  • 1.0.7 [2017-01-02]: Suppressed deprecation warnings for Python 2.6
  • 1.0.6 [2016-06-13]: Fixed Windows continuous integration failure
  • 1.0.5 [2016-06-11]: Minor documentation build bug fix
  • 1.0.4 [2016-05-13]: Documentation update
  • 1.0.3 [2016-05-11]: Documentation update
  • 1.0.2 [2016-05-11]: Documentation update
  • 1.0.1 [2016-05-11]: Documentation update
  • 1.0.0 [2016-05-01]: Final release of 1.0.0 branch
  • 1.0.0rc1 [2016-05-01]: Initial commit, forked a subset from putil PyPI package

License

The MIT License (MIT)

Copyright (c) 2013-2019 Pablo Acosta-Serafini

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.

API

exdoc module

This module can be used to automatically generate exceptions documentation marked up in reStructuredText with help from cog and the pexdoc.exh module.

The exceptions to auto-document need to be defined with either the pexdoc.exh.ExHandle.addex() function, the pexdoc.exh.ExHandle.addai() function or via contracts of the pexdoc.pcontracts module, and “traced” before the documentation is generated. In general tracing consists of calling the methods, functions and/or class properties so that all the required exceptions are covered (exceptions raised by contracts are automatically traced when the functions with the contracts are used). A convenient way of tracing a module is to simply run its test suite, provided that it covers the exceptions that need to be documented (for maximum speed the unit tests that cover the exceptions may be segregated so that only these tests need to be executed).

For example, it is desired to auto-document the exceptions of a module my_module.py, which has tests in test_my_module.py. Then a tracing module trace_my_module.py can be created to leverage the already written tests:

# trace_my_module_1.py
# Option 1: use already written test bench
from __future__ import print_function
import copy, os, pytest, pexdoc


def trace_module(no_print=True):
    """Trace my_module exceptions."""
    pwd = os.path.dirname(__file__)
    script_name = os.path.join(pwd, "test_my_module.py")
    with pexdoc.ExDocCxt() as exdoc_obj:
        if pytest.main(["-s", "-vv", "-x", "{0}".format(script_name)]):
            raise RuntimeError("Tracing did not complete successfully")
    if not no_print:
        module_prefix = "docs.support.my_module."
        callable_names = ["func", "MyClass.value"]
        for callable_name in callable_names:
            callable_name = module_prefix + callable_name
            print("\nCallable: {0}".format(callable_name))
            print(exdoc_obj.get_sphinx_doc(callable_name, width=70))
            print("\n")
    return copy.copy(exdoc_obj)


if __name__ == "__main__":
    trace_module(False)

The context manager pexdoc.exdoc.ExDocCxt sets up the tracing environment and returns a pexdoc.exdoc.ExDoc object that can the be used in the documentation string of each callable to extract the exceptions documentation. In this example it is assumed that the tests are written using pytest, but any test framework can be used. Another way to trace the module is to simply call all the functions, methods or class properties that need to be documented. For example:

# trace_my_module_2.py
# Option 2: manually use all callables to document
from __future__ import print_function
import copy, pexdoc, docs.support.my_module


def trace_module(no_print=True):
    """Trace my_module_original exceptions."""
    with pexdoc.ExDocCxt() as exdoc_obj:
        try:
            docs.support.my_module.func("John")
            obj = docs.support.my_module.MyClass()
            obj.value = 5
            obj.value
        except:
            raise RuntimeError("Tracing did not complete successfully")
    if not no_print:
        module_prefix = "docs.support.my_module."
        callable_names = ["func", "MyClass.value"]
        for callable_name in callable_names:
            callable_name = module_prefix + callable_name
            print("\nCallable: {0}".format(callable_name))
            print(exdoc_obj.get_sphinx_doc(callable_name, width=70))
            print("\n")
    return copy.copy(exdoc_obj)


if __name__ == "__main__":
    trace_module(False)

And the actual module my_module code is (before auto-documentation):

"""
Test module.

[[[cog
import os, sys
sys.path.append(os.environ['TRACER_DIR'])
import trace_my_module_1
exobj = trace_my_module_1.trace_module(no_print=True)
]]]
[[[end]]]
"""
# my_module.py

import pexdoc


def func(name):
    r"""
    Print your name.

    :param name: Name to print
    :type  name: string

    .. [[[cog cog.out(exobj.get_sphinx_autodoc(width=69))]]]
    .. [[[end]]]

    """
    # Raise condition evaluated in same call as exception addition
    pexdoc.addex(TypeError, "Argument `name` is not valid", not isinstance(name, str))
    return "My name is {0}".format(name)


class MyClass(object):
    """Store a value."""

    def __init__(self, value=None):  # noqa
        self._value = None if not value else value

    def _get_value(self):
        # Raise condition not evaluated in same call as
        # exception additions
        exobj = pexdoc.addex(RuntimeError, "Attribute `value` not set")
        exobj(not self._value)
        return self._value

    def _set_value(self, value):
        exobj = pexdoc.addex(RuntimeError, "Argument `value` is not valid")
        exobj(not isinstance(value, int))
        self._value = value

    value = property(_get_value, _set_value)
    r"""
    Set or return a value

    :type:  integer
    :rtype: integer or None

    .. [[[cog cog.out(exobj.get_sphinx_autodoc(width=69))]]]
    .. [[[end]]]
    """

A simple shell script can be written to automate the cogging of the my_module.py file:

#!/bin/bash
set -e

finish() {
    export TRACER_DIR=""
    cd ${cpwd}
}
trap finish EXIT

input_file=${1:-my_module.py}
output_file=${2:-my_module.py}
export TRACER_DIR=$(dirname ${input_file})
cog.py -e -x -o ${input_file}.tmp ${input_file} > /dev/null && \
    mv -f ${input_file}.tmp ${input_file}
cog.py -e -o ${input_file}.tmp ${input_file} > /dev/null && \
    mv -f ${input_file}.tmp ${output_file}

After the script is run and the auto-documentation generated, each callable has a reStructuredText marked-up :raises: section:

"""
Test module.

[[[cog
import os, sys
sys.path.append(os.environ['TRACER_DIR'])
import trace_my_module_1
exobj = trace_my_module_1.trace_module(no_print=True)
]]]
[[[end]]]
"""
# my_module_ref.py

import pexdoc


def func(name):
    r"""
    Print your name.

    :param name: Name to print
    :type  name: string

    .. [[[cog cog.out(exobj.get_sphinx_autodoc(width=69))]]]
    .. Auto-generated exceptions documentation for
    .. docs.support.my_module.func

    :raises: TypeError (Argument \`name\` is not valid)

    .. [[[end]]]

    """
    # Raise condition evaluated in same call as exception addition
    pexdoc.addex(TypeError, "Argument `name` is not valid", not isinstance(name, str))
    return "My name is {0}".format(name)


class MyClass(object):
    """Store a value."""

    def __init__(self, value=None):  # noqa
        self._value = None if not value else value

    def _get_value(self):
        # Raise condition not evaluated in same call as
        # exception additions
        exobj = pexdoc.addex(RuntimeError, "Attribute `value` not set")
        exobj(not self._value)
        return self._value

    def _set_value(self, value):
        exobj = pexdoc.addex(RuntimeError, "Argument `value` is not valid")
        exobj(not isinstance(value, int))
        self._value = value

    value = property(_get_value, _set_value)
    r"""
    Set or return a value

    :type:  integer
    :rtype: integer or None

    .. [[[cog cog.out(exobj.get_sphinx_autodoc(width=69))]]]
    .. Auto-generated exceptions documentation for
    .. docs.support.my_module.MyClass.value

    :raises:
     * When assigned

       * RuntimeError (Argument \`value\` is not valid)

     * When retrieved

       * RuntimeError (Attribute \`value\` not set)

    .. [[[end]]]
    """

Warning

Due to the limited introspection capabilities of class properties, only properties defined using the property built-in function can be documented with pexdoc.exdoc.ExDoc.get_sphinx_autodoc(). Properties defined by other methods can still be auto-documented with pexdoc.exdoc.ExDoc.get_sphinx_doc() and explicitly providing the method/function name.

Context managers

class pexdoc.exdoc.ExDocCxt(exclude=None, pickle_fname=None, in_callables_fname=None, out_callables_fname=None)

Bases: object

Context manager to simplify exception tracing.

This manager sets up the tracing environment and returns a pexdoc.ExDoc object that can the be used in the documentation string of each callable to extract the exceptions documentation with either pexdoc.ExDoc.get_sphinx_doc() or pexdoc.ExDoc.get_sphinx_autodoc().

Parameters:
  • exclude (list of strings or None) – Module exclusion list. A particular callable in an otherwise fully qualified name is omitted if it belongs to a module in this list. If None all callables are included
  • pickle_fname (FileName or None) – File name to pickle traced exception handler (useful for debugging purposes). If None all pickle file is created
  • in_callables_fname (FileNameExists or None) – File name that contains traced modules information. File can be produced by either the pexdoc.Callables.save() or pexdoc.exh.ExHandle.save_callables() methods
  • out_callables_fname (FileNameExists or None) – File name to save traced modules information to in JSON format. If the file exists it is overwritten
Raises:
  • OSError (File [in_callables_fname] could not be found)
  • RuntimeError (Argument `in_callables_fname` is not valid)
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `out_callables_fname` is not valid)
  • RuntimeError (Argument `pickle_fname` is not valid)

For example:

# exdoc_example.py
from pexdoc import contract
@contract(number='int|float', frac_length='int,>=0', rjust=bool)
def peng(number, frac_length, rjust=True):
    return str(number)
>>> from __future__ import print_function
>>> import docs.support.exdoc_example
>>> from pexdoc import ExDocCxt
>>> with ExDocCxt() as exdoc_obj:
...     value = docs.support.exdoc_example.peng(1e6, 3, False)
>>> print(exdoc_obj.get_sphinx_doc('docs.support.exdoc_example.peng'))
.. Auto-generated exceptions documentation for
.. docs.support.exdoc_example.peng
<BLANKLINE>
:raises:
 * RuntimeError (Argument \`frac_length\` is not valid)
<BLANKLINE>
 * RuntimeError (Argument \`number\` is not valid)
<BLANKLINE>
 * RuntimeError (Argument \`rjust\` is not valid)
<BLANKLINE>
<BLANKLINE>

Classes

class pexdoc.exdoc.ExDoc(exh_obj, depth=None, exclude=None)

Bases: object

Generate exception documentation with reStructuredText mark-up.

Parameters:
  • exh_obj (pexdoc.exh.ExHandle) – Exception handler containing exception information for the callable(s) to be documented
  • depth (non-negative integer or None) – Default hierarchy levels to include in the exceptions per callable (see pexdoc.ExDoc.depth). If None exceptions at all depths are included
  • exclude (list of strings or None) – Default list of (potentially partial) module and callable names to exclude from exceptions per callable (see pexdoc.ExDoc.exclude). If None all callables are included
Return type:

pexdoc.ExDoc

Raises:
  • RuntimeError (Argument `depth` is not valid)
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `exh_obj` is not valid)
  • RuntimeError (Exceptions database is empty)
  • RuntimeError (Exceptions do not have a common callable)
  • ValueError (Object of argument `exh_obj` does not have any exception trace information)
get_sphinx_autodoc(depth=None, exclude=None, width=72, error=False, raised=False, no_comment=False)

Return exception list in reStructuredText auto-determining callable name.

Parameters:
  • depth (non-negative integer or None) – Hierarchy levels to include in the exceptions list (overrides default depth argument; see pexdoc.ExDoc.depth). If None exceptions at all depths are included
  • exclude (list of strings or None) – List of (potentially partial) module and callable names to exclude from exceptions list (overrides default exclude argument, see pexdoc.ExDoc.exclude). If None all callables are included
  • width (integer) – Maximum width of the lines of text (minimum 40)
  • error (boolean) – Flag that indicates whether an exception should be raised if the callable is not found in the callables exceptions database (True) or not (False)
  • raised (boolean) – Flag that indicates whether only exceptions that were raised (and presumably caught) should be documented (True) or all registered exceptions should be documented (False)
  • no_comment (boolean) – Flag that indicates whether a reStructuredText comment labeling the callable (method, function or class property) should be printed (False) or not (True) before the exceptions documentation
Raises:
  • RuntimeError (Argument `depth` is not valid)
  • RuntimeError (Argument `error` is not valid)
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `no_comment` is not valid)
  • RuntimeError (Argument `raised` is not valid)
  • RuntimeError (Argument `width` is not valid)
  • RuntimeError (Callable not found in exception list: [name])
  • RuntimeError (Unable to determine callable name)
get_sphinx_doc(name, depth=None, exclude=None, width=72, error=False, raised=False, no_comment=False)

Return an exception list marked up in reStructuredText.

Parameters:
  • name (string) – Name of the callable (method, function or class property) to generate exceptions documentation for
  • depth (non-negative integer or None) – Hierarchy levels to include in the exceptions list (overrides default depth argument; see pexdoc.ExDoc.depth). If None exceptions at all depths are included
  • exclude (list of strings or None) – List of (potentially partial) module and callable names to exclude from exceptions list (overrides default exclude argument; see pexdoc.ExDoc.exclude). If None all callables are included
  • width (integer) – Maximum width of the lines of text (minimum 40)
  • error (boolean) – Flag that indicates whether an exception should be raised if the callable is not found in the callables exceptions database (True) or not (False)
  • raised (boolean) – Flag that indicates whether only exceptions that were raised (and presumably caught) should be documented (True) or all registered exceptions should be documented (False)
  • no_comment (boolean) – Flag that indicates whether a reStructuredText comment labeling the callable (method, function or class property) should be printed (False) or not (True) before the exceptions documentation
Raises:
  • RuntimeError (Argument `depth` is not valid)
  • RuntimeError (Argument `error` is not valid)
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `no_comment` is not valid)
  • RuntimeError (Argument `raised` is not valid)
  • RuntimeError (Argument `width` is not valid)
  • RuntimeError (Callable not found in exception list: [name])
depth

Get or set the default hierarchy levels to include in the exceptions per callable.

For example, a function my_func() calls two other functions, get_data() and process_data(), and in turn get_data() calls another function, open_socket(). In this scenario, the calls hierarchy is:

my_func            <- depth = 0
├get_data          <- depth = 1
│└open_socket      <- depth = 2
└process_data      <- depth = 1

Setting depth=0 means that only exceptions raised by my_func() are going to be included in the documentation; Setting depth=1 means that only exceptions raised by my_func(), get_data() and process_data() are going to be included in the documentation; and finally setting depth=2 (in this case it has the same effects as depth=None) means that only exceptions raised by my_func(), get_data(), process_data() and open_socket() are going to be included in the documentation.

Return type:non-negative integer
Raises:RuntimeError (Argument `depth` is not valid)
exclude

Get or set default list of (potentially partial) module and callable names to exclude from exceptions per callable.

For example, ['firstmod.ex'] excludes all exceptions from modules firstmod.exh and firstmod.exdoc (it acts as r'firstmod.ex*'). In addition to these modules, ['firstmod.ex', 'secmod.peng'] excludes exceptions from the function secmod.peng.

Return type:list
Raises:RuntimeError (Argument `exclude` is not valid)

exh module

This module can be used to register exceptions and then raise them if a given condition is true. For example:

# exh_example.py
from __future__ import print_function
from pexdoc.exh import addex


def my_func(name):
    """Sample function."""
    # Add exception
    exobj = addex(TypeError, "Argument `name` is not valid")
    # Conditionally raise exception
    exobj(not isinstance(name, str))
    print("My name is {0}".format(name))
>>> import docs.support.exh_example
>>> docs.support.exh_example.my_func('Tom')
My name is Tom
>>> docs.support.exh_example.my_func(5) 
Traceback (most recent call last):
    ...
TypeError: Argument `name` is not valid

When my_func() gets called with anything but a string as an argument a TypeError exception is raised with the message 'Argument `name` is not valid'. While adding an exception with pexdoc.exh.addex() and conditionally raising it takes the same number of lines of code as an exception raised inside an if block (or less since the raise condition can be evaluated in the same pexdoc.exh.addex() call) and incurs a slight performance penalty, using the pexdoc.exh module allows for automatic documentation of the exceptions raised by any function, method or class property with the help of the pexdoc.exdoc module.

Functions

pexdoc.exh.addex(extype, exmsg, condition=None, edata=None)

Add an exception in the global exception handler.

Parameters:
  • extype (Exception type object, i.e. RuntimeError, TypeError, etc.) – Exception type; must be derived from the Exception class
  • exmsg (string) – Exception message; it can contain fields to be replaced when the exception is raised via pexdoc.ExHandle.raise_exception_if(). A field starts with the characters '*[' and ends with the characters ']*', the field name follows the same rules as variable names and is between these two sets of characters. For example, '*[fname]*' defines the fname field
  • condition (boolean or None) – Flag that indicates whether the exception is raised (True) or not (False). If None the flag is not used an no exception is raised
  • edata

    Replacement values for fields in the exception message (see pexdoc.ExHandle.add_exception() for how to define fields). Each dictionary entry can only have these two keys:

    • field (string) – Field name
    • value (any) – Field value, to be converted into a string with the format string method

    If None no field replacement is done

Return type:

(if condition is not given or None) function

Raises:
  • RuntimeError (Argument `condition` is not valid)
  • RuntimeError (Argument `edata` is not valid)
  • RuntimeError (Argument `exmsg` is not valid)
  • RuntimeError (Argument `extype` is not valid)
pexdoc.exh.addai(argname, condition=None)

Add an “AI” exception in the global exception handler.

An “AI” exception is of the type RuntimeError('Argument `*[argname]*` is not valid') where *[argname]* is the value of the argname argument

Parameters:
  • argname (string) – Argument name
  • condition (boolean or None) – Flag that indicates whether the exception is raised (True) or not (False). If None the flag is not used and no exception is raised
Return type:

(if condition is not given or None) function

Raises:
  • RuntimeError (Argument `argname` is not valid)
  • RuntimeError (Argument `condition` is not valid)
pexdoc.exh.get_exh_obj()

Return the global exception handler.

Return type:pexdoc.ExHandle if global exception handler is set, None otherwise
pexdoc.exh.get_or_create_exh_obj(full_cname=False, exclude=None, callables_fname=None)

Return global exception handler if set, otherwise create a new one and return it.

Parameters:
  • full_cname (boolean) –

    Flag that indicates whether fully qualified function/method/class property names are obtained for functions/methods/class properties that use the exception manager (True) or not (False).

    There is a performance penalty if the flag is True as the call stack needs to be traced. This argument is only relevant if the global exception handler is not set and a new one is created

  • exclude (list of strings or None) – Module exclusion list. A particular callable in an otherwise fully qualified name is omitted if it belongs to a module in this list. If None all callables are included
  • callables_fname (FileNameExists or None) – File name that contains traced modules information. File can be produced by either the pexdoc.pinspect.Callables.save() or pexdoc.ExHandle.save_callables() methods
Return type:

pexdoc.ExHandle

Raises:
  • OSError (File [callables_fname] could not be found
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `callables_fname` is not valid)
  • RuntimeError (Argument `full_cname` is not valid)
pexdoc.exh.del_exh_obj()

Delete global exception handler (if set).

pexdoc.exh.set_exh_obj(obj)

Set the global exception handler.

Parameters:obj (pexdoc.ExHandle) – Exception handler
Raises:RuntimeError (Argument `obj` is not valid)

Classes

class pexdoc.exh.ExHandle(full_cname=False, exclude=None, callables_fname=None)

Bases: object

Create exception handler.

Parameters:
  • full_cname (boolean) –

    Flag that indicates whether fully qualified function/method/class property names are obtained for functions/methods/class properties that use the exception manager (True) or not (False).

    There is a performance penalty if the flag is True as the call stack needs to be traced

  • exclude (list of strings or None) – Module exclusion list. A particular callable in an otherwise fully qualified name is omitted if it belongs to a module in this list. If None all callables are included
  • callables_fname (FileNameExists or None) – File name that contains traced modules information. File can be produced by either the pexdoc.pinspect.Callables.save() or pexdoc.ExHandle.save_callables() methods
Return type:

pexdoc.ExHandle

Raises:
  • OSError (File [callables_fname] could not be found
  • RuntimeError (Argument `exclude` is not valid)
  • RuntimeError (Argument `callables_fname` is not valid)
  • RuntimeError (Argument `full_cname` is not valid)
  • ValueError (Source for module [module_name] could not be found)
__add__(other)

Merge two objects.

Raises:
  • RuntimeError (Incompatible exception handlers)
  • TypeError (Unsupported operand type(s) for +: pexdoc.ExHandle and [other_type])

For example:

>>> import copy, pexdoc
>>> @pexdoc.contract(num=int)
... def mynumber(num):
...     return num+1
>>> @pexdoc.contract(char=str)
... def mystring(char):
...     return 'Hello '+char
>>> pexdoc.set_exh_obj(pexdoc.ExHandle())
>>> mynumber(1)
2
>>> mystring('Tom')
'Hello Tom'
>>> obj1 = copy.copy(pexdoc.get_exh_obj())
>>> pexdoc.del_exh_obj()
>>> exhobj = pexdoc.get_or_create_exh_obj()
>>> mynumber(1)
2
>>> obj2 = copy.copy(pexdoc.get_exh_obj())
>>> pexdoc.del_exh_obj()
>>> exhobj = pexdoc.get_or_create_exh_obj()
>>> mystring('Tom')
'Hello Tom'
>>> obj3 = copy.copy(pexdoc.get_exh_obj())
>>> obj1 == obj2
False
>>> obj1 == obj3
False
>>> obj1 == obj2+obj3
True
__bool__()

Return False if exception handler is empty, True otherwise.

Note

This method applies to Python 3.x

For example:

>>> from __future__ import print_function
>>> import pexdoc
>>> obj = pexdoc.ExHandle()
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: False
>>> def my_func(exhobj):
...     exhobj.add_exception('test', RuntimeError, 'Message')
>>> my_func(obj)
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: True
__copy__()

Copy object.

For example:

>>> import copy, pexdoc
>>> @pexdoc.contract(num=int)
... def mynumber(num):
...     return num+1
>>> exhobj = pexdoc.set_exh_obj(pexdoc.ExHandle())
>>> mynumber(1)
2
>>> obj1 = pexdoc.get_exh_obj()
>>> obj2 = copy.copy(obj1)
>>> obj1 == obj2
True
__eq__(other)

Test object equality.

For example:

>>> import copy, pexdoc
>>> @pexdoc.contract(num=int)
... def mynumber(num):
...     return num+1
>>> exhobj = pexdoc.set_exh_obj(pexdoc.ExHandle())
>>> mynumber(1)
2
>>> obj1 = pexdoc.get_exh_obj()
>>> obj2 = copy.copy(obj1)
>>> obj1 == obj2
True
>>> 5 == obj1
False
__iadd__(other)

Merge an object into an existing object.

Raises:
  • RuntimeError (Incompatible exception handlers)
  • TypeError (Unsupported operand type(s) for +: pexdoc.ExHandle and [other_type])

For example:

>>> import copy, pexdoc
>>> @pexdoc.contract(num=int)
... def mynumber(num):
...     return num+1
>>> @pexdoc.contract(char=str)
... def mystring(char):
...     return 'Hello '+char
>>> exhobj = pexdoc.set_exh_obj(pexdoc.ExHandle())
>>> mynumber(1)
2
>>> mystring('Tom')
'Hello Tom'
>>> obj1 = copy.copy(pexdoc.get_exh_obj())
>>> pexdoc.del_exh_obj()
>>> exhobj = pexdoc.get_or_create_exh_obj()
>>> mynumber(1)
2
>>> obj2 = copy.copy(pexdoc.get_exh_obj())
>>> pexdoc.del_exh_obj()
>>> exhobj = pexdoc.get_or_create_exh_obj()
>>> mystring('Tom')
'Hello Tom'
>>> obj3 = copy.copy(pexdoc.get_exh_obj())
>>> obj1 == obj2
False
>>> obj1 == obj3
False
>>> obj2 += obj3
>>> obj1 == obj2
True
__nonzero__()

Return False if exception handler is empty, True otherwise.

Note

This method applies to Python 2.7

For example:

>>> from __future__ import print_function
>>> import pexdoc
>>> obj = pexdoc.ExHandle()
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: False
>>> def my_func(exhobj):
...     exhobj.add_exception('test', RuntimeError, 'Message')
>>> my_func(obj)
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: True
__str__()

Return a string with a detailed description of the object’s contents.

For example:

>>> from __future__ import print_function
>>> import docs.support.exh_example
>>> pexdoc.del_exh_obj()
>>> docs.support.exh_example.my_func('Tom')
My name is Tom
>>> print(str(pexdoc.get_exh_obj())) #doctest: +ELLIPSIS
Name    : ...
Type    : TypeError
Message : Argument `name` is not valid
Function: None
add_exception(exname, extype, exmsg)

Add an exception to the handler.

Parameters:
  • exname (non-numeric string) – Exception name; has to be unique within the namespace, duplicates are eliminated
  • extype (Exception type object, i.e. RuntimeError, TypeError, etc.) –

    Exception type; must be derived from the Exception class

  • exmsg (string) – Exception message; it can contain fields to be replaced when the exception is raised via pexdoc.ExHandle.raise_exception_if(). A field starts with the characters '*[' and ends with the characters ']*', the field name follows the same rules as variable names and is between these two sets of characters. For example, '*[fname]*' defines the fname field
Return type:

tuple

The returned tuple has the following items:

  • callable id (string) first returned item, identification (as reported by the id built-in function) of the callable where the exception was added
  • exception definition (tuple), second returned item, first item is the exception type and the second item is the exception message
  • callable name (string), third returned item, callable full name (encoded with the ExHandle.encode_call() method
Raises:
  • RuntimeError (Argument `exmsg` is not valid)
  • RuntimeError (Argument `exname` is not valid)
  • RuntimeError (Argument `extype` is not valid)
decode_call(call)

Replace callable tokens with callable names.

Parameters:call (string) – Encoded callable name
Return type:string
encode_call(call)

Replace callables with tokens to reduce object memory footprint.

A callable token is an integer that denotes the order in which the callable was encountered by the encoder, i.e. the first callable encoded is assigned token 0, the second callable encoded is assigned token 1, etc.

Parameters:call (string) – Callable name
Return type:string
raise_exception_if(exname, condition, edata=None)

Raise exception conditionally.

Parameters:
  • exname (string) – Exception name
  • condition (boolean) – Flag that indicates whether the exception is raised (True) or not (False)
  • edata (dictionary, iterable of dictionaries or None) –

    Replacement values for fields in the exception message (see pexdoc.ExHandle.add_exception() for how to define fields). Each dictionary entry can only have these two keys:

    • field (string) – Field name
    • value (any) – Field value, to be converted into a string with the format string method

    If None no field replacement is done

Raises:
  • RuntimeError (Argument `condition` is not valid)
  • RuntimeError (Argument `edata` is not valid)
  • RuntimeError (Argument `exname` is not valid)
  • RuntimeError (Field [field_name] not in exception message)
  • ValueError (Exception name [name] not found’)
save_callables(callables_fname)

Save traced modules information to a JSON file.

If the file exists it is overwritten

Parameters:callables_fname (FileName) – File name
Raises:RuntimeError (Argument `callables_fname` is not valid)
callables_db

Return the callables database of the modules using the exception handler, as reported by pexdoc.pinspect.Callables.callables_db()

callables_separator

Return the character ('/') used to separate the sub-parts of fully qualified function names in pexdoc.ExHandle.callables_db() and name key in pexdoc.ExHandle.exceptions_db()

exceptions_db

Return the exceptions database. This database is a list of dictionaries that contain the following keys:

  • name (string) – Exception name of the form '[callable_identifier]/[exception_name]'. The contents of [callable_identifier] depend on the value of the argument full_cname used to create the exception handler.

    If full_cname is True, [callable_identifier] is the fully qualified callable name as it appears in the callables database (pexdoc.ExHandle.callables_db()).

    If full_cname is False, then [callable_identifier] is a decimal string representation of the callable’s code identifier as reported by the id() function.

    In either case [exception_name] is the name of the exception provided when it was defined in pexdoc.ExHandle.add_exception() (exname argument)

  • data (string) – Text of the form '[exception_type] ([exception_message])[raised]' where [exception_type] and [exception_message] are the exception type and exception message, respectively, given when the exception was defined by pexdoc.ExHandle.add_exception() (extype and exmsg arguments); and raised is an asterisk ('*') when the exception has been raised via pexdoc.ExHandle.raise_exception_if(), the empty string ('') otherwise

pcontracts module

This module is a thin wrapper around the PyContracts library that enables customization of the exception type raised and limited customization of the exception message. Additionally, custom contracts specified via pexdoc.pcontracts.new_contract() and enforced via pexdoc.pcontracts.contract() register exceptions using the pexdoc.exh module, which means that the exceptions raised by these contracts can be automatically documented using the pexdoc.exdoc module.

The way a contract is specified is identical to the decorator way of specifying a contract with the PyContracts library. By default a RuntimeError exception with the message 'Argument `*[argument_name]*` is not valid' is raised unless a custom contract specifies a different exception (the token *[argument_name]* is replaced by the argument name the contract is attached to). For example, the definitions of the custom contracts pexdoc.ptypes.file_name() and pexdoc.ptypes.file_name_exists() are:

@pexdoc.pcontracts.new_contract()
def file_name(obj):
    r"""
    Validate if an object is a legal name for a file.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not
     valid). The token \*[argument_name]\* is replaced by the name
     of the argument the contract is attached to

    :rtype: None
    """
    msg = pexdoc.pcontracts.get_exdesc()
    # Check that argument is a string
    if not isinstance(obj, str) or (isinstance(obj, str) and ("\0" in obj)):
        raise ValueError(msg)
    # If file exists, argument is a valid file name, otherwise test
    # if file can be created. User may not have permission to
    # write file, but call to os.access should not fail if the file
    # name is correct
    try:
        if not os.path.exists(obj):
            os.access(obj, os.W_OK)
    except (TypeError, ValueError):  # pragma: no cover
        raise ValueError(msg)
@pexdoc.pcontracts.new_contract(
    argument_invalid="Argument `*[argument_name]*` is not valid",
    file_not_found=(OSError, "File *[fname]* could not be found"),
)
def file_name_exists(obj):
    r"""
    Validate if an object is a legal name for a file *and* that the file exists.

    :param obj: Object
    :type  obj: any

    :raises:
     * OSError (File *[fname]* could not be found). The
       token \*[fname]\* is replaced by the *value* of the
       argument the contract is attached to

     * RuntimeError (Argument \`*[argument_name]*\` is not valid).
       The token \*[argument_name]\* is replaced by the name of
       the argument the contract is attached to

    :rtype: None
    """
    exdesc = pexdoc.pcontracts.get_exdesc()
    msg = exdesc["argument_invalid"]
    # Check that argument is a string
    if not isinstance(obj, str) or (isinstance(obj, str) and ("\0" in obj)):
        raise ValueError(msg)
    # Check that file name is valid
    try:
        os.path.exists(obj)
    except (TypeError, ValueError):  # pragma: no cover
        raise ValueError(msg)
    # Check that file exists
    obj = _normalize_windows_fname(obj)
    if not os.path.exists(obj):
        msg = exdesc["file_not_found"]
        raise ValueError(msg)

This is nearly identical to the way custom contracts are defined using the PyContracts library with two exceptions:

  1. To avoid repetition and errors, the exception messages defined in the pexdoc.pcontracts.new_contract() decorator are available in the contract definition function via pexdoc.pcontracts.get_exdesc().
  2. A PyContracts new contract can return False or raise a ValueError exception to indicate a contract breach, however a new contract specified via the pexdoc.pcontracts.new_contract() decorator has to raise a ValueError exception to indicate a contract breach.

Exceptions can be specified in a variety of ways and verbosity is minimized by having reasonable defaults (see pexdoc.pcontracts.new_contract() for a full description). What follows is a simple usage example of the two contracts shown above and the exceptions they produce:

# pcontracts_example_1.py
from __future__ import print_function
import pexdoc


@pexdoc.pcontracts.contract(name="file_name")
def print_if_fname_valid(name):
    """Sample function 1."""
    print("Valid file name: {0}".format(name))


@pexdoc.pcontracts.contract(num=int, name="file_name_exists")
def print_if_fname_exists(num, name):
    """Sample function 2."""
    print("Valid file name: [{0}] {1}".format(num, name))
>>> import os
>>> from docs.support.pcontracts_example_1 import *
>>> print_if_fname_valid('some_file.txt')
Valid file name: some_file.txt
>>> print_if_fname_valid('invalid_fname.txt\0')
Traceback (most recent call last):
    ...
RuntimeError: Argument `name` is not valid
>>> fname = os.path.join('..', 'docs', 'pcontracts.rst') #doctest: +ELLIPSIS
>>> print_if_fname_exists(10, fname)
Valid file name: [10] ...pcontracts.rst
>>> print_if_fname_exists('hello', fname)
Traceback (most recent call last):
    ...
RuntimeError: Argument `num` is not valid
>>> print_if_fname_exists(5, 'another_invalid_fname.txt\0')
Traceback (most recent call last):
    ...
RuntimeError: Argument `name` is not valid
>>> print_if_fname_exists(5, '/dev/null/some_file.txt')
Traceback (most recent call last):
    ...
OSError: File /dev/null/some_file.txt could not be found

Functions

pexdoc.pcontracts.all_disabled()

Wrap PyContracts all_disabled() function.

From the PyContracts documentation: “Returns true if all contracts are disabled”

pexdoc.pcontracts.disable_all()

Wrap PyContracts disable_all() function.

From the PyContracts documentation: “Disables all contract checks”

pexdoc.pcontracts.enable_all()

Wrap PyContracts enable_all() function.

From the PyContracts documentation: “Enables all contract checks. Can be overridden by an environment variable”

pexdoc.pcontracts.get_exdesc()

Retrieve contract exception(s) message(s).

If the custom contract is specified with only one exception the return value is the message associated with that exception; if the custom contract is specified with several exceptions, the return value is a dictionary whose keys are the exception names and whose values are the exception messages.

Raises:RuntimeError (Function object could not be found for function [function_name])
Return type:string or dictionary

For example:

# pcontracts_example_2.py
import pexdoc.pcontracts

@pexdoc.pcontracts.new_contract('Only one exception')
def custom_contract_a(name):
    msg = pexdoc.pcontracts.get_exdesc()
    if not name:
        raise ValueError(msg)

@pexdoc.pcontracts.new_contract(ex1='Empty name', ex2='Invalid name')
def custom_contract_b(name):
    msg = pexdoc.pcontracts.get_exdesc()
    if not name:
        raise ValueError(msg['ex1'])
    elif name.find('[') != -1:
        raise ValueError(msg['ex2'])

In custom_contract1() the variable msg contains the string 'Only one exception', in custom_contract2() the variable msg contains the dictionary {'ex1':'Empty name', 'ex2':'Invalid name'}.

Decorators

pexdoc.pcontracts.contract(**contract_args)

Wraps PyContracts contract() decorator (only the decorator way of specifying a contract is supported and tested). A RuntimeError exception with the message 'Argument `*[argument_name]*` is not valid' is raised when a contract is breached ('*[argument_name]*' is replaced by the argument name the contract is attached to) unless the contract is custom and specified with the pexdoc.pcontracts.new_contract() decorator. In this case the exception type and message are controlled by the custom contract specification.

pexdoc.pcontracts.new_contract(*args, **kwargs)

Defines a new (custom) contract with custom exceptions.

Raises:
  • RuntimeError (Attempt to redefine custom contract `*[contract_name]*`)
  • TypeError (Argument `contract_exceptions` is of the wrong type)
  • TypeError (Argument `contract_name` is of the wrong type)
  • TypeError (Contract exception definition is of the wrong type)
  • TypeError (Illegal custom contract exception definition)
  • ValueError (Empty custom contract exception message)
  • ValueError (Contract exception messages are not unique)
  • ValueError (Contract exception names are not unique)
  • ValueError (Multiple replacement fields to be substituted by argument value)

The decorator argument(s) is(are) the exception(s) that can be raised by the contract. The most general way to define an exception is using a 2-item tuple with the following members:

  • exception type (type) – Either a built-in exception or sub-classed from Exception. Default is RuntimeError
  • exception message (string) – Default is 'Argument `*[argument_name]*` is not valid', where the token *[argument_name]* is replaced by the argument name the contract is attached to

The order of the tuple elements is not important, i.e. the following are valid exception specifications and define the same exception:

@pexdoc.pcontracts.new_contract(ex1=(RuntimeError, 'Invalid name'))
def custom_contract1(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract(ex1=('Invalid name', RuntimeError))
def custom_contract2(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

The exception definition simplifies to just one of the exception definition tuple items if the other exception definition tuple item takes its default value. For example, the same exception is defined in these two contracts:

@pexdoc.pcontracts.new_contract(ex1=ValueError)
def custom_contract3(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract(ex1=(
    ValueError,
    'Argument `*[argument_name]*` is not valid'
))
def custom_contract4(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

and these contracts also define the same exception (but different from that of the previous example):

@pexdoc.pcontracts.new_contract(ex1='Invalid name')
def custom_contract5(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract(ex1=('Invalid name', RuntimeError))
def custom_contract6(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

In fact the exception need not be specified by keyword if the contract only uses one exception. All of the following are valid one-exception contract specifications:

@pexdoc.pcontracts.new_contract(
    (OSError, 'File could not be opened')
)
def custom_contract7(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract('Invalid name')
def custom_contract8(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract(TypeError)
def custom_contract9(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

No arguments are needed if a contract only needs a single exception and the default exception type and message suffice:

@pexdoc.pcontracts.new_contract()
def custom_contract10(arg):
    if not arg:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

For code conciseness and correctness the exception message(s) should be retrieved via the pexdoc.pcontracts.get_exdesc() function.

A PyContracts new contract can return False or raise a ValueError exception to indicate a contract breach, however a new contract specified via the pexdoc.pcontracts.new_contract() decorator has to raise a ValueError exception to indicate a contract breach.

The exception message can have substitution “tokens” of the form *[token_name]*. The token *[argument_name]* is substituted with the argument name the contract it is attached to. For example:

@pexdoc.pcontracts.new_contract((
    TypeError,
    'Argument `*[argument_name]*` has to be a string'
))
def custom_contract11(city):
    if not isinstance(city, str):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.contract(city_name='custom_contract11')
def print_city_name(city_name):
    return 'City: {0}'.format(city_name)
>>> from __future__ import print_function
>>> from docs.support.pcontracts_example_3 import print_city_name
>>> print(print_city_name('Omaha'))
City: Omaha
>>> print(print_city_name(5))   
Traceback (most recent call last):
    ...
TypeError: Argument `city_name` has to be a string

Any other token is substituted with the argument value. For example:

@pexdoc.pcontracts.new_contract((
    OSError, 'File `*[fname]*` not found'
))
def custom_contract12(fname):
    if not os.path.exists(fname):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.contract(fname='custom_contract12')
def print_fname(fname):
    print('File name to find: {0}'.format(fname))
>>> from __future__ import print_function
>>> import os
>>> from docs.support.pcontracts_example_3 import print_fname
>>> fname = os.path.join(os.sep, 'dev', 'null', '_not_a_file_')
>>> print(print_fname(fname))   
Traceback (most recent call last):
    ...
OSError: File `..._not_a_file_` not found

pinspect module

This module supplements Python’s introspection capabilities. The class pexdoc.pinspect.Callables “traces” modules and produces a database of callables (functions, classes, methods and class properties) and their attributes (callable type, file name, starting line number). Enclosed functions and classes are supported. For example:

# pinspect_example_1.py
from __future__ import print_function
import math


def my_func(version):  # noqa: D202
    """Enclosing function."""

    class MyClass(object):
        """Enclosed class."""

        if version == 2:
            import docs.support.python2_module as pm
        else:
            import docs.support.python3_module as pm

        def __init__(self, value):
            self._value = value

        def _get_value(self):
            return self._value

        value = property(_get_value, pm._set_value, None, "Value property")


def print_name(name):
    print("My name is {0}, and sqrt(2) = {1}".format(name, math.sqrt(2)))

with

# python2_module.py

def _set_value(self, value):
    self._value = value + 2

and

# python3_module.py

def _set_value(self, value):
    self._value = value + 3

gives:

>>> from __future__ import print_function
>>> import docs.support.pinspect_example_1, pexdoc.pinspect, sys
>>> cobj = pexdoc.pinspect.Callables(
...     [sys.modules['docs.support.pinspect_example_1'].__file__]
... )
>>> print(cobj)
Modules:
   docs.support.pinspect_example_1
Classes:
   docs.support.pinspect_example_1.my_func.MyClass
docs.support.pinspect_example_1.my_func: func (10-29)
docs.support.pinspect_example_1.my_func.MyClass: class (13-29)
docs.support.pinspect_example_1.my_func.MyClass.__init__: meth (21-23)
docs.support.pinspect_example_1.my_func.MyClass._get_value: meth (24-26)
docs.support.pinspect_example_1.my_func.MyClass.value: prop (27-29)
docs.support.pinspect_example_1.print_name: func (30-31)

The numbers in parenthesis indicate the line number in which the callable starts and ends within the file it is defined in.

Functions

pexdoc.pinspect.get_function_args(func, no_self=False, no_varargs=False)

Return tuple of the function argument names in the order of the function signature.

Parameters:
  • func (function object) – Function
  • no_self (boolean) – Flag that indicates whether the function argument self, if present, is included in the output (False) or not (True)
  • no_varargs (boolean) – Flag that indicates whether keyword arguments are included in the output (True) or not (False)
Return type:

tuple

For example:

>>> import pexdoc.pinspect
>>> class MyClass(object):
...     def __init__(self, value, **kwargs):
...         pass
...
>>> pexdoc.pinspect.get_function_args(MyClass.__init__)
('self', 'value', '**kwargs')
>>> pexdoc.pinspect.get_function_args(
...     MyClass.__init__, no_self=True
... )
('value', '**kwargs')
>>> pexdoc.pinspect.get_function_args(
...     MyClass.__init__, no_self=True, no_varargs=True
... )
('value',)
>>> pexdoc.pinspect.get_function_args(
...     MyClass.__init__, no_varargs=True
... )
('self', 'value')
pexdoc.pinspect.get_module_name(module_obj)

Retrieve the module name from a module object.

Parameters:

module_obj (object) – Module object

Return type:

string

Raises:
  • RuntimeError (Argument `module_obj` is not valid)
  • RuntimeError (Module object `*[module_name]*` could not be found in loaded modules)

For example:

>>> import pexdoc.pinspect
>>> pexdoc.pinspect.get_module_name(sys.modules['pexdoc.pinspect'])
'pexdoc.pinspect'
pexdoc.pinspect.is_object_module(obj)

Test if the argument is a module object.

Parameters:obj (any) – Object
Return type:boolean
pexdoc.pinspect.is_special_method(name)

Test if callable name is a special Python method.

Parameters:name (string) – Callable name
Return type:boolean
pexdoc.pinspect.private_props(obj)

Yield private properties of an object.

A private property is defined as one that has a single underscore (_) before its name

Parameters:obj (object) – Object
Returns:iterator

Classes

class pexdoc.pinspect.Callables(fnames=None)

Bases: object

Generate a list of module callables and get their attributes.

Callables are functions, classes, methods and class properties; attributes are callable type, file name, and lines span.

Information from multiple modules can be stored in the callables database of the object by repeatedly calling pexdoc.pinspect.Callables.trace() with different module file names. A pexdoc.pinspect.Callables object retains knowledge of which modules have been traced so repeated calls to pexdoc.pinspect.Callables.trace() with the same module object will not result in module re-traces (and the consequent performance hit)

Parameters:

fnames (list of strings or None) – File names of the modules to trace. If None no immediate tracing is done

Raises:
  • OSError (File [fname] could not be found)
  • RuntimeError (Argument `fnames` is not valid)
__add__(other)

Merge two objects.

Raises:RuntimeError (Conflicting information between objects)

For example:

>>> import pexdoc.pcontracts, pexdoc.exh, pexdoc.pinspect, sys
>>> obj1 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> obj2 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.pcontracts'].__file__]
... )
>>> obj3 = pexdoc.pinspect.Callables([
... sys.modules['pexdoc.exh'].__file__,
... sys.modules['pexdoc.pcontracts'].__file__,
... ])
>>> obj1 == obj3
False
>>> obj1 == obj2
False
>>> obj1+obj2 == obj3
True
__copy__()

Copy object.

For example:

>>> import copy, pexdoc.exh, pexdoc.pinspect, sys
>>> obj1 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> obj2 = copy.copy(obj1)
>>> obj1 == obj2
True
__eq__(other)

Test object equality.

For example:

>>> import pexdoc.pcontracts, pexdoc.exh, pexdoc.pinspect, sys
>>> obj1 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> obj2 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> obj3 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.pcontracts'].__file__]
... )
>>> obj1 == obj2
True
>>> obj1 == obj3
False
>>> 5 == obj3
False
__iadd__(other)

Merge an object into an existing object.

Raises:RuntimeError (Conflicting information between objects)

For example:

>>> import pexdoc.pcontracts, pexdoc.exh, pexdoc.pinspect, sys
>>> obj1 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> obj2 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.pcontracts'].__file__]
... )
>>> obj3 = pexdoc.pinspect.Callables([
...     sys.modules['pexdoc.exh'].__file__,
...     sys.modules['pexdoc.pcontracts'].__file__,
... ])
>>> obj1 == obj3
False
>>> obj1 == obj2
False
>>> obj1 += obj2
>>> obj1 == obj3
True
__nonzero__()

Return False if no modules have been traced, True otherwise.

For example:

>>> from __future__ import print_function
>>> import pexdoc.pcontracts, pexdoc.pinspect, sys
>>> obj = pexdoc.pinspect.Callables()
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: False
>>> obj.trace([sys.modules['pexdoc.pcontracts'].__file__])
>>> if obj:
...     print('Boolean test returned: True')
... else:
...     print('Boolean test returned: False')
Boolean test returned: True
__repr__()

Return a string with the expression needed to re-create the object.

For example:

>>> import pexdoc.exh, pexdoc.pinspect, sys
>>> obj1 = pexdoc.pinspect.Callables(
...     [sys.modules['pexdoc.exh'].__file__]
... )
>>> repr(obj1)  #doctest: +ELLIPSIS
"pexdoc.pinspect.Callables(['...exh.py'])"
>>> exec("obj2="+repr(obj1))
>>> obj1 == obj2
True
__str__()

Return a string with a detailed description of the object’s contents.

For example:

>>> from __future__ import print_function
>>> import pexdoc.pinspect, os, sys
>>> import docs.support.pinspect_example_1
>>> cobj = pexdoc.pinspect.Callables([
...     sys.modules['docs.support.pinspect_example_1'].__file__
... ])
>>> print(cobj) #doctest: +ELLIPSIS
Modules:
   ...pinspect_example_1
Classes:
   ...pinspect_example_1.my_func.MyClass
...pinspect_example_1.my_func: func (10-29)
...pinspect_example_1.my_func.MyClass: class (13-29)
...pinspect_example_1.my_func.MyClass.__init__: meth (21-23)
...pinspect_example_1.my_func.MyClass._get_value: meth (24-26)
...pinspect_example_1.my_func.MyClass.value: prop (27-29)
...pinspect_example_1.print_name: func (30-31)

The numbers in parenthesis indicate the line number in which the callable starts and ends within the file it is defined in

load(callables_fname)

Load traced modules information from a JSON file.

The loaded module information is merged with any existing module information

Parameters:

callables_fname (FileNameExists) – File name

Raises:
  • OSError (File [fname] could not be found)
  • RuntimeError (Argument `callables_fname` is not valid)
refresh()

Re-traces modules modified since the time they were traced.

save(callables_fname)

Save traced modules information to a JSON file.

If the file exists it is overwritten

Parameters:callables_fname (FileName) – File name
Raises:RuntimeError (Argument `fname` is not valid)
trace(fnames)

Generate a list of module callables and gets their attributes.

Callables are functions, classes, methods and class properties; their attributes are callable type, file name, and lines span

Parameters:

fnames (list) – File names of the modules to trace

Raises:
  • OSError (File [fname] could not be found)
  • RuntimeError (Argument `fnames` is not valid)
callables_db

Return the callables database.

Return type:dictionary

The callable database is a dictionary that has the following structure:

  • full callable name (string) – Dictionary key. Elements in the callable path are separated by periods ('.'). For example, method my_method() from class MyClass from module my_module appears as 'my_module.MyClass.my_method'
  • callable properties (dictionary) – Dictionary value. The elements of this dictionary are:
  • type (string)'class' for classes, 'meth' for methods, 'func' for functions or 'prop' for properties or class attributes
  • code_id (tuple or None) – A tuple with the following items:
    • file name (string) – the first item contains the file name where the callable can be found
    • line number (integer) – the second item contains the line number in which the callable code starts (including decorators)
  • last_lineno (integer) – line number in which the callable code ends (including blank lines and comments regardless of their indentation level)
reverse_callables_db

Returns the reverse callables database

Return type:dictionary

The reverse callable database is a dictionary that has the following structure:

  • callable id (tuple) – Dictionary key. Two-element tuple in which the first tuple item is the file name where the callable is defined and the second tuple item is the line number where the callable definition starts
  • full callable name (string) – Dictionary value. Elements in the callable path are separated by periods ('.'). For example, method my_method() from class MyClass from module my_module appears as 'my_module.MyClass.my_method'

ptypes module

The pseudo-types defined below can be used in contracts of the PyContracts library or pexdoc.pcontracts module. As an example, with the latter:

>>> from __future__ import print_function
>>> import pexdoc
>>> from pexdoc.ptypes import non_negative_integer
>>> @pexdoc.pcontracts.contract(num='non_negative_integer')
... def myfunc(num):
...     print('Number received: '+str(num))
...
>>> myfunc(10)
Number received: 10
>>> myfunc('a')
Traceback (most recent call last):
    ...
RuntimeError: Argument `num` is not valid

Alternatively each pseudo-type has a checker function associated with it that can be used to verify membership. For example:

>>> import pexdoc
>>> # None is returned if object belongs to pseudo-type
>>> pexdoc.ptypes.non_negative_integer(10)
>>> # ValueError is raised if object does not belong to pseudo-type
>>> pexdoc.ptypes.non_negative_integer('a') 
Traceback (most recent call last):
    ...
ValueError: [START CONTRACT MSG: non_negative_integer]...

Description

FileName

Import as file_name. String with a valid file name

FileNameExists

Import as file_name_exists. String with a file name that exists in the file system

Function

Import as function. Callable pointer or None

NonNegativeInteger

Import as non_negative_integer. Integer greater or equal to zero

NonNullString

Import as non_null_string. String of length 1 or higher

OffsetRange

Import as offset_range. Number in the [0, 1] range

PositiveRealNum

Import as positive_real_num. Integer or float greater than zero or None

RealNum

Import as real_num. Integer, float or None

Checker functions

pexdoc.ptypes.file_name(obj)

Validate if an object is a legal name for a file.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.file_name_exists(obj)

Validate if an object is a legal name for a file and that the file exists.

Parameters:

obj (any) – Object

Raises:
  • OSError (File [fname] could not be found). The token *[fname]* is replaced by the value of the argument the contract is attached to
  • RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:

None

pexdoc.ptypes.function(obj)

Validate if an object is a function pointer or None.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.non_negative_integer(obj)

Validate if an object is a non-negative (zero or positive) integer.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.non_null_string(obj)

Validate if an object is a non-null string.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.offset_range(obj)

Validate if an object is a number in the [0, 1] range.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.positive_real_num(obj)

Validate if an object is a positive integer, positive float or None.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None
pexdoc.ptypes.real_num(obj)

Validate if an object is an integer, float or None.

Parameters:obj (any) – Object
Raises:RuntimeError (Argument `*[argument_name]*` is not valid). The token *[argument_name]* is replaced by the name of the argument the contract is attached to
Return type:None

Indices and tables