Writing Plugins#
OpenMDAO was designed to allow you to code up your own components, groups, etc., and to use them within the framework, but what if you want others to be able to discover and use your creations? The OpenMDAO plugin system was created to make that easier.
Before laying out the steps to follow in order to create your plugin, a brief discussion of
entry points is in order. An entry point is simply a string passed into the setup()
function
in the setup.py
file for your python package. The string has the form:
'my_ep_name=my_plugin_module_path:my_module_attribute'
where typically, my_module_attribute
is a class or a function.
The plugin system uses entry points in order to provide local discovery, and in some cases to support adding new functionality to openmdao, e.g., adding new openmdao command line tools.
Every entry point is associated with an entry point group, and the entry point groups that openmdao recognizes are shown in the table below:
Entry Point Group |
Type |
Entry Point Refers To |
---|---|---|
openmdao_component |
Component |
class or factory funct |
openmdao_group |
Group |
class or factory funct |
openmdao_driver |
Driver |
class or factory funct |
openmdao_lin_solver |
LinearSolver |
class or factory funct |
openmdao_nl_solver |
NonlinearSolver |
class or factory funct |
openmdao_surrogate_model |
SurrogateModel |
class or factory funct |
openmdao_case_recorder |
CaseRecorder |
class or factory funct |
openmdao_case_reader |
BaseCaseReader |
funct returning (file_ext, class or factor funct) |
openmdao_command |
command line tool |
funct returning (setup_parser_func, exec_func, help_string) |
‘Typical’ Plugins#
Most OpenMDAO plugins are created simply by registering an entry point that refers to the class definition of the plugin or to some factory function that returns an instance of the plugin. The following entry point types are all handled in this way:
component
group
driver
nl_solver
lin_solver
surrogate_model
case_recorder
For these types of plugins, the entry point does nothing other than allow them to be listed using the openmdao list_installed command.
Here’s an example of how to specify the entry_points arg to the setup call in setup.py
for a component plugin class called MyComponent
in a package called my_plugins_package
in a module called my_comp_plugin.py
:
entry_points={
'openmdao_component': [
'mycompplugin=my_plugins_package.my_comp_plugin:MyComponent'
]
}
Note that the actual entry point name, mycompplugin
in the example above, isn’t used for
anything in the case of a ‘typical’ plugin.
CaseReader Plugins#
The entry point for a case reader should point to a function that returns a tuple of the form (file_extension, class), where file_extension contains the leading dot, for example ‘.sql’, and class could either be the class definition of the plugin or a factory function returning an instance of the plugin. The file extension is used to provide an automatic mapping to the correct case reader based on the file extension of the file being read.
Command Line Tool Plugins#
An entry point for an OpenMDAO command line tool plugin should point to a function that returns a tuple of the form (setup_parser_func, exec_func, help_string). For example:
def _hello_setup():
"""
This command prints a hello message after final setup.
"""
return (_hello_setup_parser, _hello_exec, 'Print hello message after final setup.')
The setup_parser_func is a function taking a single parser argument that adds any arguments
expected by the plugin to the parser object. The parser is an argparse.ArgumentParser object.
For example, the following code sets up a subparser for a openmdao hello
command that adds a file
argument and a --repeat
option:
def _hello_setup_parser(parser):
"""
Set up the openmdao subparser (using argparse) for the 'openmdao hello' command.
Parameters
----------
parser : argparse subparser
The parser we're adding options to.
"""
parser.add_argument('-r', '--repeat', action='store', dest='repeats',
default=1, type=int, help='Number of times to say hello.')
parser.add_argument('file', metavar='file', nargs=1,
help='Script to execute.')
The exec_func is a function that performs whatever action is necessary for the command line
tool plugin to operate. Typically this will involve registering another function that is to
execute at some point during the execution of a script file. For example, the following
function registers a function that prints a hello
message, specifying that it should execute
after the Problem._final_setup
method.
def _hello_exec(options, user_args):
"""
This registers the hook function and executes the user script.
Parameters
----------
options : argparse Namespace
Command line options.
user_args : list of str
Args to be passed to the user script.
"""
script = options.file[0]
def _hello_after_final_setup(prob):
for i in range(options.repeats):
print('*** hello ***')
exit() # If you want to exit after your command, you must explicitly do that here
# register the hook to execute after Problem.final_setup
_register_hook('final_setup', class_name='Problem', post=_hello_after_final_setup)
# load and execute the given script as __main__
_load_and_exec(script, user_args)
The final entry in the tuple returned by the function referred to by the entry point
(in this case _hello_setup)
is a string containing a high level description of the command. This description will be displayed
along with the name of the command when a user runs openmdao -h
.
Here’s an example of how to specify the entry_points arg to the setup call in setup.py
for our command line tool described above if it were inside of a package called my_plugins_package
in a file called hello_cmd.py
:
entry_points={
'openmdao_command': [
'hello=my_plugins_package.hello_cmd:_hello_setup'
]
}
In this case, the name of our entry point, hello
, will be the name of the openmdao command line
tool, so the user will activate the tool by typing openmdao hello
.
Local Discovery#
After a python package containing OpenMDAO plugins has been installed in a user’s python
environment, they will be able to print a list of installed plugins using the
openmdao list_installed command.
For example, if a package called foobar
is installed, we could list all of the plugins
found in that package using the following command:
openmdao list_installed -i foobar
The list_installed
command simply goes through all of the entry points it finds in any of the
openmdao entry point groups described above and displays them.
Global Discovery Using github#
Entry point groups are also used for global discovery of plugins. They can be used (in slightly modified form, with underscores replaced with dashes) as topic strings in a github repository in order to allow a user to perform a global search over all of github to find any openmdao related plugin packages.
Plugin Creation from Scratch#
To create an OpenMDAO plugin from scratch, it may be helpful to use the openmdao scaffold tool. It will automatically generate the directory structure for a python package and will define the entry point of a type that you specify. For example, to create a scaffold for a python package called mypackage that contains a component plugin that’s an ExplicitComponent called MyComp, do the following:
openmdao scaffold --base=ExplicitComponent --class=MyComp --package=mypackage
To instead create a package containing an openmdao command line tool called hello
in
a package called myhello
, do the following:
openmdao scaffold --cmd=hello --package=myhello
Converting Existing Classes to Plugins#
If you already have a package containing components, groups, etc. that work in the OpenMDAO
framework, all you need to do to register them as plugins is to define an entry point in
your setup.py
file for each one.
You can use the openmdao compute_entry_points command
line tool to help you do this. Running the tool with your installed package name will print
out a list of all of the openmdao entry points required to register any openmdao compatible
classes it finds in your package. For example, if your package is called mypackage
, you
can list its entry points using
openmdao compute_entry_points mypackage
The entry points will be printed out in a form that can be pasted as a setup argument into
your setup.py
file.
Plugin Checklist#
To recap, to fully integrate your plugin into the OpenMDAO plugin infrastructure, you must do all of the following:
The plugin will be part of a pip-installable python package.
An entry point will be added to the appropriate entry point group (see above) of the entry_points argument passed to the setup call in the setup.py file for the python package containing the plugin.
If the package resides in a public github repository, the
openmdao
topic will be added to the repository, along with topics for each openmdao entry point group (with underscores converted to dashes, e.g.,openmdao_component
becomesopenmdao-component
) that contains an openmdao entry point found in the package.If the package resides on the Python Package Index (PyPI), the string
openmdao
should be mentioned in the package summary.To support the future ability to query PyPI package keywords, any openmdao entry point groups used by the package should be added to the
keywords
argument to the setup call in the setup.py file for the package.