pdoc 14.0.0__tar.gz → 14.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pdoc-14.0.0 → pdoc-14.2.0}/CHANGELOG.md +32 -0
- {pdoc-14.0.0/pdoc.egg-info → pdoc-14.2.0}/PKG-INFO +3 -2
- {pdoc-14.0.0 → pdoc-14.2.0}/README.md +1 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/__init__.py +4 -3
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/__main__.py +1 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/_compat.py +21 -67
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/doc.py +45 -13
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/doc_ast.py +8 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/doc_pyi.py +3 -7
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/doc_types.py +30 -3
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/docstrings.py +4 -2
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/extract.py +61 -25
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/render_helpers.py +40 -15
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/search.py +5 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/content.css +1 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/default/module.html.jinja2 +1 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/layout.css +2 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/web.py +8 -1
- {pdoc-14.0.0 → pdoc-14.2.0/pdoc.egg-info}/PKG-INFO +3 -2
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc.egg-info/requires.txt +1 -1
- {pdoc-14.0.0 → pdoc-14.2.0}/pyproject.toml +4 -5
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_doc.py +32 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_doc_types.py +36 -8
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_extract.py +9 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_snapshot.py +5 -3
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_web.py +6 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/LICENSE +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/MANIFEST.in +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/markdown2/LICENSE +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/markdown2/README.md +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/markdown2/__init__.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/py.typed +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/render.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/README.md +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/build-search-index.js +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/custom.css +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/default/error.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/default/frame.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/default/index.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/README.md +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/bootstrap-reboot.min.css +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/box-arrow-in-left.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/elasticlunr.min.js +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/favicon.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/navtoggle.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/pdoc-logo.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/deprecated/resources/favicon.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/livereload.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/math.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/mermaid.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/bootstrap-reboot.min.css +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/box-arrow-in-left.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/elasticlunr.min.js +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/exclamation-triangle-fill.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/info-circle-fill.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/lightning-fill.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/navtoggle.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/resources/pdoc-logo.svg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/search.html.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/search.js.jinja2 +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/syntax-highlighting.css +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc/templates/theme.css +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc.egg-info/SOURCES.txt +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc.egg-info/dependency_links.txt +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc.egg-info/entry_points.txt +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/pdoc.egg-info/top_level.txt +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/setup.cfg +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_doc_ast.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_doc_pyi.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_docstrings.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_main.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_render_helpers.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_search.py +0 -0
- {pdoc-14.0.0 → pdoc-14.2.0}/test/test_smoke.py +0 -0
@@ -4,6 +4,38 @@
|
|
4
4
|
|
5
5
|
<!-- ✨ You do not need to add a pull request reference or an author, this will be added automatically by CI. ✨ -->
|
6
6
|
|
7
|
+
## 2023-12-13: pdoc 14.2.0
|
8
|
+
|
9
|
+
- pdoc now documents PyO3 or pybind11 submodules that are not picked up by Python's builtin pkgutil module.
|
10
|
+
([#633](https://github.com/mitmproxy/pdoc/issues/633), @mhils)
|
11
|
+
- pdoc now supports Python 3.12's `type` statements and has improved `TypeAlias` rendering.
|
12
|
+
([#651](https://github.com/mitmproxy/pdoc/pull/651), @mhils)
|
13
|
+
- Imports in a TYPE_CHECKING section that reference members defined in another module's TYPE_CHECKING section now work
|
14
|
+
correctly.
|
15
|
+
([#649](https://github.com/mitmproxy/pdoc/pull/649), @mhils)
|
16
|
+
- Add support for `code-block` ReST directives
|
17
|
+
([#624](https://github.com/mitmproxy/pdoc/pull/624), @JCGoran)
|
18
|
+
- If a variable's value meets certain entropy criteria and matches an environment variable value,
|
19
|
+
pdoc will now emit a warning and display the variable's name as a placeholder instead.
|
20
|
+
This heuristic is meant to prevent accidental leakage of environment secrets and can be disabled by setting
|
21
|
+
`PDOC_DISPLAY_ENV_VARS=1`.
|
22
|
+
([#622](https://github.com/mitmproxy/pdoc/pull/622), @mhils)
|
23
|
+
|
24
|
+
## 2023-09-10: pdoc 14.1.0
|
25
|
+
|
26
|
+
- Add compatibility with Python 3.12
|
27
|
+
([#620](https://github.com/mitmproxy/pdoc/pull/620), @mhils)
|
28
|
+
- Add support for relative links. Instead of explicitly referring to `mypackage.helpers.foo`,
|
29
|
+
one can now also refer to `.helpers.foo` within the `mypackage` module, or `..helpers.foo` in a submodule.
|
30
|
+
([#544](https://github.com/mitmproxy/pdoc/pull/544), @Crozzers)
|
31
|
+
- Function signatures will now display "Foo" instead "demo.Foo" if the function is in the same module.
|
32
|
+
([#544](https://github.com/mitmproxy/pdoc/pull/544), @mhils)
|
33
|
+
- pdoc now also picks up docstrings from `.pyi` stub files.
|
34
|
+
([#619](https://github.com/mitmproxy/pdoc/pull/619), @mhils)
|
35
|
+
- Fix horizontal scroll navigation z-index issue.
|
36
|
+
([#616](https://github.com/mitmproxy/pdoc/pull/616), @Domi04151309)
|
37
|
+
- Be more strict about parsing URLs in pdoc's web server.
|
38
|
+
([#617](https://github.com/mitmproxy/pdoc/pull/617), @mhils)
|
7
39
|
|
8
40
|
## 2023-06-19: pdoc 14.0.0
|
9
41
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pdoc
|
3
|
-
Version: 14.
|
3
|
+
Version: 14.2.0
|
4
4
|
Summary: API Documentation for Python Projects
|
5
5
|
Author-email: Maximilian Hils <pdoc@maximilianhils.com>
|
6
6
|
License: Unlicense
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.9
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
24
|
Classifier: Typing :: Typed
|
24
25
|
Requires-Python: >=3.8
|
25
26
|
Description-Content-Type: text/markdown
|
@@ -30,7 +31,7 @@ License-File: LICENSE
|
|
30
31
|
<a href="https://pdoc.dev/"><img alt="pdoc" src="https://pdoc.dev/logo.svg" width="200" height="100" /></a>
|
31
32
|
<br><br>
|
32
33
|
<a href="https://pdoc.dev/docs/pdoc.html"><img height="20" alt="pdoc documentation" src="https://shields.mitmproxy.org/badge/docs-pdoc.dev-brightgreen.svg"></a>
|
33
|
-
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/workflow/status/mitmproxy/pdoc/
|
34
|
+
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/actions/workflow/status/mitmproxy/pdoc/main.yml?label=CI&logo=github">
|
34
35
|
<img height="20" alt="Code Coverage" src="https://shields.mitmproxy.org/badge/coverage-100%25-brightgreen">
|
35
36
|
<a href="https://autofix.ci"><img height="20" alt="autofix.ci: yes" src="https://shields.mitmproxy.org/badge/autofix.ci-yes-success?logo=data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCB0cmFuc2Zvcm09InNjYWxlKDAuMDYxLC0wLjA2MSkgdHJhbnNsYXRlKC0yNTAsLTE3NTApIiBkPSJNMTMyNSAtMzQwcS0xMTUgMCAtMTY0LjUgMzIuNXQtNDkuNSAxMTQuNXEwIDMyIDUgNzAuNXQxMC41IDcyLjV0NS41IDU0djIyMHEtMzQgLTkgLTY5LjUgLTE0dC03MS41IC01cS0xMzYgMCAtMjUxLjUgNjJ0LTE5MSAxNjl0LTkyLjUgMjQxcS05MCAxMjAgLTkwIDI2NnEwIDEwOCA0OC41IDIwMC41dDEzMiAxNTUuNXQxODguNSA4MXExNSA5OSAxMDAuNSAxODAuNXQyMTcgMTMwLjV0MjgyLjUgNDlxMTM2IDAgMjU2LjUgLTQ2IHQyMDkgLTEyNy41dDEyOC41IC0xODkuNXExNDkgLTgyIDIyNyAtMjEzLjV0NzggLTI5OS41cTAgLTEzNiAtNTggLTI0NnQtMTY1LjUgLTE4NC41dC0yNTYuNSAtMTAzLjVsLTI0MyAtMzAwdi01MnEwIC0yNyAzLjUgLTU2LjV0Ni41IC01Ny41dDMgLTUycTAgLTg1IC00MS41IC0xMTguNXQtMTU3LjUgLTMzLjV6TTEzMjUgLTI2MHE3NyAwIDk4IDE0LjV0MjEgNTcuNXEwIDI5IC0zIDY4dC02LjUgNzN0LTMuNSA0OHY2NGwyMDcgMjQ5IHEtMzEgMCAtNjAgNS41dC01NCAxMi41bC0xMDQgLTEyM3EtMSAzNCAtMiA2My41dC0xIDU0LjVxMCA2OSA5IDEyM2wzMSAyMDBsLTExNSAtMjhsLTQ2IC0yNzFsLTIwNSAyMjZxLTE5IC0xNSAtNDMgLTI4LjV0LTU1IC0yNi41bDIxOSAtMjQydi0yNzZxMCAtMjAgLTUuNSAtNjB0LTEwLjUgLTc5dC01IC01OHEwIC00MCAzMCAtNTMuNXQxMDQgLTEzLjV6TTEyNjIgNjE2cS0xMTkgMCAtMjI5LjUgMzQuNXQtMTkzLjUgOTYuNWw0OCA2NCBxNzMgLTU1IDE3MC41IC04NXQyMDQuNSAtMzBxMTM3IDAgMjQ5IDQ1LjV0MTc5IDEyMXQ2NyAxNjUuNWg4MHEwIC0xMTQgLTc3LjUgLTIwNy41dC0yMDggLTE0OXQtMjg5LjUgLTU1LjV6TTgwMyA1OTVxODAgMCAxNDkgMjkuNXQxMDggNzIuNWwyMjEgLTY3bDMwOSA4NnE0NyAtMzIgMTA0LjUgLTUwdDExNy41IC0xOHE5MSAwIDE2NSAzOHQxMTguNSAxMDMuNXQ0NC41IDE0Ni41cTAgNzYgLTM0LjUgMTQ5dC05NS41IDEzNHQtMTQzIDk5IHEtMzcgMTA3IC0xMTUuNSAxODMuNXQtMTg2IDExNy41dC0yMzAuNSA0MXEtMTAzIDAgLTE5Ny41IC0yNnQtMTY5IC03Mi41dC0xMTcuNSAtMTA4dC00MyAtMTMxLjVxMCAtMzQgMTQuNSAtNjIuNXQ0MC41IC01MC41bC01NSAtNTlxLTM0IDI5IC01NCA2NS41dC0yNSA4MS41cS04MSAtMTggLTE0NSAtNzB0LTEwMSAtMTI1LjV0LTM3IC0xNTguNXEwIC0xMDIgNDguNSAtMTgwLjV0MTI5LjUgLTEyM3QxNzkgLTQ0LjV6Ii8+PC9zdmc+"></a>
|
36
37
|
<a href="https://pypi.python.org/pypi/pdoc"><img height="20" alt="PyPI Version" src="https://shields.mitmproxy.org/pypi/v/pdoc.svg"></a>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<a href="https://pdoc.dev/"><img alt="pdoc" src="https://pdoc.dev/logo.svg" width="200" height="100" /></a>
|
3
3
|
<br><br>
|
4
4
|
<a href="https://pdoc.dev/docs/pdoc.html"><img height="20" alt="pdoc documentation" src="https://shields.mitmproxy.org/badge/docs-pdoc.dev-brightgreen.svg"></a>
|
5
|
-
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/workflow/status/mitmproxy/pdoc/
|
5
|
+
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/actions/workflow/status/mitmproxy/pdoc/main.yml?label=CI&logo=github">
|
6
6
|
<img height="20" alt="Code Coverage" src="https://shields.mitmproxy.org/badge/coverage-100%25-brightgreen">
|
7
7
|
<a href="https://autofix.ci"><img height="20" alt="autofix.ci: yes" src="https://shields.mitmproxy.org/badge/autofix.ci-yes-success?logo=data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCB0cmFuc2Zvcm09InNjYWxlKDAuMDYxLC0wLjA2MSkgdHJhbnNsYXRlKC0yNTAsLTE3NTApIiBkPSJNMTMyNSAtMzQwcS0xMTUgMCAtMTY0LjUgMzIuNXQtNDkuNSAxMTQuNXEwIDMyIDUgNzAuNXQxMC41IDcyLjV0NS41IDU0djIyMHEtMzQgLTkgLTY5LjUgLTE0dC03MS41IC01cS0xMzYgMCAtMjUxLjUgNjJ0LTE5MSAxNjl0LTkyLjUgMjQxcS05MCAxMjAgLTkwIDI2NnEwIDEwOCA0OC41IDIwMC41dDEzMiAxNTUuNXQxODguNSA4MXExNSA5OSAxMDAuNSAxODAuNXQyMTcgMTMwLjV0MjgyLjUgNDlxMTM2IDAgMjU2LjUgLTQ2IHQyMDkgLTEyNy41dDEyOC41IC0xODkuNXExNDkgLTgyIDIyNyAtMjEzLjV0NzggLTI5OS41cTAgLTEzNiAtNTggLTI0NnQtMTY1LjUgLTE4NC41dC0yNTYuNSAtMTAzLjVsLTI0MyAtMzAwdi01MnEwIC0yNyAzLjUgLTU2LjV0Ni41IC01Ny41dDMgLTUycTAgLTg1IC00MS41IC0xMTguNXQtMTU3LjUgLTMzLjV6TTEzMjUgLTI2MHE3NyAwIDk4IDE0LjV0MjEgNTcuNXEwIDI5IC0zIDY4dC02LjUgNzN0LTMuNSA0OHY2NGwyMDcgMjQ5IHEtMzEgMCAtNjAgNS41dC01NCAxMi41bC0xMDQgLTEyM3EtMSAzNCAtMiA2My41dC0xIDU0LjVxMCA2OSA5IDEyM2wzMSAyMDBsLTExNSAtMjhsLTQ2IC0yNzFsLTIwNSAyMjZxLTE5IC0xNSAtNDMgLTI4LjV0LTU1IC0yNi41bDIxOSAtMjQydi0yNzZxMCAtMjAgLTUuNSAtNjB0LTEwLjUgLTc5dC01IC01OHEwIC00MCAzMCAtNTMuNXQxMDQgLTEzLjV6TTEyNjIgNjE2cS0xMTkgMCAtMjI5LjUgMzQuNXQtMTkzLjUgOTYuNWw0OCA2NCBxNzMgLTU1IDE3MC41IC04NXQyMDQuNSAtMzBxMTM3IDAgMjQ5IDQ1LjV0MTc5IDEyMXQ2NyAxNjUuNWg4MHEwIC0xMTQgLTc3LjUgLTIwNy41dC0yMDggLTE0OXQtMjg5LjUgLTU1LjV6TTgwMyA1OTVxODAgMCAxNDkgMjkuNXQxMDggNzIuNWwyMjEgLTY3bDMwOSA4NnE0NyAtMzIgMTA0LjUgLTUwdDExNy41IC0xOHE5MSAwIDE2NSAzOHQxMTguNSAxMDMuNXQ0NC41IDE0Ni41cTAgNzYgLTM0LjUgMTQ5dC05NS41IDEzNHQtMTQzIDk5IHEtMzcgMTA3IC0xMTUuNSAxODMuNXQtMTg2IDExNy41dC0yMzAuNSA0MXEtMTAzIDAgLTE5Ny41IC0yNnQtMTY5IC03Mi41dC0xMTcuNSAtMTA4dC00MyAtMTMxLjVxMCAtMzQgMTQuNSAtNjIuNXQ0MC41IC01MC41bC01NSAtNTlxLTM0IDI5IC01NCA2NS41dC0yNSA4MS41cS04MSAtMTggLTE0NSAtNzB0LTEwMSAtMTI1LjV0LTM3IC0xNTguNXEwIC0xMDIgNDguNSAtMTgwLjV0MTI5LjUgLTEyM3QxNzkgLTQ0LjV6Ii8+PC9zdmc+"></a>
|
8
8
|
<a href="https://pypi.python.org/pypi/pdoc"><img height="20" alt="PyPI Version" src="https://shields.mitmproxy.org/pypi/v/pdoc.svg"></a>
|
@@ -180,13 +180,14 @@ ways.
|
|
180
180
|
- If `__all__` is defined in the module, then all identifiers in that list will be considered public.
|
181
181
|
No other identifiers will be considered public.
|
182
182
|
- If `__all__` is not defined, then pdoc will consider all members public that
|
183
|
-
1. do not start with an underscore
|
183
|
+
1. do not start with an underscore,
|
184
|
+
2. don't have `@private` in their docstring,
|
184
185
|
2. and are defined in the current module (i.e. they are not imported).
|
185
186
|
|
186
187
|
In general, we recommend keeping these conventions:
|
187
188
|
|
188
189
|
- If you want to document a private member, consider making it public.
|
189
|
-
- If you want to hide a public member, consider making it private
|
190
|
+
- If you want to hide a public member, consider making it private or add `@private` to their docstring,
|
190
191
|
- If you want to document a special `__dunder__` method, the recommended way to do so is
|
191
192
|
to not document the dunder method specifically, but to add some usage examples in the class documentation.
|
192
193
|
|
@@ -461,7 +462,7 @@ You can find an example in [`examples/library-usage`](https://github.com/mitmpro
|
|
461
462
|
from __future__ import annotations
|
462
463
|
|
463
464
|
__docformat__ = "markdown" # explicitly disable rST processing in the examples above.
|
464
|
-
__version__ = "14.
|
465
|
+
__version__ = "14.2.0" # this is read from setup.py
|
465
466
|
|
466
467
|
from pathlib import Path
|
467
468
|
from typing import overload
|
@@ -112,7 +112,7 @@ renderopts.add_argument(
|
|
112
112
|
"--search",
|
113
113
|
action=BooleanOptionalAction,
|
114
114
|
default=True,
|
115
|
-
help="Enable search functionality.",
|
115
|
+
help="Enable search functionality if multiple modules are documented.",
|
116
116
|
)
|
117
117
|
renderopts.add_argument(
|
118
118
|
"--show-source",
|
@@ -16,6 +16,24 @@ else: # pragma: no cover
|
|
16
16
|
def ast_unparse(t): # type: ignore
|
17
17
|
return _unparse(t).strip("\t\n \"'")
|
18
18
|
|
19
|
+
if sys.version_info >= (3, 12):
|
20
|
+
from ast import TypeAlias as ast_TypeAlias
|
21
|
+
else: # pragma: no cover
|
22
|
+
class ast_TypeAlias:
|
23
|
+
pass
|
24
|
+
|
25
|
+
if sys.version_info >= (3, 12):
|
26
|
+
from typing import TypeAliasType
|
27
|
+
else: # pragma: no cover
|
28
|
+
class TypeAliasType:
|
29
|
+
"""Placeholder class for TypeAliasType"""
|
30
|
+
|
31
|
+
if sys.version_info >= (3, 10):
|
32
|
+
from typing import TypeAlias
|
33
|
+
else: # pragma: no cover
|
34
|
+
class TypeAlias:
|
35
|
+
pass
|
36
|
+
|
19
37
|
if sys.version_info >= (3, 9):
|
20
38
|
from types import GenericAlias
|
21
39
|
else: # pragma: no cover
|
@@ -43,65 +61,6 @@ else: # pragma: no cover
|
|
43
61
|
x = x[len(prefix):]
|
44
62
|
return x
|
45
63
|
|
46
|
-
if sys.version_info >= (3, 8):
|
47
|
-
from functools import cached_property
|
48
|
-
else: # pragma: no cover
|
49
|
-
from threading import RLock
|
50
|
-
|
51
|
-
# https://github.com/python/cpython/blob/863eb7170b3017399fb2b786a1e3feb6457e54c2/Lib/functools.py#L930-L980
|
52
|
-
# ✂ start ✂
|
53
|
-
_NOT_FOUND = object()
|
54
|
-
|
55
|
-
class cached_property: # type: ignore
|
56
|
-
def __init__(self, func):
|
57
|
-
self.func = func
|
58
|
-
self.attrname = None
|
59
|
-
self.__doc__ = func.__doc__
|
60
|
-
self.lock = RLock()
|
61
|
-
|
62
|
-
def __set_name__(self, owner, name):
|
63
|
-
if self.attrname is None:
|
64
|
-
self.attrname = name
|
65
|
-
elif name != self.attrname:
|
66
|
-
raise TypeError(
|
67
|
-
"Cannot assign the same cached_property to two different names "
|
68
|
-
f"({self.attrname!r} and {name!r})."
|
69
|
-
)
|
70
|
-
|
71
|
-
def __get__(self, instance, owner=None):
|
72
|
-
if instance is None:
|
73
|
-
return self
|
74
|
-
if self.attrname is None:
|
75
|
-
raise TypeError(
|
76
|
-
"Cannot use cached_property instance without calling __set_name__ on it.")
|
77
|
-
try:
|
78
|
-
cache = instance.__dict__
|
79
|
-
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
|
80
|
-
msg = (
|
81
|
-
f"No '__dict__' attribute on {type(instance).__name__!r} "
|
82
|
-
f"instance to cache {self.attrname!r} property."
|
83
|
-
)
|
84
|
-
raise TypeError(msg) from None
|
85
|
-
val = cache.get(self.attrname, _NOT_FOUND)
|
86
|
-
if val is _NOT_FOUND:
|
87
|
-
with self.lock:
|
88
|
-
# check if another thread filled cache while we awaited lock
|
89
|
-
val = cache.get(self.attrname, _NOT_FOUND)
|
90
|
-
if val is _NOT_FOUND:
|
91
|
-
val = self.func(instance)
|
92
|
-
try:
|
93
|
-
cache[self.attrname] = val
|
94
|
-
except TypeError:
|
95
|
-
msg = (
|
96
|
-
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
|
97
|
-
f"does not support item assignment for caching {self.attrname!r} property."
|
98
|
-
)
|
99
|
-
raise TypeError(msg) from None
|
100
|
-
return val
|
101
|
-
|
102
|
-
__class_getitem__ = classmethod(GenericAlias)
|
103
|
-
# ✂ end ✂
|
104
|
-
|
105
64
|
|
106
65
|
if (3, 9) <= sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): # pragma: no cover
|
107
66
|
import inspect
|
@@ -117,12 +76,6 @@ if (3, 9) <= sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 1
|
|
117
76
|
else:
|
118
77
|
from inspect import formatannotation
|
119
78
|
|
120
|
-
if sys.version_info >= (3, 8):
|
121
|
-
from functools import singledispatchmethod
|
122
|
-
else: # pragma: no cover
|
123
|
-
class singledispatchmethod:
|
124
|
-
pass # pragma: no cover
|
125
|
-
|
126
79
|
if sys.version_info >= (3, 9):
|
127
80
|
from argparse import BooleanOptionalAction
|
128
81
|
else: # pragma: no cover
|
@@ -173,11 +126,12 @@ else: # pragma: no cover
|
|
173
126
|
__all__ = [
|
174
127
|
"cache",
|
175
128
|
"ast_unparse",
|
129
|
+
"ast_TypeAlias",
|
130
|
+
"TypeAliasType",
|
131
|
+
"TypeAlias",
|
176
132
|
"GenericAlias",
|
177
133
|
"UnionType",
|
178
134
|
"removesuffix",
|
179
|
-
"cached_property",
|
180
135
|
"formatannotation",
|
181
|
-
"singledispatchmethod",
|
182
136
|
"BooleanOptionalAction",
|
183
137
|
]
|
@@ -22,11 +22,12 @@ from abc import abstractmethod
|
|
22
22
|
from collections.abc import Callable
|
23
23
|
import dataclasses
|
24
24
|
import enum
|
25
|
+
from functools import cached_property
|
26
|
+
from functools import singledispatchmethod
|
25
27
|
from functools import wraps
|
26
28
|
import inspect
|
27
29
|
import os
|
28
30
|
from pathlib import Path
|
29
|
-
import pkgutil
|
30
31
|
import re
|
31
32
|
import sys
|
32
33
|
import textwrap
|
@@ -43,17 +44,16 @@ import warnings
|
|
43
44
|
from pdoc import doc_ast
|
44
45
|
from pdoc import doc_pyi
|
45
46
|
from pdoc import extract
|
47
|
+
from pdoc._compat import TypeAlias
|
48
|
+
from pdoc._compat import TypeAliasType
|
49
|
+
from pdoc._compat import cache
|
50
|
+
from pdoc._compat import formatannotation
|
46
51
|
from pdoc.doc_types import GenericAlias
|
47
52
|
from pdoc.doc_types import NonUserDefinedCallables
|
48
53
|
from pdoc.doc_types import empty
|
49
54
|
from pdoc.doc_types import resolve_annotations
|
50
55
|
from pdoc.doc_types import safe_eval_type
|
51
56
|
|
52
|
-
from ._compat import cache
|
53
|
-
from ._compat import cached_property
|
54
|
-
from ._compat import formatannotation
|
55
|
-
from ._compat import singledispatchmethod
|
56
|
-
|
57
57
|
|
58
58
|
def _include_fullname_in_traceback(f):
|
59
59
|
"""
|
@@ -454,9 +454,6 @@ class Module(Namespace[types.ModuleType]):
|
|
454
454
|
@cached_property
|
455
455
|
def submodules(self) -> list[Module]:
|
456
456
|
"""A list of all (direct) submodules."""
|
457
|
-
if not self.is_package:
|
458
|
-
return []
|
459
|
-
|
460
457
|
include: Callable[[str], bool]
|
461
458
|
mod_all = _safe_getattr(self.obj, "__all__", False)
|
462
459
|
if mod_all is not False:
|
@@ -471,9 +468,8 @@ class Module(Namespace[types.ModuleType]):
|
|
471
468
|
# (think of OS-specific modules, e.g. _linux.py failing to import on Windows).
|
472
469
|
return not name.startswith("_")
|
473
470
|
|
474
|
-
submodules = []
|
475
|
-
for mod in
|
476
|
-
_, _, mod_name = mod.name.rpartition(".")
|
471
|
+
submodules: list[Module] = []
|
472
|
+
for mod_name, mod in extract.iter_modules2(self.obj).items():
|
477
473
|
if not include(mod_name):
|
478
474
|
continue
|
479
475
|
try:
|
@@ -1006,7 +1002,9 @@ class Function(Doc[types.FunctionType]):
|
|
1006
1002
|
)
|
1007
1003
|
)
|
1008
1004
|
for p in sig.parameters.values():
|
1009
|
-
p._annotation = safe_eval_type(
|
1005
|
+
p._annotation = safe_eval_type( # type: ignore
|
1006
|
+
p.annotation, globalns, localns, mod, self.fullname
|
1007
|
+
)
|
1010
1008
|
return sig
|
1011
1009
|
|
1012
1010
|
@cached_property
|
@@ -1092,6 +1090,11 @@ class Variable(Doc[None]):
|
|
1092
1090
|
else:
|
1093
1091
|
return False
|
1094
1092
|
|
1093
|
+
@cached_property
|
1094
|
+
def is_type_alias_type(self) -> bool:
|
1095
|
+
"""`True` if the variable is a `typing.TypeAliasType`, `False` otherwise."""
|
1096
|
+
return isinstance(self.default_value, TypeAliasType)
|
1097
|
+
|
1095
1098
|
@cached_property
|
1096
1099
|
def is_enum_member(self) -> bool:
|
1097
1100
|
"""`True` if the variable is an enum member, `False` otherwise."""
|
@@ -1105,6 +1108,27 @@ class Variable(Doc[None]):
|
|
1105
1108
|
"""The variable's default value as a pretty-printed str."""
|
1106
1109
|
if self.default_value is empty:
|
1107
1110
|
return ""
|
1111
|
+
if isinstance(self.default_value, TypeAliasType):
|
1112
|
+
return formatannotation(self.default_value.__value__)
|
1113
|
+
elif self.annotation == TypeAlias:
|
1114
|
+
return formatannotation(self.default_value)
|
1115
|
+
|
1116
|
+
# This is not perfect, but a solid attempt at preventing accidental leakage of secrets.
|
1117
|
+
# If you have input on how to improve the heuristic, please send a pull request!
|
1118
|
+
value_taken_from_env_var = (
|
1119
|
+
isinstance(self.default_value, str)
|
1120
|
+
and len(self.default_value) >= 8
|
1121
|
+
and self.default_value in _environ_lookup()
|
1122
|
+
)
|
1123
|
+
if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""):
|
1124
|
+
env_var = "$" + _environ_lookup()[self.default_value]
|
1125
|
+
warnings.warn(
|
1126
|
+
f"The default value of {self.fullname} matches the {env_var} environment variable. "
|
1127
|
+
f"To prevent accidental leakage of secrets, the default value is not displayed. "
|
1128
|
+
f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.",
|
1129
|
+
RuntimeWarning,
|
1130
|
+
)
|
1131
|
+
return env_var
|
1108
1132
|
|
1109
1133
|
try:
|
1110
1134
|
pretty = repr(self.default_value)
|
@@ -1124,6 +1148,14 @@ class Variable(Doc[None]):
|
|
1124
1148
|
return ""
|
1125
1149
|
|
1126
1150
|
|
1151
|
+
@cache
|
1152
|
+
def _environ_lookup():
|
1153
|
+
"""
|
1154
|
+
A reverse lookup of os.environ. This is a cached function so that it is evaluated lazily.
|
1155
|
+
"""
|
1156
|
+
return {value: key for key, value in os.environ.items()}
|
1157
|
+
|
1158
|
+
|
1127
1159
|
class _PrettySignature(inspect.Signature):
|
1128
1160
|
"""
|
1129
1161
|
A subclass of `inspect.Signature` that pads __str__ over several lines
|
@@ -21,6 +21,7 @@ import warnings
|
|
21
21
|
|
22
22
|
import pdoc
|
23
23
|
|
24
|
+
from ._compat import ast_TypeAlias
|
24
25
|
from ._compat import ast_unparse
|
25
26
|
from ._compat import cache
|
26
27
|
|
@@ -115,7 +116,11 @@ def _walk_tree(
|
|
115
116
|
func_docstrings = {}
|
116
117
|
annotations = {}
|
117
118
|
for a, b in _pairwise_longest(_nodes(tree)):
|
118
|
-
if isinstance(a,
|
119
|
+
if isinstance(a, ast_TypeAlias):
|
120
|
+
name = a.name.id
|
121
|
+
elif (
|
122
|
+
isinstance(a, ast.AnnAssign) and isinstance(a.target, ast.Name) and a.simple
|
123
|
+
):
|
119
124
|
name = a.target.id
|
120
125
|
annotations[name] = unparse(a.annotation)
|
121
126
|
elif (
|
@@ -183,6 +188,8 @@ def sort_by_source(
|
|
183
188
|
name = a.target.id
|
184
189
|
elif isinstance(a, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
185
190
|
name = a.name
|
191
|
+
elif isinstance(a, ast_TypeAlias):
|
192
|
+
name = a.name.id
|
186
193
|
else:
|
187
194
|
continue
|
188
195
|
|
@@ -70,16 +70,12 @@ def _patch_doc(target_doc: doc.Doc, stub_mod: doc.Module) -> None:
|
|
70
70
|
if isinstance(target_doc, doc.Function) and isinstance(stub_doc, doc.Function):
|
71
71
|
target_doc.signature = stub_doc.signature
|
72
72
|
target_doc.funcdef = stub_doc.funcdef
|
73
|
+
target_doc.docstring = stub_doc.docstring or target_doc.docstring
|
73
74
|
elif isinstance(target_doc, doc.Variable) and isinstance(stub_doc, doc.Variable):
|
74
75
|
target_doc.annotation = stub_doc.annotation
|
76
|
+
target_doc.docstring = stub_doc.docstring or target_doc.docstring
|
75
77
|
elif isinstance(target_doc, doc.Namespace) and isinstance(stub_doc, doc.Namespace):
|
76
|
-
|
77
|
-
# so the regular patching won't work. We manually copy over type annotations instead.
|
78
|
-
for k, v in stub_doc._var_annotations.items():
|
79
|
-
var = target_doc.members.get(k, None)
|
80
|
-
if isinstance(var, doc.Variable):
|
81
|
-
var.annotation = v
|
82
|
-
|
78
|
+
target_doc.docstring = stub_doc.docstring or target_doc.docstring
|
83
79
|
for m in target_doc.members.values():
|
84
80
|
_patch_doc(m, stub_mod)
|
85
81
|
else:
|
@@ -124,9 +124,9 @@ def safe_eval_type(
|
|
124
124
|
|
125
125
|
# Simple _eval_type has failed. We now execute all TYPE_CHECKING sections in the module and try again.
|
126
126
|
if module:
|
127
|
+
assert module.__dict__ is globalns
|
127
128
|
try:
|
128
|
-
|
129
|
-
eval(code, globalns, globalns)
|
129
|
+
_eval_type_checking_sections(module, set())
|
130
130
|
except Exception as e:
|
131
131
|
warnings.warn(
|
132
132
|
f"Failed to run TYPE_CHECKING code while parsing {t} type annotation for {fullname}: {e}"
|
@@ -148,7 +148,34 @@ def safe_eval_type(
|
|
148
148
|
f"Error parsing type annotation {t} for {fullname}. Import of {mod} failed: {err}"
|
149
149
|
)
|
150
150
|
return t
|
151
|
-
|
151
|
+
else:
|
152
|
+
globalns[mod] = val
|
153
|
+
return safe_eval_type(t, globalns, localns, module, fullname)
|
154
|
+
|
155
|
+
|
156
|
+
def _eval_type_checking_sections(module: types.ModuleType, seen: set) -> None:
|
157
|
+
"""
|
158
|
+
Evaluate all TYPE_CHECKING sections within a module.
|
159
|
+
|
160
|
+
The added complication here is that TYPE_CHECKING sections may import members from other modules' TYPE_CHECKING
|
161
|
+
sections. So we try to recursively execute those other modules' TYPE_CHECKING sections as well.
|
162
|
+
See https://github.com/mitmproxy/pdoc/issues/648 for a real world example.
|
163
|
+
"""
|
164
|
+
if module.__name__ in seen:
|
165
|
+
raise RecursionError(f"Recursion error when importing {module.__name__}.")
|
166
|
+
seen.add(module.__name__)
|
167
|
+
|
168
|
+
code = compile(type_checking_sections(module), "<string>", "exec")
|
169
|
+
while True:
|
170
|
+
try:
|
171
|
+
eval(code, module.__dict__, module.__dict__)
|
172
|
+
except ImportError as e:
|
173
|
+
if e.name is not None and (mod := sys.modules.get(e.name, None)):
|
174
|
+
_eval_type_checking_sections(mod, seen)
|
175
|
+
else:
|
176
|
+
raise
|
177
|
+
else:
|
178
|
+
break
|
152
179
|
|
153
180
|
|
154
181
|
def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
|
@@ -393,7 +393,9 @@ def _rst_admonitions(contents: str, source_file: Path | None) -> str:
|
|
393
393
|
f"{indent(contents, ind)}\n"
|
394
394
|
f"{ind}</div>\n"
|
395
395
|
)
|
396
|
-
|
396
|
+
if type == "code-block":
|
397
|
+
return f"{ind}```{val}\n{contents}\n```\n"
|
398
|
+
if type == "versionadded":
|
397
399
|
text = f"New in version {val}"
|
398
400
|
elif type == "versionchanged":
|
399
401
|
text = f"Changed in version {val}"
|
@@ -409,7 +411,7 @@ def _rst_admonitions(contents: str, source_file: Path | None) -> str:
|
|
409
411
|
|
410
412
|
return text
|
411
413
|
|
412
|
-
admonition = "note|warning|danger|versionadded|versionchanged|deprecated|seealso|math|include"
|
414
|
+
admonition = "note|warning|danger|versionadded|versionchanged|deprecated|seealso|math|include|code-block"
|
413
415
|
return re.sub(
|
414
416
|
rf"""
|
415
417
|
^(?P<indent>[ ]*)\.\.[ ]+(?P<type>{admonition})::(?P<val>.*)
|
@@ -204,9 +204,7 @@ def mock_some_common_side_effects():
|
|
204
204
|
"os.startfile", new=_noop, create=True
|
205
205
|
), patch("sys.stdout", new=io.StringIO()), patch(
|
206
206
|
"sys.stderr", new=io.StringIO()
|
207
|
-
), patch(
|
208
|
-
"sys.stdin", new=io.StringIO()
|
209
|
-
):
|
207
|
+
), patch("sys.stdin", new=io.StringIO()):
|
210
208
|
yield
|
211
209
|
|
212
210
|
|
@@ -229,22 +227,71 @@ but we don't want to catch a user's KeyboardInterrupt.
|
|
229
227
|
"""
|
230
228
|
|
231
229
|
|
230
|
+
def iter_modules2(module: types.ModuleType) -> dict[str, pkgutil.ModuleInfo]:
|
231
|
+
"""
|
232
|
+
Returns all direct child modules of a given module.
|
233
|
+
This function is similar to `pkgutil.iter_modules`, but
|
234
|
+
|
235
|
+
1. Respects a package's `__all__` attribute if specified.
|
236
|
+
If `__all__` is defined, submodules not listed in `__all__` are excluded.
|
237
|
+
2. It will try to detect submodules that are not findable with iter_modules,
|
238
|
+
but are present in the module object.
|
239
|
+
"""
|
240
|
+
mod_all = getattr(module, "__all__", None)
|
241
|
+
|
242
|
+
submodules = {}
|
243
|
+
|
244
|
+
for submodule in pkgutil.iter_modules(
|
245
|
+
getattr(module, "__path__", []), f"{module.__name__}."
|
246
|
+
):
|
247
|
+
name = submodule.name.rpartition(".")[2]
|
248
|
+
if mod_all is None or name in mod_all:
|
249
|
+
submodules[name] = submodule
|
250
|
+
|
251
|
+
# 2023-12: PyO3 and pybind11 submodules are not detected by pkgutil
|
252
|
+
# This is a hacky workaround to register them.
|
253
|
+
members = dir(module) if mod_all is None else mod_all
|
254
|
+
for name in members:
|
255
|
+
if name in submodules or name == "__main__":
|
256
|
+
continue
|
257
|
+
member = getattr(module, name, None)
|
258
|
+
is_wild_child_module = (
|
259
|
+
isinstance(member, types.ModuleType)
|
260
|
+
# the name is either just "bar", but can also be "foo.bar",
|
261
|
+
# see https://github.com/PyO3/pyo3/issues/759#issuecomment-1811992321
|
262
|
+
and (
|
263
|
+
member.__name__ == f"{module.__name__}.{name}"
|
264
|
+
or (
|
265
|
+
member.__name__ == name
|
266
|
+
and sys.modules.get(member.__name__, None) is not member
|
267
|
+
)
|
268
|
+
)
|
269
|
+
)
|
270
|
+
if is_wild_child_module:
|
271
|
+
# fixup the module name so that the rest of pdoc does not break
|
272
|
+
assert member
|
273
|
+
member.__name__ = f"{module.__name__}.{name}"
|
274
|
+
sys.modules[f"{module.__name__}.{name}"] = member
|
275
|
+
submodules[name] = pkgutil.ModuleInfo(
|
276
|
+
None, # type: ignore
|
277
|
+
name=f"{module.__name__}.{name}",
|
278
|
+
ispkg=True,
|
279
|
+
)
|
280
|
+
|
281
|
+
submodules.pop("__main__", None) # https://github.com/mitmproxy/pdoc/issues/438
|
282
|
+
|
283
|
+
return submodules
|
284
|
+
|
285
|
+
|
232
286
|
def walk_packages2(
|
233
287
|
modules: Iterable[pkgutil.ModuleInfo],
|
234
288
|
) -> Iterator[pkgutil.ModuleInfo]:
|
235
289
|
"""
|
236
290
|
For a given list of modules, recursively yield their names and all their submodules' names.
|
237
291
|
|
238
|
-
This function is similar to `pkgutil.walk_packages`, but
|
239
|
-
If `__all__` is defined, submodules not listed in `__all__` are excluded.
|
292
|
+
This function is similar to `pkgutil.walk_packages`, but based on `iter_modules2`.
|
240
293
|
"""
|
241
|
-
|
242
|
-
# noinspection PyDefaultArgument
|
243
|
-
def seen(p, m={}): # pragma: no cover
|
244
|
-
if p in m:
|
245
|
-
return True
|
246
|
-
m[p] = True
|
247
|
-
|
294
|
+
# the original walk_packages implementation has a recursion check for path, but that does not seem to be needed?
|
248
295
|
for mod in modules:
|
249
296
|
yield mod
|
250
297
|
|
@@ -255,19 +302,8 @@ def walk_packages2(
|
|
255
302
|
warnings.warn(f"Error loading {mod.name}:\n{traceback.format_exc()}")
|
256
303
|
continue
|
257
304
|
|
258
|
-
|
259
|
-
|
260
|
-
path = [p for p in (getattr(module, "__path__", None) or []) if not seen(p)]
|
261
|
-
|
262
|
-
submodules = []
|
263
|
-
for submodule in pkgutil.iter_modules(path, f"{mod.name}."):
|
264
|
-
name = submodule.name.rpartition(".")[2]
|
265
|
-
if name == "__main__":
|
266
|
-
continue # https://github.com/mitmproxy/pdoc/issues/438
|
267
|
-
if mod_all is None or name in mod_all:
|
268
|
-
submodules.append(submodule)
|
269
|
-
|
270
|
-
yield from walk_packages2(submodules)
|
305
|
+
submodules = iter_modules2(module)
|
306
|
+
yield from walk_packages2(submodules.values())
|
271
307
|
|
272
308
|
|
273
309
|
def module_mtime(modulename: str) -> float | None:
|
@@ -43,7 +43,7 @@ formatter = pygments.formatters.HtmlFormatter(
|
|
43
43
|
anchorlinenos=True,
|
44
44
|
)
|
45
45
|
"""
|
46
|
-
The pygments formatter used for pdoc.render_helpers.highlight.
|
46
|
+
The pygments formatter used for pdoc.render_helpers.highlight.
|
47
47
|
Overwrite this to configure pygments highlighting of code blocks.
|
48
48
|
|
49
49
|
The usage of the `.codehilite` CSS selector in custom templates is deprecated since pdoc 10, use `.pdoc-code` instead.
|
@@ -51,7 +51,7 @@ The usage of the `.codehilite` CSS selector in custom templates is deprecated si
|
|
51
51
|
|
52
52
|
signature_formatter = pygments.formatters.HtmlFormatter(nowrap=True)
|
53
53
|
"""
|
54
|
-
The pygments formatter used for pdoc.render_helpers.format_signature.
|
54
|
+
The pygments formatter used for pdoc.render_helpers.format_signature.
|
55
55
|
Overwrite this to configure pygments highlighting of signatures.
|
56
56
|
"""
|
57
57
|
|
@@ -287,13 +287,30 @@ def linkify(context: Context, code: str, namespace: str = "") -> str:
|
|
287
287
|
'</span><span class="o">.</span><span class="n">', "."
|
288
288
|
)
|
289
289
|
identifier = removesuffix(plain_text, "()")
|
290
|
-
|
291
|
-
# Check if this is a local reference within this module?
|
292
290
|
mod: pdoc.doc.Module = context["module"]
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
291
|
+
|
292
|
+
# Check if this is a relative reference?
|
293
|
+
if identifier.startswith("."):
|
294
|
+
taken_from_mod = mod
|
295
|
+
if namespace and (ns := mod.get(namespace)):
|
296
|
+
# Imported from somewhere else, so the relative reference should be from the original module.
|
297
|
+
taken_from_mod = context["all_modules"].get(ns.taken_from[0], mod)
|
298
|
+
if taken_from_mod.is_package:
|
299
|
+
# If we are in __init__.py, we want `.foo` to refer to a child module.
|
300
|
+
parent_module = taken_from_mod.modulename
|
301
|
+
else:
|
302
|
+
# If we are in a leaf module, we want `.foo` to refer to the adjacent module.
|
303
|
+
parent_module = taken_from_mod.modulename.rpartition(".")[0]
|
304
|
+
while identifier.startswith(".."):
|
305
|
+
identifier = identifier[1:]
|
306
|
+
parent_module = parent_module.rpartition(".")[0]
|
307
|
+
identifier = parent_module + identifier
|
308
|
+
else:
|
309
|
+
# Check if this is a local reference within this module?
|
310
|
+
for qualname in qualname_candidates(identifier, namespace):
|
311
|
+
doc = mod.get(qualname)
|
312
|
+
if doc and context["is_public"](doc).strip():
|
313
|
+
return f'<a href="#{qualname}">{plain_text}</a>'
|
297
314
|
|
298
315
|
module = ""
|
299
316
|
qualname = ""
|
@@ -309,9 +326,9 @@ def linkify(context: Context, code: str, namespace: str = "") -> str:
|
|
309
326
|
and context["is_public"](doc).strip()
|
310
327
|
):
|
311
328
|
if plain_text.endswith("()"):
|
312
|
-
plain_text = f"{doc.
|
329
|
+
plain_text = f"{doc.qualname}()"
|
313
330
|
else:
|
314
|
-
plain_text = doc.
|
331
|
+
plain_text = doc.qualname
|
315
332
|
return f'<a href="#{qualname}">{plain_text}</a>'
|
316
333
|
except ValueError:
|
317
334
|
# possible_sources did not find a parent module.
|
@@ -326,8 +343,13 @@ def linkify(context: Context, code: str, namespace: str = "") -> str:
|
|
326
343
|
doc is not None and context["is_public"](doc).strip()
|
327
344
|
)
|
328
345
|
if target_exists_and_public:
|
346
|
+
assert doc is not None # mypy
|
329
347
|
if qualname:
|
330
348
|
qualname = f"#{qualname}"
|
349
|
+
if plain_text.endswith("()"):
|
350
|
+
plain_text = f"{doc.fullname}()"
|
351
|
+
else:
|
352
|
+
plain_text = doc.fullname
|
331
353
|
return f'<a href="{relative_link(context["module"].modulename, module)}{qualname}">{plain_text}</a>'
|
332
354
|
else:
|
333
355
|
return text
|
@@ -337,11 +359,14 @@ def linkify(context: Context, code: str, namespace: str = "") -> str:
|
|
337
359
|
r"""
|
338
360
|
# Part 1: foo.bar or foo.bar() (without backticks)
|
339
361
|
(?<![/=?#&]) # heuristic: not part of a URL
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
362
|
+
# First part of the identifier (e.g. "foo") - this is optional for relative references.
|
363
|
+
(?:
|
364
|
+
\b
|
365
|
+
(?!\d)[a-zA-Z0-9_]+
|
366
|
+
|
|
367
|
+
\.* # We may also start with multiple dots.
|
368
|
+
)
|
369
|
+
# Rest of the identifier (e.g. ".bar" or "..bar")
|
345
370
|
(?:
|
346
371
|
# A single dot or a dot surrounded with pygments highlighting.
|
347
372
|
(?:\.|</span><span\ class="o">\.</span><span\ class="n">)
|
@@ -5,6 +5,11 @@ and works without any third-party services in a privacy-preserving way. When a u
|
|
5
5
|
search box for the first time, pdoc will fetch the search index (`search.js`) and use that to
|
6
6
|
answer all upcoming queries.
|
7
7
|
|
8
|
+
##### Single-Page Documentation
|
9
|
+
|
10
|
+
If pdoc is documenting a single module only, search functionality will be disabled.
|
11
|
+
The browser's built-in search functionality will provide a better user experience in these cases.
|
12
|
+
|
8
13
|
##### Search Coverage
|
9
14
|
|
10
15
|
The search functionality covers all documented elements and their docstrings.
|
@@ -177,6 +177,7 @@ See https://pdoc.dev/docs/pdoc/render_helpers.html#DefaultMacroExtension for an
|
|
177
177
|
{% endif %}
|
178
178
|
{% enddefaultmacro %}
|
179
179
|
{% defaultmacro variable(var) -%}
|
180
|
+
{%- if var.is_type_alias_type %}<span class="def">type</span> {% endif -%}
|
180
181
|
<span class="name">{{ var.name }}</span>{{ annotation(var) }}{{ default_value(var) }}
|
181
182
|
{% enddefaultmacro %}
|
182
183
|
{% defaultmacro submodule(mod) -%}
|
@@ -124,7 +124,8 @@ nav.pdoc {
|
|
124
124
|
padding: 0 0 0 var(--pad);
|
125
125
|
overflow-wrap: anywhere;
|
126
126
|
scrollbar-width: thin; /* Scrollbar width on Firefox */
|
127
|
-
scrollbar-color: var(--accent2) transparent /* Scrollbar color on Firefox */
|
127
|
+
scrollbar-color: var(--accent2) transparent; /* Scrollbar color on Firefox */
|
128
|
+
z-index: 1
|
128
129
|
}
|
129
130
|
|
130
131
|
nav.pdoc::-webkit-scrollbar {
|
@@ -40,7 +40,7 @@ class DocHandler(http.server.BaseHTTPRequestHandler):
|
|
40
40
|
except ConnectionError: # pragma: no cover
|
41
41
|
pass
|
42
42
|
|
43
|
-
def handle_request(self) -> str
|
43
|
+
def handle_request(self) -> str:
|
44
44
|
"""Actually handle a request. Called by `do_HEAD` and `do_GET`."""
|
45
45
|
path = self.path.split("?", 1)[0]
|
46
46
|
|
@@ -51,6 +51,13 @@ class DocHandler(http.server.BaseHTTPRequestHandler):
|
|
51
51
|
self.send_header("content-type", "application/javascript")
|
52
52
|
self.end_headers()
|
53
53
|
return self.server.render_search_index()
|
54
|
+
elif "." in removesuffix(path, ".html"):
|
55
|
+
# See https://github.com/mitmproxy/pdoc/issues/615: All module separators should be normalized to "/".
|
56
|
+
# We could redirect here, but that would create the impression of a working link, which will fall apart
|
57
|
+
# when pdoc prerenders to static HTML. So we rather fail early.
|
58
|
+
self.send_response(404)
|
59
|
+
self.end_headers()
|
60
|
+
return "Not Found: Please normalize all module separators to '/'."
|
54
61
|
else:
|
55
62
|
module_name = removesuffix(path.lstrip("/"), ".html").replace("/", ".")
|
56
63
|
if module_name not in self.server.all_modules:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pdoc
|
3
|
-
Version: 14.
|
3
|
+
Version: 14.2.0
|
4
4
|
Summary: API Documentation for Python Projects
|
5
5
|
Author-email: Maximilian Hils <pdoc@maximilianhils.com>
|
6
6
|
License: Unlicense
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.9
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
24
|
Classifier: Typing :: Typed
|
24
25
|
Requires-Python: >=3.8
|
25
26
|
Description-Content-Type: text/markdown
|
@@ -30,7 +31,7 @@ License-File: LICENSE
|
|
30
31
|
<a href="https://pdoc.dev/"><img alt="pdoc" src="https://pdoc.dev/logo.svg" width="200" height="100" /></a>
|
31
32
|
<br><br>
|
32
33
|
<a href="https://pdoc.dev/docs/pdoc.html"><img height="20" alt="pdoc documentation" src="https://shields.mitmproxy.org/badge/docs-pdoc.dev-brightgreen.svg"></a>
|
33
|
-
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/workflow/status/mitmproxy/pdoc/
|
34
|
+
<img height="20" alt="CI Status" src="https://shields.mitmproxy.org/github/actions/workflow/status/mitmproxy/pdoc/main.yml?label=CI&logo=github">
|
34
35
|
<img height="20" alt="Code Coverage" src="https://shields.mitmproxy.org/badge/coverage-100%25-brightgreen">
|
35
36
|
<a href="https://autofix.ci"><img height="20" alt="autofix.ci: yes" src="https://shields.mitmproxy.org/badge/autofix.ci-yes-success?logo=data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCB0cmFuc2Zvcm09InNjYWxlKDAuMDYxLC0wLjA2MSkgdHJhbnNsYXRlKC0yNTAsLTE3NTApIiBkPSJNMTMyNSAtMzQwcS0xMTUgMCAtMTY0LjUgMzIuNXQtNDkuNSAxMTQuNXEwIDMyIDUgNzAuNXQxMC41IDcyLjV0NS41IDU0djIyMHEtMzQgLTkgLTY5LjUgLTE0dC03MS41IC01cS0xMzYgMCAtMjUxLjUgNjJ0LTE5MSAxNjl0LTkyLjUgMjQxcS05MCAxMjAgLTkwIDI2NnEwIDEwOCA0OC41IDIwMC41dDEzMiAxNTUuNXQxODguNSA4MXExNSA5OSAxMDAuNSAxODAuNXQyMTcgMTMwLjV0MjgyLjUgNDlxMTM2IDAgMjU2LjUgLTQ2IHQyMDkgLTEyNy41dDEyOC41IC0xODkuNXExNDkgLTgyIDIyNyAtMjEzLjV0NzggLTI5OS41cTAgLTEzNiAtNTggLTI0NnQtMTY1LjUgLTE4NC41dC0yNTYuNSAtMTAzLjVsLTI0MyAtMzAwdi01MnEwIC0yNyAzLjUgLTU2LjV0Ni41IC01Ny41dDMgLTUycTAgLTg1IC00MS41IC0xMTguNXQtMTU3LjUgLTMzLjV6TTEzMjUgLTI2MHE3NyAwIDk4IDE0LjV0MjEgNTcuNXEwIDI5IC0zIDY4dC02LjUgNzN0LTMuNSA0OHY2NGwyMDcgMjQ5IHEtMzEgMCAtNjAgNS41dC01NCAxMi41bC0xMDQgLTEyM3EtMSAzNCAtMiA2My41dC0xIDU0LjVxMCA2OSA5IDEyM2wzMSAyMDBsLTExNSAtMjhsLTQ2IC0yNzFsLTIwNSAyMjZxLTE5IC0xNSAtNDMgLTI4LjV0LTU1IC0yNi41bDIxOSAtMjQydi0yNzZxMCAtMjAgLTUuNSAtNjB0LTEwLjUgLTc5dC01IC01OHEwIC00MCAzMCAtNTMuNXQxMDQgLTEzLjV6TTEyNjIgNjE2cS0xMTkgMCAtMjI5LjUgMzQuNXQtMTkzLjUgOTYuNWw0OCA2NCBxNzMgLTU1IDE3MC41IC04NXQyMDQuNSAtMzBxMTM3IDAgMjQ5IDQ1LjV0MTc5IDEyMXQ2NyAxNjUuNWg4MHEwIC0xMTQgLTc3LjUgLTIwNy41dC0yMDggLTE0OXQtMjg5LjUgLTU1LjV6TTgwMyA1OTVxODAgMCAxNDkgMjkuNXQxMDggNzIuNWwyMjEgLTY3bDMwOSA4NnE0NyAtMzIgMTA0LjUgLTUwdDExNy41IC0xOHE5MSAwIDE2NSAzOHQxMTguNSAxMDMuNXQ0NC41IDE0Ni41cTAgNzYgLTM0LjUgMTQ5dC05NS41IDEzNHQtMTQzIDk5IHEtMzcgMTA3IC0xMTUuNSAxODMuNXQtMTg2IDExNy41dC0yMzAuNSA0MXEtMTAzIDAgLTE5Ny41IC0yNnQtMTY5IC03Mi41dC0xMTcuNSAtMTA4dC00MyAtMTMxLjVxMCAtMzQgMTQuNSAtNjIuNXQ0MC41IC01MC41bC01NSAtNTlxLTM0IDI5IC01NCA2NS41dC0yNSA4MS41cS04MSAtMTggLTE0NSAtNzB0LTEwMSAtMTI1LjV0LTM3IC0xNTguNXEwIC0xMDIgNDguNSAtMTgwLjV0MTI5LjUgLTEyM3QxNzkgLTQ0LjV6Ii8+PC9zdmc+"></a>
|
36
37
|
<a href="https://pypi.python.org/pypi/pdoc"><img height="20" alt="PyPI Version" src="https://shields.mitmproxy.org/pypi/v/pdoc.svg"></a>
|
@@ -27,6 +27,7 @@ classifiers = [
|
|
27
27
|
"Programming Language :: Python :: 3.9",
|
28
28
|
"Programming Language :: Python :: 3.10",
|
29
29
|
"Programming Language :: Python :: 3.11",
|
30
|
+
"Programming Language :: Python :: 3.12",
|
30
31
|
"Typing :: Typed",
|
31
32
|
]
|
32
33
|
|
@@ -43,7 +44,6 @@ pdoc = "pdoc.__main__:cli"
|
|
43
44
|
dev = [
|
44
45
|
"tox",
|
45
46
|
"ruff",
|
46
|
-
"black",
|
47
47
|
"mypy",
|
48
48
|
"types-pygments",
|
49
49
|
"pytest",
|
@@ -51,6 +51,7 @@ dev = [
|
|
51
51
|
"pytest-timeout",
|
52
52
|
"hypothesis",
|
53
53
|
"pygments >= 2.14.0",
|
54
|
+
"pdoc-pyo3-sample-library==1.0.11",
|
54
55
|
]
|
55
56
|
|
56
57
|
[build-system]
|
@@ -88,9 +89,6 @@ markers = [
|
|
88
89
|
"slow: marks tests as slow.",
|
89
90
|
]
|
90
91
|
|
91
|
-
[tool.black]
|
92
|
-
extend-exclude = "test/testdata/demo.py"
|
93
|
-
|
94
92
|
[[tool.mypy.overrides]]
|
95
93
|
module = "pytest.*"
|
96
94
|
ignore_missing_imports = true
|
@@ -100,8 +98,9 @@ module = "demopackage2"
|
|
100
98
|
ignore_missing_imports = true
|
101
99
|
|
102
100
|
[tool.ruff]
|
103
|
-
|
101
|
+
extend-exclude = ["test/testdata/demo.py"]
|
104
102
|
select = ["E", "F", "I"]
|
103
|
+
ignore = ["E501"]
|
105
104
|
|
106
105
|
[tool.ruff.isort]
|
107
106
|
force-single-line = true
|
@@ -11,6 +11,7 @@ from pdoc import extract
|
|
11
11
|
from pdoc.doc import Class
|
12
12
|
from pdoc.doc import Module
|
13
13
|
from pdoc.doc import Variable
|
14
|
+
from pdoc.doc import _environ_lookup
|
14
15
|
from pdoc.doc_types import empty
|
15
16
|
|
16
17
|
here = Path(__file__).parent
|
@@ -143,3 +144,34 @@ def test_raising_submodules():
|
|
143
144
|
assert m.submodules
|
144
145
|
finally:
|
145
146
|
f.write_bytes(b"# syntax error will be inserted by test here\n")
|
147
|
+
|
148
|
+
|
149
|
+
def test_default_value_masks_env_vars(monkeypatch):
|
150
|
+
monkeypatch.setenv("SUPER_SECRET_TOKEN", "correct horse battery staple")
|
151
|
+
monkeypatch.setenv("VERSION_NUMBER", "42.0.1")
|
152
|
+
_environ_lookup.cache_clear()
|
153
|
+
try:
|
154
|
+
v1 = Variable(
|
155
|
+
"module",
|
156
|
+
"var",
|
157
|
+
taken_from=("module", "var"),
|
158
|
+
docstring="",
|
159
|
+
annotation=empty,
|
160
|
+
default_value="correct horse battery staple",
|
161
|
+
)
|
162
|
+
with pytest.warns(
|
163
|
+
match=r"The default value of module.var matches the \$SUPER_SECRET_TOKEN environment variable."
|
164
|
+
):
|
165
|
+
assert v1.default_value_str == "$SUPER_SECRET_TOKEN"
|
166
|
+
|
167
|
+
v2 = Variable(
|
168
|
+
"module",
|
169
|
+
"version",
|
170
|
+
taken_from=("module", "version"),
|
171
|
+
docstring="",
|
172
|
+
annotation=empty,
|
173
|
+
default_value="42.0.1",
|
174
|
+
)
|
175
|
+
assert v2.default_value_str == "'42.0.1'"
|
176
|
+
finally:
|
177
|
+
_environ_lookup.cache_clear()
|
@@ -12,8 +12,9 @@ from pdoc.doc_types import safe_eval_type
|
|
12
12
|
"typestr", ["totally_unknown_module", "!!!!", "html.unknown_attr"]
|
13
13
|
)
|
14
14
|
def test_eval_fail(typestr):
|
15
|
+
a = types.ModuleType("a")
|
15
16
|
with pytest.warns(UserWarning, match="Error parsing type annotation"):
|
16
|
-
assert safe_eval_type(typestr,
|
17
|
+
assert safe_eval_type(typestr, a.__dict__, None, a, "a") == typestr
|
17
18
|
|
18
19
|
|
19
20
|
def test_eval_fail2(monkeypatch):
|
@@ -22,8 +23,9 @@ def test_eval_fail2(monkeypatch):
|
|
22
23
|
"get_source",
|
23
24
|
lambda _: "import typing\nif typing.TYPE_CHECKING:\n\traise RuntimeError()",
|
24
25
|
)
|
26
|
+
a = types.ModuleType("a")
|
25
27
|
with pytest.warns(UserWarning, match="Failed to run TYPE_CHECKING code"):
|
26
|
-
assert safe_eval_type("xyz",
|
28
|
+
assert safe_eval_type("xyz", a.__dict__, None, a, "a") == "xyz"
|
27
29
|
|
28
30
|
|
29
31
|
def test_eval_fail3(monkeypatch):
|
@@ -32,16 +34,24 @@ def test_eval_fail3(monkeypatch):
|
|
32
34
|
"get_source",
|
33
35
|
lambda _: "import typing\nif typing.TYPE_CHECKING:\n\tFooFn = typing.Callable[[],int]",
|
34
36
|
)
|
37
|
+
a = types.ModuleType("a")
|
38
|
+
a.__dict__["typing"] = typing
|
35
39
|
with pytest.warns(
|
36
40
|
UserWarning,
|
37
41
|
match="Error parsing type annotation .+ after evaluating TYPE_CHECKING blocks",
|
38
42
|
):
|
39
|
-
assert (
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
assert safe_eval_type("FooFn[int]", a.__dict__, None, a, "a") == "FooFn[int]"
|
44
|
+
|
45
|
+
|
46
|
+
def test_eval_fail_import_nonexistent(monkeypatch):
|
47
|
+
monkeypatch.setattr(
|
48
|
+
doc_ast,
|
49
|
+
"get_source",
|
50
|
+
lambda _: "import typing\nif typing.TYPE_CHECKING:\n\timport nonexistent_module",
|
51
|
+
)
|
52
|
+
a = types.ModuleType("a")
|
53
|
+
with pytest.warns(UserWarning, match="No module named 'nonexistent_module'"):
|
54
|
+
assert safe_eval_type("xyz", a.__dict__, None, a, "a") == "xyz"
|
45
55
|
|
46
56
|
|
47
57
|
def test_eval_union_types_on_old_python(monkeypatch):
|
@@ -53,3 +63,21 @@ def test_eval_union_types_on_old_python(monkeypatch):
|
|
53
63
|
):
|
54
64
|
# str never implements `|`, so we can use that to trigger the error on newer versions.
|
55
65
|
safe_eval_type('"foo" | "bar"', {}, None, None, "example")
|
66
|
+
|
67
|
+
|
68
|
+
def test_recurse(monkeypatch):
|
69
|
+
def get_source(mod):
|
70
|
+
if mod == a:
|
71
|
+
return "import typing\nif typing.TYPE_CHECKING:\n\tfrom b import Foo"
|
72
|
+
else:
|
73
|
+
return "import typing\nif typing.TYPE_CHECKING:\n\tfrom a import Foo"
|
74
|
+
|
75
|
+
a = types.ModuleType("a")
|
76
|
+
b = types.ModuleType("b")
|
77
|
+
|
78
|
+
monkeypatch.setattr(doc_ast, "get_source", get_source)
|
79
|
+
monkeypatch.setitem(sys.modules, "a", a)
|
80
|
+
monkeypatch.setitem(sys.modules, "b", b)
|
81
|
+
|
82
|
+
with pytest.warns(UserWarning, match="Recursion error when importing a"):
|
83
|
+
assert safe_eval_type("xyz", a.__dict__, None, a, "a") == "xyz"
|
@@ -27,6 +27,7 @@ def test_walk_specs():
|
|
27
27
|
"demopackage._child_e",
|
28
28
|
"demopackage.child_b",
|
29
29
|
"demopackage.child_c",
|
30
|
+
"demopackage.subpackage",
|
30
31
|
]
|
31
32
|
with pytest.raises(ValueError, match="No modules found matching spec: unknown"):
|
32
33
|
with pytest.warns(UserWarning, match="Cannot find spec for unknown"):
|
@@ -59,6 +60,14 @@ def test_walk_specs():
|
|
59
60
|
"test.mod_with_main.__main__",
|
60
61
|
]
|
61
62
|
|
63
|
+
assert walk_specs(["pdoc_pyo3_sample_library"]) == [
|
64
|
+
"pdoc_pyo3_sample_library",
|
65
|
+
"pdoc_pyo3_sample_library.submodule",
|
66
|
+
"pdoc_pyo3_sample_library.submodule.subsubmodule",
|
67
|
+
"pdoc_pyo3_sample_library.explicit_submodule",
|
68
|
+
"pdoc_pyo3_sample_library.correct_name_submodule",
|
69
|
+
]
|
70
|
+
|
62
71
|
|
63
72
|
def test_parse_spec(monkeypatch):
|
64
73
|
p = sys.path
|
@@ -29,14 +29,14 @@ class Snapshot:
|
|
29
29
|
def __init__(
|
30
30
|
self,
|
31
31
|
id: str,
|
32
|
-
|
32
|
+
specs: list[str] | None = None,
|
33
33
|
render_options: dict | None = None,
|
34
34
|
with_output_directory: bool = False,
|
35
35
|
min_version: tuple[int, int] = (3, 7),
|
36
36
|
warnings: list[str] | None = None,
|
37
37
|
):
|
38
38
|
self.id = id
|
39
|
-
self.specs =
|
39
|
+
self.specs = specs or [f"{id}.py"]
|
40
40
|
self.render_options = render_options or {}
|
41
41
|
self.with_output_directory = with_output_directory
|
42
42
|
self.min_version = min_version
|
@@ -145,6 +145,7 @@ snapshots = [
|
|
145
145
|
min_version=(3, 12),
|
146
146
|
),
|
147
147
|
Snapshot("math_demo", render_options={"math": True}),
|
148
|
+
Snapshot("math_misc", render_options={"math": True}),
|
148
149
|
Snapshot("mermaid_demo", render_options={"mermaid": True}, min_version=(3, 9)),
|
149
150
|
Snapshot(
|
150
151
|
"render_options",
|
@@ -159,8 +160,9 @@ snapshots = [
|
|
159
160
|
},
|
160
161
|
with_output_directory=True,
|
161
162
|
),
|
163
|
+
Snapshot("pyo3_sample_library", specs=["pdoc_pyo3_sample_library"]),
|
162
164
|
Snapshot("top_level_reimports", ["top_level_reimports"]),
|
163
|
-
Snapshot("type_checking_imports"),
|
165
|
+
Snapshot("type_checking_imports", ["type_checking_imports.main"]),
|
164
166
|
Snapshot("type_stub", min_version=(3, 10)),
|
165
167
|
Snapshot(
|
166
168
|
"visibility",
|
@@ -91,3 +91,9 @@ def test_get_module_mtime():
|
|
91
91
|
|
92
92
|
def test_get_unknown():
|
93
93
|
assert b"404 Not Found" in handle_request(b"GET /unknown HTTP/1.1\r\n\r\n")
|
94
|
+
|
95
|
+
|
96
|
+
def test_get_not_normalized():
|
97
|
+
assert b"Not Found: Please normalize all module separators" in handle_request(
|
98
|
+
b"GET /module.submodule HTTP/1.1\r\n\r\n"
|
99
|
+
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|