Rose Macro API
Rose macros manipulate or check configurations, often based on their metadata. There are four types of macros:
- Checkers (validators)
Check a configuration, perhaps using metadata.
- Changers (transformers)
Change a configuration e.g. adding/removing options.
- Upgraders
Special transformer macros for upgrading and downgrading configurations (covered in the Upgrade Macro API).
- Reporters
output information about a configuration.
They can be run within rose config-edit or via rose macro.
Note
This section covers validator, transformer and reporter macros. For upgrader macros see Upgrade Macro API.
There are built-in Rose macros that handle standard behaviour such as trigger changing and type checking.
Macros use a Python API, and should be written in Python, unless you are doing something very fancy. In the absence of a Python house style, it’s usual to follow the standard Python style guidance (PEP8, PEP257).
Tip
You should avoid writing validator macros if the checking can be expressed via metadata.
Location
A module containing macros should be stored under a directory
lib/python/macros/
in the metadata for a configuration. This directory
should be a Python package.
When developing macros for Rose internals, macros should be placed in the
metomi.rose.macros
package in the Rose Python library. They should be
referenced by the lib/python/rose/macros/__init__.py
classes and a call to
them can be added in the lib/python/rose/config_editor/main.py
module if
they need to be run implicitly by the config editor.
Writing Macros
Note
For basic usage see the macro tutorial.
Validator, transformer and reporter macros are Python classes which subclass
from metomi.rose.macro.MacroBase
(API).
These macros implement their behaviours by providing a validate
,
transform
or report
method. A macro can contain any combination of
these methods so, for example, a macro might be both a validator and a
transformer.
These methods should accept two metomi.rose.config.ConfigNode
instances as arguments - one is the configuration, and one is the metadata
configuration that provides information about the configuration items.
Tip
See also Python.
Validator / Reporter Macros
A validator macro should look like:
import metomi.rose.macro
class SomeValidator(rose.macro.MacroBase):
"""This does some kind of check."""
def validate(self, config, meta_config=None):
# Some check on config appends to self.reports using self.add_report
return self.reports
The returned list should be a list of metomi.rose.macro.MacroReport
objects containing the section, option, value, and warning strings (info) for
each setting that is in error. These are initialised behind the scenes by
calling the inherited method metomi.rose.macro.MacroBase.add_report()
.
This has the form:
def add_report(self, section=None, option=None, value=None, info=None,
is_warning=False):
This means that you should call it with the relevant section first, then the
relevant option, then the relevant value, then the relevant error message,
and optionally a warning flag that we’ll discuss later. If the setting is a
section, the option should be None
and the value None. For example,
def validate(self, config, meta_config=None):
editor_value = config.get(["env", "MY_FAVOURITE_STREAM_EDITOR"]).value
if editor_value != "sed":
self.add_report("env", # Section
"MY_FAVOURITE_STREAM_EDITOR", # Option
editor_value, # Value
"Should be 'sed'!") # Message
return self.reports
Validator macros have the option to give warnings, which do not count as
formal errors in the Rose config editor GUI. These should be used when
something may be wrong, such as warning when using an
advanced-developer-only option. They are invoked by passing a 5th argument
to metomi.rose.macro.MacroBase.add_report()
, is_warning
, like so:
self.add_report("env",
"MY_FAVOURITE_STREAM_EDITOR",
editor_value,
"Could be 'sed'",
is_warning=True)
Transformer Macros
A transformer macro should look like:
import metomi.rose.macro
class SomeTransformer(rose.macro.MacroBase):
"""This does some kind of change to the config."""
def transform(self, config, meta_config=None):
# Some operation on config which calls self.add_report for each change.
return config, self.reports
The returned list should be a list of 4-tuples containing the section,
option, value, and information strings for each setting that was changed
(e.g. added, removed, value changed). If the setting is a section, the
option should be None
and the value None. If an option was removed,
the value should be the old value - otherwise it should be the new one
(added/changed). For example,
def transform(self, config, meta_config=None):
"""Add some more snow control."""
if config.get(["namelist:snowflakes"]) is None:
config.set(["namelist:snowflakes"])
self.add_report(list_of_changes,
"namelist:snowflakes", None, None,
"Updated snow handling in time for Christmas")
config.set(["namelist:snowflakes", "l_unique"], ".true.")
self.add_report("namelist:snowflakes", "l_unique", ".true.",
"So far, anyway.")
return config, self.reports
The current working directory within a macro is always the configuration’s
directory. This makes it easy to access non-rose-app.conf
files (e.g.
in the file/
subdirectory).
There are also reporter macros which can be used where you need to output some information about a configuration. A reporter macro takes the same form as validator and transform macros but does not require a return value.
def report(self, config, meta_config=None):
""" Write some information about the configuration to a report file.
Note: report methods do not have a return value.
"""
with open('report/file', 'r') as report_file:
report_file.write(str(config.get(["namelist:snowflakes"])))
Optional Arguments
Macros also support the use of keyword arguments, giving you the ability to
have the user specify some input or override to your macro. For example a
transformer macro could be written as follows to allow the user to input
some_value
:
def transform(self, config, meta_config=None, some_value=None):
"""Some transformer macro"""
return
On running your macro the user will be prompted to supply values for these arguments or accept the default values.
There is a special keyword argument called optional_config_name
which is
set to the name of the optional configuration the macro is being run on, or
None
if only the default configuration is being used.
Note
Python keyword arguments require default values (=None
in this
example). You should add error handling for the input accordingly.
Python API
Module to list or run available custom macros for a configuration.
It also stores macro base classes and macro library functions.
- class metomi.rose.macro.MacroBase[source]
Base class for macros for validating or transforming configurations.
- Synopsis:
>>> import metomi.rose.macro ... >>> class SomeValidator(metomi.rose.macro.MacroBase): ... ... '''Important: Add a docstring for your macro like this. ... ... A macro class should implement one of the following methods: ... ... ''' ... ... def validate(self, config, meta_config=None): ... # Some check on config appends to self.reports using ... # self.add_report. ... return self.reports ... ... def transform(self, config, meta_config=None): ... # Some operation on config which calls self.add_report ... # for each change. ... return config, self.reports ... ... def report(self, config, meta_config=None): ... # Perform some analysis of the config but return nothing. ... pass
Keyword arguments can be used,
rose macro
will prompt the user to provide values for these arguments when the macro is run.>>> def validate(self, config, meta_config=None, answer=None): ... # User will be prompted to provide a value for "answer". ... return self.reports
There is a special keyword argument called
optional_config_name
which is set to the name of the optional configuration a macro is running on, orNone
if only the default configuration is being used.>>> def report(self, config, meta_config=None, ... optional_config_name=None): ... if optional_config_name: ... print('Macro is being run using the "%s" ' ... 'optional configuration' % optional_config_name)
- add_report(*args, **kwargs)[source]
Add a metomi.rose.macro.MacroReport.
See
metomi.rose.macro.MacroReport
for details of arguments.Examples
>>> # An example validator macro which adds a report to the setting >>> # env=MY_FAVOURITE_STREAM_EDITOR. >>> class My_Macro(MacroBase): ... def validate(self, config, meta_config=None): ... editor_value = config.get( ... ['env', 'MY_FAVOURITE_STREAM_EDITOR']).value ... if editor_value != 'sed': ... self.add_report( ... 'env', # Section ... 'MY_FAVOURITE_STREAM_EDITOR', # Option ... editor_value, # Value ... 'Should be "sed"!') # Message ... return self.reports
- get_metadata_for_config_id(setting_id, meta_config)[source]
Return a dict of metadata properties and values for a setting id.
- Parameters:
setting_id (str) – The name of the setting to extract metadata for.
meta_config (metomi.rose.config.ConfigNode) – Config node containing the metadata to extract from.
- Returns:
A dictionary containing metadata options.
- Return type:
Example
>>> # Create a rose app. >>> with open('rose-app.conf', 'w+') as app_config: ... _ = app_config.write(''' ... [foo] ... bar=2 ... ''') >>> os.mkdir('meta') >>> with open('meta/rose-meta.conf', 'w+') as meta_config: ... _ = meta_config.write(''' ... [foo=bar] ... values = 1,2,3 ... ''') ... >>> # Load config. >>> app_conf, config_map, meta_config = load_conf_from_file( ... '.', 'rose-app.conf') ... >>> # Extract metadata for foo=bar. >>> get_metadata_for_config_id('foo=bar', meta_config) {'values': '1,2,3', 'id': 'foo=bar'}
- get_resource_path(filename='')[source]
Load the resource according to the path of the calling macro.
The returned path will be based on the macro location under
lib/python
in the metadata directory.If the calling macro is
lib/python/macro/levels.py
, and the filename isrules.json
, the returned path will beetc/macro/levels/rules.json
.
- pretty_format_config(config)[source]
Standardise the keys and values of a config node.
- Parameters:
config (metomi.rose.config.ConfigNode) – The config node to convert.
- standard_format_config(config)[source]
Standardise any degenerate representations e.g. namelist repeats.
- Parameters:
config (metomi.rose.config.ConfigNode) – The config node to convert.
- class metomi.rose.macro.MacroReport(section=None, option=None, value=None, info=None, is_warning=False)[source]
Class to hold information about a macro issue.
- Parameters:
section (str) – The name of the section to attach this report to.
option (str) – The name of the option (within the section) to attach this report to.
value (object) – The value of the configuration associated with this report.
info (str) – Text information describing the nature of the report.
is_warning (bool) – If True then this report will be logged as a warning.
Example
>>> report = MacroReport('env', 'WORLD', 'Earth', ... 'World changed to Earth', True)