Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.. _api:

API
===

.. currentmodule:: paths_cli

CLI and Plugins
---------------

.. autosummary::
:toctree: generated

OpenPathSamplingCLI
plugin_management.CLIPluginLoader
plugin_management.FilePluginLoader
plugin_management.NamespacePluginLoader


Parameter Decorators
--------------------

These are the functions used to create the reusable parameter decorators.
Note that you will probably never need to use these; instead, use the
existing parameter decorators.

.. autosummary::
:toctree: generated

param_core.Option
param_core.Argument
param_core.AbstractLoader
param_core.StorageLoader
param_core.OPSStorageLoadNames
param_core.OPSStorageLoadSingle

Search strategies
-----------------

These are the various strategies for finding objects in a storage, in
particular if we have to guess because the user didn't provide an explicit
choice or didn't tag.

.. autosummary::
:toctree: generated

param_core.Getter
param_core.GetByName
param_core.GetByNumber
param_core.GetPredefinedName
param_core.GetOnly
param_core.GetOnlyNamed
param_core.GetOnlySnapshot


Commands
--------

.. autosummary::
:toctree: generated
:recursive:

commands.visit_all
commands.equilibrate
commands.pathsampling
commands.append
commands.contents
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx_click.ext',
]

autosummary_generate = True

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

Expand Down
1 change: 0 additions & 1 deletion docs/for_core/README.md

This file was deleted.

174 changes: 0 additions & 174 deletions docs/for_core/cli.rst

This file was deleted.

1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ wrappers around well-tested OPS code.
parameters
workflows
full_cli
api/index

64 changes: 49 additions & 15 deletions docs/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@ Plugin Infrastructure
=====================

All subcommands to the OpenPathSampling CLI use a plugin infrastructure.
They simply need to be Python modules, following a few rules, that are
placed into the user's ``~/.openpathsampling/cli-plugins/`` directory.
There are two possible ways to distribute plugins (file plugins and
namespace plugins), but a given plugin script could be distributed either
way.

Technically, the code searches two directories for plugins: first,
``$DIRECTORY/commands``, where ``$DIRECTORY`` is the directory where the
main OPS CLI script has been installed (i.e., the directory that corresponds
to the Python package ``paths_cli``). This is where the default commands are
kept. Then it searches the user directory. Duplicate commands will lead to
errors when running the CLI, as you can't register the same name twice.
Writing a plugin script
-----------------------

Other than being in the right place, the script must do the following:
An OPS plugin is simply a Python module that follows a few rules.

* It must be possible to ``exec`` it in an empty namespace (mainly, this
can mean no relative imports).
* It must define a variable ``CLI`` that is the main CLI function is
assigned to.
* It must define a variable ``SECTION`` to determine where to show it in
Expand All @@ -27,11 +22,15 @@ Other than being in the right place, the script must do the following:
``openpathsampling --help``, but might still be usable. If your command
doesn't show in the help, carefully check your spelling of the ``SECTION``
variable.
* The main CLI function must be decorated as a ``click.command``.
* (If distributed as a file plugin) It must be possible to ``exec`` it in an
empty namespace (mainly, this can mean no relative imports).

As a suggestion, I (DWHS) tend to structure my plugins as follows:

.. code:: python

@click.command("plugin", short_help="brief description")
@PARAMETER.clicked(required)
def plugin(parameter):
plugin_main(PARAMETER.get(parameter))
Expand All @@ -40,25 +39,60 @@ As a suggestion, I (DWHS) tend to structure my plugins as follows:
import openpathsampling as paths
# do the real stuff with OPS
...
return final_status, simulation

CLI = plugin
SECTION = "MySection"

The basic idea is that there's a ``plugin_main`` function that is based on
pure OPS, using only inputs that OPS can immediately understand (no need to
go to storage, etc). This is easy to develop/test with OPS. Then there's a
wrapper function whose sole purpose is to convert the command line
process the command line). This is easy to develop/test with OPS. Then
there's a wrapper function whose sole purpose is to convert the command line
parameters to something OPS can understand (using the ``get`` method). This
wrapper is the ``CLI`` variable. Give it an allowed ``SECTION``, and the
plugin is ready!

The result is that plugins are astonishingly easy to develop, once you have
the scientific code implemented in a library.
the scientific code implemented in a library. This structure also makes it
very easy to test the plugins: a mock replaces the ``plugin_main`` in
``plugin`` to check that the integration works, and then a simple smoke test
for the ``plugin_main`` is sufficient, since the core code should already be
well-tested.

Note that we recommend that the import of OpenPathSampling only be done
inside the ``plugin_main`` function. Although this is contrary to normal
Python practice, we do this because tools like tab-autocomplete require
that you run the program each time. The import of OPS is rather slow, so we
delay it until it is needed, keeping the CLI interface fast and responsive.

.. TODO : look into having the plugin auto-installed using setuptools
Finally, the ``plugin_main`` function returns some sort of final status and
the simulation object that was created (or ``None`` if there wasn't one).
This makes it very easy to chain multiple main functions to make a workflow.


Distributing file plugins
-------------------------

Once you have a plugin module written, the easiest way to install it is to
put it in your ``~/.openpathsampling/cli_plugins/`` directory. This is the
file-based plugin distribution mechanism -- you send the file to someone,
and they put in that directory.

This is great for plugins shared in a single team, or for creating
reproducible workflows that aren't intended for wide distribution.


Distributing namespace plugins
------------------------------

If the plugin is part of a larger Python package, or if it is important to
track version numbers or to be able to change which plugins are installed
in particular Python environments, the namespace distribution mechanism is a
better choice. We use `native namespace packages`_, which is a standard way
of making plugins in Python. Plugins should be in the ``paths_cli.plugins``
namespace.

.. _native namespace packages:
https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages


1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ numpy

packaging
sphinx-click
sphinx >= 3.1 # will this work?