@@ -4,20 +4,15 @@ Plugin Infrastructure
44=====================
55
66All subcommands to the OpenPathSampling CLI use a plugin infrastructure.
7- They simply need to be Python modules, following a few rules, that are
8- placed into the user's ``~/.openpathsampling/cli-plugins/ `` directory.
7+ There are two possible ways to distribute plugins (file plugins and
8+ namespace plugins), but a given plugin script could be distributed either
9+ way.
910
10- Technically, the code searches two directories for plugins: first,
11- ``$DIRECTORY/commands ``, where ``$DIRECTORY `` is the directory where the
12- main OPS CLI script has been installed (i.e., the directory that corresponds
13- to the Python package ``paths_cli ``). This is where the default commands are
14- kept. Then it searches the user directory. Duplicate commands will lead to
15- errors when running the CLI, as you can't register the same name twice.
11+ Writing a plugin script
12+ -----------------------
1613
17- Other than being in the right place, the script must do the following:
14+ An OPS plugin is simply a Python module that follows a few rules.
1815
19- * It must be possible to ``exec `` it in an empty namespace (mainly, this
20- can mean no relative imports).
2116* It must define a variable ``CLI `` that is the main CLI function is
2217 assigned to.
2318* It must define a variable ``SECTION `` to determine where to show it in
@@ -27,11 +22,15 @@ Other than being in the right place, the script must do the following:
2722 ``openpathsampling --help ``, but might still be usable. If your command
2823 doesn't show in the help, carefully check your spelling of the ``SECTION ``
2924 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).
3028
3129As a suggestion, I (DWHS) tend to structure my plugins as follows:
3230
3331.. code :: python
3432
33+ @click.command (" plugin" , short_help = " brief description" )
3534 @PARAMETER.clicked (required)
3635 def plugin (parameter ):
3736 plugin_main(PARAMETER .get(parameter))
@@ -40,25 +39,60 @@ As a suggestion, I (DWHS) tend to structure my plugins as follows:
4039 import openpathsampling as paths
4140 # do the real stuff with OPS
4241 ...
42+ return final_status, simulation
4343
4444 CLI = plugin
4545 SECTION = " MySection"
4646
4747 The basic idea is that there's a ``plugin_main `` function that is based on
4848pure OPS, using only inputs that OPS can immediately understand (no need to
49- go to storage, etc ). This is easy to develop/test with OPS. Then there's a
50- wrapper function whose sole purpose is to convert the command line
49+ process the command line ). This is easy to develop/test with OPS. Then
50+ there's a wrapper function whose sole purpose is to convert the command line
5151parameters to something OPS can understand (using the ``get `` method). This
5252wrapper is the ``CLI `` variable. Give it an allowed ``SECTION ``, and the
5353plugin is ready!
5454
5555The result is that plugins are astonishingly easy to develop, once you have
56- the scientific code implemented in a library.
56+ the scientific code implemented in a library. This structure also makes it
57+ very easy to test the plugins: a mock replaces the ``plugin_main `` in
58+ ``plugin `` to check that the integration works, and then a simple smoke test
59+ for the ``plugin_main `` is sufficient, since the core code should already be
60+ well-tested.
5761
5862Note that we recommend that the import of OpenPathSampling only be done
5963inside the ``plugin_main `` function. Although this is contrary to normal
6064Python practice, we do this because tools like tab-autocomplete require
6165that you run the program each time. The import of OPS is rather slow, so we
6266delay it until it is needed, keeping the CLI interface fast and responsive.
6367
64- .. TODO : look into having the plugin auto-installed using setuptools
68+ Finally, the ``plugin_main `` function returns some sort of final status and
69+ the simulation object that was created (or ``None `` if there wasn't one).
70+ This makes it very easy to chain multiple main functions to make a workflow.
71+
72+
73+ Distributing file plugins
74+ -------------------------
75+
76+ Once you have a plugin module written, the easiest way to install it is to
77+ put it in your ``~/.openpathsampling/cli_plugins/ `` directory. This is the
78+ file-based plugin distribution mechanism -- you send the file to someone,
79+ and they put in that directory.
80+
81+ This is great for plugins shared in a single team, or for creating
82+ reproducible workflows that aren't intended for wide distribution.
83+
84+
85+ Distributing namespace plugins
86+ ------------------------------
87+
88+ If the plugin is part of a larger Python package, or if it is important to
89+ track version numbers or to be able to change which plugins are installed
90+ in particular Python environments, the namespace distribution mechanism is a
91+ better choice. We use `native namespace packages `_, which is a standard way
92+ of making plugins in Python. Plugins should be in the ``paths_cli.plugins ``
93+ namespace.
94+
95+ .. _native namespace packages :
96+ https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages
97+
98+
0 commit comments