Overview for plugin development¶
The main way to customize a NOMAD installation is through the use of plugins. A NOMAD plugin is a Python package that an administrator can install into a NOMAD distribution to add custom features. This page helps you develop and publish a NOMAD plugin. For a high-level overview of the plugin mechanism, see the NOMAD plugin system -page.
A single Python plugin package can contain multiple plugin entry points. These entry points represent different types of customizations including:
See the FAIRmat-NFDI GitHub organization page for a list of plugins developed by FAIRmat. You can also see the list of activated plugins and plugin entry points at the bottom of the Information page (about/information) of any NOMAD installation, for example check out the central NOMAD installation.
Plugin anatomy¶
We provide a template repository which you can use to create the initial plugin repository layout for you. The repository layout as generated by the template looks like this:
├── nomad-example
│ ├── src
| │ ├── nomad_example
| | │ ├── apps
| | │ │ ├── __init__.py
| | │ ├── normalizers
| | │ │ ├── mynormalizer.py
| | │ │ ├── __init__.py
| | │ ├── schema_packages
| | │ │ ├── mypackage.py
| | │ │ ├── __init__.py
| | │ ├── parsers
| | │ │ ├── myparser.py
| | │ │ ├── __init__.py
│ ├── docs
│ ├── tests
│ ├── pyproject.toml
│ ├── LICENSE.txt
│ ├── README.md
We suggest using the following convention for naming the repository name and the plugin package:
- repository name:
nomad-<plugin name> - package name:
nomad_<plugin name>
Controlling loading of plugin entry points¶
By default, plugin entry points are automatically loaded, and as an administrator you only need to install the Python package. You can, however, control which entry points to load by explicitly including/excluding them in your nomad.yaml. For example, if a plugin has the following pyproject.toml:
You could disable the parser entry point in your nomad.yaml with:
Accessing plugin configuration at runtime¶
When developing plugins, you can access both the plugin's configuration (metadata and parameters) and the actual plugin implementation (the resource). NOMAD uses a lazy-loading pattern where these are kept separate: get_plugin_entry_point() returns the entry point configuration, while EntryPoint.load() returns the actual plugin resource.
Configuring plugin parameters¶
Administrators can override plugin-specific parameters using the plugins.entry_points.options section in nomad.yaml. For example:
plugins:
entry_points:
options:
nomad_example.parsers:myparser:
parameter: "custom_value"
another_setting: 42
For comprehensive documentation on plugin configuration options, see the Configuration reference.
Retrieving configuration with get_plugin_entry_point()¶
To access your plugin's configuration from within your plugin code, use the get_plugin_entry_point() function from nomad.config. This returns your plugin's entry point configuration with all administrator overrides applied, giving you access to custom parameters and other relevant configurations, while ensuring all settings are up-to-date.
from nomad.config import config
# Get your plugin's entry point configuration
entry_point = config.get_plugin_entry_point('nomad_example.parsers:myparser')
# Access configuration parameters
print(f'Parameter value: {entry_point.parameter}') # Output: Parameter value: custom_value
print(f'Another setting: {entry_point.another_setting}') # Output: Another setting: 42
The entry point name passed to get_plugin_entry_point() must match the name defined in your plugin's pyproject.toml under [project.entry-points.'nomad.plugin'] (the same name used when configuring the plugin in nomad.yaml).
Additional resources¶
- Plugin entry point models reference - Documentation for all entry point types
- NOMAD plugin system explanation - Conceptual overview of the plugin architecture
- Plugin type-specific guides for examples:
Plugin development guidelines¶
Linting and formatting¶
While developing NOMAD plugins, we highly recommend using a Python linter, such as Ruff, to analyze and enforce coding standards in your plugin projects. This also ensures smoother integration and collaboration. If you have used our template repository, you will automatically have ruff defined as a development dependency with suitable defaults set in pyproject.toml together with a GitHub actions that runs the linting and formatting checks on each push to the Git repository.
Testing¶
For testing, you should use pytest, and a folder structure that mimics the package layout with test modules named after the tested module. For example, if you are developing a parser in myparser.py, the test folder structure should look like this:
├── nomad-example-plugin
│ ├── src
| │ ├── nomad_example
| | │ ├── parsers
| | │ │ ├── myparser.py
| | │ │ ├── __init__.py
│ ├── tests
| │ ├── parsers
| | │ ├── test_myparser.py
| | │ ├── conftest.py
| │ ├── conftest.py
Any shared test utilities (such as pytest fixtures) should live in conftest.py modules placed at the appropriate level in the folder hierarchy, i.e. utilities dealing with parsers would live in tests/parsers/conftest.py, while root level utilities would live in tests/conftest.py. If you have used our template repository, you will automatically have an initial test folder structure, pytest defined as a development dependency in pyproject.toml and a GitHub action that runs the test suite on each push to the Git repository.
In the pytest framework, test cases are created by defining functions with the test_ prefix, which perform assertions. A typical test case could look like this:
def test_parse_file():
parser = MyParser()
archive = EntryArchive()
parser.parse('tests/data/example.out', archive, logging)
sim = archive.data
assert len(sim.model) == 2
assert len(sim.output) == 2
assert archive.workflow2.x_example_magic_value == 42
You can run all the tests in the tests/ directory with:
Documentation¶
As your plugin matures, you should also think about documenting its usage. We recommend using mkdocs to create your documentation as a set of markdown files. If you have used our template repository, you will automatically have an initial documentation folder structure, mkdocs defined as a development dependency in pyproject.toml and a GitHub action that builds the docs to a separate gh-pages branch each push to the Git repository. Note that if you wish to host the documentation using GitHub pages, you need to enable this in the repository settings.
Publishing a plugin¶
Attention
The standard processes for publishing plugins and using plugins from other developers are still being worked out. The "best" practices mentioned in the following are preliminary. We aim to set up a dedicated plugin registry that allows you to publish your plugin and find plugins from others.
GitHub repository¶
The simplest way to publish a plugin is to have it live in a publicly shared Git repository. The package can then be installed with:
Note
If you develop a plugin in the context of FAIRmat or the NOMAD CoE, put your plugin repositories in the corresponding GitHub organization.
PyPI/pip package¶
You may additionally publish the plugin package in PyPI. Learn from the PyPI documentation how to create a package for PyPI. We recommend to use the pyproject.toml-based approach.
The PyPI documentation provides further information about how to publish a package to PyPI. If you have access to the MPCDF GitLab and NOMAD's presence there, you can also
use the nomad-FAIR package registry:
pip install twine
twine upload \
-u <username> -p <password> \
--repository-url https://gitlab.mpcdf.mpg.de/api/v4/projects/2187/packages/pypi \
dist/nomad-example-plugin-*.tar.gz
Installing a plugin¶
See our documentation on How to install plugins into a NOMAD Oasis.