Skip to content

Commit 4b3ab8e

Browse files
committed
update docs on plugins; fix tests
1 parent acc1304 commit 4b3ab8e

4 files changed

Lines changed: 39 additions & 24 deletions

File tree

‎docs/plugins.rst‎

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,33 @@ There are two possible ways to distribute plugins (file plugins and
88
namespace plugins), but a given plugin script could be distributed either
99
way.
1010

11-
Writing a plugin script
12-
-----------------------
13-
14-
An OPS plugin is simply a Python module that follows a few rules.
15-
16-
* It must define a variable ``CLI`` that is the main CLI function is
17-
assigned to.
18-
* It must define a variable ``SECTION`` to determine where to show it in
19-
help (what kind of command it is). Valid values are ``"Simulation"``,
20-
``"Analysis"``, ``"Miscellaneous"``, or ``"Workflow"``. If ``SECTION`` is
21-
defined but doesn't have one of these values, it won't show in
22-
``openpathsampling --help``, but might still be usable. If your command
23-
doesn't show in the help, carefully check your spelling of the ``SECTION``
24-
variable.
25-
* The main CLI function must be decorated as a ``click.command``.
26-
* (If distributed as a file plugin) It must be possible to ``exec`` it in an
27-
empty namespace (mainly, this can mean no relative imports).
11+
Writing a command plugin
12+
------------------------
13+
14+
To write an OPS command plugin, you simply need to create an instance of
15+
:class:`paths_cli.OPSCommandPlugin` and to install the module in a location
16+
where the CLI knows to look for it. The input parameters to
17+
``OPSCommandPlugin`` are:
18+
19+
* ``command``: This is the main CLI function for the subcommand. It must be
20+
decorated as a ``click.Command``.
21+
* ``section``: This is a string to determine where to show it in help (what
22+
kind of command it is). Valid values are ``"Simulation"``,
23+
``"Analysis"``, ``"Miscellaneous"``, or ``"Workflow"``. If ``section``
24+
doesn't have one of these values, it won't show in ``openpathsampling
25+
--help``, but might still be usable. If your command doesn't show in the
26+
help, carefully check your spelling of the ``section`` variable.
27+
* ``requires_ops`` (optional, default ``(1, 0)``): Minimum allowed version
28+
of OpenPathSampling. Note that this is currently informational only, and
29+
has no effect on functionality.
30+
* ``requires_cli`` (optional, default ``(0, 3)``): Minimum allowed version
31+
of the OpenPathSampling CLI. Note that this is currently informational
32+
only, and has no effect on functionality.
33+
34+
35+
If you distribute your plugin as a file-based plugin, be aware that it must
36+
be possible to ``exec`` it in an empty namespace (mainly, this can mean no
37+
relative imports).
2838

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

@@ -41,16 +51,15 @@ As a suggestion, I (DWHS) tend to structure my plugins as follows:
4151
...
4252
return final_status, simulation
4353
44-
CLI = plugin
45-
SECTION = "MySection"
54+
PLUGIN = OPSCommandPlugin(command=plugin, section="MySection")
4655
4756
The basic idea is that there's a ``plugin_main`` function that is based on
4857
pure OPS, using only inputs that OPS can immediately understand (no need to
4958
process the command line). This is easy to develop/test with OPS. Then
5059
there's a wrapper function whose sole purpose is to convert the command line
5160
parameters to something OPS can understand (using the ``get`` method). This
52-
wrapper is the ``CLI`` variable. Give it an allowed ``SECTION``, and the
53-
plugin is ready!
61+
wrapper is the ``command`` in you ``OPSCommandPlugin``. Also provide an
62+
allowed ``section``, and the plugin is ready!
5463

5564
The result is that plugins are astonishingly easy to develop, once you have
5665
the scientific code implemented in a library. This structure also makes it

‎paths_cli/param_core.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class OPSStorageLoadSingle(AbstractLoader):
269269
parameter. Each should be a callable taking a storage and the string
270270
input from the CLI, and should return the desired object or None if
271271
it cannot be found.
272-
none_strategies : List[Callable[:class:`openpathsampling.Storage, Any]]
272+
none_strategies : List[Callable[:class:`openpathsampling.Storage`, Any]]
273273
The strategies to be used when the CLI does not provide a value for
274274
this parameter. Each should be a callable taking a storage, and
275275
returning the desired object or None if it cannot be found.

‎paths_cli/plugin_management.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ class OPSPlugin(Plugin):
4747
Really just to rename ``requires_lib`` to ``requires_ops``.
4848
"""
4949
def __init__(self, requires_ops, requires_cli):
50-
super(OPSPlugin, self).__init__(requires_ops, requires_cli)
50+
super().__init__(requires_ops, requires_cli)
5151

5252
@property
5353
def requires_ops(self):
5454
return self.requires_lib
5555

5656

57-
class OPSCommandPlugin(Plugin):
57+
class OPSCommandPlugin(OPSPlugin):
5858
"""Plugin for subcommands to the OPS CLI
5959
6060
Parameters

‎paths_cli/tests/test_plugin_management.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
# need to check that CLI is assigned to correct type
1111
import click
1212

13+
def test_ops_plugin():
14+
plugin = OPSPlugin(requires_ops=(1,2), requires_cli=(0,4))
15+
assert plugin.requires_ops == (1, 2)
16+
assert plugin.requires_cli == (0, 4)
17+
assert plugin.requires_lib == (1, 2)
18+
1319
class PluginLoaderTest(object):
1420
def setup(self):
1521
self.expected_section = {'pathsampling': "Simulation",

0 commit comments

Comments
 (0)