Skip to content

Commit

Permalink
Merge pull request #21 from takluyver/rst-config-docs
Browse files Browse the repository at this point in the history
API to generate rST docs for configurable options
  • Loading branch information
Carreau committed May 28, 2015
2 parents 034f6fb + c664f99 commit 605133a
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 30 deletions.
41 changes: 27 additions & 14 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from decorator import decorator

from traitlets.config.configurable import SingletonConfigurable
from traitlets.config.configurable import Configurable, SingletonConfigurable
from traitlets.config.loader import (
KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
)
Expand Down Expand Up @@ -128,15 +128,20 @@ class Application(SingletonConfigurable):
# A sequence of Configurable subclasses whose config=True attributes will
# be exposed at the command line.
classes = []
@property
def _help_classes(self):
"""Define `App.help_classes` if CLI classes should differ from config file classes"""
return getattr(self, 'help_classes', self.classes)

@property
def _config_classes(self):
"""Define `App.config_classes` if config file classes should differ from CLI classes."""
return getattr(self, 'config_classes', self.classes)

def _classes_inc_parents(self):
"""Iterate through configurable classes, including configurable parents
Children should always be after parents, and each class should only be
yielded once.
"""
seen = set()
for c in self.classes:
# We want to sort parents before children, so we reverse the MRO
for parent in reversed(c.mro()):
if issubclass(parent, Configurable) and (parent not in seen):
seen.add(parent)
yield parent

# The version string of this application.
version = Unicode(u'0.0')
Expand Down Expand Up @@ -269,7 +274,7 @@ def print_alias_help(self):

lines = []
classdict = {}
for cls in self._help_classes:
for cls in self.classes:
# include all parents (up to, but excluding Configurable) in available names
for c in cls.mro()[:-3]:
classdict[c.__name__] = c
Expand Down Expand Up @@ -344,7 +349,7 @@ def print_help(self, classes=False):
self.print_options()

if classes:
help_classes = self._help_classes
help_classes = self.classes
if help_classes:
print("Class parameters")
print("----------------")
Expand All @@ -362,6 +367,14 @@ def print_help(self, classes=False):

self.print_examples()

def document_config_options(self):
"""Generate rST format documentation for the config options this application
Returns a multiline string.
"""
return '\n'.join(c.class_config_rst_doc()
for c in self._classes_inc_parents())


def print_description(self):
"""Print the application description."""
Expand Down Expand Up @@ -426,7 +439,7 @@ def flatten_flags(self):
# it will be a dict by parent classname of classes in our list
# that are descendents
mro_tree = defaultdict(list)
for cls in self._help_classes:
for cls in self.classes:
clsname = cls.__name__
for parent in cls.mro()[1:-3]:
# exclude cls itself and Configurable,HasTraits,object
Expand Down Expand Up @@ -556,7 +569,7 @@ def generate_config_file(self):
"""generate default config file from Configurables"""
lines = ["# Configuration file for %s." % self.name]
lines.append('')
for cls in self._config_classes:
for cls in self._classes_inc_parents():
lines.append(cls.class_config_section())
return '\n'.join(lines)

Expand Down
64 changes: 48 additions & 16 deletions traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from .loader import Config, LazyConfigValue
from traitlets.traitlets import HasTraits, Instance
from ipython_genutils.text import indent, wrap_paragraphs
from ipython_genutils.text import indent, dedent, wrap_paragraphs
from ipython_genutils.py3compat import iteritems


Expand Down Expand Up @@ -256,27 +256,59 @@ def c(s):
lines.append(c(desc))
lines.append('')

parents = []
for parent in cls.mro():
# only include parents that are not base classes
# and are not the class itself
# and have some configurable traits to inherit
if parent is not cls and issubclass(parent, Configurable) and \
parent.class_traits(config=True):
parents.append(parent)

if parents:
pstr = ', '.join([ p.__name__ for p in parents ])
lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
lines.append('')

for name, trait in iteritems(cls.class_traits(config=True)):
for name, trait in iteritems(cls.class_own_traits(config=True)):
help = trait.get_metadata('help') or ''
lines.append(c(help))
lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
lines.append('')
return '\n'.join(lines)

@classmethod
def class_config_rst_doc(cls):
"""Generate rST documentation for this class' config options.
Excludes traits defined on parent classes.
"""
lines = []
classname = cls.__name__
for k, trait in sorted(cls.class_own_traits(config=True).items()):
ttype = trait.__class__.__name__

termline = classname + '.' + trait.name

# Choices or type
if 'Enum' in ttype:
# include Enum choices
termline += ' : ' + '|'.join(repr(x) for x in trait.values)
else:
termline += ' : ' + ttype
lines.append(termline)

# Default value
try:
dv = trait.get_default_value()
dvr = repr(dv)
except Exception:
dvr = dv = None # ignore defaults we can't construct
if (dv is not None) and (dvr is not None):
if len(dvr) > 64:
dvr = dvr[:61]+'...'
# Double up backslashes, so they get to the rendered docs
dvr = dvr.replace('\\n', '\\\\n')
lines.append(' Default: ``%s``' % dvr)
lines.append('')

help = trait.get_metadata('help')
if help is not None:
lines.append(indent(dedent(help), 4))
else:
lines.append(' No description')

# Blank line
lines.append('')

return '\n'.join(lines)



class SingletonConfigurable(Configurable):
Expand Down
10 changes: 10 additions & 0 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,16 @@ def class_traits(cls, **metadata):

return result

@classmethod
def class_own_traits(cls, **metadata):
"""Get a dict of all the traitlets defined on this class, not a parent.
Works like `class_traits`, except for excluding traits from parents.
"""
sup = super(cls, cls)
return {n: t for (n, t) in cls.class_traits(**metadata).items()
if getattr(sup, n, None) is not t}

def trait_names(self, **metadata):
"""Get a list of all the names of this class' traits."""
return self.traits(**metadata).keys()
Expand Down

0 comments on commit 605133a

Please sign in to comment.