pax_global_header00006660000000000000000000000064140357031720014514gustar00rootroot0000000000000052 comment=1b85ff5306f02985ab00c73b41504bb5b17943eb sphinx-autodoc-typehints-1.12.0/000077500000000000000000000000001403570317200165675ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/.github/000077500000000000000000000000001403570317200201275ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001403570317200223125ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000010721403570317200250040ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Please provide a **minimal** reproducible example that developers can run to investigate the problem. You can find help for creating such an example [here](https://stackoverflow.com/help/minimal-reproducible-example). **Expected behavior** A clear and concise description of what you expected to happen. **Additional context** Add any other context about the problem here. sphinx-autodoc-typehints-1.12.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341403570317200260360ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. sphinx-autodoc-typehints-1.12.0/.github/workflows/000077500000000000000000000000001403570317200221645ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/.github/workflows/codeqa-test.yml000066400000000000000000000033431403570317200251230ustar00rootroot00000000000000name: Python codeqa/test on: push: branches: [master] pull_request: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.x - uses: actions/cache@v2 with: path: ~/.cache/pip key: pip-lint - name: Install dependencies run: pip install flake8 isort - name: Run flake8 run: flake8 sphinx_autodoc_typehints.py tests - name: Run isort run: isort -c sphinx_autodoc_typehints.py tests test: needs: [lint] strategy: fail-fast: false matrix: os: [ubuntu-latest] python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.5] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - uses: actions/cache@v2 with: path: ~/.cache/pip key: pip-test-${{ matrix.python-version }}-${{ matrix.os }} - name: Install dependencies run: pip install .[test,type_comments] coveralls - name: Test with pytest run: coverage run -m pytest - name: Upload Coverage run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ matrix.test-name }} COVERALLS_PARALLEL: true coveralls: name: Finish Coveralls needs: test runs-on: ubuntu-latest container: python:3-slim steps: - name: Finished run: | pip install coveralls coveralls --service=github --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} sphinx-autodoc-typehints-1.12.0/.github/workflows/publish.yml000066400000000000000000000011771403570317200243630ustar00rootroot00000000000000name: Publish packages to PyPI on: push: tags: - "[0-9]+.[0-9]+.[0-9]+" - "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+" - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.x - name: Install dependencies run: pip install build - name: Create packages run: python -m build -s -w . - name: Upload packages uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.pypi_password }} sphinx-autodoc-typehints-1.12.0/.gitignore000066400000000000000000000002071403570317200205560ustar00rootroot00000000000000.project .pydevproject .idea .tox .coverage .cache .eggs/ *.egg-info/ *.pyc __pycache__/ dist/ build/ .vscode/ .pre-commit-config.yaml sphinx-autodoc-typehints-1.12.0/CHANGELOG.rst000066400000000000000000000136121403570317200206130ustar00rootroot00000000000000**1.12.0** - Dropped Python 3.5 support - Added the simplify_optional_unions config option (PR by tillhainbach) - Fixed indentation of multiline strings (PR by Yuxin Wu) **1.11.1** - Changed formatting of ``None`` to point to the Python stdlib docs (PR by Dominic Davis-Foster) - Updated special dataclass handling (PR by Lihu Ben-Ezri-Ravin) **1.11.0** - Dropped support for Sphinx < 3.0 - Added support for alternative parameter names (``arg``, ``argument``, ``parameter``) - Fixed import path for Signature (PR by Matthew Treinish) - Fixed ``TypeError`` when formatting a parametrized ``typing.IO`` annotation - Fixed data class displaying a return type in its ``__init__()`` method **1.10.3** - Fixed ``TypeError`` (or wrong rendered class name) when an annotation is a generic class that has a ``name`` property **1.10.2** - Fixed inner classes missing their parent class name(s) when rendered **1.10.1** - Fixed ``KeyError`` when encountering mocked annotations (``autodoc_mock_imports``) **1.10.0** - Rewrote the annotation formatting logic (fixes Python 3.5.2 compatibility regressions and an ``AttributeError`` regression introduced in v1.9.0) - Fixed decorator classes not being processed as classes **1.9.0** - Added support for typing_extensions_ - Added the ``typehints_document_rtype`` option (PR by Simon-Martin Schröder) - Fixed metaclasses as annotations causing ``TypeError`` - Fixed rendering of ``typing.Literal`` - Fixed OSError when generating docs for SQLAlchemy mapped classes - Fixed unparametrized generic classes being rendered with their type parameters (e.g. ``Dict[~KT, ~VT]``) .. _typing_extensions: https://pypi.org/project/typing-extensions/ **1.8.0** - Fixed regression which caused ``TypeError`` or ``OSError`` when trying to set annotations due to PR #87 - Fixed unintentional mangling of annotation type names - Added proper ``:py:data`` targets for ``NoReturn``, ``ClassVar`` and ``Tuple`` - Added support for inline type comments (like ``(int, str) -> None``) (PR by Bernát Gábor) - Use the native AST parser for type comment support on Python 3.8+ **1.7.0** - Dropped support for Python 3.4 - Fixed unwrapped local functions causing errors (PR by Kimiyuki Onaka) - Fixed ``AttributeError`` when documenting the ``__init__()`` method of a data class - Added support for type hint comments (PR by Markus Unterwaditzer) - Added flag for rendering classes with their fully qualified names (PR by Holly Becker) **1.6.0** - Fixed ``TypeError`` when formatting annotations from a class that inherits from a concrete generic type (report and tests by bpeake-illuscio) - Added support for ``typing_extensions.Protocol`` (PR by Ian Good) - Added support for ``typing.NewType`` (PR by George Leslie-Waksman) **1.5.2** - Emit a warning instead of crashing when an unresolvable forward reference is encountered in type annotations **1.5.1** - Fixed escape characters in parameter default values getting lost during signature processing - Replaced use of the ``config-inited`` event (which inadvertently required Sphinx 1.8) with the ``builder-inited`` event **1.5.0** - The setting of the ``typing.TYPECHECKING`` flag is now configurable using the ``set_type_checking_flag`` option **1.4.0** - The extension now sets ``typing.TYPECHECKING`` to ``True`` during setup to include conditional imports which may be used in type annotations - Fixed parameters with trailing underscores (PR by Daniel Knell) - Fixed KeyError with private methods (PR by Benito Palacios Sánchez) - Fixed deprecation warning about the use of formatargspec (PR by Y. Somda) - The minimum Sphinx version is now v1.7.0 **1.3.1** - Fixed rendering of generic types outside the typing module (thanks to Tim Poterba for the PR) **1.3.0** - Fixed crash when processing docstrings from nested classes (thanks to dilyanpalauzov for the fix) - Added support for Python 3.7 - Dropped support for Python 3.5.0 and 3.5.1 **1.2.5** - Ensured that ``:rtype:`` doesn't get joined with a paragraph of text (thanks to Bruce Merry for the PR) **1.2.4** - Removed support for ``backports.typing`` as it has been removed from the PyPI - Fixed first parameter being cut out from class methods and static methods (thanks to Josiah Wolf Oberholtzer for the PR) **1.2.3** - Fixed `process_signature()` clobbering any explicitly overridden signatures from the docstring **1.2.2** - Explicitly prefix ``:class:``, ``:mod:`` et al with ``:py:``, in case ``py`` is not the default domain of the project (thanks Monty Taylor) **1.2.1** - Fixed `ValueError` when `getargspec()` encounters a built-in function - Fixed `AttributeError` when `Any` is combined with another type in a `Union` (thanks Davis Kirkendall) **1.2.0** - Fixed compatibility with Python 3.6 and 3.5.3 - Fixed ``NameError`` when processing signatures of wrapped functions with type hints - Fixed handling of slotted classes with no ``__init__()`` method - Fixed Sphinx warning about parallel reads - Fixed return type being added to class docstring from its ``__init__()`` method (thanks to Manuel Krebber for the patch) - Fixed return type hints of ``@property`` methods being omitted (thanks to pknight for the patch) - Added a test suite (thanks Manuel Krebber) **1.1.0** - Added proper support for ``typing.Tuple`` (pull request by Manuel Krebber) **1.0.6** - Fixed wrong placement of ``:rtype:`` if a multi-line ``:param:`` or a ``:returns:`` is used **1.0.5** - Fixed coroutine functions' signatures not being processed when using sphinxcontrib-asyncio **1.0.4** - Fixed compatibility with Sphinx 1.4 **1.0.3** - Fixed "self" parameter not being removed from exception class constructor signatures - Fixed process_signature() erroneously removing the first argument of a static method **1.0.2** - Fixed exception classes not being processed like normal classes **1.0.1** - Fixed errors caused by forward references not being looked up with the right globals **1.0.0** - Initial release sphinx-autodoc-typehints-1.12.0/LICENSE000066400000000000000000000021521403570317200175740ustar00rootroot00000000000000This 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. sphinx-autodoc-typehints-1.12.0/README.rst000066400000000000000000000114101403570317200202530ustar00rootroot00000000000000sphinx-autodoc-typehints ======================== This extension allows you to use Python 3 annotations for documenting acceptable argument types and return value types of functions. This allows you to use type hints in a very natural fashion, allowing you to migrate from this: .. code-block:: python def format_unit(value, unit): """ Formats the given value as a human readable string using the given units. :param float|int value: a numeric value :param str unit: the unit for the value (kg, m, etc.) :rtype: str """ return '{} {}'.format(value, unit) to this: .. code-block:: python from typing import Union def format_unit(value: Union[float, int], unit: str) -> str: """ Formats the given value as a human readable string using the given units. :param value: a numeric value :param unit: the unit for the value (kg, m, etc.) """ return '{} {}'.format(value, unit) Installation and setup ---------------------- First, use pip to download and install the extension:: $ pip install sphinx-autodoc-typehints Then, add the extension to your ``conf.py``: .. code-block:: python extensions = [ 'sphinx.ext.autodoc', 'sphinx_autodoc_typehints' ] Options ------- The following configuration options are accepted: * ``set_type_checking_flag`` (default: ``False``): if ``True``, set ``typing.TYPE_CHECKING`` to ``True`` to enable "expensive" typing imports * ``typehints_fully_qualified`` (default: ``False``): if ``True``, class names are always fully qualified (e.g. ``module.for.Class``). If ``False``, just the class name displays (e.g. ``Class``) * ``always_document_param_types`` (default: ``False``): If ``False``, do not add type info for undocumented parameters. If ``True``, add stub documentation for undocumented parameters to be able to add type info. * ``typehints_document_rtype`` (default: ``True``): If ``False``, never add an ``:rtype:`` directive. If ``True``, add the ``:rtype:`` directive if no existing ``:rtype:`` is found. * ``simplify_optional_unions`` (default: ``True``): If ``True``, optional parameters of type "Union[...]" are simplified as being of type Union[..., None] in the resulting documention (e.g. Optional[Union[A, B]] -> Union[A, B, None]). If ``False``, the "Optional"-type is kept. Note: If ``False``, **any** Union containing ``None`` will be displayed as Optional! Note: If an optional parameter has only a single type (e.g Optional[A] or Union[A, None]), it will **always** be displayed as Optional! How it works ------------ The extension listens to the ``autodoc-process-signature`` and ``autodoc-process-docstring`` Sphinx events. In the former, it strips the annotations from the function signature. In the latter, it injects the appropriate ``:type argname:`` and ``:rtype:`` directives into the docstring. Only arguments that have an existing ``:param:`` directive in the docstring get their respective ``:type:`` directives added. The ``:rtype:`` directive is added if and only if no existing ``:rtype:`` is found. Compatibility with sphinx.ext.napoleon -------------------------------------- To use `sphinx.ext.napoleon`_ with sphinx-autodoc-typehints, make sure you load `sphinx.ext.napoleon`_ first, **before** sphinx-autodoc-typehints. See `Issue 15`_ on the issue tracker for more information. .. _sphinx.ext.napoleon: http://www.sphinx-doc.org/en/stable/ext/napoleon.html .. _Issue 15: https://github.com/agronholm/sphinx-autodoc-typehints/issues/15 Dealing with circular imports ----------------------------- Sometimes functions or classes from two different modules need to reference each other in their type annotations. This creates a circular import problem. The solution to this is the following: #. Import only the module, not the classes/functions from it #. Use forward references in the type annotations (e.g. ``def methodname(self, param1: 'othermodule.OtherClass'):``) On Python 3.7, you can even use ``from __future__ import annotations`` and remove the quotes. Using type hint comments ------------------------ If you're documenting code that needs to stay compatible with Python 2.7, you cannot use regular type annotations. Instead, you must either be using Python 3.8 or later or have typed_ast_ installed. The package extras ``type_comments`` will pull in the appropiate dependencies automatically. Then you can add type hint comments in the following manner: .. code-block:: python def myfunction(arg1, arg2): # type: (int, str) -> int return 42 or alternatively: .. code-block:: python def myfunction( arg1, # type: int arg2 # type: str ): # type: (...) -> int return 42 .. _typed_ast: https://pypi.org/project/typed-ast/ sphinx-autodoc-typehints-1.12.0/pre-commit-config.sample.yaml000066400000000000000000000012731403570317200242550ustar00rootroot00000000000000# This is the configuration file for pre-commit (https://pre-commit.com/). # To use: # * Install pre-commit (https://pre-commit.com/#installation) # * Copy this file as ".pre-commit-config.yaml" # * Run "pre-commit install". repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: - id: check-toml - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending args: ["--fix=lf"] - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.5.6 hooks: - id: autopep8 - repo: https://github.com/pycqa/isort rev: 5.8.0 hooks: - id: isort additional_dependencies: [toml] sphinx-autodoc-typehints-1.12.0/pyproject.toml000066400000000000000000000005261403570317200215060ustar00rootroot00000000000000[build-system] requires = [ "setuptools >= 40.0.4", "setuptools_scm >= 2.0.0", "wheel >= 0.29.0", ] build-backend = 'setuptools.build_meta' [tool.isort] skip_gitignore = true line_length = 99 multi_line_output = 4 [tool.autopep8] max_line_length = 99 [tool.pytest.ini_options] addopts = "-rsx --tb=short" testpaths = ["tests"] sphinx-autodoc-typehints-1.12.0/setup.cfg000066400000000000000000000026631403570317200204170ustar00rootroot00000000000000[metadata] name = sphinx-autodoc-typehints description = Type hints (PEP 484) support for the Sphinx autodoc extension long_description = file: README.rst author = Alex Grönholm author_email = alex.gronholm@nextday.fi license = MIT project_urls = Change log = https://github.com/agronholm/sphinx-autodoc-typehints/blob/master/CHANGELOG.rst Source code = https://github.com/agronholm/sphinx-autodoc-typehints Issue tracker = https://github.com/agronholm/sphinx-autodoc-typehints/issues classifiers = Development Status :: 5 - Production/Stable Framework :: Sphinx :: Extension Intended Audience :: Developers License :: OSI Approved :: MIT License Framework :: Sphinx :: Extension Topic :: Documentation :: Sphinx Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 [options] py_modules = sphinx_autodoc_typehints python_requires = >=3.6 install_requires = Sphinx >= 3.0 [options.extras_require] test = pytest >= 3.1.0 typing_extensions >= 3.5 dataclasses; python_version == "3.6" sphobjinv >= 2.0 Sphinx >= 3.2.0 type_comments = typed_ast >= 1.4.0; python_version < "3.8" [flake8] max-line-length = 99 [tool:pytest] addopts = -rsx --tb=short testpaths = tests sphinx-autodoc-typehints-1.12.0/setup.py000066400000000000000000000003571403570317200203060ustar00rootroot00000000000000from setuptools import setup setup( use_scm_version={ 'version_scheme': 'post-release', 'local_scheme': 'dirty-tag' }, setup_requires=[ 'setuptools_scm >= 1.7.0', 'setuptools >= 36.2.7' ] ) sphinx-autodoc-typehints-1.12.0/sphinx_autodoc_typehints.py000066400000000000000000000422271403570317200243060ustar00rootroot00000000000000import inspect import sys import textwrap import typing from typing import Any, AnyStr, Tuple, TypeVar, get_type_hints from sphinx.util import logging from sphinx.util.inspect import signature as Signature from sphinx.util.inspect import stringify_signature logger = logging.getLogger(__name__) pydata_annotations = {'Any', 'AnyStr', 'Callable', 'ClassVar', 'Literal', 'NoReturn', 'Optional', 'Tuple', 'Union'} def get_annotation_module(annotation) -> str: # Special cases if annotation is None: return 'builtins' if hasattr(annotation, '__module__'): return annotation.__module__ if hasattr(annotation, '__origin__'): return annotation.__origin__.__module__ raise ValueError('Cannot determine the module of {}'.format(annotation)) def get_annotation_class_name(annotation, module: str) -> str: # Special cases if annotation is None: return 'None' elif annotation is Any: return 'Any' elif annotation is AnyStr: return 'AnyStr' elif inspect.isfunction(annotation) and hasattr(annotation, '__supertype__'): return 'NewType' if getattr(annotation, '__qualname__', None): return annotation.__qualname__ elif getattr(annotation, '_name', None): # Required for generic aliases on Python 3.7+ return annotation._name elif (module in ('typing', 'typing_extensions') and isinstance(getattr(annotation, 'name', None), str)): # Required for at least Pattern and Match return annotation.name origin = getattr(annotation, '__origin__', None) if origin: if getattr(origin, '__qualname__', None): # Required for Protocol subclasses return origin.__qualname__ elif getattr(origin, '_name', None): # Required for Union on Python 3.7+ return origin._name else: return origin.__class__.__qualname__.lstrip('_') # Required for Union on Python < 3.7 annotation_cls = annotation if inspect.isclass(annotation) else annotation.__class__ return annotation_cls.__qualname__.lstrip('_') def get_annotation_args(annotation, module: str, class_name: str) -> Tuple: try: original = getattr(sys.modules[module], class_name) except (KeyError, AttributeError): pass else: if annotation is original: return () # This is the original, unparametrized type # Special cases if class_name in ('Pattern', 'Match') and hasattr(annotation, 'type_var'): # Python < 3.7 return annotation.type_var, elif class_name == 'ClassVar' and hasattr(annotation, '__type__'): # ClassVar on Python < 3.7 return annotation.__type__, elif class_name == 'NewType' and hasattr(annotation, '__supertype__'): return annotation.__supertype__, elif class_name == 'Literal' and hasattr(annotation, '__values__'): return annotation.__values__ elif class_name == 'Generic': return annotation.__parameters__ return getattr(annotation, '__args__', ()) def format_annotation(annotation, fully_qualified: bool = False, simplify_optional_unions: bool = True) -> str: # Special cases if annotation is None or annotation is type(None): # noqa: E721 return ':py:obj:`None`' elif annotation is Ellipsis: return '...' # Type variables are also handled specially try: if isinstance(annotation, TypeVar) and annotation is not AnyStr: return '\\' + repr(annotation) except TypeError: pass try: module = get_annotation_module(annotation) class_name = get_annotation_class_name(annotation, module) args = get_annotation_args(annotation, module, class_name) except ValueError: return str(annotation).strip("'") # Redirect all typing_extensions types to the stdlib typing module if module == 'typing_extensions': module = 'typing' full_name = (module + '.' + class_name) if module != 'builtins' else class_name prefix = '' if fully_qualified or full_name == class_name else '~' role = 'data' if class_name in pydata_annotations else 'class' args_format = '\\[{}]' formatted_args = '' # Some types require special handling if full_name == 'typing.NewType': args_format = '\\(:py:data:`~{name}`, {{}})'.format(name=annotation.__name__) role = 'func' elif full_name == 'typing.Union' and type(None) in args: if len(args) == 2: full_name = 'typing.Optional' args = tuple(x for x in args if x is not type(None)) # noqa: E721 elif not simplify_optional_unions: full_name = 'typing.Optional' args_format = '\\[:py:data:`{prefix}typing.Union`\\[{{}}]]'.format(prefix=prefix) args = tuple(x for x in args if x is not type(None)) # noqa: E721 elif full_name == 'typing.Callable' and args and args[0] is not ...: formatted_args = '\\[\\[' + ', '.join( format_annotation( arg, simplify_optional_unions=simplify_optional_unions) for arg in args[:-1]) + ']' formatted_args += ', ' + format_annotation( args[-1], simplify_optional_unions=simplify_optional_unions) + ']' elif full_name == 'typing.Literal': formatted_args = '\\[' + ', '.join(repr(arg) for arg in args) + ']' if args and not formatted_args: formatted_args = args_format.format(', '.join( format_annotation(arg, fully_qualified, simplify_optional_unions) for arg in args)) return ':py:{role}:`{prefix}{full_name}`{formatted_args}'.format( role=role, prefix=prefix, full_name=full_name, formatted_args=formatted_args) # reference: https://github.com/pytorch/pytorch/pull/46548/files def normalize_source_lines(sourcelines: str) -> str: """ This helper function accepts a list of source lines. It finds the indentation level of the function definition (`def`), then it indents all lines in the function body to a point at or greater than that level. This allows for comments and continued string literals that are at a lower indentation than the rest of the code. Arguments: sourcelines: source code Returns: source lines that have been correctly aligned """ sourcelines = sourcelines.split("\n") def remove_prefix(text, prefix): return text[text.startswith(prefix) and len(prefix):] # Find the line and line number containing the function definition for i, l in enumerate(sourcelines): if l.lstrip().startswith("def"): idx = i break else: return "\n".join(sourcelines) fn_def = sourcelines[idx] # Get a string representing the amount of leading whitespace whitespace = fn_def.split("def")[0] # Add this leading whitespace to all lines before and after the `def` aligned_prefix = [whitespace + remove_prefix(s, whitespace) for s in sourcelines[:idx]] aligned_suffix = [whitespace + remove_prefix(s, whitespace) for s in sourcelines[idx + 1:]] # Put it together again aligned_prefix.append(fn_def) return "\n".join(aligned_prefix + aligned_suffix) def process_signature(app, what: str, name: str, obj, options, signature, return_annotation): if not callable(obj): return original_obj = obj if inspect.isclass(obj): obj = getattr(obj, '__init__', getattr(obj, '__new__', None)) if not getattr(obj, '__annotations__', None): return obj = inspect.unwrap(obj) signature = Signature(obj) parameters = [ param.replace(annotation=inspect.Parameter.empty) for param in signature.parameters.values() ] # The generated dataclass __init__() and class are weird and need extra checks # This helper function operates on the generated class and methods # of a dataclass, not an instantiated dataclass object. As such, # it cannot be replaced by a call to `dataclasses.is_dataclass()`. def _is_dataclass(name: str, what: str, qualname: str) -> bool: if what == 'method' and name.endswith('.__init__'): # generated __init__() return True if what == 'class' and qualname.endswith('.__init__'): # generated class return True return False if '' in obj.__qualname__ and not _is_dataclass(name, what, obj.__qualname__): logger.warning( 'Cannot treat a function defined as a local function: "%s" (use @functools.wraps)', name) return if parameters: if inspect.isclass(original_obj) or (what == 'method' and name.endswith('.__init__')): del parameters[0] elif what == 'method': outer = inspect.getmodule(obj) for clsname in obj.__qualname__.split('.')[:-1]: outer = getattr(outer, clsname) method_name = obj.__name__ if method_name.startswith("__") and not method_name.endswith("__"): # If the method starts with double underscore (dunder) # Python applies mangling so we need to prepend the class name. # This doesn't happen if it always ends with double underscore. class_name = obj.__qualname__.split('.')[-2] method_name = "_{c}{m}".format(c=class_name, m=method_name) method_object = outer.__dict__[method_name] if outer else obj if not isinstance(method_object, (classmethod, staticmethod)): del parameters[0] signature = signature.replace( parameters=parameters, return_annotation=inspect.Signature.empty) return stringify_signature(signature).replace('\\', '\\\\'), None def get_all_type_hints(obj, name): rv = {} try: rv = get_type_hints(obj) except (AttributeError, TypeError, RecursionError): # Introspecting a slot wrapper will raise TypeError, and and some recursive type # definitions will cause a RecursionError (https://github.com/python/typing/issues/574). pass except NameError as exc: logger.warning('Cannot resolve forward reference in type annotations of "%s": %s', name, exc) rv = obj.__annotations__ if rv: return rv rv = backfill_type_hints(obj, name) try: obj.__annotations__ = rv except (AttributeError, TypeError): return rv try: rv = get_type_hints(obj) except (AttributeError, TypeError): pass except NameError as exc: logger.warning('Cannot resolve forward reference in type annotations of "%s": %s', name, exc) rv = obj.__annotations__ return rv def backfill_type_hints(obj, name): parse_kwargs = {} if sys.version_info < (3, 8): try: import typed_ast.ast3 as ast except ImportError: return {} else: import ast parse_kwargs = {'type_comments': True} def _one_child(module): children = module.body # use the body to ignore type comments if len(children) != 1: logger.warning( 'Did not get exactly one node from AST for "%s", got %s', name, len(children)) return return children[0] try: obj_ast = ast.parse(textwrap.dedent( normalize_source_lines(inspect.getsource(obj))), **parse_kwargs) except (OSError, TypeError): return {} obj_ast = _one_child(obj_ast) if obj_ast is None: return {} try: type_comment = obj_ast.type_comment except AttributeError: return {} if not type_comment: return {} try: comment_args_str, comment_returns = type_comment.split(' -> ') except ValueError: logger.warning('Unparseable type hint comment for "%s": Expected to contain ` -> `', name) return {} rv = {} if comment_returns: rv['return'] = comment_returns args = load_args(obj_ast) comment_args = split_type_comment_args(comment_args_str) is_inline = len(comment_args) == 1 and comment_args[0] == "..." if not is_inline: if args and args[0].arg in ("self", "cls") and len(comment_args) != len(args): comment_args.insert(0, None) # self/cls may be omitted in type comments, insert blank if len(args) != len(comment_args): logger.warning('Not enough type comments found on "%s"', name) return rv for at, arg in enumerate(args): arg_key = getattr(arg, "arg", None) if arg_key is None: continue if is_inline: # the type information now is tied to the argument value = getattr(arg, "type_comment", None) else: # type data from comment value = comment_args[at] if value is not None: rv[arg_key] = value return rv def load_args(obj_ast): func_args = obj_ast.args args = [] pos_only = getattr(func_args, 'posonlyargs', None) if pos_only: args.extend(pos_only) args.extend(func_args.args) if func_args.vararg: args.append(func_args.vararg) args.extend(func_args.kwonlyargs) if func_args.kwarg: args.append(func_args.kwarg) return args def split_type_comment_args(comment): def add(val): result.append(val.strip().lstrip("*")) # remove spaces, and var/kw arg marker comment = comment.strip().lstrip("(").rstrip(")") result = [] if not comment: return result brackets, start_arg_at, at = 0, 0, 0 for at, char in enumerate(comment): if char in ("[", "("): brackets += 1 elif char in ("]", ")"): brackets -= 1 elif char == "," and brackets == 0: add(comment[start_arg_at:at]) start_arg_at = at + 1 add(comment[start_arg_at: at + 1]) return result def process_docstring(app, what, name, obj, options, lines): original_obj = obj if isinstance(obj, property): obj = obj.fget if callable(obj): if inspect.isclass(obj): obj = getattr(obj, '__init__') obj = inspect.unwrap(obj) type_hints = get_all_type_hints(obj, name) for argname, annotation in type_hints.items(): if argname == 'return': continue # this is handled separately later if argname.endswith('_'): argname = '{}\\_'.format(argname[:-1]) formatted_annotation = format_annotation( annotation, fully_qualified=app.config.typehints_fully_qualified, simplify_optional_unions=app.config.simplify_optional_unions) searchfor = [':{} {}:'.format(field, argname) for field in ('param', 'parameter', 'arg', 'argument')] insert_index = None for i, line in enumerate(lines): if any(line.startswith(search_string) for search_string in searchfor): insert_index = i break if insert_index is None and app.config.always_document_param_types: lines.append(':param {}:'.format(argname)) insert_index = len(lines) if insert_index is not None: lines.insert( insert_index, ':type {}: {}'.format(argname, formatted_annotation) ) if 'return' in type_hints and not inspect.isclass(original_obj): # This avoids adding a return type for data class __init__ methods if what == 'method' and name.endswith('.__init__'): return formatted_annotation = format_annotation( type_hints['return'], fully_qualified=app.config.typehints_fully_qualified, simplify_optional_unions=app.config.simplify_optional_unions ) insert_index = len(lines) for i, line in enumerate(lines): if line.startswith(':rtype:'): insert_index = None break elif line.startswith(':return:') or line.startswith(':returns:'): insert_index = i if insert_index is not None and app.config.typehints_document_rtype: if insert_index == len(lines): # Ensure that :rtype: doesn't get joined with a paragraph of text, which # prevents it being interpreted. lines.append('') insert_index += 1 lines.insert(insert_index, ':rtype: {}'.format(formatted_annotation)) def builder_ready(app): if app.config.set_type_checking_flag: typing.TYPE_CHECKING = True def setup(app): app.add_config_value('set_type_checking_flag', False, 'html') app.add_config_value('always_document_param_types', False, 'html') app.add_config_value('typehints_fully_qualified', False, 'env') app.add_config_value('typehints_document_rtype', True, 'env') app.add_config_value('simplify_optional_unions', True, 'env') app.connect('builder-inited', builder_ready) app.connect('autodoc-process-signature', process_signature) app.connect('autodoc-process-docstring', process_docstring) return dict(parallel_read_safe=True) sphinx-autodoc-typehints-1.12.0/tests/000077500000000000000000000000001403570317200177315ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/tests/conftest.py000066400000000000000000000030741403570317200221340ustar00rootroot00000000000000import os import pathlib import re import shutil import sys import pytest from sphinx.testing.path import path from sphobjinv import Inventory pytest_plugins = 'sphinx.testing.fixtures' collect_ignore = ['roots'] @pytest.fixture(scope='session') def inv(pytestconfig): cache_path = 'python{v.major}.{v.minor}/objects.inv'.format(v=sys.version_info) inv_dict = pytestconfig.cache.get(cache_path, None) if inv_dict is not None: return Inventory(inv_dict) print("Downloading objects.inv") url = 'https://docs.python.org/{v.major}.{v.minor}/objects.inv'.format(v=sys.version_info) inv = Inventory(url=url) pytestconfig.cache.set(cache_path, inv.json_dict()) return inv @pytest.fixture(autouse=True) def remove_sphinx_projects(sphinx_test_tempdir): # Remove any directory which appears to be a Sphinx project from # the temporary directory area. # See https://github.com/sphinx-doc/sphinx/issues/4040 roots_path = pathlib.Path(sphinx_test_tempdir) for entry in roots_path.iterdir(): try: if entry.is_dir() and pathlib.Path(entry, '_build').exists(): shutil.rmtree(str(entry)) except PermissionError: pass @pytest.fixture def rootdir(): return path(os.path.dirname(__file__) or '.').abspath() / 'roots' def pytest_ignore_collect(path, config): version_re = re.compile(r'_py(\d)(\d)\.py$') match = version_re.search(path.basename) if match: version = tuple(int(x) for x in match.groups()) if sys.version_info < version: return True sphinx-autodoc-typehints-1.12.0/tests/roots/000077500000000000000000000000001403570317200210775ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/tests/roots/test-dummy/000077500000000000000000000000001403570317200232075ustar00rootroot00000000000000sphinx-autodoc-typehints-1.12.0/tests/roots/test-dummy/conf.py000066400000000000000000000003761403570317200245140ustar00rootroot00000000000000import pathlib import sys # Make dummy_module.py available for autodoc. sys.path.insert(0, str(pathlib.Path(__file__).parent)) master_doc = 'index' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx_autodoc_typehints', ] sphinx-autodoc-typehints-1.12.0/tests/roots/test-dummy/dummy_module.py000066400000000000000000000110601403570317200262570ustar00rootroot00000000000000import typing from dataclasses import dataclass from mailbox import Mailbox from typing import Callable, Union def get_local_function(): def wrapper(self) -> str: """ Wrapper """ return wrapper class Class: """ Initializer docstring. :param x: foo :param y: bar :param z: baz """ def __init__(self, x: bool, y: int, z: typing.Optional[str] = None) -> None: pass def a_method(self, x: bool, y: int, z: typing.Optional[str] = None) -> str: """ Method docstring. :param x: foo :param y: bar :param z: baz """ def _private_method(self, x: str) -> str: """ Private method docstring. :param x: foo """ def __dunder_method(self, x: str) -> str: """ Dunder method docstring. :param x: foo """ def __magic_custom_method__(self, x: str) -> str: """ Magic dunder method docstring. :param x: foo """ @classmethod def a_classmethod(cls, x: bool, y: int, z: typing.Optional[str] = None) -> str: """ Classmethod docstring. :param x: foo :param y: bar :param z: baz """ @staticmethod def a_staticmethod(x: bool, y: int, z: typing.Optional[str] = None) -> str: """ Staticmethod docstring. :param x: foo :param y: bar :param z: baz """ @property def a_property(self) -> str: """ Property docstring """ class InnerClass: """ Inner class. """ def inner_method(self, x: bool) -> str: """ Inner method. :param x: foo """ def __dunder_inner_method(self, x: bool) -> str: """ Dunder inner method. :param x: foo """ locally_defined_callable_field = get_local_function() class DummyException(Exception): """ Exception docstring :param message: blah """ def __init__(self, message: str) -> None: super().__init__(message) def function(x: bool, y: int, z_: typing.Optional[str] = None) -> str: """ Function docstring. :param x: foo :param y: bar :param z\\_: baz :return: something :rtype: bytes """ def function_with_escaped_default(x: str = '\b'): """ Function docstring. :param x: foo """ def function_with_unresolvable_annotation(x: 'a.b.c'): # noqa: F821 """ Function docstring. :arg x: foo """ def function_with_typehint_comment( x, # type: int y # type: str ): # type: (...) -> None """ Function docstring. :parameter x: foo :param y: bar """ class ClassWithTypehints(object): """ Class docstring. :param x: foo """ def __init__( self, x # type: int ): # type: (...) -> None pass def foo( self, x # type: str ): # type: (...) -> int """ Method docstring. :arg x: foo """ return 42 def method_without_typehint(self, x): """ Method docstring. """ # test that multiline str can be correctly indented multiline_str = """ test """ return multiline_str def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs): # type: (Union[str, bytes], *str, bytes, **int) -> None """ Function docstring. :arg x: foo :argument y: bar :parameter z: baz :parameter kwargs: some kwargs """ class ClassWithTypehintsNotInline(object): """ Class docstring. :param x: foo """ def __init__(self, x=None): # type: (Callable[[int, bytes], int]) -> None pass def foo(self, x=1): # type: (Callable[[int, bytes], int]) -> int """ Method docstring. :param x: foo """ return x(1, b'') @classmethod def mk(cls, x=None): # type: (Callable[[int, bytes], int]) -> ClassWithTypehintsNotInline """ Method docstring. :param x: foo """ return cls(x) def undocumented_function(x: int) -> str: """Hi""" return str(x) @dataclass class DataClass: """Class docstring.""" x: int class Decorator: """ Initializer docstring. :param func: function """ def __init__(self, func: Callable[[int, str], str]): pass def mocked_import(x: Mailbox): """ A docstring. :param x: function """ sphinx-autodoc-typehints-1.12.0/tests/roots/test-dummy/index.rst000066400000000000000000000016211403570317200250500ustar00rootroot00000000000000Dummy Module ============ .. autoclass:: dummy_module.Class :members: :undoc-members: :private-members: :special-members: __magic_custom_method__ .. autoexception:: dummy_module.DummyException :members: :undoc-members: .. autofunction:: dummy_module.function .. autofunction:: dummy_module.function_with_escaped_default .. autofunction:: dummy_module.function_with_unresolvable_annotation .. autofunction:: dummy_module.function_with_typehint_comment .. autoclass:: dummy_module.ClassWithTypehints :members: .. autofunction:: dummy_module.function_with_typehint_comment_not_inline .. autoclass:: dummy_module.ClassWithTypehintsNotInline :members: .. autofunction:: dummy_module.undocumented_function .. autoclass:: dummy_module.DataClass :undoc-members: :special-members: __init__ .. autodecorator:: dummy_module.Decorator .. autofunction:: dummy_module.mocked_import sphinx-autodoc-typehints-1.12.0/tests/test_sphinx_autodoc_typehints.py000066400000000000000000000434311403570317200265050ustar00rootroot00000000000000import pathlib import re import sys import textwrap import typing from typing import ( IO, Any, AnyStr, Callable, Dict, Generic, Mapping, Match, NewType, Optional, Pattern, Tuple, Type, TypeVar, Union) import pytest import typing_extensions from sphinx_autodoc_typehints import ( format_annotation, get_annotation_args, get_annotation_class_name, get_annotation_module, process_docstring) T = TypeVar('T') U = TypeVar('U', covariant=True) V = TypeVar('V', contravariant=True) W = NewType('W', str) class A: def get_type(self): return type(self) class Inner: pass class B(Generic[T]): # This is set to make sure the correct class name ("B") is picked up name = 'Foo' class C(B[str]): pass class D(typing_extensions.Protocol): pass class E(typing_extensions.Protocol[T]): pass class Slotted: __slots__ = () class Metaclass(type): pass @pytest.mark.parametrize('annotation, module, class_name, args', [ pytest.param(str, 'builtins', 'str', (), id='str'), pytest.param(None, 'builtins', 'None', (), id='None'), pytest.param(Any, 'typing', 'Any', (), id='Any'), pytest.param(AnyStr, 'typing', 'AnyStr', (), id='AnyStr'), pytest.param(Dict, 'typing', 'Dict', (), id='Dict'), pytest.param(Dict[str, int], 'typing', 'Dict', (str, int), id='Dict_parametrized'), pytest.param(Dict[T, int], 'typing', 'Dict', (T, int), id='Dict_typevar'), pytest.param(Tuple, 'typing', 'Tuple', (), id='Tuple'), pytest.param(Tuple[str, int], 'typing', 'Tuple', (str, int), id='Tuple_parametrized'), pytest.param(Union[str, int], 'typing', 'Union', (str, int), id='Union'), pytest.param(Callable, 'typing', 'Callable', (), id='Callable'), pytest.param(Callable[..., str], 'typing', 'Callable', (..., str), id='Callable_returntype'), pytest.param(Callable[[int, str], str], 'typing', 'Callable', (int, str, str), id='Callable_all_types'), pytest.param(Pattern, 'typing', 'Pattern', (), id='Pattern'), pytest.param(Pattern[str], 'typing', 'Pattern', (str,), id='Pattern_parametrized'), pytest.param(Match, 'typing', 'Match', (), id='Match'), pytest.param(Match[str], 'typing', 'Match', (str,), id='Match_parametrized'), pytest.param(IO, 'typing', 'IO', (), id='IO'), pytest.param(W, 'typing', 'NewType', (str,), id='W'), pytest.param(Metaclass, __name__, 'Metaclass', (), id='Metaclass'), pytest.param(Slotted, __name__, 'Slotted', (), id='Slotted'), pytest.param(A, __name__, 'A', (), id='A'), pytest.param(B, __name__, 'B', (), id='B'), pytest.param(C, __name__, 'C', (), id='C'), pytest.param(D, __name__, 'D', (), id='D'), pytest.param(E, __name__, 'E', (), id='E'), pytest.param(E[int], __name__, 'E', (int,), id='E_parametrized'), pytest.param(A.Inner, __name__, 'A.Inner', (), id='Inner') ]) def test_parse_annotation(annotation, module, class_name, args): assert get_annotation_module(annotation) == module assert get_annotation_class_name(annotation, module) == class_name assert get_annotation_args(annotation, module, class_name) == args @pytest.mark.parametrize('annotation, expected_result', [ (str, ':py:class:`str`'), (int, ':py:class:`int`'), (type(None), ':py:obj:`None`'), (type, ':py:class:`type`'), (Type, ':py:class:`~typing.Type`'), (Type[A], ':py:class:`~typing.Type`\\[:py:class:`~%s.A`]' % __name__), (Any, ':py:data:`~typing.Any`'), (AnyStr, ':py:data:`~typing.AnyStr`'), (Generic[T], ':py:class:`~typing.Generic`\\[\\~T]'), (Mapping, ':py:class:`~typing.Mapping`'), (Mapping[T, int], ':py:class:`~typing.Mapping`\\[\\~T, :py:class:`int`]'), (Mapping[str, V], ':py:class:`~typing.Mapping`\\[:py:class:`str`, \\-V]'), (Mapping[T, U], ':py:class:`~typing.Mapping`\\[\\~T, \\+U]'), (Mapping[str, bool], ':py:class:`~typing.Mapping`\\[:py:class:`str`, ' ':py:class:`bool`]'), (Dict, ':py:class:`~typing.Dict`'), (Dict[T, int], ':py:class:`~typing.Dict`\\[\\~T, :py:class:`int`]'), (Dict[str, V], ':py:class:`~typing.Dict`\\[:py:class:`str`, \\-V]'), (Dict[T, U], ':py:class:`~typing.Dict`\\[\\~T, \\+U]'), (Dict[str, bool], ':py:class:`~typing.Dict`\\[:py:class:`str`, ' ':py:class:`bool`]'), (Tuple, ':py:data:`~typing.Tuple`'), (Tuple[str, bool], ':py:data:`~typing.Tuple`\\[:py:class:`str`, ' ':py:class:`bool`]'), (Tuple[int, int, int], ':py:data:`~typing.Tuple`\\[:py:class:`int`, ' ':py:class:`int`, :py:class:`int`]'), (Tuple[str, ...], ':py:data:`~typing.Tuple`\\[:py:class:`str`, ...]'), (Union, ':py:data:`~typing.Union`'), (Union[str, bool], ':py:data:`~typing.Union`\\[:py:class:`str`, ' ':py:class:`bool`]'), (Union[str, bool, None], ':py:data:`~typing.Union`\\[:py:class:`str`, ' ':py:class:`bool`, :py:obj:`None`]'), pytest.param(Union[str, Any], ':py:data:`~typing.Union`\\[:py:class:`str`, ' ':py:data:`~typing.Any`]', marks=pytest.mark.skipif((3, 5, 0) <= sys.version_info[:3] <= (3, 5, 2), reason='Union erases the str on 3.5.0 -> 3.5.2')), (Optional[str], ':py:data:`~typing.Optional`\\[:py:class:`str`]'), (Optional[Union[str, bool]], ':py:data:`~typing.Union`\\[:py:class:`str`, ' ':py:class:`bool`, :py:obj:`None`]'), (Callable, ':py:data:`~typing.Callable`'), (Callable[..., int], ':py:data:`~typing.Callable`\\[..., :py:class:`int`]'), (Callable[[int], int], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`], ' ':py:class:`int`]'), (Callable[[int, str], bool], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, ' ':py:class:`str`], :py:class:`bool`]'), (Callable[[int, str], None], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, ' ':py:class:`str`], :py:obj:`None`]'), (Callable[[T], T], ':py:data:`~typing.Callable`\\[\\[\\~T], \\~T]'), (Pattern, ':py:class:`~typing.Pattern`'), (Pattern[str], ':py:class:`~typing.Pattern`\\[:py:class:`str`]'), (IO, ':py:class:`~typing.IO`'), (IO[str], ':py:class:`~typing.IO`\\[:py:class:`str`]'), (Metaclass, ':py:class:`~%s.Metaclass`' % __name__), (A, ':py:class:`~%s.A`' % __name__), (B, ':py:class:`~%s.B`' % __name__), (B[int], ':py:class:`~%s.B`\\[:py:class:`int`]' % __name__), (C, ':py:class:`~%s.C`' % __name__), (D, ':py:class:`~%s.D`' % __name__), (E, ':py:class:`~%s.E`' % __name__), (E[int], ':py:class:`~%s.E`\\[:py:class:`int`]' % __name__), (W, ':py:func:`~typing.NewType`\\(:py:data:`~W`, :py:class:`str`)') ]) def test_format_annotation(inv, annotation, expected_result): result = format_annotation(annotation) assert result == expected_result # Test with the "simplify_optional_unions" flag turned off: if re.match(r'^:py:data:`~typing\.Union`\\\[.*``None``.*\]', expected_result): # strip None - argument and copy string to avoid conflicts with # subsequent tests expected_result_not_simplified = expected_result.replace(', ``None``', '') # encapsulate Union in typing.Optional expected_result_not_simplified = ':py:data:`~typing.Optional`\\[' + \ expected_result_not_simplified expected_result_not_simplified += ']' assert format_annotation(annotation, simplify_optional_unions=False) == \ expected_result_not_simplified # Test with the "fully_qualified" flag turned on if 'typing' in expected_result_not_simplified: expected_result_not_simplified = expected_result_not_simplified.replace('~typing', 'typing') assert format_annotation(annotation, fully_qualified=True, simplify_optional_unions=False) == \ expected_result_not_simplified # Test with the "fully_qualified" flag turned on if 'typing' in expected_result or __name__ in expected_result: expected_result = expected_result.replace('~typing', 'typing') expected_result = expected_result.replace('~' + __name__, __name__) assert format_annotation(annotation, fully_qualified=True) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory if 'typing' in expected_result: m = re.match('^:py:(?Pclass|data|func):`~(?P[^`]+)`', result) assert m, 'No match' name = m.group('name') expected_role = next((o.role for o in inv.objects if o.name == name), None) if expected_role: if expected_role == 'function': expected_role = 'func' assert m.group('role') == expected_role @pytest.mark.parametrize('library', [typing, typing_extensions], ids=['typing', 'typing_extensions']) @pytest.mark.parametrize('annotation, params, expected_result', [ ('ClassVar', int, ":py:data:`~typing.ClassVar`\\[:py:class:`int`]"), ('NoReturn', None, ":py:data:`~typing.NoReturn`"), ('Literal', ('a', 1), ":py:data:`~typing.Literal`\\['a', 1]"), ('Type', None, ':py:class:`~typing.Type`'), ('Type', (A,), ':py:class:`~typing.Type`\\[:py:class:`~%s.A`]' % __name__) ]) def test_format_annotation_both_libs(inv, library, annotation, params, expected_result): try: annotation_cls = getattr(library, annotation) except AttributeError: pytest.skip('{} not available in the {} module'.format(annotation, library.__name__)) ann = annotation_cls if params is None else annotation_cls[params] result = format_annotation(ann) assert result == expected_result def test_process_docstring_slot_wrapper(): lines = [] process_docstring(None, 'class', 'SlotWrapper', Slotted, None, lines) assert not lines @pytest.mark.parametrize('always_document_param_types', [True, False]) @pytest.mark.sphinx('text', testroot='dummy') def test_sphinx_output(app, status, warning, always_document_param_types): test_path = pathlib.Path(__file__).parent # Add test directory to sys.path to allow imports of dummy module. if str(test_path) not in sys.path: sys.path.insert(0, str(test_path)) app.config.always_document_param_types = always_document_param_types app.config.autodoc_mock_imports = ['mailbox'] app.build() assert 'build succeeded' in status.getvalue() # Build succeeded # There should be a warning about an unresolved forward reference warnings = warning.getvalue().strip() assert 'Cannot resolve forward reference in type annotations of ' in warnings, warnings format_args = {} for indentation_level in range(2): key = f'undoc_params_{indentation_level}' if always_document_param_types: format_args[key] = textwrap.indent( '\n\n Parameters:\n **x** ("int") --', ' ' * indentation_level ) else: format_args[key] = '' text_path = pathlib.Path(app.srcdir) / '_build' / 'text' / 'index.txt' with text_path.open('r') as f: text_contents = f.read().replace('–', '--') expected_contents = textwrap.dedent('''\ Dummy Module ************ class dummy_module.Class(x, y, z=None) Initializer docstring. Parameters: * **x** ("bool") – foo * **y** ("int") – bar * **z** ("Optional"["str"]) – baz class InnerClass Inner class. __dunder_inner_method(x) Dunder inner method. Parameters: **x** ("bool") -- foo Return type: "str" inner_method(x) Inner method. Parameters: **x** ("bool") -- foo Return type: "str" __dunder_method(x) Dunder method docstring. Parameters: **x** ("str") -- foo Return type: "str" __magic_custom_method__(x) Magic dunder method docstring. Parameters: **x** ("str") -- foo Return type: "str" _private_method(x) Private method docstring. Parameters: **x** ("str") -- foo Return type: "str" classmethod a_classmethod(x, y, z=None) Classmethod docstring. Parameters: * **x** ("bool") – foo * **y** ("int") – bar * **z** ("Optional"["str"]) – baz Return type: "str" a_method(x, y, z=None) Method docstring. Parameters: * **x** ("bool") – foo * **y** ("int") – bar * **z** ("Optional"["str"]) – baz Return type: "str" property a_property Property docstring Return type: "str" static a_staticmethod(x, y, z=None) Staticmethod docstring. Parameters: * **x** ("bool") – foo * **y** ("int") – bar * **z** ("Optional"["str"]) – baz Return type: "str" locally_defined_callable_field() -> str Wrapper Return type: "str" exception dummy_module.DummyException(message) Exception docstring Parameters: **message** ("str") – blah dummy_module.function(x, y, z_=None) Function docstring. Parameters: * **x** ("bool") – foo * **y** ("int") – bar * **z_** ("Optional"["str"]) – baz Returns: something Return type: bytes dummy_module.function_with_escaped_default(x='\\\\x08') Function docstring. Parameters: **x** ("str") – foo dummy_module.function_with_unresolvable_annotation(x) Function docstring. Parameters: **x** (*a.b.c*) – foo dummy_module.function_with_typehint_comment(x, y) Function docstring. Parameters: * **x** ("int") – foo * **y** ("str") – bar Return type: "None" class dummy_module.ClassWithTypehints(x) Class docstring. Parameters: **x** ("int") -- foo foo(x) Method docstring. Parameters: **x** ("str") -- foo Return type: "int" method_without_typehint(x) Method docstring. dummy_module.function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs) Function docstring. Parameters: * **x** ("Union"["str", "bytes", "None"]) -- foo * **y** ("str") -- bar * **z** ("bytes") -- baz * **kwargs** ("int") -- some kwargs Return type: "None" class dummy_module.ClassWithTypehintsNotInline(x=None) Class docstring. Parameters: **x** ("Optional"["Callable"[["int", "bytes"], "int"]]) -- foo foo(x=1) Method docstring. Parameters: **x** ("Callable"[["int", "bytes"], "int"]) -- foo Return type: "int" classmethod mk(x=None) Method docstring. Parameters: **x** ("Optional"["Callable"[["int", "bytes"], "int"]]) -- foo Return type: "ClassWithTypehintsNotInline" dummy_module.undocumented_function(x) Hi{undoc_params_0} Return type: "str" class dummy_module.DataClass(x) Class docstring.{undoc_params_0} __init__(x) Initialize self. See help(type(self)) for accurate signature.{undoc_params_1} @dummy_module.Decorator(func) Initializer docstring. Parameters: **func** ("Callable"[["int", "str"], "str"]) -- function dummy_module.mocked_import(x) A docstring. Parameters: **x** ("Mailbox") -- function ''') expected_contents = expected_contents.format(**format_args).replace('–', '--') assert text_contents == expected_contents sphinx-autodoc-typehints-1.12.0/tox.ini000066400000000000000000000004661403570317200201100ustar00rootroot00000000000000[tox] minversion = 3.3.0 envlist = py36, py37, py38, py39, py310, flake8 skip_missing_interpreters = true isolated_build = true [testenv] extras = test, type_comments commands = python -m pytest {posargs} [testenv:flake8] deps = flake8 commands = flake8 sphinx_autodoc_typehints.py tests skip_install = true