Skip to content

Commit ba3e668

Browse files
authored
Merge pull request #48 from dwhswenson/new_plugins
New plugin infrastructure
2 parents 728db4d + 4771349 commit ba3e668

16 files changed

Lines changed: 276 additions & 167 deletions

‎docs/plugins.rst‎

Lines changed: 30 additions & 22 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
@@ -95,4 +104,3 @@ namespace.
95104
.. _native namespace packages:
96105
https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages
97106

98-

‎example_plugins/one_pot_tps.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import click
2+
from paths_cli import OPSCommandPlugin
23
from paths_cli.parameters import (INPUT_FILE, OUTPUT_FILE, ENGINE, STATES,
34
N_STEPS_MC, INIT_SNAP)
45
from paths_cli.commands.visit_all import visit_all_main
@@ -44,6 +45,15 @@ def one_pot_tps_main(output_storage, states, engine, engine_hot,
4445
equil_multiplier, equil_extra)
4546
return pathsampling_main(output_storage, scheme, equil_set, nsteps)
4647

48+
# these lines enable this plugin to support OPS CLI < 0.3
4749
CLI = one_pot_tps
4850
SECTION = "Workflow"
4951
REQUIRES_OPS = (1, 2)
52+
53+
# these lines enable this plugin to support OPS CLI >= 0.3
54+
PLUGIN = OPSCommandPlugin(
55+
command=one_pot_tps,
56+
section="Workflow",
57+
requires_ops=(1, 2),
58+
requires_cli=(0, 3)
59+
)

‎example_plugins/tps.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import click
2+
from paths_cli import OPSCommandPlugin
23
from paths_cli.parameters import (
34
INPUT_FILE, OUTPUT_FILE, ENGINE, STATES, INIT_CONDS, N_STEPS_MC
45
)
@@ -40,8 +41,17 @@ def tps_main(engine, states, init_traj, output_storage, n_steps):
4041
return simulation.sample_set, simulation
4142

4243

44+
# these lines enable this plugin to support OPS CLI < 0.3
4345
CLI = tps
4446
SECTION = "Simulation"
4547

48+
# these lines enable this plugin to support OPS CLI >= 0.3
49+
PLUGIN = OPSCommandPlugin(
50+
command=tps,
51+
section="Simulation"
52+
)
53+
54+
55+
# this allows you to use this as a script, independently of the OPS CLI
4656
if __name__ == "__main__":
4757
tps()

‎paths_cli/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from .plugin_management import (
2+
OPSCommandPlugin,
3+
)
14
from .cli import OpenPathSamplingCLI
25
from . import commands
36
from . import version

‎paths_cli/cli.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
# import click_completion
1414
# click_completion.init()
1515

16-
from .plugin_management import FilePluginLoader, NamespacePluginLoader
16+
from .plugin_management import (FilePluginLoader, NamespacePluginLoader,
17+
OPSCommandPlugin)
1718

1819
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
1920

@@ -31,10 +32,10 @@ def app_dir_plugins(posix):
3132
).resolve() / 'cli-plugins')
3233

3334
self.plugin_loaders = [
34-
FilePluginLoader(commands),
35-
FilePluginLoader(app_dir_plugins(posix=False)),
36-
FilePluginLoader(app_dir_plugins(posix=True)),
37-
NamespacePluginLoader('paths_cli_plugins')
35+
FilePluginLoader(commands, OPSCommandPlugin),
36+
FilePluginLoader(app_dir_plugins(posix=False), OPSCommandPlugin),
37+
FilePluginLoader(app_dir_plugins(posix=True), OPSCommandPlugin),
38+
NamespacePluginLoader('paths_cli_plugins', OPSCommandPlugin)
3839
]
3940

4041
plugins = sum([loader() for loader in self.plugin_loaders], [])

