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 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 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 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 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 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 rose.macro.MacroBase.add_report() via self.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 self.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 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 rose.macro.MacroBase[source]

Base class for macros for validating or transforming configurations.

Synopsis:
>>> import rose.macro
...
>>> class SomeValidator(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, or None 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 rose.macro.MacroReport.

See 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 (rose.config.ConfigNode) – Config node containing the metadata to extract from.
Returns:

A dictionary containing metadata options.

Return type:

dict

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 is rules.json, the returned path will be etc/macro/levels/rules.json.

Parameters:filename (str) – The filename of the resource to request the path to.
Returns:The path to the requested resource.
Return type:str
pretty_format_config(config)[source]

Standardise the keys and values of a config node.

Parameters:config (rose.config.ConfigNode) – The config node to convert.
standard_format_config(config)[source]

Standardise any degenerate representations e.g. namelist repeats.

Parameters:config (rose.config.ConfigNode) – The config node to convert.
class 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 (obj) – 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)