-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathautodoc_traits.py
241 lines (195 loc) · 10.6 KB
/
autodoc_traits.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""
sphinx.ext.autodoc extension for classes with configurable traits.
The code here is similar to the official code example in
https://www.sphinx-doc.org/en/master/development/tutorials/autodoc_ext.html#writing-the-extension.
"""
import warnings
from sphinx.ext.autodoc import AttributeDocumenter, ClassDocumenter, ObjectMember
from sphinx.util.inspect import safe_getattr
from traitlets import MetaHasTraits, TraitType, Undefined
# __version__ should be updated using tbump, based on configuration in
# pyproject.toml, according to instructions in RELEASE.md.
#
__version__ = "1.2.3.dev"
class ConfigurableDocumenter(ClassDocumenter):
"""
Specialized Documenter subclass for traits with config=True
Links to relevant source code in sphinx.ext.autodoc:
- Documenter: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L270-L299
- ClassDocumenter: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L1395-L1408
"""
# objtype: The suffix to "auto" for a Sphinx directive name that will be
# created (and the default value for "directivetype").
objtype = "configurable"
# directivetype: Clarifies that this Documenter class could be capable of
# documenting this kind of members of other parent like
# Documenter classes.
directivetype = "class"
# priority: Declares this class' priority for use if multiple classes
# "can_document_member" of the "directivetype". This is only
# relevant if a parent class has a member.
#
# ConfigurableDocumenter can document traitlets configurable
# classes, so a parent like Documenter class can be the
# ModuleDocumenter.
#
priority = 100 # higher priority than ClassDocumenter's 10
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
"""
If the member is a class with traitlets then we can document it, and we
will document it thanks to a high priority.
This function is not considered if the ``autoconfigurable`` or
``autoclass`` directives are called directly. can_document_member is
only used by other parent like Documenter classes having members of this
class' configured "documentertype" - such as ModuleDocumenter.
"""
return isinstance(member, MetaHasTraits)
def get_object_members(self, want_all):
"""
This get_object_members function override is a hack, manipulating
__doc__ values of trait configuration objects, but otherwise behaving
exactly like the super class get_object_members.
It sets truthy strings to the class' traits __doc__ attributes. They
will otherwise be filtered out by the Documenter.filter_members
function, unless ``undoc-members`` option is set.
Links to relevant source code in sphinx.ext.autodoc:
- Documenter.filter_members: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L616-L769
- Documenter.get_object_members: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L607-L614
- ClassDocumenter.get_object_members: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L1656-L1674
- traitlets.HasTraits.class_traits: https://github.com/ipython/traitlets/blob/v5.6.0/traitlets/traitlets.py#L1620-L1652
"""
check, members = super().get_object_members(want_all)
existing_member_names = {m.__name__ for m in members}
# We add all _configurable_ traits, including inherited, even if they
# weren't included via :members: or :inherited-members: options.
#
# Being added at this stage doesn't mean they are presented. Any members
# added here must also have a truthy __doc__ string attribute and not be
# part of the :exclude-members: option.
#
# FIXME: We have been adding the configurable trait_members
# unconditionally, but should we keep doing that?
#
# See https://github.com/jupyterhub/autodoc-traits/issues/27
#
config_trait_members = self.object.class_traits(config=True).items()
for trait_tuple in config_trait_members:
name, trait = trait_tuple
if not trait.__doc__:
warnings.warn(
f"""
Documenting {self.object.__name__}.{trait.name} without a help string because it has config=True.
Including undocumented config=True traits is deprecated in autodoc-traits 1.1.
Add a help string:
{trait.name} = {trait.__class__.__name__}(
help="...",
)
to keep this trait in your docs,
or include it explicitly via :members:
""",
FutureWarning,
)
# FIXME: in the unlikely event that the patched trait
# is documented multiple times in the same build,
# this patch will cause it to have a truthy help string
# elsewhere, not just in this autoconfigurable instance.
trait.__doc__ = trait.help = "No help string is provided."
if name not in existing_member_names:
existing_member_names.add(name)
members.append(ObjectMember(name, trait))
return check, members
class TraitDocumenter(AttributeDocumenter):
"""
Links to relevant source code in sphinx.ext.autodoc:
- Documenter: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L270-L299
- AttributeDocumenter: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L2509-L2524
"""
# objtype: The suffix to "auto" for a Sphinx directive name that will be
# created (and the default value for "directivetype").
objtype = "trait"
# directivetype: Clarifies that this Documenter class could be capable of
# documenting this kind of members of other parent like
# Documenter classes.
directivetype = "attribute"
# priority: Declares this class' priority for use if multiple classes
# "can_document_member" of the "directivetype". This is only
# relevant if a parent class has a member.
#
# TraitsDocumenter can document traitlets type attributes, so the
# parent like Documenter class is typically ClassDocumenter, but
# can also be the ConfigurableDocumenter.
#
priority = 100 # AttributeDocumenter has 10
# order: order if the autodoc_member_order in conf.py is set to "groupwise",
# by default it is "alphabetical", where lowest order comes first.
# Since traits are relevant configuration, we declare the lowest
# order for high visual priority.
member_order = 0 # AttributeDocumenter has 60
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
"""
If the member is a traitlets type we can document it, and we will
document it thanks to a high priority.
This function is not considered if the ``autotrait`` or
``autoattribute`` directives are called directly. can_document_member is
only used by other parent like Documenter classes having members of this
class' configured "documentertype" - such as ClassDocumenter.
"""
return isinstance(member, TraitType)
def add_directive_header(self, sig):
"""
add_directive_header is called by the base class' Documenter.generate
method. It is provided by both AttributeDocumenter and Documenter. This
override retains use of the super classes implementations, but influence
them.
For functions, the directive header describes the function's call
signature, but not the function's docstring.
Similarly, we look to emit rST to describe how the traitlets
configuration option can be configured and its default value.
- Documenter.generate: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L918-L929
- AttributeDocumenter.add_directive_header: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L2592-L2620
- Documenter.add_directive_header: https://github.com/sphinx-doc/sphinx/blob/v6.0.0b2/sphinx/ext/autodoc/__init__.py#L504-L524
"""
default_value = self.object.default_value
if default_value is Undefined:
default_value = ""
else:
default_value = repr(default_value)
traitlets_type = self.object.__class__.__name__ # Bool
if self.object.metadata.get("config"):
# add config prefix (c.TestConfigurator.trait = ) if it's configurable
config_prefix = f"c.{self.format_name()} = "
else:
config_prefix = ""
self.options.annotation = f"{config_prefix}{traitlets_type}({default_value})"
super().add_directive_header(sig)
def hastraits_attrgetter(obj, name, *defargs):
"""getattr for trait
Ensures when HasTraits are documented, their __doc__ attr is defined
as the .help string.
This backports a change in traitlets 5.8.0.
"""
attr = safe_getattr(obj, name, *defargs)
if isinstance(attr, TraitType):
# ensure __doc__ is defined as the trait's help string
# if help is empty, that's the same as undocumented
attr.__doc__ = attr.help
return attr
def setup(app):
"""
The setup function is required for Sphinx extensions.
In this function we register the sphinx.ext.autodoc extension that this
extension needs to function, and we register our sphinx.ext.autodoc
Documenter classes we provide.
"""
# setup_extension reference:
# https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.setup_extension
app.setup_extension("sphinx.ext.autodoc")
# add_autodocumenter reference:
# https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_autodocumenter
app.add_autodocumenter(ConfigurableDocumenter)
app.add_autodocumenter(TraitDocumenter)
# add_autodoc_attrgetter reference:
# https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_autodoc_attrgetter
app.add_autodoc_attrgetter(MetaHasTraits, hastraits_attrgetter)