Skip to content

Commit 70e37b3

Browse files
authored
Add prose extension for numpydoc (#116)
1 parent 396625b commit 70e37b3

File tree

15 files changed

+224
-77
lines changed

15 files changed

+224
-77
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{# https://raw.githubusercontent.com/sphinx-doc/sphinx/master/sphinx/ext/autosummary/templates/autosummary/module.rst #}
2+
{% extends "!autosummary/module.rst" %}
3+
4+
{% block classes %}
5+
{% if classes -%}
6+
Classes
7+
-------
8+
9+
{%- for class in classes -%}
10+
{%- if not class.startswith('_') %}
11+
.. autoclass:: {{ class }}
12+
:members:
13+
{%- endif -%}
14+
{%- endfor -%}
15+
{%- endif %}
16+
{% endblock %}
17+
18+
{% block functions %}
19+
{% if functions -%}
20+
Functions
21+
---------
22+
23+
{%- for function in functions -%}
24+
{%- if not function.startswith('_') and not function.startswith('example_') %}
25+
.. autofunction:: {{ function }}
26+
{%- endif -%}
27+
{%- endfor -%}
28+
{%- if functions | select('search', '^example_') | first %}
29+
30+
Examples
31+
--------
32+
33+
{%- for function in functions -%}
34+
{%- if function.startswith('example_') %}
35+
.. autofunction:: {{ function }}
36+
{%- endif -%}
37+
{%- endfor -%}
38+
{%- endif -%}
39+
{%- endif %}
40+
{% endblock %}

docs/conf.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from datetime import timezone as tz
99
from importlib.metadata import metadata
1010

11+
from jinja2.tests import TESTS
12+
1113

1214
if TYPE_CHECKING:
1315
from sphinx.application import Sphinx
@@ -51,6 +53,20 @@
5153
# Don’t add module paths to documented functions’ names
5254
add_module_names = False
5355

56+
napoleon_google_docstring = False
57+
napoleon_numpy_docstring = True
58+
59+
60+
def test_search(value: str, pattern: str) -> bool:
61+
"""Tests if `pattern` can be found in `value`."""
62+
import re
63+
64+
return bool(re.search(pattern, value))
65+
66+
67+
# IDK if there’s a good way to do this without modifying the global list
68+
TESTS["search"] = test_search
69+
5470
html_theme = "scanpydoc"
5571
html_theme_options = dict(
5672
repository_url="https://github.com/theislab/scanpydoc",

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,14 @@ ignore = [
7272
]
7373
[tool.ruff.lint.flake8-type-checking]
7474
strict = true
75+
exempt-modules = []
7576
[tool.ruff.lint.isort]
7677
length-sort = true
7778
lines-after-imports = 2
7879
known-first-party = ['scanpydoc']
7980
required-imports = ["from __future__ import annotations"]
81+
[tool.ruff.lint.pydocstyle]
82+
convention = 'numpy'
8083

8184
[tool.mypy]
8285
strict = true

src/scanpydoc/__init__.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
# Can’t seem to be able to do this in numpydoc style:
2121
# https://github.com/sphinx-doc/sphinx/issues/5887
2222
setup_sig_str = """\
23-
Args:
24-
app: Sphinx app to set this :term:`sphinx:extension` up for
25-
26-
Returns:
27-
:ref:`Metadata <sphinx:ext-metadata>` for this extension.
23+
Arguments
24+
---------
25+
app
26+
Sphinx app to set this :term:`sphinx:extension` up for
27+
28+
Returns
29+
-------
30+
:ref:`Metadata <sphinx:ext-metadata>` for this extension.
2831
"""
2932

3033
C = TypeVar("C", bound=Callable[..., Any])

src/scanpydoc/autosummary_generate_imported.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77

88
from __future__ import annotations
99

10-
import sys
1110
from typing import TYPE_CHECKING
1211

13-
14-
if sys.version_info >= (3, 11):
15-
from typing import Never
16-
else: # pragma: no cover
17-
from typing import NoReturn as Never
18-
1912
from . import _setup_sig
2013

2114

2215
if TYPE_CHECKING:
16+
import sys
17+
18+
if sys.version_info >= (3, 11):
19+
from typing import Never
20+
else: # pragma: no cover
21+
from typing import NoReturn as Never
22+
2323
from sphinx.application import Sphinx
2424

2525

src/scanpydoc/elegant_typehints/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,28 @@ def x() -> Tuple[int, float]:
4747

4848
from __future__ import annotations
4949

50-
from typing import TYPE_CHECKING, Any
50+
from typing import TYPE_CHECKING
5151
from pathlib import Path
5252
from collections import ChainMap
5353
from dataclasses import dataclass
5454

5555
from sphinx.ext.autodoc import ClassDocumenter
56+
from sphinx.ext.napoleon import NumpyDocstring # type: ignore[attr-defined]
5657

5758
from scanpydoc import metadata, _setup_sig
5859

59-
from .example import example_func
60+
from .example import example_func_prose, example_func_tuple
6061

6162

6263
if TYPE_CHECKING:
64+
from typing import Any
6365
from collections.abc import Callable
6466

6567
from sphinx.config import Config
6668
from sphinx.application import Sphinx
6769

6870

69-
__all__ = ["example_func", "setup"]
71+
__all__ = ["example_func_prose", "example_func_tuple", "setup"]
7072

7173

7274
HERE = Path(__file__).parent.resolve()
@@ -123,7 +125,9 @@ def setup(app: Sphinx) -> dict[str, Any]:
123125
)
124126

125127
from ._return_tuple import process_docstring # , process_signature
128+
from ._return_patch_numpydoc import _parse_returns_section
126129

130+
NumpyDocstring._parse_returns_section = _parse_returns_section # type: ignore[method-assign,assignment] # noqa: SLF001
127131
app.connect("autodoc-process-docstring", process_docstring)
128132

129133
return metadata

src/scanpydoc/elegant_typehints/_formatting.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import sys
44
import inspect
5-
from typing import TYPE_CHECKING, Any
5+
from typing import TYPE_CHECKING
66

77

88
if sys.version_info >= (3, 10):
@@ -15,6 +15,8 @@
1515

1616

1717
if TYPE_CHECKING:
18+
from typing import Any
19+
1820
from sphinx.config import Config
1921

2022

@@ -24,12 +26,16 @@ def typehints_formatter(annotation: type[Any], config: Config) -> str | None:
2426
Can be used as ``typehints_formatter`` for :mod:`sphinx_autodoc_typehints`,
2527
to respect the ``qualname_overrides`` option.
2628
27-
Args:
28-
annotation: A type or class used as type annotation.
29-
config: Sphinx config containing ``sphinx-autodoc-typehints``’s options.
29+
Arguments
30+
---------
31+
annotation
32+
A type or class used as type annotation.
33+
config
34+
Sphinx config containing ``sphinx-autodoc-typehints``’s options.
3035
31-
Returns:
32-
reStructuredText describing the type
36+
Returns
37+
-------
38+
reStructuredText describing the type
3339
"""
3440
if inspect.isclass(annotation) and annotation.__module__ == "builtins":
3541
return None
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
6+
if TYPE_CHECKING:
7+
from sphinx.ext.napoleon import NumpyDocstring # type: ignore[attr-defined]
8+
9+
__all__ = ["_parse_returns_section"]
10+
11+
12+
def _parse_returns_section(self: NumpyDocstring, section: str) -> list[str]: # noqa: ARG001
13+
lines_raw = list(self._dedent(self._consume_to_next_section()))
14+
if lines_raw[0] == ":":
15+
# Remove the “:” inserted by sphinx-autodoc-typehints
16+
# https://github.com/tox-dev/sphinx-autodoc-typehints/blob/a5c091f725da8374347802d54c16c3d38833d41c/src/sphinx_autodoc_typehints/patches.py#L66
17+
lines_raw.pop(0)
18+
lines = self._format_block(":returns: ", lines_raw)
19+
if lines and lines[-1]:
20+
lines.append("")
21+
return lines

src/scanpydoc/elegant_typehints/_return_tuple.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import re
44
import sys
55
import inspect
6-
from typing import TYPE_CHECKING, Any, Union, get_args, get_origin, get_type_hints
6+
from typing import TYPE_CHECKING, Union, get_args, get_origin, get_type_hints
77
from typing import Tuple as t_Tuple # noqa: UP035
88
from logging import getLogger
99

1010
from sphinx_autodoc_typehints import format_annotation
1111

1212

1313
if TYPE_CHECKING:
14+
from typing import Any
1415
from collections.abc import Sequence
1516

1617
from sphinx.application import Sphinx
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
from __future__ import annotations
22

33

4-
def example_func(
4+
def example_func_prose(
55
a: str | None, b: str | int | None = None
66
) -> dict[str, int]: # pragma: no cover
7-
"""Example function.
7+
"""Example function with a paragraph return section.
88
9-
Hover over the parameter and return type annotations to see the long versions.
9+
Parameters
10+
----------
11+
a
12+
An example parameter
13+
b
14+
Another, with a default
1015
11-
Args:
12-
a: An example parameter
13-
b: Another, with a default
14-
Returns:
15-
An example return value
16+
Returns
17+
-------
18+
An example dict
1619
"""
1720
return {}
21+
22+
23+
def example_func_tuple() -> tuple[int, str]: # pragma: no cover
24+
"""Example function with return tuple.
25+
26+
Returns
27+
-------
28+
x
29+
An example int
30+
y
31+
An example str
32+
"""
33+
return (1, "foo")

0 commit comments

Comments
 (0)