Skip to content

Commit

Permalink
Generate doc stubs for transplanted methods
Browse files Browse the repository at this point in the history
The `transplant_stub.py` script introspects the groups and topology
attributes to write files in `documentation_pages/core` that contain the
documentation for the transplanted methods.

For the stubs to be picked up by sphinx, the docstring of the class to
document must contain

    .. include:: XXX.txt

where "XXX" is the name of the class.

A stub contains a table of the methods, their short descriptions, and
what topology attribute they require.

Fixes #1845
  • Loading branch information
jbarnoud committed Jan 12, 2019
1 parent 24972e6 commit 3336dab
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
16 changes: 16 additions & 0 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ class GroupBase(_MutableBase):
| | | that are part of ``s`` or |
| | | ``t`` but not both |
+-------------------------------+------------+----------------------------+
.. include:: GroupBase.txt
"""
def __init__(self, *args):
try:
Expand Down Expand Up @@ -1781,6 +1784,8 @@ class AtomGroup(GroupBase):
new :class:`AtomGroup` for multiple matches. This makes it difficult to use
the feature consistently in scripts.
.. include:: AtomGroup.txt
See Also
--------
Expand Down Expand Up @@ -2702,6 +2707,9 @@ class ResidueGroup(GroupBase):
*Instant selectors* of Segments will be removed in the 1.0 release.
See :ref:`Instant selectors <instance-selectors>` for details and
alternatives.
.. include:: ResidueGroup.txt
"""

@property
Expand Down Expand Up @@ -2864,6 +2872,7 @@ class SegmentGroup(GroupBase):
*Instant selectors* of Segments will be removed in the 1.0 release.
See :ref:`Instant selectors <instance-selectors>` for details and
alternatives.
"""

@property
Expand Down Expand Up @@ -3102,6 +3111,9 @@ class Atom(ComponentBase):
:class:`~MDAnalysis.core.topologyattrs.TopologyAttr` components are obtained
from :class:`ComponentBase`, so this class only includes ad-hoc methods
specific to :class:`Atoms<Atom>`.
.. include:: Atom.txt
"""
def __getattr__(self, attr):
"""Try and catch known attributes and give better error message"""
Expand Down Expand Up @@ -3241,6 +3253,9 @@ class Residue(ComponentBase):
:class:`~MDAnalysis.core.topologyattrs.TopologyAttr` components are obtained
from :class:`ComponentBase`, so this class only includes ad-hoc methods
specific to :class:`Residues<Residue>`.
.. include:: Residue.txt
"""
def __repr__(self):
me = '<Residue'
Expand Down Expand Up @@ -3288,6 +3303,7 @@ class Segment(ComponentBase):
*Instant selectors* of :class:`Segments<Segment>` will be removed in the
1.0 release. See :ref:`Instant selectors <instance-selectors>` for
details and alternatives.
"""
def __repr__(self):
me = '<Segment'
Expand Down
151 changes: 151 additions & 0 deletions package/doc/sphinx/source/transplant_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env python

from __future__ import print_function
import sys
from pprint import pprint
import collections
import os
import textwrap
import re
import inspect
import MDAnalysis as mda
import tabulate

from sphinx.ext.napoleon import NumpyDocstring


class TransplantedMethod:
def __init__(self, method):
self.method = method
try:
# We may be dealing with a property; then we need to get the
# actual method out of it.
self.method = method.fget
except AttributeError:
# Well, it was not a property
pass

@property
def name(self):
return self.method.__name__

@property
def doc(self):
dedent_doc = textwrap.dedent(' ' + self.method.__doc__)
numpy_doc = NumpyDocstring(dedent_doc)
doc_clear = clear_citations(str(numpy_doc))
return doc_clear

@property
def signature(self):
return get_signature(self.method)

@property
def short_desc(self):
return self.doc.splitlines()[0].strip()

@property
def is_private(self):
return self.name.startswith('_') or self.name.endswith('_')

@property
def formatted(self):
text = '.. method:: {}{}\n\n{}\n\n'.format(
self.name,
self.signature,
textwrap.indent(self.doc, prefix=' ' * 8)
)
return text


def clear_citations(doc):
citation_re = re.compile(r'^ *\.\. \[[^]]+\]')

result = []
in_citation = False
for line in doc.splitlines():
match = citation_re.match(line)
if match is not None:
in_citation = True
elif in_citation and not line.strip():
in_citation = False
elif not in_citation:
result.append(line)

return '\n'.join(result)


def get_signature(method):
signature = str(inspect.signature(method))
return re.sub(r'\(self,? *', '(', signature)


# Collect the transplanted functions from the topopoly attributes
targets = collections.defaultdict(lambda : collections.defaultdict(list))
for attribute_key, attribute in mda.core.topologyattrs._TOPOLOGY_ATTRS.items():
for target, methods in attribute.transplants.items():
all_methods = []
for method in methods:
function = TransplantedMethod(method[1])
if not function.is_private:
all_methods.append(function)
if all_methods:
targets[target][attribute.attrname] = all_methods


for target_key, target_dict in targets.items():
try:
target_name = target_key.__name__
except AttributeError:
# For some reason, some target are not classes but str
target_name = target_key
if hasattr(target_key, '__mro__'):
for parent in target_key.__mro__:
for attribute_key, method_list in targets.get(parent, {}).items():
if attribute_key not in target_dict:
target_dict[attribute_key] = []
for method in method_list:
if method not in target_dict[attribute_key]:
target_dict[attribute_key].append(method)


for target_key, target_dict in targets.items():
try:
target_name = target_key.__name__
except AttributeError:
# For some reason, some target are not classes but str
target_name = target_key
table = []
for attribute_key, method_list in target_dict.items():
table.append([f'**Requires {attribute_key}**', ''])
for method in method_list:
table.append([method.name, method.short_desc])
print(tabulate.tabulate(table, tablefmt='grid'))


for target_key, target_dict in targets.items():
try:
target_name = target_key.__name__
except AttributeError:
# For some reason, some target are not classes but str
target_name = target_key
file_name = os.path.join(
'documentation_pages',
'core',
'{}.txt'.format(target_name)
)
with open(file_name, 'w') as outfile:
table = []
for attribute_key, method_list in target_dict.items():
table.append([f'**Requires {attribute_key}**', ''])
for method in method_list:
table.append([f':meth:`{method.name}`', method.short_desc])
print(tabulate.tabulate(table, tablefmt='grid'), file=outfile)

for attribute_key, method_list in target_dict.items():
print(file=outfile)

for method in method_list:
print(method.formatted, file=outfile)


0 comments on commit 3336dab

Please sign in to comment.