Skip to content

Commit 163407d

Browse files
committed
Add defaults to elegant typehints
1 parent eb0af18 commit 163407d

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

scanpydoc/elegant_typehints.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
2121
Once either `sphinx issue 4826`_ or `sphinx-autodoc-typehints issue 38`_ are fixed,
2222
this part of the functionality will no longer be necessary.
23+
#. The config value ``annotate_defaults`` (default: :data:`True`) controls if rST code
24+
like ``(default: `42`)`` is added after the type.
2325
2426
.. _sphinx issue 4826: https://github.com/sphinx-doc/sphinx/issues/4826
2527
.. _sphinx-autodoc-typehints issue 38: https://github.com/agronholm/sphinx-autodoc-typehints/issues/38
@@ -60,10 +62,13 @@
6062
"scipy.sparse.csc.csc_matrix": "scipy.sparse.csc_matrix",
6163
}
6264
qualname_overrides = ChainMap({}, qualname_overrides_default)
65+
annotate_defaults = True
6366

6467

6568
def _init_vars(app: Sphinx, config: Config):
69+
global annotate_defaults
6670
qualname_overrides.update(config.qualname_overrides)
71+
annotate_defaults = config.annotate_defaults
6772
config.html_static_path.append(str(HERE / "static"))
6873

6974

@@ -119,13 +124,15 @@ def _format_terse(annotation: Type[Any], fully_qualified: bool = False) -> str:
119124

120125

121126
def format_annotation(annotation: Type[Any], fully_qualified: bool = False) -> str:
122-
"""Generate reStructuredText containing links to the types.
127+
r"""Generate reStructuredText containing links to the types.
123128
124129
Unlike :func:`sphinx_autodoc_typehints.format_annotation`,
125130
it tries to achieve a simpler style as seen in numeric packages like numpy.
126131
127132
Args:
128133
annotation: A type or class used as type annotation.
134+
fully_qualified: If links should be formatted as fully qualified
135+
(e.g. ``:py:class:`foo.Bar```) or not (e.g. ``:py:class:`~foo.Bar```).
129136
130137
Returns:
131138
reStructuredText describing the type
@@ -139,10 +146,18 @@ def format_annotation(annotation: Type[Any], fully_qualified: bool = False) -> s
139146
curframe = inspect.currentframe()
140147
calframe = inspect.getouterframes(curframe, 2)
141148
if calframe[1][3] == "process_docstring":
142-
return (
149+
annot_fmt = (
143150
f":annotation-terse:`{_escape(_format_terse(annotation, fully_qualified))}`\\ "
144151
f":annotation-full:`{_escape(_format_full(annotation, fully_qualified))}`"
145152
)
153+
if annotate_defaults:
154+
variables = calframe[1].frame.f_locals
155+
sig = inspect.signature(variables["obj"])
156+
if variables["argname"] != "return":
157+
default = sig.parameters[variables["argname"]].default
158+
if default is not inspect.Parameter.empty:
159+
annot_fmt += f" (default: ``{_escape(repr(default))}``)"
160+
return annot_fmt
146161
else: # recursive use
147162
return _format_full(annotation, fully_qualified)
148163

@@ -184,7 +199,10 @@ def _unescape(rst: str) -> str:
184199
@_setup_sig
185200
def setup(app: Sphinx) -> Dict[str, Any]:
186201
"""Patches :mod:`sphinx_autodoc_typehints` for a more elegant display."""
187-
app.add_config_value("qualname_overrides", {}, "")
202+
# TODO: Unsure if “html” is sufficient or if we need to do “env”;
203+
# Depends on when the autodoc-process-docstring event is handled.
204+
app.add_config_value("qualname_overrides", {}, "html")
205+
app.add_config_value("annotate_defaults", True, "html")
188206
app.add_css_file("typehints.css")
189207
app.connect("config-inited", _init_vars)
190208
sphinx_autodoc_typehints.format_annotation = format_annotation

tests/test_elegant_typehints.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,49 @@ def fn_test(s: str):
4545
]
4646

4747

48+
def test_defaults_simple(process_doc):
49+
def fn_test(s: str = "foo", n: None = None, i: int = 1):
50+
"""
51+
:param s: Test S
52+
:param n: Test N
53+
:param i: Test I
54+
"""
55+
56+
assert process_doc(fn_test) == [
57+
":type s: "
58+
r":annotation-terse:`:py:class:\`str\``\ "
59+
r":annotation-full:`:py:class:\`str\`` "
60+
"(default: ``'foo'``)",
61+
":param s: Test S",
62+
":type n: "
63+
r":annotation-terse:`\`\`None\`\``\ "
64+
r":annotation-full:`\`\`None\`\`` "
65+
"(default: ``None``)",
66+
":param n: Test N",
67+
":type i: "
68+
r":annotation-terse:`:py:class:\`int\``\ "
69+
r":annotation-full:`:py:class:\`int\`` "
70+
"(default: ``1``)",
71+
":param i: Test I",
72+
]
73+
74+
75+
def test_defaults_complex(process_doc):
76+
def fn_test(m: t.Mapping[str, int] = {}):
77+
"""
78+
:param m: Test M
79+
"""
80+
81+
assert process_doc(fn_test) == [
82+
":type m: "
83+
r":annotation-terse:`:py:class:\`typing.Mapping\``\ "
84+
r":annotation-full:`"
85+
r":py:class:\`typing.Mapping\`\[:py:class:\`str\`, :py:class:\`int\`]"
86+
"` (default: ``{}``)",
87+
":param m: Test M",
88+
]
89+
90+
4891
def test_mapping(app):
4992
assert _format_terse(t.Mapping[str, t.Any]) == ":py:class:`~typing.Mapping`"
5093
assert _format_full(t.Mapping[str, t.Any]) == (

0 commit comments

Comments
 (0)