diff --git a/.github/workflows/unittests_old.yml b/.github/workflows/unittests_old.yml index 530e10dd4..c8cb5716c 100644 --- a/.github/workflows/unittests_old.yml +++ b/.github/workflows/unittests_old.yml @@ -13,7 +13,7 @@ jobs: - name: Setup Mambaforge uses: conda-incubator/setup-miniconda@v3 with: - python-version: '3.9' + python-version: '3.10' miniforge-variant: Mambaforge channels: conda-forge channel-priority: strict diff --git a/pyiron_base/interfaces/has_dict.py b/pyiron_base/interfaces/has_dict.py index e3eb01634..5e619df9b 100644 --- a/pyiron_base/interfaces/has_dict.py +++ b/pyiron_base/interfaces/has_dict.py @@ -16,6 +16,7 @@ """ from abc import ABC, abstractmethod +from collections import defaultdict from typing import Any from pyiron_base.interfaces.has_hdf import HasHDF @@ -54,7 +55,7 @@ def create_from_dict(obj_dict): type_field = obj_dict["TYPE"] module_path, class_name = _extract_module_class_name(type_field) class_object = _import_class(module_path, class_name) - version = obj_dict.get("VERSION", None) + version = obj_dict.get("DICT_VERSION", None) obj = class_object.instantiate(obj_dict, version) obj.from_dict(obj_dict, version) return obj @@ -122,6 +123,9 @@ def load(inner_dict): return {k: load(v) for k, v in inner_dict.items()} return create_from_dict(inner_dict) + obj_dict = self._split_children_dict(obj_dict) + if version is None: + version = obj_dict.get("DICT_VERSION", None) self._from_dict({k: load(v) for k, v in obj_dict.items()}, version) @abstractmethod @@ -208,9 +212,28 @@ def _join_children_dict(children: dict[str, dict[str, Any]]) -> dict[str, Any]: return { "/".join((k1, k2)): v2 for k1, v1 in children.items() - for k2, v2 in v2.items() + for k2, v2 in v1.items() } + @staticmethod + def _split_children_dict( + obj_dict: dict[str, Any], + ) -> dict[str, Any | dict[str, Any]]: + """ + Undoes _join_children_dict. + """ + subs = defaultdict(dict) + plain = {} + for k, v in obj_dict.items(): + if "/" not in k: + plain[k] = v + continue + root, k = k.split("/", maxsplit=1) + subs[root][k] = v + # using update keeps type stability, i.e. we always return a plain dict + plain.update(subs) + return plain + class HasHDFfromDict(HasHDF, HasDict): """ diff --git a/pyiron_base/storage/datacontainer.py b/pyiron_base/storage/datacontainer.py index 46346c063..bba832713 100644 --- a/pyiron_base/storage/datacontainer.py +++ b/pyiron_base/storage/datacontainer.py @@ -40,6 +40,7 @@ "HDF_VERSION", "DICT_VERSION", "READ_ONLY", + "KEY_ORDER", ] @@ -827,6 +828,7 @@ def _on_unlock(self): class DataContainer(DataContainerBase, HasHDF, HasDict): + __dict_version__ = "0.2.0" __doc__ = f"""{DataContainerBase.__doc__} If instantiated with the argument `lazy=True`, data read from HDF5 later via :method:`.from_hdf` are not actually @@ -1027,13 +1029,28 @@ def to(v): return data def _to_dict(self): - return {"data": self.to_builtin(), "READ_ONLY": self.read_only} + # stringify keys in case we are acting like a list + data = {str(k): v for k, v in dict(self).items()} + order = list(data) + data["READ_ONLY"] = self.read_only + data["KEY_ORDER"] = order + return data def _from_dict(self, obj_dict, version=None): + if version == "0.2.0": + order = obj_dict.pop("KEY_ORDER") + else: + order = None + self.read_only = obj_dict.pop("READ_ONLY", False) + for key in _internal_hdf_nodes: + obj_dict.pop(key, None) with self.unlocked(): self.clear() - self.update(obj_dict["data"], wrap=True) - self.read_only = obj_dict.get("READ_ONLY", False) + if order is not None: + for key in order: + self[key] = obj_dict[key] + else: + self.update(obj_dict) HDFStub.register(DataContainer, lambda h, g: h[g].to_object(lazy=True)) diff --git a/pyproject.toml b/pyproject.toml index 8a590e0ea..6118082bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Intended Audience :: Science/Research", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12",