diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 9b1ecbd8..81a4b562 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -4,6 +4,8 @@ Release Notes Upcoming Version ---------------- +* ``Model.to_netcdf`` now records the writing linopy version in the ``_linopy_version`` dataset attribute. Files written by older versions (without the attribute) continue to read unchanged. + Version 0.8.0 ------------- diff --git a/linopy/io.py b/linopy/io.py index 1c4714c4..220a230a 100644 --- a/linopy/io.py +++ b/linopy/io.py @@ -12,6 +12,7 @@ import time import warnings from collections.abc import Callable, Iterable +from importlib.metadata import version from io import BufferedWriter from pathlib import Path from tempfile import TemporaryDirectory @@ -37,6 +38,8 @@ logger = logging.getLogger(__name__) +NETCDF_VERSION_ATTR = "_linopy_version" + ufunc_kwargs = dict(vectorize=True) concat_kwargs = dict(dim=CONCAT_DIM, coords="minimal") @@ -971,6 +974,7 @@ def with_prefix(ds: xr.Dataset, prefix: str) -> xr.Dataset: scalars = {k: getattr(m, k) for k in m.scalar_attrs} ds = xr.merge(vars + cons + obj + params, combine_attrs="drop_conflicts") ds = ds.assign_attrs(scalars) + ds.attrs[NETCDF_VERSION_ATTR] = version("linopy") if m._relaxed_registry: ds.attrs["_relaxed_registry"] = json.dumps(m._relaxed_registry) if m._piecewise_formulations: diff --git a/test/test_io.py b/test/test_io.py index fba65aab..dd614c5e 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -221,6 +221,20 @@ def test_read_netcdf_with_multiindex_legacy_list_attr( assert_model_equal(m, read_netcdf(fn_legacy)) +def test_read_netcdf_without_version_stamp(model: Model, tmp_path: Path) -> None: + from linopy.io import NETCDF_VERSION_ATTR + + fn = tmp_path / "test.nc" + model.to_netcdf(fn) + + ds = xr.load_dataset(fn).load() + del ds.attrs[NETCDF_VERSION_ATTR] + fn_legacy = tmp_path / "legacy.nc" + ds.to_netcdf(fn_legacy) + + assert_model_equal(model, read_netcdf(fn_legacy)) + + @pytest.mark.skipif("gurobi" not in available_solvers, reason="Gurobipy not installed") def test_to_file_lp(model: Model, tmp_path: Path) -> None: import gurobipy