From 2888a15dbdd95c8e24b05bf232db127ffbc9e719 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 14:09:14 +0200 Subject: [PATCH 01/16] Add sphinxext module --- .gitignore | 3 +- aiida/sphinxext/__init__.py | 16 ++ aiida/sphinxext/tests/conftest.py | 65 ++++++++ aiida/sphinxext/tests/demo_workchain.py | 56 +++++++ aiida/sphinxext/tests/test_workchain.py | 15 ++ .../sphinxext/tests/workchain_source/conf.py | 151 ++++++++++++++++++ .../tests/workchain_source/index.rst | 16 ++ aiida/sphinxext/workchain.py | 134 ++++++++++++++++ setup_requirements.py | 5 + 9 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 aiida/sphinxext/__init__.py create mode 100644 aiida/sphinxext/tests/conftest.py create mode 100644 aiida/sphinxext/tests/demo_workchain.py create mode 100644 aiida/sphinxext/tests/test_workchain.py create mode 100644 aiida/sphinxext/tests/workchain_source/conf.py create mode 100644 aiida/sphinxext/tests/workchain_source/index.rst create mode 100644 aiida/sphinxext/workchain.py diff --git a/.gitignore b/.gitignore index 2cb1fdd79f..16f4d2db16 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ # files created by coverage *\,cover -coverage/ \ No newline at end of file +coverage/ +.cache diff --git a/aiida/sphinxext/__init__.py b/aiida/sphinxext/__init__.py new file mode 100644 index 0000000000..59ea8bdefe --- /dev/null +++ b/aiida/sphinxext/__init__.py @@ -0,0 +1,16 @@ +""" +Defines reStructuredText directives to simplify documenting AiiDA and its plugins. +""" + +__version__ = '0.0.0a1' + +from . import workchain + + +def setup(app): + """ + Setup function to add the extension classes / nodes to Sphinx. + """ + workchain.setup_aiida_workchain(app) + + return {'version': __version__, 'parallel_read_safe': True} diff --git a/aiida/sphinxext/tests/conftest.py b/aiida/sphinxext/tests/conftest.py new file mode 100644 index 0000000000..e6416ea661 --- /dev/null +++ b/aiida/sphinxext/tests/conftest.py @@ -0,0 +1,65 @@ +import sys +import json +import shutil +import tempfile +import operator +import subprocess +from os.path import join + +import pytest + + +@pytest.fixture +def build_dir(): + # Python 2 doesn't have tempfile.TemporaryDirectory + dirname = tempfile.mkdtemp() + try: + yield dirname + except Exception as e: + raise e + finally: + shutil.rmtree(dirname) + + +@pytest.fixture +def build_sphinx(build_dir): + def inner(source_dir, builder='xml'): + doctree_dir = join(build_dir, 'doctrees') + out_dir = join(build_dir, builder) + + subprocess.check_call([ + sys.executable, '-m', 'sphinx', '-b', builder, '-d', doctree_dir, + source_dir, out_dir + ]) + + return out_dir + + return inner + + +@pytest.fixture +def test_name(request): + """Returns module_name.function_name for a given test""" + return request.module.__name__ + '/' + request._parent_request._pyfuncitem.name + + +@pytest.fixture +def compare_data(request, test_name, scope="session"): + """Returns a function which either saves some data to a file or (if that file exists already) compares it to pre-existing data using a given comparison function.""" + + def inner(compare_fct, data, tag=None): + full_name = test_name + (tag or '') + val = request.config.cache.get(full_name, None) + if val is None: + request.config.cache.set(full_name, json.loads(json.dumps(data))) + raise ValueError('Reference data does not exist.') + else: + assert compare_fct(val, json.loads(json.dumps(data)) + ) # get rid of json-specific quirks + + return inner + + +@pytest.fixture +def compare_equal(compare_data): + return lambda data, tag=None: compare_data(operator.eq, data, tag) diff --git a/aiida/sphinxext/tests/demo_workchain.py b/aiida/sphinxext/tests/demo_workchain.py new file mode 100644 index 0000000000..62b6006f79 --- /dev/null +++ b/aiida/sphinxext/tests/demo_workchain.py @@ -0,0 +1,56 @@ +"""This module defines an example workchain for the aiida-workchain documentation directive.""" + +from aiida.work.workchain import WorkChain +from aiida.orm.data.base import Int, Float, Bool + + +class DemoWorkChain(WorkChain): # pylint: disable=abstract-method + """ + A demo workchain to show how the workchain auto-documentation works. + """ + + @classmethod + def define(cls, spec): + super(DemoWorkChain, cls).define(spec) + + spec.input('x', valid_type=Float, help='First input argument.') + spec.output('x', valid_type=Float) + spec.expose_inputs(SubWorkChain, namespace='sub') + spec.expose_outputs(SubWorkChain) + + +class SubWorkChain(WorkChain): # pylint: disable=abstract-method + """ + A sub-workchain, to show how port namespaces are handled. + """ + + @classmethod + def define(cls, spec): + super(SubWorkChain, cls).define(spec) + + spec.input( + 'y', + valid_type=Int, + help="The second, nested input.", + required=False + ) + spec.output('y', valid_type=Int) + spec.expose_inputs(NestedSubWorkChain, namespace='sub') + spec.expose_outputs(NestedSubWorkChain) + + +class NestedSubWorkChain(WorkChain): # pylint: disable=abstract-method + """ + A nested workchain, to show how second-level port namespaces are handled. + """ + + @classmethod + def define(cls, spec): + super(NestedSubWorkChain, cls).define(spec) + + spec.input( + 'z', + valid_type=Bool, + help="A third input variable, that is nested two levels deep." + ) + spec.output('z', valid_type=Bool) diff --git a/aiida/sphinxext/tests/test_workchain.py b/aiida/sphinxext/tests/test_workchain.py new file mode 100644 index 0000000000..e6362f5ca2 --- /dev/null +++ b/aiida/sphinxext/tests/test_workchain.py @@ -0,0 +1,15 @@ +"""Tests for the AiiDA workchain Sphinx directive.""" + +from os.path import join, dirname + +import pytest + +WORKCHAIN = join(dirname(__file__), 'workchain_source') + + +@pytest.mark.parametrize('builder', ['xml', 'html']) +def test_workchain_build(build_sphinx, compare_equal, builder): + out_dir = build_sphinx(WORKCHAIN, builder=builder) + index_file = join(out_dir, 'index.' + builder) + with open(index_file, 'r') as f: + compare_equal(f.read()) diff --git a/aiida/sphinxext/tests/workchain_source/conf.py b/aiida/sphinxext/tests/workchain_source/conf.py new file mode 100644 index 0000000000..d9f2199131 --- /dev/null +++ b/aiida/sphinxext/tests/workchain_source/conf.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# +# sphinx-aiida-demo documentation build configuration file, created by +# sphinx-quickstart on Mon Oct 2 13:04:07 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.mathjax', 'sphinx_aiida'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'sphinx-aiida-demo' +copyright = u'2017, Dominik Gresch' +author = u'Dominik Gresch' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.0.0' +# The full version, including alpha/beta/rc tags. +release = u'0.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'sphinx-aiida-demodoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, 'sphinx-aiida-demo.tex', + u'sphinx-aiida-demo Documentation', u'Dominik Gresch', 'manual' + ), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [( + master_doc, 'sphinx-aiida-demo', u'sphinx-aiida-demo Documentation', + [author], 1 +)] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, 'sphinx-aiida-demo', u'sphinx-aiida-demo Documentation', + author, 'sphinx-aiida-demo', 'One line description of project.', + 'Miscellaneous' + ), +] diff --git a/aiida/sphinxext/tests/workchain_source/index.rst b/aiida/sphinxext/tests/workchain_source/index.rst new file mode 100644 index 0000000000..f02ea8e185 --- /dev/null +++ b/aiida/sphinxext/tests/workchain_source/index.rst @@ -0,0 +1,16 @@ +sphinx-aiida demo +================= + +This is a demo documentation to show off the features of the ``sphinx-aiida`` extension. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. aiida-workchain:: demo_workchain.DemoWorkChain + + +You can add the ``:hidden-ports:`` option to also show inputs / outputs starting with ``_``: + +.. aiida-workchain:: demo_workchain.DemoWorkChain + :hidden-ports: diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py new file mode 100644 index 0000000000..f5498a6231 --- /dev/null +++ b/aiida/sphinxext/workchain.py @@ -0,0 +1,134 @@ +""" +Defines an rst directive to auto-document AiiDA workchains. +""" + +from docutils import nodes +from docutils.parsers.rst import Directive, directives +from sphinx import addnodes +import aiida +from plum.util import load_class +from plum.port import InputPort, OutputPort + + +def setup_aiida_workchain(app): + app.add_directive('aiida-workchain', AiidaWorkchainDirective) + + +class AiidaWorkchainDirective(Directive): + """ + Directive to auto-document AiiDA workchains. + """ + required_arguments = 1 + HIDDEN_PORTS_FLAG = 'hidden-ports' + option_spec = {HIDDEN_PORTS_FLAG: directives.flag} + has_content = False + + def run(self): + aiida.try_load_dbenv() + self.load_workchain() + return self.build_node_tree() + + def load_workchain(self): + """Loads the workchain and sets up additional attributes.""" + # pylint: disable=attribute-defined-outside-init + self.workchain_name = self.arguments[0] + self.module_name, self.class_name = self.workchain_name.rsplit('.', 1) + self.workchain = load_class(self.workchain_name) + self.workchain_spec = self.workchain.spec() + + def build_node_tree(self): + """Returns the docutils node tree.""" + workchain_node = addnodes.desc( + desctype='class', domain='py', noindex=False, objtype='class' + ) + workchain_node += self.build_signature() + workchain_node += self.build_content() + return [workchain_node] + + def build_signature(self): + """Returns the signature of the workchain.""" + signature = addnodes.desc_signature(first=False, fullname="Workchain") + signature += addnodes.desc_annotation(text='workchain') + signature += addnodes.desc_addname(text=self.module_name + '.') + signature += addnodes.desc_name(text=self.class_name) + return signature + + def build_content(self): + """ + Returns the main content (docstring, inputs, outputs) of the workchain documentation. + """ + content = addnodes.desc_content() + content += nodes.paragraph(text=self.workchain.__doc__) + + content += self.build_doctree( + title='Inputs:', port_namespace=self.workchain_spec.inputs + ) + content += self.build_doctree( + title='Outputs:', port_namespace=self.workchain_spec.outputs + ) + + return content + + def build_doctree(self, title, port_namespace): + """ + Returns a doctree for a given port namespace, including a title. + """ + paragraph = nodes.paragraph() + paragraph += nodes.strong(text=title) + paragraph += self.build_portnamespace_doctree(port_namespace) + + return paragraph + + def build_portnamespace_doctree(self, portnamespace): + """ + Builds the doctree for a port namespace. + """ + from aiida.work.process import PortNamespace + + result = nodes.bullet_list(bullet='*') + for name, port in sorted(portnamespace.items()): + if name.startswith( + '_' + ) and self.HIDDEN_PORTS_FLAG not in self.options: + continue + if name == 'dynamic': + continue + item = nodes.list_item() + if isinstance(port, (InputPort, OutputPort)): + item += self.build_port_paragraph(name, port) + elif isinstance(port, PortNamespace): + # item += addnodes.literal_strong( + # text='Namespace {}'.format(name) + # ) + item += addnodes.literal_strong(text=name) + item += nodes.Text(', ') + item += nodes.emphasis(text='Namespace') + item += self.build_portnamespace_doctree(port) + else: + raise NotImplementedError + result += item + return result + + def build_port_paragraph(self, name, port): + """ + Build the paragraph that describes a single port. + """ + paragraph = nodes.paragraph() + paragraph += addnodes.literal_strong(text=name) + paragraph += nodes.Text(', ') + paragraph += nodes.emphasis( + text=self.format_valid_types(port.valid_type) + ) + paragraph += nodes.Text(', ') + paragraph += nodes.Text('required' if port.required else 'optional') + if port.help: + paragraph += nodes.Text(' -- ') + paragraph += nodes.Text(port.help) + return paragraph + + @staticmethod + def format_valid_types(valid_type): + try: + return valid_type.__name__ + except TypeError: + return str(valid_type) diff --git a/setup_requirements.py b/setup_requirements.py index e3b5369825..efec1ecc5c 100644 --- a/setup_requirements.py +++ b/setup_requirements.py @@ -135,6 +135,11 @@ 'yapf==0.19.0', 'prospector==0.12.7', 'pylint==1.7.4' + ], + 'dev_sphinxext': [ + 'sphinx', + 'sphinx-rtd-theme', + 'pytest' ] } From 741a6904d6995ef54603da5d8c5b849b6af07183 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 14:17:04 +0200 Subject: [PATCH 02/16] Add sphinext test case --- .travis-data/test_script.sh | 5 ++++- .travis.yml | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis-data/test_script.sh b/.travis-data/test_script.sh index 7918d0d5ed..5b730b56b5 100755 --- a/.travis-data/test_script.sh +++ b/.travis-data/test_script.sh @@ -5,7 +5,7 @@ set -ev case "$TEST_TYPE" in docs) - # Compile the docs (HTML format); -W to convert warnings in errors, + # Compile the docs (HTML format); -W to convert warnings in errors, # -n to warn about all missing references SPHINXOPTS="-nW" make -C docs html ;; @@ -26,4 +26,7 @@ case "$TEST_TYPE" in pre-commit) pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) ;; + sphinxext) + cd aiida/sphinxext/tests; py.test + ;; esac diff --git a/.travis.yml b/.travis.yml index e012bbe869..8253823412 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ install: - pip install -U pip wheel setuptools - pip install -r requirements.txt # Install AiiDA with some optional dependencies - - pip install .[REST,docs,atomic_tools,testing,dev_precommit] + - pip install .[REST,docs,atomic_tools,testing,dev_precommit,dev_sphinxext] env: @@ -53,6 +53,7 @@ env: ## also when building the docs ## because otherwise the code would complain. Also, I need latex. - TEST_TYPE="pre-commit" +- TEST_TYPE="sphinxext" - TEST_AIIDA_BACKEND=django TEST_TYPE="docs" - TEST_AIIDA_BACKEND=django TEST_TYPE="tests" - TEST_AIIDA_BACKEND=sqlalchemy TEST_TYPE="tests" From d747f83c4fd7c86a0d5d7fc9a12f2000ad5fff58 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 14:27:49 +0200 Subject: [PATCH 03/16] Change Travis call for sphinxext tests, directly include demo_workchain source --- .travis-data/test_script.sh | 2 +- .travis.yml | 2 +- aiida/sphinxext/tests/workchain_source/conf.py | 6 +++--- .../tests/{ => workchain_source}/demo_workchain.py | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename aiida/sphinxext/tests/{ => workchain_source}/demo_workchain.py (100%) diff --git a/.travis-data/test_script.sh b/.travis-data/test_script.sh index 5b730b56b5..ade0130c8d 100755 --- a/.travis-data/test_script.sh +++ b/.travis-data/test_script.sh @@ -27,6 +27,6 @@ case "$TEST_TYPE" in pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) ;; sphinxext) - cd aiida/sphinxext/tests; py.test + py.test aiida/sphinxext/tests ;; esac diff --git a/.travis.yml b/.travis.yml index 8253823412..b187801532 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,7 +53,7 @@ env: ## also when building the docs ## because otherwise the code would complain. Also, I need latex. - TEST_TYPE="pre-commit" -- TEST_TYPE="sphinxext" +- TEST_AIIDA_BACKEND=django TEST_TYPE="sphinxext" - TEST_AIIDA_BACKEND=django TEST_TYPE="docs" - TEST_AIIDA_BACKEND=django TEST_TYPE="tests" - TEST_AIIDA_BACKEND=sqlalchemy TEST_TYPE="tests" diff --git a/aiida/sphinxext/tests/workchain_source/conf.py b/aiida/sphinxext/tests/workchain_source/conf.py index d9f2199131..f229fd13b7 100644 --- a/aiida/sphinxext/tests/workchain_source/conf.py +++ b/aiida/sphinxext/tests/workchain_source/conf.py @@ -16,9 +16,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +import os +import sys +sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ diff --git a/aiida/sphinxext/tests/demo_workchain.py b/aiida/sphinxext/tests/workchain_source/demo_workchain.py similarity index 100% rename from aiida/sphinxext/tests/demo_workchain.py rename to aiida/sphinxext/tests/workchain_source/demo_workchain.py From 077bb83dff60ac25028c99f50075c2da69dfcfdb Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 16:11:07 +0200 Subject: [PATCH 04/16] Explicitly compare XML for the sphinxext test --- aiida/sphinxext/tests/conftest.py | 50 +++++++++---------- .../tests/reference_results/workchain.xml | 32 ++++++++++++ aiida/sphinxext/tests/test_workchain.py | 10 ++-- 3 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 aiida/sphinxext/tests/reference_results/workchain.xml diff --git a/aiida/sphinxext/tests/conftest.py b/aiida/sphinxext/tests/conftest.py index e6416ea661..6e12b65529 100644 --- a/aiida/sphinxext/tests/conftest.py +++ b/aiida/sphinxext/tests/conftest.py @@ -1,13 +1,18 @@ +import os import sys -import json import shutil import tempfile -import operator import subprocess -from os.path import join +from os.path import join, dirname +import xml.etree.ElementTree as ET import pytest +@pytest.fixture +def reference_result(): + def inner(name): + return join(dirname(__file__), 'reference_results', name) + return inner @pytest.fixture def build_dir(): @@ -38,28 +43,23 @@ def inner(source_dir, builder='xml'): @pytest.fixture -def test_name(request): - """Returns module_name.function_name for a given test""" - return request.module.__name__ + '/' + request._parent_request._pyfuncitem.name - - -@pytest.fixture -def compare_data(request, test_name, scope="session"): - """Returns a function which either saves some data to a file or (if that file exists already) compares it to pre-existing data using a given comparison function.""" - - def inner(compare_fct, data, tag=None): - full_name = test_name + (tag or '') - val = request.config.cache.get(full_name, None) - if val is None: - request.config.cache.set(full_name, json.loads(json.dumps(data))) - raise ValueError('Reference data does not exist.') - else: - assert compare_fct(val, json.loads(json.dumps(data)) - ) # get rid of json-specific quirks - +def xml_equal(): + def inner(test_file, reference_file): + if not os.path.isfile(reference_file): + shutil.copyfile(test_file, reference_file) + raise ValueError('Reference file does not exist!') + assert _flatten_xml(test_file) == _flatten_xml(reference_file) + print(_flatten_xml(test_file)) + # assert ET.parse(test_file) == ET.parse(reference_file) return inner -@pytest.fixture -def compare_equal(compare_data): - return lambda data, tag=None: compare_data(operator.eq, data, tag) +def _flatten_xml(filename): + return [ + ( + el.tag, + {k: v for k, v in el.attrib.items() if k not in ['source']}, + el.text + ) + for el in ET.parse(filename).iter() + ] diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml new file mode 100644 index 0000000000..2ddc50959a --- /dev/null +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -0,0 +1,32 @@ + + + + +
+ sphinx-aiida demo + This is a demo documentation to show off the features of the sphinx-aiida extension. + + + + workchaindemo_workchain.DemoWorkChain + + + A demo workchain to show how the workchain auto-documentation works. + + Inputs:sub, Namespacesub, Namespacez, Bool, required – A third input variable, that is nested two levels deep.y, Int, optional – The second, nested input.x, Float, required – First input argument. + Outputs:x, Float, requiredy, Int, requiredz, Bool, required + + + You can add the :hidden-ports: option to also show inputs / outputs starting with _: + + workchaindemo_workchain.DemoWorkChain + + + A demo workchain to show how the workchain auto-documentation works. + + Inputs:_description, basestring, optional_label, basestring, optional_store_provenance, bool, optionalsub, Namespacesub, Namespacez, Bool, required – A third input variable, that is nested two levels deep.y, Int, optional – The second, nested input.x, Float, required – First input argument. + Outputs:x, Float, requiredy, Int, requiredz, Bool, required + + +
+
diff --git a/aiida/sphinxext/tests/test_workchain.py b/aiida/sphinxext/tests/test_workchain.py index e6362f5ca2..dab5302323 100644 --- a/aiida/sphinxext/tests/test_workchain.py +++ b/aiida/sphinxext/tests/test_workchain.py @@ -7,9 +7,7 @@ WORKCHAIN = join(dirname(__file__), 'workchain_source') -@pytest.mark.parametrize('builder', ['xml', 'html']) -def test_workchain_build(build_sphinx, compare_equal, builder): - out_dir = build_sphinx(WORKCHAIN, builder=builder) - index_file = join(out_dir, 'index.' + builder) - with open(index_file, 'r') as f: - compare_equal(f.read()) +def test_workchain_build(build_sphinx, xml_equal, reference_result): + out_dir = build_sphinx(WORKCHAIN) + index_file = join(out_dir, 'index.xml') + xml_equal(index_file, reference_result('workchain.xml')) From 088b2345617a3c4a192cd73a25a472a98637fae7 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 16:21:36 +0200 Subject: [PATCH 05/16] Add verbosity to pytest run --- .travis-data/test_script.sh | 2 +- aiida/sphinxext/tests/conftest.py | 2 -- setup_requirements.py | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis-data/test_script.sh b/.travis-data/test_script.sh index ade0130c8d..be8ea9c45e 100755 --- a/.travis-data/test_script.sh +++ b/.travis-data/test_script.sh @@ -27,6 +27,6 @@ case "$TEST_TYPE" in pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) ;; sphinxext) - py.test aiida/sphinxext/tests + py.test -vv aiida/sphinxext/tests ;; esac diff --git a/aiida/sphinxext/tests/conftest.py b/aiida/sphinxext/tests/conftest.py index 6e12b65529..79c6158979 100644 --- a/aiida/sphinxext/tests/conftest.py +++ b/aiida/sphinxext/tests/conftest.py @@ -49,8 +49,6 @@ def inner(test_file, reference_file): shutil.copyfile(test_file, reference_file) raise ValueError('Reference file does not exist!') assert _flatten_xml(test_file) == _flatten_xml(reference_file) - print(_flatten_xml(test_file)) - # assert ET.parse(test_file) == ET.parse(reference_file) return inner diff --git a/setup_requirements.py b/setup_requirements.py index efec1ecc5c..dc28b6fae0 100644 --- a/setup_requirements.py +++ b/setup_requirements.py @@ -137,12 +137,12 @@ 'pylint==1.7.4' ], 'dev_sphinxext': [ - 'sphinx', - 'sphinx-rtd-theme', 'pytest' ] } +extras_require['dev_sphinxext'] += extras_require['docs'] + # There are a number of optional dependencies that are not # listed even as optional dependencies as they are quite # cumbersome to install and there is a risk that a user, wanting From 48b043e4a0b1bcb460ded6c0f66e00f2e1ed7b4f Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 22:42:50 +0200 Subject: [PATCH 06/16] Improve format_valid_types --- aiida/sphinxext/workchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index f5498a6231..352168ccf4 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -130,5 +130,5 @@ def build_port_paragraph(self, name, port): def format_valid_types(valid_type): try: return valid_type.__name__ - except TypeError: + except (TypeError, AttriuteError): return str(valid_type) From 0f33a5683ac805d6eae560a10435a4b316e37aa5 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 22:43:53 +0200 Subject: [PATCH 07/16] Fix typo --- aiida/sphinxext/workchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index 352168ccf4..a914c0891e 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -130,5 +130,5 @@ def build_port_paragraph(self, name, port): def format_valid_types(valid_type): try: return valid_type.__name__ - except (TypeError, AttriuteError): + except (TypeError, AttributeError): return str(valid_type) From 2bc4585c0aa821972e0f78f7e0993022592c6bbc Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 9 Oct 2017 22:52:02 +0200 Subject: [PATCH 08/16] Make doc nicer for empty Input / Output namespaces, multiple valid types --- aiida/sphinxext/workchain.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index a914c0891e..dc2b2cac94 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -75,7 +75,11 @@ def build_doctree(self, title, port_namespace): """ paragraph = nodes.paragraph() paragraph += nodes.strong(text=title) - paragraph += self.build_portnamespace_doctree(port_namespace) + namespace_doctree = self.build_portnamespace_doctree(port_namespace) + if len(namespace_doctree) > 0: + paragraph += namespace_doctree + else: + paragraph += nodes.paragraph(text='None defined.') return paragraph @@ -130,5 +134,8 @@ def build_port_paragraph(self, name, port): def format_valid_types(valid_type): try: return valid_type.__name__ - except (TypeError, AttributeError): - return str(valid_type) + except AttributeError: + try: + return '(' + ', '.join(v.__name__ for v in valid_type) + ')' + except (AttributeError, TypeError): + return str(valid_type) From 31a240e8b0f8281aa89e06d5cf8ff36468402d08 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Wed, 11 Oct 2017 14:39:36 +0200 Subject: [PATCH 09/16] Add support and test for formatting in help string. --- aiida/sphinxext/tests/reference_results/workchain.xml | 4 ++-- aiida/sphinxext/tests/workchain_source/demo_workchain.py | 2 +- aiida/sphinxext/workchain.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml index 2ddc50959a..2f162edb25 100644 --- a/aiida/sphinxext/tests/reference_results/workchain.xml +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -13,7 +13,7 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:sub, Namespacesub, Namespacez, Bool, required – A third input variable, that is nested two levels deep.y, Int, optional – The second, nested input.x, Float, required – First input argument. + Inputs:sub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. Outputs:x, Float, requiredy, Int, requiredz, Bool, required @@ -24,7 +24,7 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:_description, basestring, optional_label, basestring, optional_store_provenance, bool, optionalsub, Namespacesub, Namespacez, Bool, required – A third input variable, that is nested two levels deep.y, Int, optional – The second, nested input.x, Float, required – First input argument. + Inputs:_description, basestring, optional_label, basestring, optional_store_provenance, bool, optionalsub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. Outputs:x, Float, requiredy, Int, requiredz, Bool, required diff --git a/aiida/sphinxext/tests/workchain_source/demo_workchain.py b/aiida/sphinxext/tests/workchain_source/demo_workchain.py index 62b6006f79..ad7c2319dc 100644 --- a/aiida/sphinxext/tests/workchain_source/demo_workchain.py +++ b/aiida/sphinxext/tests/workchain_source/demo_workchain.py @@ -31,7 +31,7 @@ def define(cls, spec): spec.input( 'y', valid_type=Int, - help="The second, nested input.", + help="The second, nested input called ``y``.", required=False ) spec.output('y', valid_type=Int) diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index dc2b2cac94..9548590fde 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -3,6 +3,7 @@ """ from docutils import nodes +from docutils.core import publish_doctree from docutils.parsers.rst import Directive, directives from sphinx import addnodes import aiida @@ -127,7 +128,9 @@ def build_port_paragraph(self, name, port): paragraph += nodes.Text('required' if port.required else 'optional') if port.help: paragraph += nodes.Text(' -- ') - paragraph += nodes.Text(port.help) + # publish_doctree returns >. + # Here we only want the content (children) of the paragraph. + paragraph.extend(publish_doctree(port.help)[0].children) return paragraph @staticmethod From 5a09bfa46dc8c542b54880c9de4fb15724246347 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 16 Oct 2017 18:55:26 +0200 Subject: [PATCH 10/16] Hook 'aiida-workchain' directive into sphinx.ext.autodoc --- aiida/sphinxext/__init__.py | 2 +- .../tests/reference_results/workchain.xml | 41 +++++++++++++++++++ .../tests/workchain_source/demo_workchain.py | 3 ++ .../tests/workchain_source/index.rst | 11 ++++- aiida/sphinxext/workchain.py | 26 +++++++++--- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/aiida/sphinxext/__init__.py b/aiida/sphinxext/__init__.py index 59ea8bdefe..26b66fccd6 100644 --- a/aiida/sphinxext/__init__.py +++ b/aiida/sphinxext/__init__.py @@ -2,7 +2,7 @@ Defines reStructuredText directives to simplify documenting AiiDA and its plugins. """ -__version__ = '0.0.0a1' +__version__ = '0.1.0' from . import workchain diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml index 2f162edb25..b361a090a5 100644 --- a/aiida/sphinxext/tests/reference_results/workchain.xml +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -28,5 +28,46 @@ Outputs:x, Float, requiredy, Int, requiredz, Bool, required + The command is also hooked into sphinx.ext.autodoc, so you can also use that. + + + This module defines an example workchain for the aiida-workchain documentation directive. + + workchaindemo_workchain.DemoWorkChain + + + A demo workchain to show how the workchain auto-documentation works. + + Inputs:sub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. + Outputs:x, Float, requiredy, Int, requiredz, Bool, required + + + + workchaindemo_workchain.NestedSubWorkChain + + + A nested workchain, to show how second-level port namespaces are handled. + + Inputs:z, Bool, required -- A third input variable, that is nested two levels deep. + Outputs:z, Bool, required + + + + + class demo_workchain.NormalClass + + This is here to check that we didn't break the regular 'autoclass. + + + + workchaindemo_workchain.SubWorkChain + + + A sub-workchain, to show how port namespaces are handled. + + Inputs:sub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y. + Outputs:y, Int, requiredz, Bool, required + + diff --git a/aiida/sphinxext/tests/workchain_source/demo_workchain.py b/aiida/sphinxext/tests/workchain_source/demo_workchain.py index ad7c2319dc..d372a91a40 100644 --- a/aiida/sphinxext/tests/workchain_source/demo_workchain.py +++ b/aiida/sphinxext/tests/workchain_source/demo_workchain.py @@ -54,3 +54,6 @@ def define(cls, spec): help="A third input variable, that is nested two levels deep." ) spec.output('z', valid_type=Bool) + +class NormalClass(object): + """This is here to check that we didn't break the regular 'autoclass.""" diff --git a/aiida/sphinxext/tests/workchain_source/index.rst b/aiida/sphinxext/tests/workchain_source/index.rst index f02ea8e185..65524ca33f 100644 --- a/aiida/sphinxext/tests/workchain_source/index.rst +++ b/aiida/sphinxext/tests/workchain_source/index.rst @@ -7,10 +7,17 @@ This is a demo documentation to show off the features of the ``sphinx-aiida`` ex :maxdepth: 2 :caption: Contents: -.. aiida-workchain:: demo_workchain.DemoWorkChain +.. aiida-workchain:: DemoWorkChain + :module: demo_workchain You can add the ``:hidden-ports:`` option to also show inputs / outputs starting with ``_``: -.. aiida-workchain:: demo_workchain.DemoWorkChain +.. aiida-workchain:: DemoWorkChain + :module: demo_workchain :hidden-ports: + +The command is also hooked into ``sphinx.ext.autodoc``, so you can also use that. + +.. automodule:: demo_workchain + :members: diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index 9548590fde..189bb42ecf 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -6,14 +6,28 @@ from docutils.core import publish_doctree from docutils.parsers.rst import Directive, directives from sphinx import addnodes +from sphinx.ext.autodoc import ClassDocumenter + import aiida from plum.util import load_class from plum.port import InputPort, OutputPort def setup_aiida_workchain(app): - app.add_directive('aiida-workchain', AiidaWorkchainDirective) + app.add_directive_to_domain('py', 'aiida-workchain', AiidaWorkchainDirective) + app.add_autodocumenter(WorkChainDocumenter) + +class WorkChainDocumenter(ClassDocumenter): + directivetype = 'aiida-workchain' + objtype = 'workchain' + priority = 20 + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + import aiida + aiida.try_load_dbenv() + from aiida.work.workchain import WorkChain + return issubclass(member, WorkChain) class AiidaWorkchainDirective(Directive): """ @@ -21,8 +35,9 @@ class AiidaWorkchainDirective(Directive): """ required_arguments = 1 HIDDEN_PORTS_FLAG = 'hidden-ports' - option_spec = {HIDDEN_PORTS_FLAG: directives.flag} - has_content = False + optional_arguments = 2 + option_spec = {'module': directives.unchanged, HIDDEN_PORTS_FLAG: directives.flag} + has_content = True def run(self): aiida.try_load_dbenv() @@ -32,8 +47,9 @@ def run(self): def load_workchain(self): """Loads the workchain and sets up additional attributes.""" # pylint: disable=attribute-defined-outside-init - self.workchain_name = self.arguments[0] - self.module_name, self.class_name = self.workchain_name.rsplit('.', 1) + self.class_name = self.arguments[0].split('(')[0] + self.module_name = self.options['module'] + self.workchain_name = self.module_name + '.' + self.class_name self.workchain = load_class(self.workchain_name) self.workchain_spec = self.workchain.spec() From c6fc1a64f121823ae9290a35d7dbcce952bf8960 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 16 Oct 2017 19:07:49 +0200 Subject: [PATCH 11/16] Add updated developer's documentation for aiida.sphinxext --- .../developer_guide/aiida_sphinxext.rst | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/source/developer_guide/aiida_sphinxext.rst diff --git a/docs/source/developer_guide/aiida_sphinxext.rst b/docs/source/developer_guide/aiida_sphinxext.rst new file mode 100644 index 0000000000..91ee55cea3 --- /dev/null +++ b/docs/source/developer_guide/aiida_sphinxext.rst @@ -0,0 +1,21 @@ +.. _aiida-sphinxext: + +AiiDA Sphinx extension +++++++++++++++++++++++ + +AiiDA defines a Sphinx extension to simplify documenting some of its features. To use this extension, you need to add ``aiida.sphinxext`` to the ``extensions`` list in your Sphinx ``conf.py`` file. + +WorkChain directive +------------------- + +The following directive can be used to auto-document AiiDA workchains: + +:: + + .. aiida-workchain:: MyWorkChain + :module: my_plugin + :hidden-ports: + +The argument ``MyWorkChain`` is the name of the workchain, and ``:module:`` is the module from which it can be imported. If the ``:hidden-ports:`` option is given, inputs and outputs starting with ``_`` will also be documented. + +The ``aiida-workchain`` directive is also hooked into ``sphinx.ext.autodoc``, so if you use the corresponding directives (``automodule``, ``autoclass``), it will automatically use the ``aiida-workchain`` command for ``WorkChain`` classes. From d4cfca42621edb77414777ba4749c086f23929d4 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Mon, 16 Oct 2017 19:10:19 +0200 Subject: [PATCH 12/16] Add aiida_sphinxext to developer's guide index --- docs/source/developer_guide/index.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/source/developer_guide/index.rst b/docs/source/developer_guide/index.rst index a94ba1107a..d808ce438b 100644 --- a/docs/source/developer_guide/index.rst +++ b/docs/source/developer_guide/index.rst @@ -5,6 +5,7 @@ Developer guide .. toctree:: :maxdepth: 1 +<<<<<<< HEAD developers internals plugins/index @@ -19,3 +20,24 @@ Developer guide control/index caching ../restapi/index +======= + developers + internals + plugins/index + devel_tutorial/code_plugin_int_sum + devel_tutorial/code_plugin_float_sum + devel_tutorial/code_plugin_qe + devel_tutorial/parser_warnings_policy + devel_tutorial/extend_restapi + parsertests + workflows + dev_workflow + data_cmdline + tcod_exporter + git_cheatsheet + sphinx_cheatsheet + aiida_sphinxext + ../verdi/properties + database_schema + control/index +>>>>>>> a6b54448... Add aiida_sphinxext to developer's guide index From 0fbe2ac2cb732d669bbe2ab2305d6b75970c6be9 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 20 Feb 2018 10:23:36 +0100 Subject: [PATCH 13/16] Fix import issues --- .../tests/reference_results/workchain.xml | 48 ++----------------- aiida/sphinxext/tests/test_workchain.py | 2 +- .../sphinxext/tests/workchain_source/conf.py | 6 +-- .../tests/workchain_source/demo_workchain.py | 41 +--------------- aiida/sphinxext/workchain.py | 10 ++-- docs/source/developer_guide/index.rst | 23 +-------- 6 files changed, 16 insertions(+), 114 deletions(-) diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml index b361a090a5..31f7314b82 100644 --- a/aiida/sphinxext/tests/reference_results/workchain.xml +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -13,8 +13,8 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:sub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. - Outputs:x, Float, requiredy, Int, requiredz, Bool, required + Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument. + Outputs:x, Float, required -- Output of the demoworkchain. You can add the :hidden-ports: option to also show inputs / outputs starting with _: @@ -24,50 +24,10 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:_description, basestring, optional_label, basestring, optional_store_provenance, bool, optionalsub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. - Outputs:x, Float, requiredy, Int, requiredz, Bool, required + Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument. + Outputs:x, Float, required -- Output of the demoworkchain. The command is also hooked into sphinx.ext.autodoc, so you can also use that. - - - This module defines an example workchain for the aiida-workchain documentation directive. - - workchaindemo_workchain.DemoWorkChain - - - A demo workchain to show how the workchain auto-documentation works. - - Inputs:sub, Namespacesub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y.x, Float, required -- First input argument. - Outputs:x, Float, requiredy, Int, requiredz, Bool, required - - - - workchaindemo_workchain.NestedSubWorkChain - - - A nested workchain, to show how second-level port namespaces are handled. - - Inputs:z, Bool, required -- A third input variable, that is nested two levels deep. - Outputs:z, Bool, required - - - - - class demo_workchain.NormalClass - - This is here to check that we didn't break the regular 'autoclass. - - - - workchaindemo_workchain.SubWorkChain - - - A sub-workchain, to show how port namespaces are handled. - - Inputs:sub, Namespacez, Bool, required -- A third input variable, that is nested two levels deep.y, Int, optional -- The second, nested input called y. - Outputs:y, Int, requiredz, Bool, required - - diff --git a/aiida/sphinxext/tests/test_workchain.py b/aiida/sphinxext/tests/test_workchain.py index dab5302323..694c252a12 100644 --- a/aiida/sphinxext/tests/test_workchain.py +++ b/aiida/sphinxext/tests/test_workchain.py @@ -10,4 +10,4 @@ def test_workchain_build(build_sphinx, xml_equal, reference_result): out_dir = build_sphinx(WORKCHAIN) index_file = join(out_dir, 'index.xml') - xml_equal(index_file, reference_result('workchain.xml')) + xml_equal(index_file, reference_result('workchain.xml')) diff --git a/aiida/sphinxext/tests/workchain_source/conf.py b/aiida/sphinxext/tests/workchain_source/conf.py index f229fd13b7..ac494693b7 100644 --- a/aiida/sphinxext/tests/workchain_source/conf.py +++ b/aiida/sphinxext/tests/workchain_source/conf.py @@ -29,7 +29,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.mathjax', 'sphinx_aiida'] +extensions = ['sphinx.ext.mathjax', 'aiida.sphinxext'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -45,8 +45,8 @@ # General information about the project. project = u'sphinx-aiida-demo' -copyright = u'2017, Dominik Gresch' -author = u'Dominik Gresch' +copyright = u'2018, The AiiDA team' +author = u'The AiiDA team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/aiida/sphinxext/tests/workchain_source/demo_workchain.py b/aiida/sphinxext/tests/workchain_source/demo_workchain.py index d372a91a40..323871714b 100644 --- a/aiida/sphinxext/tests/workchain_source/demo_workchain.py +++ b/aiida/sphinxext/tests/workchain_source/demo_workchain.py @@ -14,46 +14,7 @@ def define(cls, spec): super(DemoWorkChain, cls).define(spec) spec.input('x', valid_type=Float, help='First input argument.') - spec.output('x', valid_type=Float) - spec.expose_inputs(SubWorkChain, namespace='sub') - spec.expose_outputs(SubWorkChain) - - -class SubWorkChain(WorkChain): # pylint: disable=abstract-method - """ - A sub-workchain, to show how port namespaces are handled. - """ - - @classmethod - def define(cls, spec): - super(SubWorkChain, cls).define(spec) - - spec.input( - 'y', - valid_type=Int, - help="The second, nested input called ``y``.", - required=False - ) - spec.output('y', valid_type=Int) - spec.expose_inputs(NestedSubWorkChain, namespace='sub') - spec.expose_outputs(NestedSubWorkChain) - - -class NestedSubWorkChain(WorkChain): # pylint: disable=abstract-method - """ - A nested workchain, to show how second-level port namespaces are handled. - """ - - @classmethod - def define(cls, spec): - super(NestedSubWorkChain, cls).define(spec) - - spec.input( - 'z', - valid_type=Bool, - help="A third input variable, that is nested two levels deep." - ) - spec.output('z', valid_type=Bool) + spec.output('x', valid_type=Float, help='Output of the demoworkchain.') class NormalClass(object): """This is here to check that we didn't break the regular 'autoclass.""" diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index 189bb42ecf..e522e7d0d7 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -9,8 +9,7 @@ from sphinx.ext.autodoc import ClassDocumenter import aiida -from plum.util import load_class -from plum.port import InputPort, OutputPort +from plumpy.ports import OutputPort def setup_aiida_workchain(app): @@ -47,10 +46,13 @@ def run(self): def load_workchain(self): """Loads the workchain and sets up additional attributes.""" # pylint: disable=attribute-defined-outside-init + aiida.try_load_dbenv() + from aiida.work import CLASS_LOADER + self.class_name = self.arguments[0].split('(')[0] self.module_name = self.options['module'] self.workchain_name = self.module_name + '.' + self.class_name - self.workchain = load_class(self.workchain_name) + self.workchain = CLASS_LOADER.load_class(self.workchain_name) self.workchain_spec = self.workchain.spec() def build_node_tree(self): @@ -104,7 +106,7 @@ def build_portnamespace_doctree(self, portnamespace): """ Builds the doctree for a port namespace. """ - from aiida.work.process import PortNamespace + from aiida.work.ports import InputPort, PortNamespace result = nodes.bullet_list(bullet='*') for name, port in sorted(portnamespace.items()): diff --git a/docs/source/developer_guide/index.rst b/docs/source/developer_guide/index.rst index d808ce438b..ed11c581b4 100644 --- a/docs/source/developer_guide/index.rst +++ b/docs/source/developer_guide/index.rst @@ -5,7 +5,6 @@ Developer guide .. toctree:: :maxdepth: 1 -<<<<<<< HEAD developers internals plugins/index @@ -15,29 +14,9 @@ Developer guide tcod_exporter git_cheatsheet sphinx_cheatsheet + aiida_sphinxext ../verdi/properties database_schema control/index caching ../restapi/index -======= - developers - internals - plugins/index - devel_tutorial/code_plugin_int_sum - devel_tutorial/code_plugin_float_sum - devel_tutorial/code_plugin_qe - devel_tutorial/parser_warnings_policy - devel_tutorial/extend_restapi - parsertests - workflows - dev_workflow - data_cmdline - tcod_exporter - git_cheatsheet - sphinx_cheatsheet - aiida_sphinxext - ../verdi/properties - database_schema - control/index ->>>>>>> a6b54448... Add aiida_sphinxext to developer's guide index From acb5961c8d78baf0048b7583ef2ceb61cddcf787 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 20 Feb 2018 10:32:06 +0100 Subject: [PATCH 14/16] Add Makefile for creating HTML output --- aiida/sphinxext/tests/.gitignore | 1 + aiida/sphinxext/tests/Makefile | 36 +++++++++++++++++++ .../tests/reference_results/workchain.xml | 28 ++++++++++++--- .../sphinxext/tests/workchain_source/conf.py | 2 +- .../tests/workchain_source/demo_workchain.py | 3 +- 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 aiida/sphinxext/tests/.gitignore create mode 100644 aiida/sphinxext/tests/Makefile diff --git a/aiida/sphinxext/tests/.gitignore b/aiida/sphinxext/tests/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/aiida/sphinxext/tests/.gitignore @@ -0,0 +1 @@ +build diff --git a/aiida/sphinxext/tests/Makefile b/aiida/sphinxext/tests/Makefile new file mode 100644 index 0000000000..d044c9266a --- /dev/null +++ b/aiida/sphinxext/tests/Makefile @@ -0,0 +1,36 @@ +# Makefile for Sphinx documentation +# + +SPHINXBUILD = sphinx-build +BUILDDIR = build +HTMLBUILDDIR = build/html +SOURCEDIR = workchain_source + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR) + +.PHONY: all help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +all: html + +clean: + rm -r $(BUILDDIR) + rm -r $(HTMLBUILDDIR) + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(HTMLBUILDDIR) + @echo + @echo "Build finished. The HTML pages are in $(HTMLBUILDDIR)." + + +view: + xdg-open $(HTMLBUILDDIR)/index.html diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml index 31f7314b82..d6e5b5da2b 100644 --- a/aiida/sphinxext/tests/reference_results/workchain.xml +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -13,8 +13,8 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument. - Outputs:x, Float, required -- Output of the demoworkchain. + Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Outputs:z, Bool, required -- Output of the demoworkchain. You can add the :hidden-ports: option to also show inputs / outputs starting with _: @@ -24,10 +24,30 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument. - Outputs:x, Float, required -- Output of the demoworkchain. + Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Outputs:z, Bool, required -- Output of the demoworkchain. The command is also hooked into sphinx.ext.autodoc, so you can also use that. + + + This module defines an example workchain for the aiida-workchain documentation directive. + + workchaindemo_workchain.DemoWorkChain + + + A demo workchain to show how the workchain auto-documentation works. + + Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Outputs:z, Bool, required -- Output of the demoworkchain. + + + + + class demo_workchain.NormalClass + + This is here to check that we didn't break the regular 'autoclass. + + diff --git a/aiida/sphinxext/tests/workchain_source/conf.py b/aiida/sphinxext/tests/workchain_source/conf.py index ac494693b7..1d4f99a7c9 100644 --- a/aiida/sphinxext/tests/workchain_source/conf.py +++ b/aiida/sphinxext/tests/workchain_source/conf.py @@ -29,7 +29,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.mathjax', 'aiida.sphinxext'] +extensions = ['sphinx.ext.mathjax', 'sphinx.ext.autodoc', 'aiida.sphinxext'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/aiida/sphinxext/tests/workchain_source/demo_workchain.py b/aiida/sphinxext/tests/workchain_source/demo_workchain.py index 323871714b..f798690c06 100644 --- a/aiida/sphinxext/tests/workchain_source/demo_workchain.py +++ b/aiida/sphinxext/tests/workchain_source/demo_workchain.py @@ -14,7 +14,8 @@ def define(cls, spec): super(DemoWorkChain, cls).define(spec) spec.input('x', valid_type=Float, help='First input argument.') - spec.output('x', valid_type=Float, help='Output of the demoworkchain.') + spec.input('y.z', valid_type=Int, help='Input in a separate namespace.') + spec.output('z', valid_type=Bool, help='Output of the demoworkchain.') class NormalClass(object): """This is here to check that we didn't break the regular 'autoclass.""" From 997e1637493889b950e17d0b27a9c18d4aaebebf Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 20 Feb 2018 10:49:09 +0100 Subject: [PATCH 15/16] Add 'hide-unstored-inputs' flag, and move non-db inputs to a separate paragraph --- .../tests/reference_results/workchain.xml | 10 +++--- .../tests/workchain_source/index.rst | 4 +-- aiida/sphinxext/workchain.py | 33 +++++++++++-------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/aiida/sphinxext/tests/reference_results/workchain.xml b/aiida/sphinxext/tests/reference_results/workchain.xml index d6e5b5da2b..37b4d18f6a 100644 --- a/aiida/sphinxext/tests/reference_results/workchain.xml +++ b/aiida/sphinxext/tests/reference_results/workchain.xml @@ -13,18 +13,19 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Inputs:x, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Inputs not stored in the database:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optional Outputs:z, Bool, required -- Output of the demoworkchain. - You can add the :hidden-ports: option to also show inputs / outputs starting with _: + You can use the :hide-unstored-inputs: option to not show the inputs which are not stored in the DB: workchaindemo_workchain.DemoWorkChain A demo workchain to show how the workchain auto-documentation works. - Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Inputs:x, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. Outputs:z, Bool, required -- Output of the demoworkchain. @@ -38,7 +39,8 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optionalx, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Inputs:x, Float, required -- First input argument.y, Namespacez, Int, required -- Input in a separate namespace. + Inputs not stored in the database:description, basestring, optionallabel, basestring, optionalstore_provenance, bool, optional Outputs:z, Bool, required -- Output of the demoworkchain. diff --git a/aiida/sphinxext/tests/workchain_source/index.rst b/aiida/sphinxext/tests/workchain_source/index.rst index 65524ca33f..437ebdae2e 100644 --- a/aiida/sphinxext/tests/workchain_source/index.rst +++ b/aiida/sphinxext/tests/workchain_source/index.rst @@ -11,11 +11,11 @@ This is a demo documentation to show off the features of the ``sphinx-aiida`` ex :module: demo_workchain -You can add the ``:hidden-ports:`` option to also show inputs / outputs starting with ``_``: +You can use the ``:hide-unstored-inputs:`` option to not show the inputs which are not stored in the DB: .. aiida-workchain:: DemoWorkChain :module: demo_workchain - :hidden-ports: + :hide-unstored-inputs: The command is also hooked into ``sphinx.ext.autodoc``, so you can also use that. diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index e522e7d0d7..e7e5e4a6e2 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -33,9 +33,9 @@ class AiidaWorkchainDirective(Directive): Directive to auto-document AiiDA workchains. """ required_arguments = 1 - HIDDEN_PORTS_FLAG = 'hidden-ports' + HIDE_UNSTORED_INPUTS_FLAG = 'hide-unstored-inputs' optional_arguments = 2 - option_spec = {'module': directives.unchanged, HIDDEN_PORTS_FLAG: directives.flag} + option_spec = {'module': directives.unchanged, HIDE_UNSTORED_INPUTS_FLAG: directives.flag} has_content = True def run(self): @@ -80,21 +80,27 @@ def build_content(self): content += nodes.paragraph(text=self.workchain.__doc__) content += self.build_doctree( - title='Inputs:', port_namespace=self.workchain_spec.inputs + title='Inputs:', port_namespace=self.workchain_spec.inputs, + filter_fct=lambda port: not _is_non_db(port) ) + if self.HIDE_UNSTORED_INPUTS_FLAG not in self.options: + content += self.build_doctree( + title='Inputs not stored in the database:', port_namespace=self.workchain_spec.inputs, + filter_fct=_is_non_db + ) content += self.build_doctree( title='Outputs:', port_namespace=self.workchain_spec.outputs ) return content - def build_doctree(self, title, port_namespace): + def build_doctree(self, title, port_namespace, filter_fct=lambda x: True): """ Returns a doctree for a given port namespace, including a title. """ paragraph = nodes.paragraph() paragraph += nodes.strong(text=title) - namespace_doctree = self.build_portnamespace_doctree(port_namespace) + namespace_doctree = self.build_portnamespace_doctree(port_namespace, filter_fct=filter_fct) if len(namespace_doctree) > 0: paragraph += namespace_doctree else: @@ -102,21 +108,17 @@ def build_doctree(self, title, port_namespace): return paragraph - def build_portnamespace_doctree(self, portnamespace): + def build_portnamespace_doctree(self, port_namespace, filter_fct): """ Builds the doctree for a port namespace. """ from aiida.work.ports import InputPort, PortNamespace result = nodes.bullet_list(bullet='*') - for name, port in sorted(portnamespace.items()): - if name.startswith( - '_' - ) and self.HIDDEN_PORTS_FLAG not in self.options: - continue - if name == 'dynamic': - continue + for name, port in sorted(port_namespace.items()): item = nodes.list_item() + if not filter_fct(port): + continue if isinstance(port, (InputPort, OutputPort)): item += self.build_port_paragraph(name, port) elif isinstance(port, PortNamespace): @@ -126,7 +128,7 @@ def build_portnamespace_doctree(self, portnamespace): item += addnodes.literal_strong(text=name) item += nodes.Text(', ') item += nodes.emphasis(text='Namespace') - item += self.build_portnamespace_doctree(port) + item += self.build_portnamespace_doctree(port, filter_fct=filter_fct) else: raise NotImplementedError result += item @@ -160,3 +162,6 @@ def format_valid_types(valid_type): return '(' + ', '.join(v.__name__ for v in valid_type) + ')' except (AttributeError, TypeError): return str(valid_type) + +def _is_non_db(port): + return getattr(port, 'non_db', False) From e40172ae1fd1f39a38a64be8fc9adf040a9f54e2 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 20 Feb 2018 10:59:27 +0100 Subject: [PATCH 16/16] Rename flag to 'hide-nondb-inputs', update developer's guide --- aiida/sphinxext/tests/workchain_source/index.rst | 2 +- aiida/sphinxext/workchain.py | 2 +- docs/source/developer_guide/aiida_sphinxext.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aiida/sphinxext/tests/workchain_source/index.rst b/aiida/sphinxext/tests/workchain_source/index.rst index 437ebdae2e..48353c2637 100644 --- a/aiida/sphinxext/tests/workchain_source/index.rst +++ b/aiida/sphinxext/tests/workchain_source/index.rst @@ -15,7 +15,7 @@ You can use the ``:hide-unstored-inputs:`` option to not show the inputs which a .. aiida-workchain:: DemoWorkChain :module: demo_workchain - :hide-unstored-inputs: + :hide-nondb-inputs: The command is also hooked into ``sphinx.ext.autodoc``, so you can also use that. diff --git a/aiida/sphinxext/workchain.py b/aiida/sphinxext/workchain.py index e7e5e4a6e2..16e99920e3 100644 --- a/aiida/sphinxext/workchain.py +++ b/aiida/sphinxext/workchain.py @@ -33,7 +33,7 @@ class AiidaWorkchainDirective(Directive): Directive to auto-document AiiDA workchains. """ required_arguments = 1 - HIDE_UNSTORED_INPUTS_FLAG = 'hide-unstored-inputs' + HIDE_UNSTORED_INPUTS_FLAG = 'hide-nondb-inputs' optional_arguments = 2 option_spec = {'module': directives.unchanged, HIDE_UNSTORED_INPUTS_FLAG: directives.flag} has_content = True diff --git a/docs/source/developer_guide/aiida_sphinxext.rst b/docs/source/developer_guide/aiida_sphinxext.rst index 91ee55cea3..1fd0fd768d 100644 --- a/docs/source/developer_guide/aiida_sphinxext.rst +++ b/docs/source/developer_guide/aiida_sphinxext.rst @@ -14,8 +14,8 @@ The following directive can be used to auto-document AiiDA workchains: .. aiida-workchain:: MyWorkChain :module: my_plugin - :hidden-ports: + :hide-nondb-inputs: -The argument ``MyWorkChain`` is the name of the workchain, and ``:module:`` is the module from which it can be imported. If the ``:hidden-ports:`` option is given, inputs and outputs starting with ``_`` will also be documented. +The argument ``MyWorkChain`` is the name of the workchain, and ``:module:`` is the module from which it can be imported. By default, the inputs which are not stored in the database are also shown. This can be disabled by passing the ``:hide-unstored-inputs:`` flag. The ``aiida-workchain`` directive is also hooked into ``sphinx.ext.autodoc``, so if you use the corresponding directives (``automodule``, ``autoclass``), it will automatically use the ``aiida-workchain`` command for ``WorkChain`` classes.