|
6 | 6 | import logging
|
7 | 7 | import os
|
8 | 8 | from copy import deepcopy
|
| 9 | +from io import StringIO |
9 | 10 | from pathlib import Path
|
10 | 11 |
|
11 | 12 | # We import netCDF4 before xarray to mitigate a numpy warning:
|
|
23 | 24 | logger = logging.getLogger(__name__)
|
24 | 25 |
|
25 | 26 | CONFIG_DIR = importlib.resources.files("calliope") / "config"
|
| 27 | +YAML_INDENT = 2 |
| 28 | +YAML_BLOCK_SEQUENCE_INDENT = 0 |
26 | 29 |
|
27 | 30 |
|
28 | 31 | def read_netcdf(path):
|
@@ -75,7 +78,7 @@ def _serialise(attrs: dict) -> None:
|
75 | 78 | dict_attrs = [k for k, v in attrs.items() if isinstance(v, dict)]
|
76 | 79 | attrs["serialised_dicts"] = dict_attrs
|
77 | 80 | for attr in dict_attrs:
|
78 |
| - attrs[attr] = AttrDict(attrs[attr]).to_yaml() |
| 81 | + attrs[attr] = to_yaml(attrs[attr]) |
79 | 82 |
|
80 | 83 | # Convert boolean attrs to ints
|
81 | 84 | bool_attrs = [k for k, v in attrs.items() if isinstance(v, bool)]
|
@@ -287,3 +290,42 @@ def _resolve_yaml_imports(
|
287 | 290 | loaded_dict.del_key("import")
|
288 | 291 |
|
289 | 292 | 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() |
0 commit comments