Source code for xbudget.presets
"""Load and save xbudget recipes.
A recipe is an ordinary nested dict, so it can be written by hand in YAML or
built in Python. These helpers move it between the two, and validate it on the
way out — see :doc:`the recipes guide </recipes>` for the schema.
"""
import yaml
from os import path
from .parse import parse_budgets
__all__ = ["load_preset_budget", "load_yaml", "save_yaml"]
[docs]
def load_preset_budget(model="MOM6"):
"""Loads preset xbudget dictionary from yaml file for supported models.
Parameters
----------
model : str (default "MOM6")
Name of any recipe shipped in ``xbudget/recipes/`` (without the
``.yaml`` suffix). Currently: "MOM6", "MOM6_3Donly", "MOM6_drift",
"MOM6_surface", "ECCOV4r4_native".
Please open an Issue if you would like to contribute an xbudget yaml
file for a new model–see /recipes/ for examples.
Returns
-------
Python dictionary
See also
--------
load_yaml, save_yaml, xbudget.parse_budgets
"""
return load_yaml(f"{path.dirname(__file__)}/recipes/{model}.yaml")
[docs]
def load_yaml(filepath):
"""Loads a yaml file as a Python dictionary.
This is a thin reader: it does not check that the result is a valid xbudget
recipe, because the shipped recipes contain placeholder terms that
the parser deliberately warns about and skips, and re-reading a file should
not re-raise those warnings. To validate, call
:func:`xbudget.parse_budgets` on the result (or use :func:`save_yaml`, which
validates before writing).
Parameters
----------
filepath : path to yaml file, as str
Returns
-------
Python dictionary
Raises
------
yaml.YAMLError
If the file is not valid YAML.
See also
--------
save_yaml, xbudget.parse_budgets
"""
with open(filepath, "r") as stream:
return yaml.safe_load(stream)
[docs]
def save_yaml(recipe, filepath):
"""Write a recipe to a yaml file, after checking that it is valid.
Validating here means a malformed recipe cannot reach disk: you get a
:class:`xbudget.BudgetParseError` naming the offending path while you are
authoring it, rather than a confusing failure the next time it is loaded.
Key order is preserved rather than sorted, because it is meaningful: the
operands of a ``sum``/``product`` are reported in recipe order by
:meth:`xbudget.BudgetQuery.get_vars`.
Parameters
----------
recipe : dict
A recipe in xbudget format.
filepath : str
Path to write to.
Raises
------
xbudget.BudgetParseError
If the recipe does not match the schema. Nothing is written.
Examples
--------
>>> recipe = {"heat": {"rhs": {"sum": {"forcing": {"var": "surface_heat_flux"}}}}}
>>> xbudget.save_yaml(recipe, "my_model.yaml")
>>> xbudget.load_yaml("my_model.yaml") == recipe
True
See also
--------
load_yaml, xbudget.parse_budgets
"""
if filepath is None:
raise TypeError("save_yaml() missing required argument: 'filepath'")
parse_budgets(recipe) # raises BudgetParseError before we write
with open(filepath, "w") as stream:
yaml.safe_dump(
recipe, stream, sort_keys=False, default_flow_style=False
)