Rules, Warnings, and Rulesets
Overview
- Rules
Methods defined on Pywr component types (
PywrNode
,PywrParameter
, etc.) which perform arbitrary tests on the data defining that type.If the tests are passed an instance of the type can be created, but if the tests are failed no instance is created, the containing network is considered invalid and an
error
state is added to the parsing report describing the test that has failed and the input data which caused the failure.The methods which define rules are applied automatically to the input data by the parser during the parsing process.
- Warnings
Methods defined on Pywr component types which operate similarly to
rules
but result in awarning
which does not prevent the creation of an instance of the component type. As such, warnings are used to identify component definitions which are in some way suboptimal or may be deprecated in future releases, but which by themselves do not preclude the creation of a functioning component of the specified type.A network which may contain warnings but no errors is considered valid.
- Rulesets
Collections of
rules
andwarnings
which are customisable and which may applied as a group to a Pywr network.Custom rulesets allow different collections of
rules
andwarnings
to be applied to a network depending on the context in which the parser operates.
Defining Rules, Warnings, and Rulesets
Rules and Warnings
Both rules
and warnings
are defined as methods on the class representing the
component which they validate.
The method is defined using a structured method name, and an optional match()
decorator which specifies the sub-types of the component to which the rule or warning should
be applied.
For example, the following rule ensures that the name attribute of its data has a specified minimum length:
from pywrparser.types.node import PywrNode
class MyPywrNode(PywrNode):
...
def rule_node_name_min_len(self):
assert len(self.name) > 6, "Node name too short"
If a rule returns indeed any value, including None
, it is deemed to have passed.
If a rule raises any form of exception, it has failed and an error instance is
logged by the parser.
Methods which implement rules are identified automatically by the parser and applied
to the relevant type when an instance of that type is created. Any method which begins
with the special prefix rule_
is interpreted as a validation rule. Any method
beginning with the prefix warn_
indicates a warning. Warnings have identical
behaviour to rules in terms of returning a value or raising an exception, but in the
event of a warning method failing, the parser logs a warning instance and this does
not preclude creation of the corresponding component instance or the network as a whole.
The match()
decorator
The match()
decorator is defined in the pywrparser.utils
module and can
be applied to a rule or warning to limit the applicability of that rule or warning
to instances of Pywr components which contain a type
key equal to that specified
as an argument to the match()
decorator.
For example, the following excerpt illustrates a rule which will be applied to only nodes which have a type equal to storage.
from pywrparser.types.node import PywrNode
from pywrparser.utils import match
class MyPywrNode(PywrNode):
...
@match("storage")
def rule_node_name_min_len(self):
assert len(self.name) > 6, "Node name too short"
If no match()
decorator is present on a rule, that rule is applied to every
instance of the class on which it is defined.
The match()
decorator also supports the fuzzy
boolean keyword argument. If fuzzy
is set equal to True
, the decorated rule or warning becomes applicable to any instance
having a type
key whose value contains the decorator’s string argument. This comparison
is case-insensitive.
For example, to define a rule which is applied only to components whose type contains the
string “interpolated”, the following arguments to the match()
decorator may be used…
...
@match("interpolated", fuzzy=True)
def rule_kind_required(self):
assert "kind" in self.data, "An interpolation kind must be provided"
Rulesets
A ruleset
is simply a module in the pywrparser.rulesets
package which defines
custom component classes derived from the base types:
PywrMetadata
PywrTimestepper
PywrNode
PywrParameter
PywrRecorder
PywrScenario
The base types are each defined in a dedicated module in pywrparser.types
and
should always be imported directly from the module in which they are declared rather
than the types
package when being subclassed. E.g.
from pywrparser.types.node import PywrNode
Any classes which derive from the base types in a ruleset module are automatically identified by the parser and become part of the ruleset defined by that module.
In addition, a ruleset module must contain certain module-level variables which are used by pywrparser to describe the ruleset.
- __key__
A short string which acts as the identifier for the ruleset, e.g. “strict”
- __ruleset_name__
A string holding the full name for the ruleset, e.g. “Strict Ruleset”
- __version__
A string containing the version number of the ruleset, e.g. “0.1.0”
- __description__
A string of arbitrary length which describes the ruleset, e.g. “A ruleset which enforces strict naming conventions”
Finally, each ruleset module must be imported into the ruleset
package’s
__init__.py
. For example, to import the ruleset defined in the
/pywrparser/rulesets/strict.py
file, add…
from . import strict
…to /pywrparser/rulesets/__init__.py
.
The procedure to create a custom ruleset may therefore be summarised as:
Create a ruleset module which imports the required base types and defines the required module-level variables as described above.
Define subclasses of each required base type, defining appropriate
rule_
andwarn_
methods inside these.Add an import of the new ruleset module in the
ruleset
package’s__init__.py
The ruleset will then be visible in the output from the command line utility’s
--list-rulesets
option…
$ pywrparser --list-rulesets
Available Rulesets:
[1] Name: 'Strict Ruleset' Version: 0.1.0 Key: strict
A ruleset which enforces strict naming conventions
…and may be applied to input using the --use-ruleset <key>
option…
$ pywrparser --use-ruleset strict ...