Codebase list sphinx-autodoc-typehints / 336ce21
New upstream version 1.12.0 Dmitry Shachnev 2 years ago
18 changed file(s) with 710 addition(s) and 420 deletion(s). Raw diff Collapse all Expand all
0 ---
1 name: Bug report
2 about: Create a report to help us improve
3 title: ''
4 labels: ''
5 assignees: ''
6
7 ---
8
9 **Describe the bug**
10 A clear and concise description of what the bug is.
11
12 **To Reproduce**
13 Please provide a **minimal** reproducible example that developers can run to investigate the problem.
14 You can find help for creating such an example [here](https://stackoverflow.com/help/minimal-reproducible-example).
15
16 **Expected behavior**
17 A clear and concise description of what you expected to happen.
18
19 **Additional context**
20 Add any other context about the problem here.
0 ---
1 name: Feature request
2 about: Suggest an idea for this project
3 title: ''
4 labels: enhancement
5 assignees: ''
6
7 ---
8
9 **Is your feature request related to a problem? Please describe.**
10 A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11
12 **Describe the solution you'd like**
13 A clear and concise description of what you want to happen.
14
15 **Describe alternatives you've considered**
16 A clear and concise description of any alternative solutions or features you've considered.
17
18 **Additional context**
19 Add any other context or screenshots about the feature request here.
0 name: Python codeqa/test
1
2 on:
3 push:
4 branches: [master]
5 pull_request:
6
7 jobs:
8 lint:
9 runs-on: ubuntu-latest
10 steps:
11 - uses: actions/checkout@v2
12 - name: Set up Python
13 uses: actions/setup-python@v2
14 with:
15 python-version: 3.x
16 - uses: actions/cache@v2
17 with:
18 path: ~/.cache/pip
19 key: pip-lint
20 - name: Install dependencies
21 run: pip install flake8 isort
22 - name: Run flake8
23 run: flake8 sphinx_autodoc_typehints.py tests
24 - name: Run isort
25 run: isort -c sphinx_autodoc_typehints.py tests
26
27 test:
28 needs: [lint]
29 strategy:
30 fail-fast: false
31 matrix:
32 os: [ubuntu-latest]
33 python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.5]
34 runs-on: ${{ matrix.os }}
35 steps:
36 - uses: actions/checkout@v2
37 - name: Set up Python ${{ matrix.python-version }}
38 uses: actions/setup-python@v2
39 with:
40 python-version: ${{ matrix.python-version }}
41 - uses: actions/cache@v2
42 with:
43 path: ~/.cache/pip
44 key: pip-test-${{ matrix.python-version }}-${{ matrix.os }}
45 - name: Install dependencies
46 run: pip install .[test,type_comments] coveralls
47 - name: Test with pytest
48 run: coverage run -m pytest
49 - name: Upload Coverage
50 run: coveralls --service=github
51 env:
52 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 COVERALLS_FLAG_NAME: ${{ matrix.test-name }}
54 COVERALLS_PARALLEL: true
55
56 coveralls:
57 name: Finish Coveralls
58 needs: test
59 runs-on: ubuntu-latest
60 container: python:3-slim
61 steps:
62 - name: Finished
63 run: |
64 pip install coveralls
65 coveralls --service=github --finish
66 env:
67 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
0 name: Publish packages to PyPI
1
2 on:
3 push:
4 tags:
5 - "[0-9]+.[0-9]+.[0-9]+"
6 - "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+"
7 - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
8
9 jobs:
10 publish:
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v2
14 - name: Set up Python
15 uses: actions/setup-python@v2
16 with:
17 python-version: 3.x
18 - name: Install dependencies
19 run: pip install build
20 - name: Create packages
21 run: python -m build -s -w .
22 - name: Upload packages
23 uses: pypa/gh-action-pypi-publish@master
24 with:
25 user: __token__
26 password: ${{ secrets.pypi_password }}
1010 dist/
1111 build/
1212 .vscode/
13 .pre-commit-config.yaml
+0
-52
.travis.yml less more
0 dist: xenial
1 language: python
2 python: "3.6"
3
4 stages:
5 - name: static analysis
6 - name: test
7 - name: deploy to pypi
8 if: type = push AND tag =~ ^\d+\.\d+\.\d+
9
10 jobs:
11 include:
12 - stage: static analysis
13 env: TOXENV=flake8
14
15 - stage: test
16 env: TOXENV=py35
17 python: "3.5"
18 after_success: &after_success
19 - pip install coveralls
20 - coveralls
21
22 - stage: test
23 env: TOXENV=py36
24 python: "3.6"
25 after_success: *after_success
26
27 - stage: test
28 env: TOXENV=py37
29 python: "3.7"
30 after_success: *after_success
31
32 - stage: test
33 env: TOXENV=py38
34 python: "3.8"
35 after_success: *after_success
36
37 - stage: deploy to pypi
38 install: skip
39 script: skip
40 deploy:
41 provider: pypi
42 user: agronholm
43 password:
44 secure: duaV12IvSrtlrjcqkbOToB0YTQkFRMM3SADKPVL4JapNYbhGCHsNgauAptnIZrTIFy3B2ZQH5QxOu1xapR3LHbwCrh9VV6QYTU1BFV8ju5gTcnCWcuN0Sr42LuwB3v5sCjijMrNIfo04ovhgJKCPfOiFV3bsXv+PSUm221qLixG8vHmoP2Vqhb+8+McV/JeMMjxfMv/XFb3fWoQwaspERVu/Xt4f/taJ7JFNOJBjYYwYY79mxE6TJOTypgnrgypO0YyqjrvVsjFNuCH3QeQYtDIcJRTekp/Oo9hiNt6T4nuf3X09F9vKhFuGXtpmdwjnIktQb2jkP4FSHGJ3z/6UJP7yPgMXaFezzih5WjBVuMwDu9HOo4EHE+0hgkL5aQfbFulF2moE7PGEqhTWZkEzxGKc/ds+YbfYGigrcpuCm+KvDtQHAUkrIa8mEw5wM5+QGiiBGEzxZ6ifsZzxADEoCNshU3r6rHBWlA4ze5Q0PFCC7Jns2uqe51+9qqBz+cGKjQafn+1DwGBIr/tZusx8cjRJpsvZ116Zq7viCfzBmxEt4yA5UPYmpljS7bBSJrbrXVRNZGmAm9oO5adI99MnrQsDdVMM3KoC0R3JOmiGaKuM573am57EZ6c/hKKqyLs4MS6WLkYbygNPq3N0bQG6JKtvVKGPB1xTA116Mve6cJc=
45 distributions: sdist bdist_wheel
46 on:
47 tags: true
48
49 install: pip install tox
50
51 script: tox
0 1.9.0
1 =====
0 **1.12.0**
21
3 * Added support for typing_extensions_
4 * Added the ``typehints_document_rtype`` option (PR by Simon-Martin Schröder)
5 * Fixed metaclasses as annotations causing ``TypeError``
6 * Fixed rendering of ``typing.Literal``
7 * Fixed OSError when generating docs for SQLAlchemy mapped classes
8 * Fixed unparametrized generic classes being rendered with their type parameters
2 - Dropped Python 3.5 support
3 - Added the simplify_optional_unions config option (PR by tillhainbach)
4 - Fixed indentation of multiline strings (PR by Yuxin Wu)
5
6 **1.11.1**
7
8 - Changed formatting of ``None`` to point to the Python stdlib docs (PR by Dominic Davis-Foster)
9 - Updated special dataclass handling (PR by Lihu Ben-Ezri-Ravin)
10
11 **1.11.0**
12
13 - Dropped support for Sphinx < 3.0
14 - Added support for alternative parameter names (``arg``, ``argument``, ``parameter``)
15 - Fixed import path for Signature (PR by Matthew Treinish)
16 - Fixed ``TypeError`` when formatting a parametrized ``typing.IO`` annotation
17 - Fixed data class displaying a return type in its ``__init__()`` method
18
19 **1.10.3**
20
21 - Fixed ``TypeError`` (or wrong rendered class name) when an annotation is a generic class that has
22 a ``name`` property
23
24 **1.10.2**
25
26 - Fixed inner classes missing their parent class name(s) when rendered
27
28 **1.10.1**
29
30 - Fixed ``KeyError`` when encountering mocked annotations (``autodoc_mock_imports``)
31
32 **1.10.0**
33
34 - Rewrote the annotation formatting logic (fixes Python 3.5.2 compatibility regressions and an
35 ``AttributeError`` regression introduced in v1.9.0)
36 - Fixed decorator classes not being processed as classes
37
38 **1.9.0**
39
40 - Added support for typing_extensions_
41 - Added the ``typehints_document_rtype`` option (PR by Simon-Martin Schröder)
42 - Fixed metaclasses as annotations causing ``TypeError``
43 - Fixed rendering of ``typing.Literal``
44 - Fixed OSError when generating docs for SQLAlchemy mapped classes
45 - Fixed unparametrized generic classes being rendered with their type parameters
946 (e.g. ``Dict[~KT, ~VT]``)
1047
1148 .. _typing_extensions: https://pypi.org/project/typing-extensions/
1249
50 **1.8.0**
1351
14 1.8.0
15 =====
52 - Fixed regression which caused ``TypeError`` or ``OSError`` when trying to set annotations due to
53 PR #87
54 - Fixed unintentional mangling of annotation type names
55 - Added proper ``:py:data`` targets for ``NoReturn``, ``ClassVar`` and ``Tuple``
56 - Added support for inline type comments (like ``(int, str) -> None``) (PR by Bernát Gábor)
57 - Use the native AST parser for type comment support on Python 3.8+
1658
17 * Fixed regression which caused ``TypeError`` or ``OSError`` when trying to set annotations due to
18 PR #87
19 * Fixed unintentional mangling of annotation type names
20 * Added proper ``:py:data`` targets for ``NoReturn``, ``ClassVar`` and ``Tuple``
21 * Added support for inline type comments (like ``(int, str) -> None``) (PR by Bernát Gábor)
22 * Use the native AST parser for type comment support on Python 3.8+
59 **1.7.0**
2360
61 - Dropped support for Python 3.4
62 - Fixed unwrapped local functions causing errors (PR by Kimiyuki Onaka)
63 - Fixed ``AttributeError`` when documenting the ``__init__()`` method of a data class
64 - Added support for type hint comments (PR by Markus Unterwaditzer)
65 - Added flag for rendering classes with their fully qualified names (PR by Holly Becker)
2466
25 1.7.0
26 =====
67 **1.6.0**
2768
28 * Dropped support for Python 3.4
29 * Fixed unwrapped local functions causing errors (PR by Kimiyuki Onaka)
30 * Fixed ``AttributeError`` when documenting the ``__init__()`` method of a data class
31 * Added support for type hint comments (PR by Markus Unterwaditzer)
32 * Added flag for rendering classes with their fully qualified names (PR by Holly Becker)
69 - Fixed ``TypeError`` when formatting annotations from a class that inherits from a concrete
70 generic type (report and tests by bpeake-illuscio)
71 - Added support for ``typing_extensions.Protocol`` (PR by Ian Good)
72 - Added support for ``typing.NewType`` (PR by George Leslie-Waksman)
3373
74 **1.5.2**
3475
35 1.6.0
36 =====
37
38 * Fixed ``TypeError`` when formatting annotations from a class that inherits from a concrete
39 generic type (report and tests by bpeake-illuscio)
40 * Added support for ``typing_extensions.Protocol`` (PR by Ian Good)
41 * Added support for ``typing.NewType`` (PR by George Leslie-Waksman)
42
43
44 1.5.2
45 =====
46
47 * Emit a warning instead of crashing when an unresolvable forward reference is encountered in type
76 - Emit a warning instead of crashing when an unresolvable forward reference is encountered in type
4877 annotations
4978
79 **1.5.1**
5080
51 1.5.1
52 =====
53
54 * Fixed escape characters in parameter default values getting lost during signature processing
55 * Replaced use of the ``config-inited`` event (which inadvertently required Sphinx 1.8) with the
81 - Fixed escape characters in parameter default values getting lost during signature processing
82 - Replaced use of the ``config-inited`` event (which inadvertently required Sphinx 1.8) with the
5683 ``builder-inited`` event
5784
85 **1.5.0**
5886
59 1.5.0
60 =====
61
62 * The setting of the ``typing.TYPECHECKING`` flag is now configurable using the
87 - The setting of the ``typing.TYPECHECKING`` flag is now configurable using the
6388 ``set_type_checking_flag`` option
6489
90 **1.4.0**
6591
66 1.4.0
67 =====
92 - The extension now sets ``typing.TYPECHECKING`` to ``True`` during setup to include conditional
93 imports which may be used in type annotations
94 - Fixed parameters with trailing underscores (PR by Daniel Knell)
95 - Fixed KeyError with private methods (PR by Benito Palacios Sánchez)
96 - Fixed deprecation warning about the use of formatargspec (PR by Y. Somda)
97 - The minimum Sphinx version is now v1.7.0
6898
69 * The extension now sets ``typing.TYPECHECKING`` to ``True`` during setup to include conditional
70 imports which may be used in type annotations
71 * Fixed parameters with trailing underscores (PR by Daniel Knell)
72 * Fixed KeyError with private methods (PR by Benito Palacios Sánchez)
73 * Fixed deprecation warning about the use of formatargspec (PR by Y. Somda)
74 * The minimum Sphinx version is now v1.7.0
99 **1.3.1**
75100
101 - Fixed rendering of generic types outside the typing module (thanks to Tim Poterba for the PR)
76102
77 1.3.1
78 =====
103 **1.3.0**
79104
80 * Fixed rendering of generic types outside the typing module (thanks to Tim Poterba for the PR)
105 - Fixed crash when processing docstrings from nested classes (thanks to dilyanpalauzov for the fix)
106 - Added support for Python 3.7
107 - Dropped support for Python 3.5.0 and 3.5.1
81108
109 **1.2.5**
82110
83 1.3.0
84 =====
85
86 * Fixed crash when processing docstrings from nested classes (thanks to dilyanpalauzov for the fix)
87 * Added support for Python 3.7
88 * Dropped support for Python 3.5.0 and 3.5.1
89
90
91 1.2.5
92 =====
93
94 * Ensured that ``:rtype:`` doesn't get joined with a paragraph of text
111 - Ensured that ``:rtype:`` doesn't get joined with a paragraph of text
95112 (thanks to Bruce Merry for the PR)
96113
114 **1.2.4**
97115
98 1.2.4
99 =====
100
101 * Removed support for ``backports.typing`` as it has been removed from the PyPI
102 * Fixed first parameter being cut out from class methods and static methods
116 - Removed support for ``backports.typing`` as it has been removed from the PyPI
117 - Fixed first parameter being cut out from class methods and static methods
103118 (thanks to Josiah Wolf Oberholtzer for the PR)
104119
120 **1.2.3**
105121
106 1.2.3
107 =====
122 - Fixed `process_signature()` clobbering any explicitly overridden signatures from the docstring
108123
109 * Fixed `process_signature()` clobbering any explicitly overridden signatures from the docstring
124 **1.2.2**
110125
111
112 1.2.2
113 =====
114
115 * Explicitly prefix ``:class:``, ``:mod:`` et al with ``:py:``, in case ``py`` is not the default
126 - Explicitly prefix ``:class:``, ``:mod:`` et al with ``:py:``, in case ``py`` is not the default
116127 domain of the project (thanks Monty Taylor)
117128
129 **1.2.1**
118130
119 1.2.1
120 =====
121
122 * Fixed `ValueError` when `getargspec()` encounters a built-in function
123 * Fixed `AttributeError` when `Any` is combined with another type in a `Union`
131 - Fixed `ValueError` when `getargspec()` encounters a built-in function
132 - Fixed `AttributeError` when `Any` is combined with another type in a `Union`
124133 (thanks Davis Kirkendall)
125134
135 **1.2.0**
126136
127 1.2.0
128 =====
137 - Fixed compatibility with Python 3.6 and 3.5.3
138 - Fixed ``NameError`` when processing signatures of wrapped functions with type hints
139 - Fixed handling of slotted classes with no ``__init__()`` method
140 - Fixed Sphinx warning about parallel reads
141 - Fixed return type being added to class docstring from its ``__init__()`` method
142 (thanks to Manuel Krebber for the patch)
143 - Fixed return type hints of ``@property`` methods being omitted (thanks to pknight for the patch)
144 - Added a test suite (thanks Manuel Krebber)
129145
130 * Fixed compatibility with Python 3.6 and 3.5.3
131 * Fixed ``NameError`` when processing signatures of wrapped functions with type hints
132 * Fixed handling of slotted classes with no ``__init__()`` method
133 * Fixed Sphinx warning about parallel reads
134 * Fixed return type being added to class docstring from its ``__init__()`` method
135 (thanks to Manuel Krebber for the patch)
136 * Fixed return type hints of ``@property`` methods being omitted (thanks to pknight for the patch)
137 * Added a test suite (thanks Manuel Krebber)
146 **1.1.0**
138147
148 - Added proper support for ``typing.Tuple`` (pull request by Manuel Krebber)
139149
140 1.1.0
141 =====
150 **1.0.6**
142151
143 * Added proper support for ``typing.Tuple`` (pull request by Manuel Krebber)
152 - Fixed wrong placement of ``:rtype:`` if a multi-line ``:param:`` or a ``:returns:`` is used
144153
154 **1.0.5**
145155
146 1.0.6
147 =====
156 - Fixed coroutine functions' signatures not being processed when using sphinxcontrib-asyncio
148157
149 * Fixed wrong placement of ``:rtype:`` if a multi-line ``:param:`` or a ``:returns:`` is used
158 **1.0.4**
150159
160 - Fixed compatibility with Sphinx 1.4
151161
152 1.0.5
153 =====
162 **1.0.3**
154163
155 * Fixed coroutine functions' signatures not being processed when using sphinxcontrib-asyncio
164 - Fixed "self" parameter not being removed from exception class constructor signatures
165 - Fixed process_signature() erroneously removing the first argument of a static method
156166
167 **1.0.2**
157168
158 1.0.4
159 =====
169 - Fixed exception classes not being processed like normal classes
160170
161 * Fixed compatibility with Sphinx 1.4
171 **1.0.1**
162172
173 - Fixed errors caused by forward references not being looked up with the right globals
163174
164 1.0.3
165 =====
175 **1.0.0**
166176
167 * Fixed "self" parameter not being removed from exception class constructor signatures
168 * Fixed process_signature() erroneously removing the first argument of a static method
169
170
171 1.0.2
172 =====
173
174 * Fixed exception classes not being processed like normal classes
175
176
177 1.0.1
178 =====
179
180 * Fixed errors caused by forward references not being looked up with the right globals
181
182
183 1.0.0
184 =====
185
186 * Initial release
177 - Initial release
6464 be able to add type info.
6565 * ``typehints_document_rtype`` (default: ``True``): If ``False``, never add an ``:rtype:`` directive.
6666 If ``True``, add the ``:rtype:`` directive if no existing ``:rtype:`` is found.
67
67 * ``simplify_optional_unions`` (default: ``True``): If ``True``, optional parameters of type "Union[...]"
68 are simplified as being of type Union[..., None] in the resulting documention
69 (e.g. Optional[Union[A, B]] -> Union[A, B, None]).
70 If ``False``, the "Optional"-type is kept.
71 Note: If ``False``, **any** Union containing ``None`` will be displayed as Optional!
72 Note: If an optional parameter has only a single type (e.g Optional[A] or Union[A, None]),
73 it will **always** be displayed as Optional!
6874
6975 How it works
7076 ------------
0 # This is the configuration file for pre-commit (https://pre-commit.com/).
1 # To use:
2 # * Install pre-commit (https://pre-commit.com/#installation)
3 # * Copy this file as ".pre-commit-config.yaml"
4 # * Run "pre-commit install".
5 repos:
6 - repo: https://github.com/pre-commit/pre-commit-hooks
7 rev: v3.4.0
8 hooks:
9 - id: check-toml
10 - id: debug-statements
11 - id: end-of-file-fixer
12 - id: mixed-line-ending
13 args: ["--fix=lf"]
14 - id: trailing-whitespace
15 - repo: https://github.com/pre-commit/mirrors-autopep8
16 rev: v1.5.6
17 hooks:
18 - id: autopep8
19 - repo: https://github.com/pycqa/isort
20 rev: 5.8.0
21 hooks:
22 - id: isort
23 additional_dependencies: [toml]
44 "wheel >= 0.29.0",
55 ]
66 build-backend = 'setuptools.build_meta'
7
8 [tool.isort]
9 skip_gitignore = true
10 line_length = 99
11 multi_line_output = 4
12
13 [tool.autopep8]
14 max_line_length = 99
15
16 [tool.pytest.ini_options]
17 addopts = "-rsx --tb=short"
18 testpaths = ["tests"]
1717 Topic :: Documentation :: Sphinx
1818 Programming Language :: Python
1919 Programming Language :: Python :: 3
20 Programming Language :: Python :: 3.5
2120 Programming Language :: Python :: 3.6
2221 Programming Language :: Python :: 3.7
2322 Programming Language :: Python :: 3.8
23 Programming Language :: Python :: 3.9
24 Programming Language :: Python :: 3.10
2425
2526 [options]
2627 py_modules = sphinx_autodoc_typehints
27 python_requires = >=3.5.2
28 install_requires = Sphinx >= 2.1
28 python_requires = >=3.6
29 install_requires = Sphinx >= 3.0
2930
3031 [options.extras_require]
3132 test =
3334 typing_extensions >= 3.5
3435 dataclasses; python_version == "3.6"
3536 sphobjinv >= 2.0
37 Sphinx >= 3.2.0
3638 type_comments =
3739 typed_ast >= 1.4.0; python_version < "3.8"
3840
11 import sys
22 import textwrap
33 import typing
4 from typing import get_type_hints, TypeVar, Generic
4 from typing import Any, AnyStr, Tuple, TypeVar, get_type_hints
55
66 from sphinx.util import logging
7 from sphinx.util.inspect import Signature
8
9 try:
10 from typing_extensions import Protocol
11 except ImportError:
12 Protocol = None
7 from sphinx.util.inspect import signature as Signature
8 from sphinx.util.inspect import stringify_signature
139
1410 logger = logging.getLogger(__name__)
1511 pydata_annotations = {'Any', 'AnyStr', 'Callable', 'ClassVar', 'Literal', 'NoReturn', 'Optional',
1612 'Tuple', 'Union'}
1713
1814
19 def format_annotation(annotation, fully_qualified=False):
20 if inspect.isclass(annotation) and annotation.__module__ == 'builtins':
21 if annotation.__qualname__ == 'NoneType':
22 return '``None``'
15 def get_annotation_module(annotation) -> str:
16 # Special cases
17 if annotation is None:
18 return 'builtins'
19
20 if hasattr(annotation, '__module__'):
21 return annotation.__module__
22
23 if hasattr(annotation, '__origin__'):
24 return annotation.__origin__.__module__
25
26 raise ValueError('Cannot determine the module of {}'.format(annotation))
27
28
29 def get_annotation_class_name(annotation, module: str) -> str:
30 # Special cases
31 if annotation is None:
32 return 'None'
33 elif annotation is Any:
34 return 'Any'
35 elif annotation is AnyStr:
36 return 'AnyStr'
37 elif inspect.isfunction(annotation) and hasattr(annotation, '__supertype__'):
38 return 'NewType'
39
40 if getattr(annotation, '__qualname__', None):
41 return annotation.__qualname__
42 elif getattr(annotation, '_name', None): # Required for generic aliases on Python 3.7+
43 return annotation._name
44 elif (module in ('typing', 'typing_extensions')
45 and isinstance(getattr(annotation, 'name', None), str)):
46 # Required for at least Pattern and Match
47 return annotation.name
48
49 origin = getattr(annotation, '__origin__', None)
50 if origin:
51 if getattr(origin, '__qualname__', None): # Required for Protocol subclasses
52 return origin.__qualname__
53 elif getattr(origin, '_name', None): # Required for Union on Python 3.7+
54 return origin._name
2355 else:
24 return ':py:class:`{}`'.format(annotation.__qualname__)
25
26 annotation_cls = annotation if inspect.isclass(annotation) else type(annotation)
27 if annotation_cls.__module__ in ('typing', 'typing_extensions'):
28 class_name = str(annotation).split('[')[0].split('.')[-1]
29 params = None
30 module = 'typing'
31 extra = ''
32
33 origin = getattr(annotation, '__origin__', None)
34 if inspect.isclass(origin):
35 annotation_cls = annotation.__origin__
36 try:
37 mro = annotation_cls.mro()
38 if Generic in mro or (Protocol and Protocol in mro):
39 module = annotation_cls.__module__
40 except TypeError:
41 pass # annotation_cls was either the "type" object or typing.Type
42
43 if class_name == 'Any':
44 return ':py:data:`{}typing.Any`'.format("" if fully_qualified else "~")
45 elif class_name == '~AnyStr':
46 return ':py:data:`{}typing.AnyStr`'.format("" if fully_qualified else "~")
47 elif isinstance(annotation, TypeVar):
48 return '\\%r' % annotation
49 elif class_name == 'Union':
50 if hasattr(annotation, '__union_params__'):
51 params = annotation.__union_params__
52 elif hasattr(annotation, '__args__'):
53 params = annotation.__args__
54
55 if params and len(params) == 2 and (hasattr(params[1], '__qualname__') and
56 params[1].__qualname__ == 'NoneType'):
57 class_name = 'Optional'
58 params = (params[0],)
59 elif class_name == 'Tuple' and hasattr(annotation, '__tuple_params__'):
60 params = annotation.__tuple_params__
61 if annotation.__tuple_use_ellipsis__:
62 params += (Ellipsis,)
63 elif class_name == 'Callable':
64 arg_annotations = result_annotation = None
65 if hasattr(annotation, '__result__'):
66 arg_annotations = annotation.__args__
67 result_annotation = annotation.__result__
68 elif getattr(annotation, '__args__', None):
69 arg_annotations = annotation.__args__[:-1]
70 result_annotation = annotation.__args__[-1]
71
72 if arg_annotations in (Ellipsis, (Ellipsis,)):
73 params = [Ellipsis, result_annotation]
74 elif arg_annotations is not None:
75 params = [
76 '\\[{}]'.format(
77 ', '.join(
78 format_annotation(param, fully_qualified)
79 for param in arg_annotations)),
80 result_annotation
81 ]
82 elif class_name == 'Literal':
83 annotation_args = getattr(annotation, '__args__', ()) or annotation.__values__
84 extra = '\\[{}]'.format(', '.join(repr(arg) for arg in annotation_args))
85 elif class_name == 'ClassVar' and hasattr(annotation, '__type__'):
86 # < py3.7
87 params = (annotation.__type__,)
88 elif hasattr(annotation, 'type_var'):
89 # Type alias
90 class_name = annotation.name
91 params = (annotation.type_var,)
92 elif getattr(annotation, '__args__', None) is not None:
93 params = annotation.__args__
94 elif hasattr(annotation, '__parameters__'):
95 params = annotation.__parameters__
96
97 if params and annotation is not getattr(sys.modules[module], class_name):
98 extra = '\\[{}]'.format(', '.join(
99 format_annotation(param, fully_qualified) for param in params))
100
101 return '{prefix}`{qualify}{module}.{name}`{extra}'.format(
102 prefix=':py:data:' if class_name in pydata_annotations else ':py:class:',
103 qualify="" if fully_qualified else "~",
104 module=module,
105 name=class_name,
106 extra=extra
107 )
56 return origin.__class__.__qualname__.lstrip('_') # Required for Union on Python < 3.7
57
58 annotation_cls = annotation if inspect.isclass(annotation) else annotation.__class__
59 return annotation_cls.__qualname__.lstrip('_')
60
61
62 def get_annotation_args(annotation, module: str, class_name: str) -> Tuple:
63 try:
64 original = getattr(sys.modules[module], class_name)
65 except (KeyError, AttributeError):
66 pass
67 else:
68 if annotation is original:
69 return () # This is the original, unparametrized type
70
71 # Special cases
72 if class_name in ('Pattern', 'Match') and hasattr(annotation, 'type_var'): # Python < 3.7
73 return annotation.type_var,
74 elif class_name == 'ClassVar' and hasattr(annotation, '__type__'): # ClassVar on Python < 3.7
75 return annotation.__type__,
76 elif class_name == 'NewType' and hasattr(annotation, '__supertype__'):
77 return annotation.__supertype__,
78 elif class_name == 'Literal' and hasattr(annotation, '__values__'):
79 return annotation.__values__
80 elif class_name == 'Generic':
81 return annotation.__parameters__
82
83 return getattr(annotation, '__args__', ())
84
85
86 def format_annotation(annotation,
87 fully_qualified: bool = False,
88 simplify_optional_unions: bool = True) -> str:
89 # Special cases
90 if annotation is None or annotation is type(None): # noqa: E721
91 return ':py:obj:`None`'
10892 elif annotation is Ellipsis:
10993 return '...'
110 elif (inspect.isfunction(annotation) and annotation.__module__ == 'typing' and
111 hasattr(annotation, '__name__') and hasattr(annotation, '__supertype__')):
112 return ':py:func:`{qualify}typing.NewType`\\(:py:data:`~{name}`, {extra})'.format(
113 qualify="" if fully_qualified else "~",
114 name=annotation.__name__,
115 extra=format_annotation(annotation.__supertype__, fully_qualified),
116 )
117 elif inspect.isclass(annotation) or inspect.isclass(getattr(annotation, '__origin__', None)):
118 if not inspect.isclass(annotation):
119 annotation_cls = annotation.__origin__
120
121 extra = ''
122 try:
123 mro = annotation_cls.mro()
124 except TypeError:
125 pass
126 else:
127 if Generic in mro or (Protocol and Protocol in mro):
128 params = (getattr(annotation, '__parameters__', None) or
129 getattr(annotation, '__args__', None))
130 if params:
131 extra = '\\[{}]'.format(', '.join(
132 format_annotation(param, fully_qualified) for param in params))
133
134 return ':py:class:`{qualify}{module}.{name}`{extra}'.format(
135 qualify="" if fully_qualified else "~",
136 module=annotation.__module__,
137 name=annotation_cls.__qualname__,
138 extra=extra
139 )
140
141 return str(annotation)
94
95 # Type variables are also handled specially
96 try:
97 if isinstance(annotation, TypeVar) and annotation is not AnyStr:
98 return '\\' + repr(annotation)
99 except TypeError:
100 pass
101
102 try:
103 module = get_annotation_module(annotation)
104 class_name = get_annotation_class_name(annotation, module)
105 args = get_annotation_args(annotation, module, class_name)
106 except ValueError:
107 return str(annotation).strip("'")
108
109 # Redirect all typing_extensions types to the stdlib typing module
110 if module == 'typing_extensions':
111 module = 'typing'
112
113 full_name = (module + '.' + class_name) if module != 'builtins' else class_name
114 prefix = '' if fully_qualified or full_name == class_name else '~'
115 role = 'data' if class_name in pydata_annotations else 'class'
116 args_format = '\\[{}]'
117 formatted_args = ''
118
119 # Some types require special handling
120 if full_name == 'typing.NewType':
121 args_format = '\\(:py:data:`~{name}`, {{}})'.format(name=annotation.__name__)
122 role = 'func'
123 elif full_name == 'typing.Union' and type(None) in args:
124 if len(args) == 2:
125 full_name = 'typing.Optional'
126 args = tuple(x for x in args if x is not type(None)) # noqa: E721
127 elif not simplify_optional_unions:
128 full_name = 'typing.Optional'
129 args_format = '\\[:py:data:`{prefix}typing.Union`\\[{{}}]]'.format(prefix=prefix)
130 args = tuple(x for x in args if x is not type(None)) # noqa: E721
131 elif full_name == 'typing.Callable' and args and args[0] is not ...:
132 formatted_args = '\\[\\[' + ', '.join(
133 format_annotation(
134 arg, simplify_optional_unions=simplify_optional_unions)
135 for arg in args[:-1]) + ']'
136 formatted_args += ', ' + format_annotation(
137 args[-1], simplify_optional_unions=simplify_optional_unions) + ']'
138 elif full_name == 'typing.Literal':
139 formatted_args = '\\[' + ', '.join(repr(arg) for arg in args) + ']'
140
141 if args and not formatted_args:
142 formatted_args = args_format.format(', '.join(
143 format_annotation(arg, fully_qualified, simplify_optional_unions)
144 for arg in args))
145
146 return ':py:{role}:`{prefix}{full_name}`{formatted_args}'.format(
147 role=role, prefix=prefix, full_name=full_name, formatted_args=formatted_args)
148
149
150 # reference: https://github.com/pytorch/pytorch/pull/46548/files
151 def normalize_source_lines(sourcelines: str) -> str:
152 """
153 This helper function accepts a list of source lines. It finds the
154 indentation level of the function definition (`def`), then it indents
155 all lines in the function body to a point at or greater than that
156 level. This allows for comments and continued string literals that
157 are at a lower indentation than the rest of the code.
158 Arguments:
159 sourcelines: source code
160 Returns:
161 source lines that have been correctly aligned
162 """
163 sourcelines = sourcelines.split("\n")
164
165 def remove_prefix(text, prefix):
166 return text[text.startswith(prefix) and len(prefix):]
167
168 # Find the line and line number containing the function definition
169 for i, l in enumerate(sourcelines):
170 if l.lstrip().startswith("def"):
171 idx = i
172 break
173 else:
174 return "\n".join(sourcelines)
175 fn_def = sourcelines[idx]
176
177 # Get a string representing the amount of leading whitespace
178 whitespace = fn_def.split("def")[0]
179
180 # Add this leading whitespace to all lines before and after the `def`
181 aligned_prefix = [whitespace + remove_prefix(s, whitespace) for s in sourcelines[:idx]]
182 aligned_suffix = [whitespace + remove_prefix(s, whitespace) for s in sourcelines[idx + 1:]]
183
184 # Put it together again
185 aligned_prefix.append(fn_def)
186 return "\n".join(aligned_prefix + aligned_suffix)
142187
143188
144189 def process_signature(app, what: str, name: str, obj, options, signature, return_annotation):
145190 if not callable(obj):
146191 return
147192
148 if what in ('class', 'exception'):
193 original_obj = obj
194 if inspect.isclass(obj):
149195 obj = getattr(obj, '__init__', getattr(obj, '__new__', None))
150196
151197 if not getattr(obj, '__annotations__', None):
155201 signature = Signature(obj)
156202 parameters = [
157203 param.replace(annotation=inspect.Parameter.empty)
158 for param in signature.signature.parameters.values()
204 for param in signature.parameters.values()
159205 ]
160206
161 if '<locals>' in obj.__qualname__:
207 # The generated dataclass __init__() and class are weird and need extra checks
208 # This helper function operates on the generated class and methods
209 # of a dataclass, not an instantiated dataclass object. As such,
210 # it cannot be replaced by a call to `dataclasses.is_dataclass()`.
211 def _is_dataclass(name: str, what: str, qualname: str) -> bool:
212 if what == 'method' and name.endswith('.__init__'):
213 # generated __init__()
214 return True
215 if what == 'class' and qualname.endswith('.__init__'):
216 # generated class
217 return True
218 return False
219
220 if '<locals>' in obj.__qualname__ and not _is_dataclass(name, what, obj.__qualname__):
162221 logger.warning(
163222 'Cannot treat a function defined as a local function: "%s" (use @functools.wraps)',
164223 name)
165224 return
166225
167226 if parameters:
168 if what in ('class', 'exception'):
227 if inspect.isclass(original_obj) or (what == 'method' and name.endswith('.__init__')):
169228 del parameters[0]
170229 elif what == 'method':
171230 outer = inspect.getmodule(obj)
184243 if not isinstance(method_object, (classmethod, staticmethod)):
185244 del parameters[0]
186245
187 signature.signature = signature.signature.replace(
246 signature = signature.replace(
188247 parameters=parameters,
189248 return_annotation=inspect.Signature.empty)
190249
191 return signature.format_args().replace('\\', '\\\\'), None
250 return stringify_signature(signature).replace('\\', '\\\\'), None
192251
193252
194253 def get_all_type_hints(obj, name):
249308 return children[0]
250309
251310 try:
252 obj_ast = ast.parse(textwrap.dedent(inspect.getsource(obj)), **parse_kwargs)
311 obj_ast = ast.parse(textwrap.dedent(
312 normalize_source_lines(inspect.getsource(obj))), **parse_kwargs)
253313 except (OSError, TypeError):
254314 return {}
255315
344404
345405
346406 def process_docstring(app, what, name, obj, options, lines):
407 original_obj = obj
347408 if isinstance(obj, property):
348409 obj = obj.fget
349410
350411 if callable(obj):
351 if what in ('class', 'exception'):
412 if inspect.isclass(obj):
352413 obj = getattr(obj, '__init__')
353414
354415 obj = inspect.unwrap(obj)
361422 argname = '{}\\_'.format(argname[:-1])
362423
363424 formatted_annotation = format_annotation(
364 annotation, fully_qualified=app.config.typehints_fully_qualified)
365
366 searchfor = ':param {}:'.format(argname)
425 annotation,
426 fully_qualified=app.config.typehints_fully_qualified,
427 simplify_optional_unions=app.config.simplify_optional_unions)
428
429 searchfor = [':{} {}:'.format(field, argname)
430 for field in ('param', 'parameter', 'arg', 'argument')]
367431 insert_index = None
368432
369433 for i, line in enumerate(lines):
370 if line.startswith(searchfor):
434 if any(line.startswith(search_string) for search_string in searchfor):
371435 insert_index = i
372436 break
373437
374438 if insert_index is None and app.config.always_document_param_types:
375 lines.append(searchfor)
439 lines.append(':param {}:'.format(argname))
376440 insert_index = len(lines)
377441
378442 if insert_index is not None:
381445 ':type {}: {}'.format(argname, formatted_annotation)
382446 )
383447
384 if 'return' in type_hints and what not in ('class', 'exception'):
448 if 'return' in type_hints and not inspect.isclass(original_obj):
449 # This avoids adding a return type for data class __init__ methods
450 if what == 'method' and name.endswith('.__init__'):
451 return
452
385453 formatted_annotation = format_annotation(
386 type_hints['return'], fully_qualified=app.config.typehints_fully_qualified)
454 type_hints['return'], fully_qualified=app.config.typehints_fully_qualified,
455 simplify_optional_unions=app.config.simplify_optional_unions
456 )
387457
388458 insert_index = len(lines)
389459 for i, line in enumerate(lines):
413483 app.add_config_value('always_document_param_types', False, 'html')
414484 app.add_config_value('typehints_fully_qualified', False, 'env')
415485 app.add_config_value('typehints_document_rtype', True, 'env')
486 app.add_config_value('simplify_optional_unions', True, 'env')
416487 app.connect('builder-inited', builder_ready)
417488 app.connect('autodoc-process-signature', process_signature)
418489 app.connect('autodoc-process-docstring', process_docstring)
00 import os
1 import pathlib
2 import re
3 import shutil
14 import sys
2 import pathlib
3 import shutil
45
56 import pytest
67 from sphinx.testing.path import path
1213
1314 @pytest.fixture(scope='session')
1415 def inv(pytestconfig):
15 inv_dict = pytestconfig.cache.get('python/objects.inv', None)
16 cache_path = 'python{v.major}.{v.minor}/objects.inv'.format(v=sys.version_info)
17 inv_dict = pytestconfig.cache.get(cache_path, None)
1618 if inv_dict is not None:
1719 return Inventory(inv_dict)
20
1821 print("Downloading objects.inv")
1922 url = 'https://docs.python.org/{v.major}.{v.minor}/objects.inv'.format(v=sys.version_info)
2023 inv = Inventory(url=url)
21 pytestconfig.cache.set('python/objects.inv', inv.json_dict())
24 pytestconfig.cache.set(cache_path, inv.json_dict())
2225 return inv
2326
2427
3942 @pytest.fixture
4043 def rootdir():
4144 return path(os.path.dirname(__file__) or '.').abspath() / 'roots'
45
46
47 def pytest_ignore_collect(path, config):
48 version_re = re.compile(r'_py(\d)(\d)\.py$')
49 match = version_re.search(path.basename)
50 if match:
51 version = tuple(int(x) for x in match.groups())
52 if sys.version_info < version:
53 return True
00 import pathlib
11 import sys
2
32
43 # Make dummy_module.py available for autodoc.
54 sys.path.insert(0, str(pathlib.Path(__file__).parent))
1110 'sphinx.ext.autodoc',
1211 'sphinx.ext.napoleon',
1312 'sphinx_autodoc_typehints',
14 ]
13 ]
00 import typing
1 from dataclasses import dataclass
2 from mailbox import Mailbox
13 from typing import Callable, Union
2
3 try:
4 from dataclasses import dataclass
5 except ImportError:
6 def dataclass(cls):
7 return cls
84
95
106 def get_local_function():
140136 """
141137 Function docstring.
142138
143 :param x: foo
139 :arg x: foo
144140 """
145141
146142
152148 """
153149 Function docstring.
154150
155 :param x: foo
151 :parameter x: foo
156152 :param y: bar
157153 """
158154
179175 """
180176 Method docstring.
181177
182 :param x: foo
178 :arg x: foo
183179 """
184180 return 42
181
182 def method_without_typehint(self, x):
183 """
184 Method docstring.
185 """
186 # test that multiline str can be correctly indented
187 multiline_str = """
188 test
189 """
190 return multiline_str
185191
186192
187193 def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs):
189195 """
190196 Function docstring.
191197
192 :param x: foo
193 :param y: bar
194 :param z: baz
195 :param kwargs: some kwargs
198 :arg x: foo
199 :argument y: bar
200 :parameter z: baz
201 :parameter kwargs: some kwargs
196202 """
197203
198204
236242 @dataclass
237243 class DataClass:
238244 """Class docstring."""
245
246 x: int
247
248
249 class Decorator:
250 """
251 Initializer docstring.
252
253 :param func: function
254 """
255
256 def __init__(self, func: Callable[[int, str], str]):
257 pass
258
259
260 def mocked_import(x: Mailbox):
261 """
262 A docstring.
263
264 :param x: function
265 """
3131 .. autoclass:: dummy_module.DataClass
3232 :undoc-members:
3333 :special-members: __init__
34
35 .. autodecorator:: dummy_module.Decorator
36
37 .. autofunction:: dummy_module.mocked_import
22 import sys
33 import textwrap
44 import typing
5 from collections import defaultdict
65 from typing import (
7 Any, AnyStr, Callable, Dict, Generic, Mapping, NewType, Optional, Pattern,
8 Tuple, TypeVar, Union, Type)
6 IO, Any, AnyStr, Callable, Dict, Generic, Mapping, Match, NewType, Optional, Pattern, Tuple,
7 Type, TypeVar, Union)
98
109 import pytest
1110 import typing_extensions
1211
13 from sphinx_autodoc_typehints import format_annotation, process_docstring
14
15 try:
16 from typing import ClassVar # not available prior to Python 3.5.3
17 except ImportError:
18 ClassVar = None
19
20 try:
21 from typing import NoReturn # not available prior to Python 3.6.5
22 except ImportError:
23 NoReturn = None
24
25 try:
26 from typing import Literal
27 except ImportError:
28 Literal = defaultdict(lambda: None)
12 from sphinx_autodoc_typehints import (
13 format_annotation, get_annotation_args, get_annotation_class_name, get_annotation_module,
14 process_docstring)
2915
3016 T = TypeVar('T')
3117 U = TypeVar('U', covariant=True)
3723 def get_type(self):
3824 return type(self)
3925
26 class Inner:
27 pass
28
4029
4130 class B(Generic[T]):
42 pass
31 # This is set to make sure the correct class name ("B") is picked up
32 name = 'Foo'
4333
4434
4535 class C(B[str]):
6050
6151 class Metaclass(type):
6252 pass
53
54
55 @pytest.mark.parametrize('annotation, module, class_name, args', [
56 pytest.param(str, 'builtins', 'str', (), id='str'),
57 pytest.param(None, 'builtins', 'None', (), id='None'),
58 pytest.param(Any, 'typing', 'Any', (), id='Any'),
59 pytest.param(AnyStr, 'typing', 'AnyStr', (), id='AnyStr'),
60 pytest.param(Dict, 'typing', 'Dict', (), id='Dict'),
61 pytest.param(Dict[str, int], 'typing', 'Dict', (str, int), id='Dict_parametrized'),
62 pytest.param(Dict[T, int], 'typing', 'Dict', (T, int), id='Dict_typevar'),
63 pytest.param(Tuple, 'typing', 'Tuple', (), id='Tuple'),
64 pytest.param(Tuple[str, int], 'typing', 'Tuple', (str, int), id='Tuple_parametrized'),
65 pytest.param(Union[str, int], 'typing', 'Union', (str, int), id='Union'),
66 pytest.param(Callable, 'typing', 'Callable', (), id='Callable'),
67 pytest.param(Callable[..., str], 'typing', 'Callable', (..., str), id='Callable_returntype'),
68 pytest.param(Callable[[int, str], str], 'typing', 'Callable', (int, str, str),
69 id='Callable_all_types'),
70 pytest.param(Pattern, 'typing', 'Pattern', (), id='Pattern'),
71 pytest.param(Pattern[str], 'typing', 'Pattern', (str,), id='Pattern_parametrized'),
72 pytest.param(Match, 'typing', 'Match', (), id='Match'),
73 pytest.param(Match[str], 'typing', 'Match', (str,), id='Match_parametrized'),
74 pytest.param(IO, 'typing', 'IO', (), id='IO'),
75 pytest.param(W, 'typing', 'NewType', (str,), id='W'),
76 pytest.param(Metaclass, __name__, 'Metaclass', (), id='Metaclass'),
77 pytest.param(Slotted, __name__, 'Slotted', (), id='Slotted'),
78 pytest.param(A, __name__, 'A', (), id='A'),
79 pytest.param(B, __name__, 'B', (), id='B'),
80 pytest.param(C, __name__, 'C', (), id='C'),
81 pytest.param(D, __name__, 'D', (), id='D'),
82 pytest.param(E, __name__, 'E', (), id='E'),
83 pytest.param(E[int], __name__, 'E', (int,), id='E_parametrized'),
84 pytest.param(A.Inner, __name__, 'A.Inner', (), id='Inner')
85 ])
86 def test_parse_annotation(annotation, module, class_name, args):
87 assert get_annotation_module(annotation) == module
88 assert get_annotation_class_name(annotation, module) == class_name
89 assert get_annotation_args(annotation, module, class_name) == args
6390
6491
6592 @pytest.mark.parametrize('annotation, expected_result', [
6693 (str, ':py:class:`str`'),
6794 (int, ':py:class:`int`'),
68 (type(None), '``None``'),
95 (type(None), ':py:obj:`None`'),
6996 (type, ':py:class:`type`'),
7097 (Type, ':py:class:`~typing.Type`'),
7198 (Type[A], ':py:class:`~typing.Type`\\[:py:class:`~%s.A`]' % __name__),
72 pytest.param(NoReturn, ':py:data:`~typing.NoReturn`',
73 marks=[pytest.mark.skipif(NoReturn is None,
74 reason='typing.NoReturn is not available')]),
75 pytest.param(ClassVar[str], ':py:data:`~typing.ClassVar`\\[:py:class:`str`]',
76 marks=[pytest.mark.skipif(ClassVar is None,
77 reason='typing.ClassVar is not available')]),
7899 (Any, ':py:data:`~typing.Any`'),
79100 (AnyStr, ':py:data:`~typing.AnyStr`'),
80101 (Generic[T], ':py:class:`~typing.Generic`\\[\\~T]'),
99120 (Union, ':py:data:`~typing.Union`'),
100121 (Union[str, bool], ':py:data:`~typing.Union`\\[:py:class:`str`, '
101122 ':py:class:`bool`]'),
123 (Union[str, bool, None], ':py:data:`~typing.Union`\\[:py:class:`str`, '
124 ':py:class:`bool`, :py:obj:`None`]'),
102125 pytest.param(Union[str, Any], ':py:data:`~typing.Union`\\[:py:class:`str`, '
103126 ':py:data:`~typing.Any`]',
104127 marks=pytest.mark.skipif((3, 5, 0) <= sys.version_info[:3] <= (3, 5, 2),
105128 reason='Union erases the str on 3.5.0 -> 3.5.2')),
106129 (Optional[str], ':py:data:`~typing.Optional`\\[:py:class:`str`]'),
130 (Optional[Union[str, bool]], ':py:data:`~typing.Union`\\[:py:class:`str`, '
131 ':py:class:`bool`, :py:obj:`None`]'),
107132 (Callable, ':py:data:`~typing.Callable`'),
108133 (Callable[..., int], ':py:data:`~typing.Callable`\\[..., :py:class:`int`]'),
109134 (Callable[[int], int], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`], '
111136 (Callable[[int, str], bool], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, '
112137 ':py:class:`str`], :py:class:`bool`]'),
113138 (Callable[[int, str], None], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, '
114 ':py:class:`str`], ``None``]'),
139 ':py:class:`str`], :py:obj:`None`]'),
115140 (Callable[[T], T], ':py:data:`~typing.Callable`\\[\\[\\~T], \\~T]'),
116141 (Pattern, ':py:class:`~typing.Pattern`'),
117142 (Pattern[str], ':py:class:`~typing.Pattern`\\[:py:class:`str`]'),
118 pytest.param(Literal['a', 1], ":py:data:`~typing.Literal`\\['a', 1]",
119 marks=[pytest.mark.skipif(isinstance(Literal, defaultdict),
120 reason='Requires Python 3.8+')]),
143 (IO, ':py:class:`~typing.IO`'),
144 (IO[str], ':py:class:`~typing.IO`\\[:py:class:`str`]'),
121145 (Metaclass, ':py:class:`~%s.Metaclass`' % __name__),
122146 (A, ':py:class:`~%s.A`' % __name__),
123 (B, ':py:class:`~%s.B`\\[\\~T]' % __name__),
147 (B, ':py:class:`~%s.B`' % __name__),
124148 (B[int], ':py:class:`~%s.B`\\[:py:class:`int`]' % __name__),
125149 (C, ':py:class:`~%s.C`' % __name__),
126150 (D, ':py:class:`~%s.D`' % __name__),
127 (E, ':py:class:`~%s.E`\\[\\~T]' % __name__),
151 (E, ':py:class:`~%s.E`' % __name__),
128152 (E[int], ':py:class:`~%s.E`\\[:py:class:`int`]' % __name__),
129153 (W, ':py:func:`~typing.NewType`\\(:py:data:`~W`, :py:class:`str`)')
130154 ])
132156 result = format_annotation(annotation)
133157 assert result == expected_result
134158
159 # Test with the "simplify_optional_unions" flag turned off:
160 if re.match(r'^:py:data:`~typing\.Union`\\\[.*``None``.*\]', expected_result):
161 # strip None - argument and copy string to avoid conflicts with
162 # subsequent tests
163 expected_result_not_simplified = expected_result.replace(', ``None``', '')
164 # encapsulate Union in typing.Optional
165 expected_result_not_simplified = ':py:data:`~typing.Optional`\\[' + \
166 expected_result_not_simplified
167 expected_result_not_simplified += ']'
168 assert format_annotation(annotation, simplify_optional_unions=False) == \
169 expected_result_not_simplified
170
171 # Test with the "fully_qualified" flag turned on
172 if 'typing' in expected_result_not_simplified:
173 expected_result_not_simplified = expected_result_not_simplified.replace('~typing',
174 'typing')
175 assert format_annotation(annotation,
176 fully_qualified=True,
177 simplify_optional_unions=False) == \
178 expected_result_not_simplified
179
135180 # Test with the "fully_qualified" flag turned on
136181 if 'typing' in expected_result or __name__ in expected_result:
137182 expected_result = expected_result.replace('~typing', 'typing')
143188 m = re.match('^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`', result)
144189 assert m, 'No match'
145190 name = m.group('name')
146 role = next((o.role for o in inv.objects if o.name == name), None)
147 if name in {'typing.Pattern', 'typing.Match', 'typing.NoReturn'}:
148 if sys.version_info < (3, 6):
149 assert role is None, 'No entry in Python 3.5’s objects.inv'
150 return
151
152 assert role is not None, 'Name {} not found'.format(name)
153 assert m.group('role') == ('func' if role == 'function' else role)
191 expected_role = next((o.role for o in inv.objects if o.name == name), None)
192 if expected_role:
193 if expected_role == 'function':
194 expected_role = 'func'
195
196 assert m.group('role') == expected_role
154197
155198
156199 @pytest.mark.parametrize('library', [typing, typing_extensions],
157200 ids=['typing', 'typing_extensions'])
158201 @pytest.mark.parametrize('annotation, params, expected_result', [
202 ('ClassVar', int, ":py:data:`~typing.ClassVar`\\[:py:class:`int`]"),
203 ('NoReturn', None, ":py:data:`~typing.NoReturn`"),
159204 ('Literal', ('a', 1), ":py:data:`~typing.Literal`\\['a', 1]"),
160205 ('Type', None, ':py:class:`~typing.Type`'),
161206 ('Type', (A,), ':py:class:`~typing.Type`\\[:py:class:`~%s.A`]' % __name__)
187232 sys.path.insert(0, str(test_path))
188233
189234 app.config.always_document_param_types = always_document_param_types
235 app.config.autodoc_mock_imports = ['mailbox']
190236 app.build()
191237
192238 assert 'build succeeded' in status.getvalue() # Build succeeded
195241 warnings = warning.getvalue().strip()
196242 assert 'Cannot resolve forward reference in type annotations of ' in warnings, warnings
197243
198 if always_document_param_types:
199 undoc_params = '''
200
201 Parameters:
202 **x** ("int") --'''
203
204 else:
205 undoc_params = ""
244 format_args = {}
245 for indentation_level in range(2):
246 key = f'undoc_params_{indentation_level}'
247 if always_document_param_types:
248 format_args[key] = textwrap.indent(
249 '\n\n Parameters:\n **x** ("int") --', ' ' * indentation_level
250 )
251 else:
252 format_args[key] = ''
206253
207254 text_path = pathlib.Path(app.srcdir) / '_build' / 'text' / 'index.txt'
208255 with text_path.open('r') as f:
226273
227274 Inner class.
228275
229 _InnerClass__dunder_inner_method(x)
276 __dunder_inner_method(x)
230277
231278 Dunder inner method.
232279
246293 Return type:
247294 "str"
248295
249 _Class__dunder_method(x)
296 __dunder_method(x)
250297
251298 Dunder method docstring.
252299
356403 Return type:
357404 bytes
358405
359 dummy_module.function_with_escaped_default(x='\\x08')
406 dummy_module.function_with_escaped_default(x='\\\\x08')
360407
361408 Function docstring.
362409
399446 Return type:
400447 "int"
401448
449 method_without_typehint(x)
450
451 Method docstring.
452
402453 dummy_module.function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs)
403454
404455 Function docstring.
437488 Method docstring.
438489
439490 Parameters:
440 **x** (*Callable**[**[**int**, **bytes**]**, **int**]*) --
491 **x** ("Optional"["Callable"[["int", "bytes"], "int"]]) --
441492 foo
442493
443494 Return type:
444 ClassWithTypehintsNotInline
495 "ClassWithTypehintsNotInline"
445496
446497 dummy_module.undocumented_function(x)
447498
448 Hi{undoc_params}
499 Hi{undoc_params_0}
449500
450501 Return type:
451502 "str"
452503
453 class dummy_module.DataClass
454
455 Class docstring.
456
457 __init__()
458 '''.format(undoc_params=undoc_params)).replace('–', '--')
459
460 if sys.version_info < (3, 6):
461 expected_contents += '''
462 Initialize self. See help(type(self)) for accurate signature.
463 '''
464 else:
465 expected_contents += '''
466 Return type:
467 "None"
468 '''
469
504 class dummy_module.DataClass(x)
505
506 Class docstring.{undoc_params_0}
507
508 __init__(x)
509
510 Initialize self. See help(type(self)) for accurate signature.{undoc_params_1}
511
512 @dummy_module.Decorator(func)
513
514 Initializer docstring.
515
516 Parameters:
517 **func** ("Callable"[["int", "str"], "str"]) -- function
518
519 dummy_module.mocked_import(x)
520
521 A docstring.
522
523 Parameters:
524 **x** ("Mailbox") -- function
525 ''')
526 expected_contents = expected_contents.format(**format_args).replace('–', '--')
470527 assert text_contents == expected_contents
00 [tox]
11 minversion = 3.3.0
2 envlist = py35, py36, py37, py38, flake8
2 envlist = py36, py37, py38, py39, py310, flake8
33 skip_missing_interpreters = true
44 isolated_build = true
55