27
27
28
28
"""
29
29
import inspect
30
- from collections import ChainMap
31
- from typing import Union , Mapping , Dict , Any , Type
30
+ from collections import abc , ChainMap
31
+ from functools import partial
32
+ from pathlib import Path
33
+ from typing import Any , Union , Optional # Meta
34
+ from typing import Type , Mapping , Sequence # ABC
35
+ from typing import Dict , List , Tuple # Concrete
32
36
33
37
import sphinx_autodoc_typehints
38
+ from sphinx_autodoc_typehints import format_annotation as _format_full
39
+ from docutils import nodes
40
+ from docutils .nodes import Node
41
+ from docutils .parsers .rst .roles import set_classes
42
+ from docutils .parsers .rst .states import Inliner , Struct
43
+ from docutils .utils import SystemMessage , unescape
34
44
from sphinx .application import Sphinx
35
45
from sphinx .config import Config
46
+ from docutils .parsers .rst import roles
36
47
37
48
from . import _setup_sig , metadata
38
49
39
50
51
+ HERE = Path (__file__ ).parent .resolve ()
52
+
40
53
qualname_overrides_default = {
41
54
"anndata.base.AnnData" : "anndata.AnnData" ,
42
55
"pandas.core.frame.DataFrame" : "pandas.DataFrame" ,
49
62
50
63
def _init_vars (app : Sphinx , config : Config ):
51
64
qualname_overrides .update (config .qualname_overrides )
65
+ config .html_static_path .append (str (HERE / "static" ))
52
66
53
67
54
- fa_orig = sphinx_autodoc_typehints .format_annotation
55
-
56
-
57
- def format_annotation (annotation : Type [Any ]) -> str :
58
- """Generate reStructuredText containing links to the types.
59
-
60
- Unlike :func:`sphinx_autodoc_typehints.format_annotation`,
61
- it tries to achieve a simpler style as seen in numeric packages like numpy.
62
-
63
- Args:
64
- annotation: A type or class used as type annotation.
65
-
66
- Returns:
67
- reStructuredText describing the type
68
- """
68
+ def _format_terse (annotation : Type [Any ]) -> str :
69
69
union_params = getattr (annotation , "__union_params__" , None )
70
+
70
71
# display `Union[A, B]` as `A, B`
71
72
if getattr (annotation , "__origin__" , None ) is Union or union_params :
72
73
params = union_params or getattr (annotation , "__args__" , None )
@@ -75,23 +76,91 @@ def format_annotation(annotation: Type[Any]) -> str:
75
76
# as is the convention in the other large numerical packages
76
77
# if len(params or []) == 2 and getattr(params[1], '__qualname__', None) == 'NoneType':
77
78
# return fa_orig(annotation) # Optional[...]
78
- return ", " .join (map (format_annotation , params ))
79
+ return ", " .join (map (_format_terse , params ))
80
+
79
81
# do not show the arguments of Mapping
80
- if getattr (annotation , "__origin__" , None ) is Mapping :
81
- return ":class:`~typing.Mapping`"
82
+ if getattr (annotation , "__origin__" , None ) in (abc .Mapping , Mapping ):
83
+ return ":py:class:`~typing.Mapping`"
84
+
85
+ # display dict as {k: v}
86
+ if getattr (annotation , "__origin__" , None ) in (dict , Dict ):
87
+ k , v = annotation .__args__
88
+ return f"{{{ _format_terse (k )} : { _format_terse (v )} }}"
89
+
82
90
if inspect .isclass (annotation ):
83
91
full_name = f"{ annotation .__module__ } .{ annotation .__qualname__ } "
84
92
override = qualname_overrides .get (full_name )
85
93
if override is not None :
86
94
return f":py:class:`~{ override } `"
87
- return fa_orig (annotation )
95
+ return _format_full (annotation )
96
+
97
+
98
+ def format_annotation (annotation : Type [Any ]) -> str :
99
+ """Generate reStructuredText containing links to the types.
100
+
101
+ Unlike :func:`sphinx_autodoc_typehints.format_annotation`,
102
+ it tries to achieve a simpler style as seen in numeric packages like numpy.
103
+
104
+ Args:
105
+ annotation: A type or class used as type annotation.
106
+
107
+ Returns:
108
+ reStructuredText describing the type
109
+ """
110
+
111
+ curframe = inspect .currentframe ()
112
+ calframe = inspect .getouterframes (curframe , 2 )
113
+ if calframe [1 ][3 ] == "process_docstring" :
114
+ return (
115
+ f":annotation-terse:`{ _escape (_format_terse (annotation ))} `\\ "
116
+ f":annotation-full:`{ _escape (_format_full (annotation ))} `"
117
+ )
118
+ else : # recursive use
119
+ return _format_full (annotation )
120
+
121
+
122
+ def _role_annot (
123
+ name : str ,
124
+ rawtext : str ,
125
+ text : str ,
126
+ lineno : int ,
127
+ inliner : Inliner ,
128
+ options : Dict [str , Any ] = {},
129
+ content : Sequence [str ] = (),
130
+ # *, # https://github.com/ambv/black/issues/613
131
+ additional_class : Optional [str ] = None ,
132
+ ) -> Tuple [List [Node ], List [SystemMessage ]]:
133
+ options = options .copy ()
134
+ set_classes (options )
135
+ if additional_class is not None :
136
+ options ["classes" ] = options .get ("classes" , []).copy ()
137
+ options ["classes" ].append (additional_class )
138
+ memo = Struct (
139
+ document = inliner .document , reporter = inliner .reporter , language = inliner .language
140
+ )
141
+ node = nodes .inline (unescape (rawtext ), "" , ** options )
142
+ children , messages = inliner .parse (_unescape (text ), lineno , memo , node )
143
+ node .extend (children )
144
+ return [node ], messages
145
+
146
+
147
+ def _escape (rst : str ) -> str :
148
+ return rst .replace ("`" , "\\ `" )
149
+
150
+
151
+ def _unescape (rst : str ) -> str :
152
+ # TODO: IDK why the [ part is necessary.
153
+ return unescape (rst ).replace ("\\ `" , "`" ).replace ("[" , "\\ [" )
88
154
89
155
90
156
@_setup_sig
91
157
def setup (app : Sphinx ) -> Dict [str , Any ]:
92
158
"""Patches :mod:`sphinx_autodoc_typehints` for a more elegant display."""
93
159
app .add_config_value ("qualname_overrides" , {}, "" )
160
+ app .add_css_file ("typehints.css" )
94
161
app .connect ("config-inited" , _init_vars )
95
162
sphinx_autodoc_typehints .format_annotation = format_annotation
163
+ for name in ["annotation-terse" , "annotation-full" ]:
164
+ roles .register_canonical_role (name , partial (_role_annot , additional_class = name ))
96
165
97
166
return metadata
0 commit comments