New upstream version 1.12.0
Dmitry Shachnev
2 years ago
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 }} |
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** | |
2 | 1 | |
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 | |
9 | 46 | (e.g. ``Dict[~KT, ~VT]``) |
10 | 47 | |
11 | 48 | .. _typing_extensions: https://pypi.org/project/typing-extensions/ |
12 | 49 | |
50 | **1.8.0** | |
13 | 51 | |
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+ | |
16 | 58 | |
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** | |
23 | 60 | |
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) | |
24 | 66 | |
25 | 1.7.0 | |
26 | ===== | |
67 | **1.6.0** | |
27 | 68 | |
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) | |
33 | 73 | |
74 | **1.5.2** | |
34 | 75 | |
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 | |
48 | 77 | annotations |
49 | 78 | |
79 | **1.5.1** | |
50 | 80 | |
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 | |
56 | 83 | ``builder-inited`` event |
57 | 84 | |
85 | **1.5.0** | |
58 | 86 | |
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 | |
63 | 88 | ``set_type_checking_flag`` option |
64 | 89 | |
90 | **1.4.0** | |
65 | 91 | |
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 | |
68 | 98 | |
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** | |
75 | 100 | |
101 | - Fixed rendering of generic types outside the typing module (thanks to Tim Poterba for the PR) | |
76 | 102 | |
77 | 1.3.1 | |
78 | ===== | |
103 | **1.3.0** | |
79 | 104 | |
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 | |
81 | 108 | |
109 | **1.2.5** | |
82 | 110 | |
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 | |
95 | 112 | (thanks to Bruce Merry for the PR) |
96 | 113 | |
114 | **1.2.4** | |
97 | 115 | |
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 | |
103 | 118 | (thanks to Josiah Wolf Oberholtzer for the PR) |
104 | 119 | |
120 | **1.2.3** | |
105 | 121 | |
106 | 1.2.3 | |
107 | ===== | |
122 | - Fixed `process_signature()` clobbering any explicitly overridden signatures from the docstring | |
108 | 123 | |
109 | * Fixed `process_signature()` clobbering any explicitly overridden signatures from the docstring | |
124 | **1.2.2** | |
110 | 125 | |
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 | |
116 | 127 | domain of the project (thanks Monty Taylor) |
117 | 128 | |
129 | **1.2.1** | |
118 | 130 | |
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` | |
124 | 133 | (thanks Davis Kirkendall) |
125 | 134 | |
135 | **1.2.0** | |
126 | 136 | |
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) | |
129 | 145 | |
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** | |
138 | 147 | |
148 | - Added proper support for ``typing.Tuple`` (pull request by Manuel Krebber) | |
139 | 149 | |
140 | 1.1.0 | |
141 | ===== | |
150 | **1.0.6** | |
142 | 151 | |
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 | |
144 | 153 | |
154 | **1.0.5** | |
145 | 155 | |
146 | 1.0.6 | |
147 | ===== | |
156 | - Fixed coroutine functions' signatures not being processed when using sphinxcontrib-asyncio | |
148 | 157 | |
149 | * Fixed wrong placement of ``:rtype:`` if a multi-line ``:param:`` or a ``:returns:`` is used | |
158 | **1.0.4** | |
150 | 159 | |
160 | - Fixed compatibility with Sphinx 1.4 | |
151 | 161 | |
152 | 1.0.5 | |
153 | ===== | |
162 | **1.0.3** | |
154 | 163 | |
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 | |
156 | 166 | |
167 | **1.0.2** | |
157 | 168 | |
158 | 1.0.4 | |
159 | ===== | |
169 | - Fixed exception classes not being processed like normal classes | |
160 | 170 | |
161 | * Fixed compatibility with Sphinx 1.4 | |
171 | **1.0.1** | |
162 | 172 | |
173 | - Fixed errors caused by forward references not being looked up with the right globals | |
163 | 174 | |
164 | 1.0.3 | |
165 | ===== | |
175 | **1.0.0** | |
166 | 176 | |
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 |
64 | 64 | be able to add type info. |
65 | 65 | * ``typehints_document_rtype`` (default: ``True``): If ``False``, never add an ``:rtype:`` directive. |
66 | 66 | 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! | |
68 | 74 | |
69 | 75 | How it works |
70 | 76 | ------------ |
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] |
4 | 4 | "wheel >= 0.29.0", |
5 | 5 | ] |
6 | 6 | 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"] |
17 | 17 | Topic :: Documentation :: Sphinx |
18 | 18 | Programming Language :: Python |
19 | 19 | Programming Language :: Python :: 3 |
20 | Programming Language :: Python :: 3.5 | |
21 | 20 | Programming Language :: Python :: 3.6 |
22 | 21 | Programming Language :: Python :: 3.7 |
23 | 22 | Programming Language :: Python :: 3.8 |
23 | Programming Language :: Python :: 3.9 | |
24 | Programming Language :: Python :: 3.10 | |
24 | 25 | |
25 | 26 | [options] |
26 | 27 | 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 | |
29 | 30 | |
30 | 31 | [options.extras_require] |
31 | 32 | test = |
33 | 34 | typing_extensions >= 3.5 |
34 | 35 | dataclasses; python_version == "3.6" |
35 | 36 | sphobjinv >= 2.0 |
37 | Sphinx >= 3.2.0 | |
36 | 38 | type_comments = |
37 | 39 | typed_ast >= 1.4.0; python_version < "3.8" |
38 | 40 |
1 | 1 | import sys |
2 | 2 | import textwrap |
3 | 3 | import typing |
4 | from typing import get_type_hints, TypeVar, Generic | |
4 | from typing import Any, AnyStr, Tuple, TypeVar, get_type_hints | |
5 | 5 | |
6 | 6 | 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 | |
13 | 9 | |
14 | 10 | logger = logging.getLogger(__name__) |
15 | 11 | pydata_annotations = {'Any', 'AnyStr', 'Callable', 'ClassVar', 'Literal', 'NoReturn', 'Optional', |
16 | 12 | 'Tuple', 'Union'} |
17 | 13 | |
18 | 14 | |
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 | |
23 | 55 | 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`' | |
108 | 92 | elif annotation is Ellipsis: |
109 | 93 | 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) | |
142 | 187 | |
143 | 188 | |
144 | 189 | def process_signature(app, what: str, name: str, obj, options, signature, return_annotation): |
145 | 190 | if not callable(obj): |
146 | 191 | return |
147 | 192 | |
148 | if what in ('class', 'exception'): | |
193 | original_obj = obj | |
194 | if inspect.isclass(obj): | |
149 | 195 | obj = getattr(obj, '__init__', getattr(obj, '__new__', None)) |
150 | 196 | |
151 | 197 | if not getattr(obj, '__annotations__', None): |
155 | 201 | signature = Signature(obj) |
156 | 202 | parameters = [ |
157 | 203 | param.replace(annotation=inspect.Parameter.empty) |
158 | for param in signature.signature.parameters.values() | |
204 | for param in signature.parameters.values() | |
159 | 205 | ] |
160 | 206 | |
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__): | |
162 | 221 | logger.warning( |
163 | 222 | 'Cannot treat a function defined as a local function: "%s" (use @functools.wraps)', |
164 | 223 | name) |
165 | 224 | return |
166 | 225 | |
167 | 226 | if parameters: |
168 | if what in ('class', 'exception'): | |
227 | if inspect.isclass(original_obj) or (what == 'method' and name.endswith('.__init__')): | |
169 | 228 | del parameters[0] |
170 | 229 | elif what == 'method': |
171 | 230 | outer = inspect.getmodule(obj) |
184 | 243 | if not isinstance(method_object, (classmethod, staticmethod)): |
185 | 244 | del parameters[0] |
186 | 245 | |
187 | signature.signature = signature.signature.replace( | |
246 | signature = signature.replace( | |
188 | 247 | parameters=parameters, |
189 | 248 | return_annotation=inspect.Signature.empty) |
190 | 249 | |
191 | return signature.format_args().replace('\\', '\\\\'), None | |
250 | return stringify_signature(signature).replace('\\', '\\\\'), None | |
192 | 251 | |
193 | 252 | |
194 | 253 | def get_all_type_hints(obj, name): |
249 | 308 | return children[0] |
250 | 309 | |
251 | 310 | 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) | |
253 | 313 | except (OSError, TypeError): |
254 | 314 | return {} |
255 | 315 | |
344 | 404 | |
345 | 405 | |
346 | 406 | def process_docstring(app, what, name, obj, options, lines): |
407 | original_obj = obj | |
347 | 408 | if isinstance(obj, property): |
348 | 409 | obj = obj.fget |
349 | 410 | |
350 | 411 | if callable(obj): |
351 | if what in ('class', 'exception'): | |
412 | if inspect.isclass(obj): | |
352 | 413 | obj = getattr(obj, '__init__') |
353 | 414 | |
354 | 415 | obj = inspect.unwrap(obj) |
361 | 422 | argname = '{}\\_'.format(argname[:-1]) |
362 | 423 | |
363 | 424 | 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')] | |
367 | 431 | insert_index = None |
368 | 432 | |
369 | 433 | for i, line in enumerate(lines): |
370 | if line.startswith(searchfor): | |
434 | if any(line.startswith(search_string) for search_string in searchfor): | |
371 | 435 | insert_index = i |
372 | 436 | break |
373 | 437 | |
374 | 438 | if insert_index is None and app.config.always_document_param_types: |
375 | lines.append(searchfor) | |
439 | lines.append(':param {}:'.format(argname)) | |
376 | 440 | insert_index = len(lines) |
377 | 441 | |
378 | 442 | if insert_index is not None: |
381 | 445 | ':type {}: {}'.format(argname, formatted_annotation) |
382 | 446 | ) |
383 | 447 | |
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 | ||
385 | 453 | 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 | ) | |
387 | 457 | |
388 | 458 | insert_index = len(lines) |
389 | 459 | for i, line in enumerate(lines): |
413 | 483 | app.add_config_value('always_document_param_types', False, 'html') |
414 | 484 | app.add_config_value('typehints_fully_qualified', False, 'env') |
415 | 485 | app.add_config_value('typehints_document_rtype', True, 'env') |
486 | app.add_config_value('simplify_optional_unions', True, 'env') | |
416 | 487 | app.connect('builder-inited', builder_ready) |
417 | 488 | app.connect('autodoc-process-signature', process_signature) |
418 | 489 | app.connect('autodoc-process-docstring', process_docstring) |
0 | 0 | import os |
1 | import pathlib | |
2 | import re | |
3 | import shutil | |
1 | 4 | import sys |
2 | import pathlib | |
3 | import shutil | |
4 | 5 | |
5 | 6 | import pytest |
6 | 7 | from sphinx.testing.path import path |
12 | 13 | |
13 | 14 | @pytest.fixture(scope='session') |
14 | 15 | 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) | |
16 | 18 | if inv_dict is not None: |
17 | 19 | return Inventory(inv_dict) |
20 | ||
18 | 21 | print("Downloading objects.inv") |
19 | 22 | url = 'https://docs.python.org/{v.major}.{v.minor}/objects.inv'.format(v=sys.version_info) |
20 | 23 | inv = Inventory(url=url) |
21 | pytestconfig.cache.set('python/objects.inv', inv.json_dict()) | |
24 | pytestconfig.cache.set(cache_path, inv.json_dict()) | |
22 | 25 | return inv |
23 | 26 | |
24 | 27 | |
39 | 42 | @pytest.fixture |
40 | 43 | def rootdir(): |
41 | 44 | 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 |
0 | 0 | import pathlib |
1 | 1 | import sys |
2 | ||
3 | 2 | |
4 | 3 | # Make dummy_module.py available for autodoc. |
5 | 4 | sys.path.insert(0, str(pathlib.Path(__file__).parent)) |
11 | 10 | 'sphinx.ext.autodoc', |
12 | 11 | 'sphinx.ext.napoleon', |
13 | 12 | 'sphinx_autodoc_typehints', |
14 | ] | |
13 | ] |
0 | 0 | import typing |
1 | from dataclasses import dataclass | |
2 | from mailbox import Mailbox | |
1 | 3 | from typing import Callable, Union |
2 | ||
3 | try: | |
4 | from dataclasses import dataclass | |
5 | except ImportError: | |
6 | def dataclass(cls): | |
7 | return cls | |
8 | 4 | |
9 | 5 | |
10 | 6 | def get_local_function(): |
140 | 136 | """ |
141 | 137 | Function docstring. |
142 | 138 | |
143 | :param x: foo | |
139 | :arg x: foo | |
144 | 140 | """ |
145 | 141 | |
146 | 142 | |
152 | 148 | """ |
153 | 149 | Function docstring. |
154 | 150 | |
155 | :param x: foo | |
151 | :parameter x: foo | |
156 | 152 | :param y: bar |
157 | 153 | """ |
158 | 154 | |
179 | 175 | """ |
180 | 176 | Method docstring. |
181 | 177 | |
182 | :param x: foo | |
178 | :arg x: foo | |
183 | 179 | """ |
184 | 180 | 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 | |
185 | 191 | |
186 | 192 | |
187 | 193 | def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs): |
189 | 195 | """ |
190 | 196 | Function docstring. |
191 | 197 | |
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 | |
196 | 202 | """ |
197 | 203 | |
198 | 204 | |
236 | 242 | @dataclass |
237 | 243 | class DataClass: |
238 | 244 | """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 | """ |
31 | 31 | .. autoclass:: dummy_module.DataClass |
32 | 32 | :undoc-members: |
33 | 33 | :special-members: __init__ |
34 | ||
35 | .. autodecorator:: dummy_module.Decorator | |
36 | ||
37 | .. autofunction:: dummy_module.mocked_import |
2 | 2 | import sys |
3 | 3 | import textwrap |
4 | 4 | import typing |
5 | from collections import defaultdict | |
6 | 5 | 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) | |
9 | 8 | |
10 | 9 | import pytest |
11 | 10 | import typing_extensions |
12 | 11 | |
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) | |
29 | 15 | |
30 | 16 | T = TypeVar('T') |
31 | 17 | U = TypeVar('U', covariant=True) |
37 | 23 | def get_type(self): |
38 | 24 | return type(self) |
39 | 25 | |
26 | class Inner: | |
27 | pass | |
28 | ||
40 | 29 | |
41 | 30 | class B(Generic[T]): |
42 | pass | |
31 | # This is set to make sure the correct class name ("B") is picked up | |
32 | name = 'Foo' | |
43 | 33 | |
44 | 34 | |
45 | 35 | class C(B[str]): |
60 | 50 | |
61 | 51 | class Metaclass(type): |
62 | 52 | 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 | |
63 | 90 | |
64 | 91 | |
65 | 92 | @pytest.mark.parametrize('annotation, expected_result', [ |
66 | 93 | (str, ':py:class:`str`'), |
67 | 94 | (int, ':py:class:`int`'), |
68 | (type(None), '``None``'), | |
95 | (type(None), ':py:obj:`None`'), | |
69 | 96 | (type, ':py:class:`type`'), |
70 | 97 | (Type, ':py:class:`~typing.Type`'), |
71 | 98 | (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')]), | |
78 | 99 | (Any, ':py:data:`~typing.Any`'), |
79 | 100 | (AnyStr, ':py:data:`~typing.AnyStr`'), |
80 | 101 | (Generic[T], ':py:class:`~typing.Generic`\\[\\~T]'), |
99 | 120 | (Union, ':py:data:`~typing.Union`'), |
100 | 121 | (Union[str, bool], ':py:data:`~typing.Union`\\[:py:class:`str`, ' |
101 | 122 | ':py:class:`bool`]'), |
123 | (Union[str, bool, None], ':py:data:`~typing.Union`\\[:py:class:`str`, ' | |
124 | ':py:class:`bool`, :py:obj:`None`]'), | |
102 | 125 | pytest.param(Union[str, Any], ':py:data:`~typing.Union`\\[:py:class:`str`, ' |
103 | 126 | ':py:data:`~typing.Any`]', |
104 | 127 | marks=pytest.mark.skipif((3, 5, 0) <= sys.version_info[:3] <= (3, 5, 2), |
105 | 128 | reason='Union erases the str on 3.5.0 -> 3.5.2')), |
106 | 129 | (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`]'), | |
107 | 132 | (Callable, ':py:data:`~typing.Callable`'), |
108 | 133 | (Callable[..., int], ':py:data:`~typing.Callable`\\[..., :py:class:`int`]'), |
109 | 134 | (Callable[[int], int], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`], ' |
111 | 136 | (Callable[[int, str], bool], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, ' |
112 | 137 | ':py:class:`str`], :py:class:`bool`]'), |
113 | 138 | (Callable[[int, str], None], ':py:data:`~typing.Callable`\\[\\[:py:class:`int`, ' |
114 | ':py:class:`str`], ``None``]'), | |
139 | ':py:class:`str`], :py:obj:`None`]'), | |
115 | 140 | (Callable[[T], T], ':py:data:`~typing.Callable`\\[\\[\\~T], \\~T]'), |
116 | 141 | (Pattern, ':py:class:`~typing.Pattern`'), |
117 | 142 | (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`]'), | |
121 | 145 | (Metaclass, ':py:class:`~%s.Metaclass`' % __name__), |
122 | 146 | (A, ':py:class:`~%s.A`' % __name__), |
123 | (B, ':py:class:`~%s.B`\\[\\~T]' % __name__), | |
147 | (B, ':py:class:`~%s.B`' % __name__), | |
124 | 148 | (B[int], ':py:class:`~%s.B`\\[:py:class:`int`]' % __name__), |
125 | 149 | (C, ':py:class:`~%s.C`' % __name__), |
126 | 150 | (D, ':py:class:`~%s.D`' % __name__), |
127 | (E, ':py:class:`~%s.E`\\[\\~T]' % __name__), | |
151 | (E, ':py:class:`~%s.E`' % __name__), | |
128 | 152 | (E[int], ':py:class:`~%s.E`\\[:py:class:`int`]' % __name__), |
129 | 153 | (W, ':py:func:`~typing.NewType`\\(:py:data:`~W`, :py:class:`str`)') |
130 | 154 | ]) |
132 | 156 | result = format_annotation(annotation) |
133 | 157 | assert result == expected_result |
134 | 158 | |
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 | ||
135 | 180 | # Test with the "fully_qualified" flag turned on |
136 | 181 | if 'typing' in expected_result or __name__ in expected_result: |
137 | 182 | expected_result = expected_result.replace('~typing', 'typing') |
143 | 188 | m = re.match('^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`', result) |
144 | 189 | assert m, 'No match' |
145 | 190 | 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 | |
154 | 197 | |
155 | 198 | |
156 | 199 | @pytest.mark.parametrize('library', [typing, typing_extensions], |
157 | 200 | ids=['typing', 'typing_extensions']) |
158 | 201 | @pytest.mark.parametrize('annotation, params, expected_result', [ |
202 | ('ClassVar', int, ":py:data:`~typing.ClassVar`\\[:py:class:`int`]"), | |
203 | ('NoReturn', None, ":py:data:`~typing.NoReturn`"), | |
159 | 204 | ('Literal', ('a', 1), ":py:data:`~typing.Literal`\\['a', 1]"), |
160 | 205 | ('Type', None, ':py:class:`~typing.Type`'), |
161 | 206 | ('Type', (A,), ':py:class:`~typing.Type`\\[:py:class:`~%s.A`]' % __name__) |
187 | 232 | sys.path.insert(0, str(test_path)) |
188 | 233 | |
189 | 234 | app.config.always_document_param_types = always_document_param_types |
235 | app.config.autodoc_mock_imports = ['mailbox'] | |
190 | 236 | app.build() |
191 | 237 | |
192 | 238 | assert 'build succeeded' in status.getvalue() # Build succeeded |
195 | 241 | warnings = warning.getvalue().strip() |
196 | 242 | assert 'Cannot resolve forward reference in type annotations of ' in warnings, warnings |
197 | 243 | |
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] = '' | |
206 | 253 | |
207 | 254 | text_path = pathlib.Path(app.srcdir) / '_build' / 'text' / 'index.txt' |
208 | 255 | with text_path.open('r') as f: |
226 | 273 | |
227 | 274 | Inner class. |
228 | 275 | |
229 | _InnerClass__dunder_inner_method(x) | |
276 | __dunder_inner_method(x) | |
230 | 277 | |
231 | 278 | Dunder inner method. |
232 | 279 | |
246 | 293 | Return type: |
247 | 294 | "str" |
248 | 295 | |
249 | _Class__dunder_method(x) | |
296 | __dunder_method(x) | |
250 | 297 | |
251 | 298 | Dunder method docstring. |
252 | 299 | |
356 | 403 | Return type: |
357 | 404 | bytes |
358 | 405 | |
359 | dummy_module.function_with_escaped_default(x='\\x08') | |
406 | dummy_module.function_with_escaped_default(x='\\\\x08') | |
360 | 407 | |
361 | 408 | Function docstring. |
362 | 409 | |
399 | 446 | Return type: |
400 | 447 | "int" |
401 | 448 | |
449 | method_without_typehint(x) | |
450 | ||
451 | Method docstring. | |
452 | ||
402 | 453 | dummy_module.function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs) |
403 | 454 | |
404 | 455 | Function docstring. |
437 | 488 | Method docstring. |
438 | 489 | |
439 | 490 | Parameters: |
440 | **x** (*Callable**[**[**int**, **bytes**]**, **int**]*) -- | |
491 | **x** ("Optional"["Callable"[["int", "bytes"], "int"]]) -- | |
441 | 492 | foo |
442 | 493 | |
443 | 494 | Return type: |
444 | ClassWithTypehintsNotInline | |
495 | "ClassWithTypehintsNotInline" | |
445 | 496 | |
446 | 497 | dummy_module.undocumented_function(x) |
447 | 498 | |
448 | Hi{undoc_params} | |
499 | Hi{undoc_params_0} | |
449 | 500 | |
450 | 501 | Return type: |
451 | 502 | "str" |
452 | 503 | |
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('–', '--') | |
470 | 527 | assert text_contents == expected_contents |