‎paths_cli/commands/append.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import click
2+
from paths_cli import OPSCommandPlugin
23
from paths_cli.parameters import (
34
INPUT_FILE, APPEND_FILE, MULTI_CV, MULTI_ENGINE, MULTI_VOLUME,
45
MULTI_NETWORK, MULTI_SCHEME, MULTI_TAG
@@ -55,6 +56,9 @@ def append(input_file, append_file, engine, cv, volume, network, scheme,
5556
storage.close()
5657

5758

58-
CLI = append
59-
SECTION = "Miscellaneous"
60-
REQUIRES_OPS = (1, 0)
59+
PLUGIN = OPSCommandPlugin(
60+
command=append,
61+
section="Miscellaneous",
62+
requires_ops=(1, 0),
63+
requires_cli=(0, 3)
64+
)

‎paths_cli/commands/contents.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
from paths_cli.parameters import INPUT_FILE
3+
from paths_cli import OPSCommandPlugin
34

45
UNNAMED_SECTIONS = ['steps', 'movechanges', 'samplesets', 'trajectories',
56
'snapshots']
@@ -112,6 +113,10 @@ def get_section_string_nameable(section, store, get_named):
112113
+ _item_or_items(n_unnamed))
113114
return out_str
114115

115-
CLI = contents
116-
SECTION = "Miscellaneous"
117-
REQUIRES_OPS = (1, 0)
116+
117+
PLUGIN = OPSCommandPlugin(
118+
command=contents,
119+
section="Miscellaneous",
120+
requires_ops=(1, 0),
121+
requires_cli=(0, 3)
122+
)

‎paths_cli/commands/equilibrate.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22
# import openpathsampling as paths
33

4+
from paths_cli import OPSCommandPlugin
45
from paths_cli.parameters import (
56
INPUT_FILE, OUTPUT_FILE, INIT_CONDS, SCHEME
67
)
@@ -58,6 +59,10 @@ def equilibrate_main(output_storage, scheme, init_conds, multiplier,
5859
return simulation.sample_set, simulation
5960

6061

61-
CLI = equilibrate
62-
SECTION = "Simulation"
63-
REQUIRES_OPS = (1, 2)
62+
PLUGIN = OPSCommandPlugin(
63+
command=equilibrate,
64+
section="Simulation",
65+
requires_ops=(1, 2),
66+
requires_cli=(0, 3)
67+
)
68+

‎paths_cli/commands/md.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22

33
import paths_cli.utils
4+
from paths_cli import OPSCommandPlugin
45
from paths_cli.parameters import (INPUT_FILE, OUTPUT_FILE, ENGINE,
56
MULTI_ENSEMBLE, INIT_SNAP)
67

@@ -197,7 +198,10 @@ def md_main(output_storage, engine, ensembles, nsteps, initial_frame):
197198
'final_conditions')
198199
return trajectory, None
199200

200-
CLI = md
201-
SECTION = "Simulation"
202-
REQUIRES_OPS = (1, 0)
203201

202+
PLUGIN = OPSCommandPlugin(
203+
command=md,
204+
section="Simulation",
205+
requires_ops=(1, 0),
206+
requires_cli=(0, 3)
207+
)

‎paths_cli/commands/pathsampling.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22
# import openpathsampling as paths
33

4+
from paths_cli import OPSCommandPlugin
45
from paths_cli.parameters import (
56
INPUT_FILE, OUTPUT_FILE, INIT_CONDS, SCHEME, N_STEPS_MC
67
)
@@ -37,6 +38,9 @@ def pathsampling_main(output_storage, scheme, init_conds, n_steps):
3738
return simulation.sample_set, simulation
3839

3940

40-
CLI = pathsampling
41-
SECTION = "Simulation"
42-
REQUIRES_OPS = (1, 0)
41+
PLUGIN = OPSCommandPlugin(
42+
command=pathsampling,
43+
section="Simulation",
44+
requires_ops=(1, 0),
45+
requires_cli=(0, 3)
46+
)

0 commit comments

Comments
 (0)