Skip to content

Commit fe12f8a

Browse files
committed
Add sphinx extension for documenting traits
1 parent 3553dd0 commit fe12f8a

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

docs/source/conf.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,6 @@
1313
# All configuration values have a default; values that are commented out
1414
# serve to show the default.
1515

16-
# If extensions (or modules to document with autodoc) are in another directory,
17-
# add these directories to sys.path here. If the directory is relative to the
18-
# documentation root, use os.path.abspath to make it absolute, like shown here.
19-
#
20-
# import os
21-
# import sys
22-
# sys.path.insert(0, os.path.abspath('.'))
23-
2416

2517
# -- General configuration ------------------------------------------------
2618

@@ -37,8 +29,17 @@
3729
'sphinx.ext.intersphinx',
3830
'sphinx.ext.napoleon',
3931
'sphinx.ext.todo',
32+
'autodoc_traits',
4033
]
4134

35+
# Ensure our extension is available:
36+
import sys
37+
from os.path import dirname, join as pjoin
38+
docs = dirname(dirname(__file__))
39+
root = dirname(docs)
40+
sys.path.insert(0, pjoin(root, '..'))
41+
sys.path.insert(0, pjoin(docs, 'sphinxext'))
42+
4243
# Add any paths that contain templates here, relative to this directory.
4344
templates_path = ['_templates']
4445

docs/sphinxext/autodoc_traits.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""autodoc extension for traits"""
2+
3+
from collections import OrderedDict
4+
5+
from traitlets import TraitType, Undefined, Container, Dict, Any
6+
from sphinx.ext.autodoc import ClassDocumenter, AttributeDocumenter
7+
8+
9+
def dict_info(trait):
10+
trait_base = trait._trait
11+
traits = trait._traits
12+
13+
if traits is None and (trait_base is None or isinstance(trait_base, Any)):
14+
value_string = 'elements of any type'
15+
else:
16+
parts = []
17+
if traits:
18+
parts.append('the following types: %r' % {k: v.info() for k,v in traits})
19+
if trait_base:
20+
parts.append('values that are: %s' % trait_base.info())
21+
value_string = 'elements with ' + ', and '.join(parts)
22+
23+
return '{} with {}'.format(trait.info(), value_string)
24+
25+
26+
def extended_trait_info(trait):
27+
if isinstance(trait, Dict):
28+
return dict_info(trait)
29+
elif isinstance(trait, Container):
30+
if trait._trait is None:
31+
return '{} of any type'.format(trait.info())
32+
return '{} with values that are: {}'.format(trait.info(), trait._trait.info())
33+
return trait.info()
34+
35+
36+
class HasTraitsDocumenter(ClassDocumenter):
37+
"""Specialized Documenter subclass for traits"""
38+
objtype = 'hastraits'
39+
directivetype = 'class'
40+
41+
def get_object_members(self, want_all):
42+
"""Add traits to members list"""
43+
check, members = super().get_object_members(want_all)
44+
get_traits = self.object.class_own_traits if self.options.inherited_members \
45+
else self.object.class_traits
46+
members_new = OrderedDict()
47+
for m in members:
48+
members_new[m[0]] = m[1]
49+
traits = tuple(get_traits().items())
50+
for name, trait in traits:
51+
if name not in members_new:
52+
# Don't add a member that would normally be filtered
53+
continue
54+
# pass # FIXME: Debugging
55+
56+
# put help in __doc__ where autodoc will look for it
57+
trait.__doc__ = trait.help or extended_trait_info(getattr(self.object, name))
58+
members_new[name] = trait
59+
60+
return check, [kv for kv in members_new.items()]
61+
62+
63+
class TraitDocumenter(AttributeDocumenter):
64+
objtype = 'trait'
65+
directivetype = 'attribute'
66+
member_order = 1
67+
priority = 100
68+
69+
@classmethod
70+
def can_document_member(cls, member, membername, isattr, parent):
71+
return isinstance(member, TraitType)
72+
73+
def format_name(self):
74+
return self.objpath[-1]
75+
76+
def add_directive_header(self, sig):
77+
default = self.object.get_default_value()
78+
if default is Undefined:
79+
default_s = ''
80+
else:
81+
default_s = repr(default)
82+
sig = ' = {}({})'.format(
83+
self.object.__class__.__name__,
84+
default_s,
85+
)
86+
return super().add_directive_header(sig)
87+
88+
89+
def setup(app):
90+
app.add_autodocumenter(HasTraitsDocumenter)
91+
app.add_autodocumenter(TraitDocumenter)

js/scripts/templates/autodoc.mustache

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
{{ className }}
55
====================================================
66

7-
.. py:class:: {{ className }}({{#unless constructor.hasParameters}}{{#each constructor.args as |arg|}}{{{ arg.name }}}{{#if arg.prop.defaultJson}}={{{ arg.prop.defaultJson }}}{{/if}}, {{/each}}{{/unless}})
7+
.. Use autoclass to fill any memebers not manually specified.
8+
This ensures it picks up any members in overridden classes.
9+
10+
.. autohastraits:: {{ className }}({{#unless constructor.hasParameters}}{{#each constructor.args as |arg|}}{{{ arg.name }}}{{#if arg.prop.defaultJson}}={{{ arg.prop.defaultJson }}}{{/if}}, {{/each}}{{/unless}})
11+
:members:
12+
:undoc-members:
813

914
{{#if hasOverride}}
1015
This widget has some manual overrides on the Python side.
@@ -16,11 +21,6 @@
1621

1722
Three.js docs: {{ threejs_docs_url }}
1823

19-
.. py:attribute:: _model_name
20-
21-
.. sourcecode:: python
22-
23-
Unicode('{{ modelName }}').tag(sync=True)
2424

2525
{{#each properties as |prop propName|}}
2626
.. py:attribute:: {{ propName }}

js/scripts/templates/autodoc_index.mustache

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
API Reference
66
=============
77

8-
.. py:module:: pythreejs
8+
.. automodule:: pythreejs
9+
:members:
10+
:undoc-members:
911

1012
{{else}}
1113

@@ -18,6 +20,6 @@ API Reference
1820
.. toctree::
1921
:maxdepth: 10
2022

21-
{{#each submodules as |module|}}
23+
{{#each submodules as |module|}}
2224
{{ module }}
23-
{{/each}}
25+
{{/each}}

0 commit comments

Comments
 (0)