Skip to content

Commit f9a377e

Browse files
committed
Move to_yaml from AttrDict to io
1 parent 3ba48a2 commit f9a377e

File tree

8 files changed

+182
-153
lines changed

8 files changed

+182
-153
lines changed

src/calliope/attrdict.py

-38
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
"""AttrDict implementation (a subclass of regular dict) used for managing model configuration."""
44

55
import copy
6-
import io
76
import logging
87

9-
import numpy as np
10-
import ruamel.yaml as ruamel_yaml
118
from typing_extensions import Self
129

1310
logger = logging.getLogger(__name__)
@@ -187,41 +184,6 @@ def as_dict_flat(self):
187184
d[k] = self.get_key(k)
188185
return d
189186

190-
def to_yaml(self, path=None):
191-
"""Conversion to YAML.
192-
193-
Saves the AttrDict to the ``path`` as a YAML file or returns a YAML string
194-
if ``path`` is None.
195-
"""
196-
result = self.copy()
197-
yaml_ = ruamel_yaml.YAML()
198-
yaml_.indent = 2
199-
yaml_.block_seq_indent = 0
200-
yaml_.sort_base_mapping_type_on_output = False
201-
202-
# Numpy objects should be converted to regular Python objects,
203-
# so that they are properly displayed in the resulting YAML output
204-
for k in result.keys_nested():
205-
# Convert numpy numbers to regular python ones
206-
v = result.get_key(k)
207-
if isinstance(v, np.floating):
208-
result.set_key(k, float(v))
209-
elif isinstance(v, np.integer):
210-
result.set_key(k, int(v))
211-
# Lists are turned into seqs so that they are formatted nicely
212-
elif isinstance(v, list):
213-
result.set_key(k, yaml_.seq(v))
214-
215-
result = result.as_dict()
216-
217-
if path is not None:
218-
with open(path, "w") as f:
219-
yaml_.dump(result, f)
220-
else:
221-
stream = io.StringIO()
222-
yaml_.dump(result, stream)
223-
return stream.getvalue()
224-
225187
def keys_nested(self, subkeys_as="list"):
226188
"""Returns all keys in the AttrDict, including nested keys.
227189

src/calliope/backend/backend_model.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from calliope.attrdict import AttrDict
3131
from calliope.backend import helper_functions, parsing
3232
from calliope.exceptions import warn as model_warn
33-
from calliope.io import load_config
33+
from calliope.io import load_config, to_yaml
3434
from calliope.preprocess.model_math import ORDERED_COMPONENTS_T, CalliopeMath
3535
from calliope.util.schema import (
3636
MODEL_SCHEMA,
@@ -449,7 +449,7 @@ def _add_to_dataset(
449449
yaml_snippet_attrs[attr] = val
450450

451451
if yaml_snippet_attrs:
452-
add_attrs["yaml_snippet"] = AttrDict(yaml_snippet_attrs).to_yaml()
452+
add_attrs["yaml_snippet"] = to_yaml(yaml_snippet_attrs)
453453

454454
da.attrs = {
455455
"obj_type": obj_type,

src/calliope/cli.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import click
1515

16-
from calliope import AttrDict, Model, examples, read_netcdf
16+
from calliope import Model, examples, io, read_netcdf
1717
from calliope._version import __version__
1818
from calliope.exceptions import BackendError
1919
from calliope.util.generate_runs import generate
@@ -400,4 +400,4 @@ def generate_scenarios(
400400
}
401401
}
402402

403-
AttrDict(scenarios).to_yaml(out_file)
403+
io.to_yaml(scenarios, path=out_file)

src/calliope/io.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import os
88
from copy import deepcopy
9+
from io import StringIO
910
from pathlib import Path
1011

1112
# We import netCDF4 before xarray to mitigate a numpy warning:
@@ -23,6 +24,8 @@
2324
logger = logging.getLogger(__name__)
2425

2526
CONFIG_DIR = importlib.resources.files("calliope") / "config"
27+
YAML_INDENT = 2
28+
YAML_BLOCK_SEQUENCE_INDENT = 0
2629

2730

2831
def read_netcdf(path):
@@ -75,7 +78,7 @@ def _serialise(attrs: dict) -> None:
7578
dict_attrs = [k for k, v in attrs.items() if isinstance(v, dict)]
7679
attrs["serialised_dicts"] = dict_attrs
7780
for attr in dict_attrs:
78-
attrs[attr] = AttrDict(attrs[attr]).to_yaml()
81+
attrs[attr] = to_yaml(attrs[attr])
7982

8083
# Convert boolean attrs to ints
8184
bool_attrs = [k for k, v in attrs.items() if isinstance(v, bool)]
@@ -287,3 +290,42 @@ def _resolve_yaml_imports(
287290
loaded_dict.del_key("import")
288291

289292
return loaded_dict
293+
294+
295+
def to_yaml(data: AttrDict | dict, path: None | str | Path = None) -> str:
296+
"""Conversion to YAML.
297+
298+
Saves the AttrDict to the ``path`` as a YAML file or returns a YAML string
299+
if ``path`` is None.
300+
"""
301+
result = AttrDict(data).copy()
302+
# Prepare YAML parsing settings
303+
yaml_ = ruamel_yaml.YAML()
304+
yaml_.indent = YAML_INDENT
305+
yaml_.block_seq_indent = YAML_BLOCK_SEQUENCE_INDENT
306+
yaml_.sort_base_mapping_type_on_output = (
307+
False # FIXME: identify if this is necessary
308+
)
309+
310+
# Numpy objects should be converted to regular Python objects,
311+
# so that they are properly displayed in the resulting YAML output
312+
for k in result.keys_nested():
313+
# Convert numpy numbers to regular python ones
314+
v = result.get_key(k)
315+
if isinstance(v, np.floating):
316+
result.set_key(k, float(v))
317+
elif isinstance(v, np.integer):
318+
result.set_key(k, int(v))
319+
# Lists are turned into seqs so that they are formatted nicely
320+
elif isinstance(v, list):
321+
result.set_key(k, yaml_.seq(v))
322+
323+
result = result.as_dict()
324+
325+
if path is not None:
326+
with open(path, "w") as f:
327+
yaml_.dump(result, f)
328+
329+
stream = StringIO()
330+
yaml_.dump(result, stream)
331+
return stream.getvalue()

src/calliope/preprocess/model_definition.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from calliope import exceptions
99
from calliope.attrdict import AttrDict
10-
from calliope.io import read_rich_yaml
10+
from calliope.io import read_rich_yaml, to_yaml
1111
from calliope.util.tools import listify
1212

1313
LOGGER = logging.getLogger(__name__)
@@ -129,7 +129,7 @@ def _combine_overrides(overrides: AttrDict, scenario_overrides: list):
129129
combined_override_dict = AttrDict()
130130
for override in scenario_overrides:
131131
try:
132-
yaml_string = overrides[override].to_yaml()
132+
yaml_string = to_yaml(overrides[override])
133133
override_with_imports = read_rich_yaml(yaml_string)
134134
except KeyError:
135135
raise exceptions.ModelError(f"Override `{override}` is not defined.")

tests/test_core_attrdict.py

-4
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,3 @@ def test_del_key_single(self, attr_dict):
206206
def test_del_key_nested(self, attr_dict):
207207
attr_dict.del_key("c.z.I")
208208
assert "I" not in attr_dict.c.z
209-
210-
def test_to_yaml_string(self, attr_dict):
211-
result = attr_dict.to_yaml()
212-
assert "a: 1" in result

0 commit comments

Comments
 (0)