diff --git a/pyiron_workflow/mixin/storage.py b/pyiron_workflow/mixin/storage.py index 24d0abca..0c092f33 100644 --- a/pyiron_workflow/mixin/storage.py +++ b/pyiron_workflow/mixin/storage.py @@ -350,7 +350,15 @@ def load(self): Raises: TypeError: when the saved node has a different class name. """ - self.storage.load() + if self.storage.has_contents: + self.storage.load() + else: + # Check for saved content using any other backend + for backend in self.allowed_backends(): + interface = self._storage_interfaces()[backend](self) + if interface.has_contents: + interface.load() + break save.__doc__ += _save_load_warnings @@ -405,6 +413,13 @@ def storage(self) -> StorageInterface: raise ValueError(f"{self.label} does not have a storage backend set") return self._storage_interfaces()[self.storage_backend](self) + @property + def any_storage_has_contents(self): + return any( + self._storage_interfaces()[backend](self).has_contents + for backend in self.allowed_backends() + ) + @property def import_ready(self) -> bool: """ @@ -444,6 +459,9 @@ def _storage_interfaces(cls): interfaces["pickle"] = PickleStorage return interfaces + @classmethod + def default_backend(cls): + return "pickle" class HasH5ioStorage(HasStorage, ABC): @classmethod @@ -467,7 +485,3 @@ def to_storage(self, storage: TinybaseStorage): @abstractmethod def from_storage(self, storage: TinybaseStorage): pass - - @classmethod - def default_backend(cls): - return "tinybase" diff --git a/pyiron_workflow/node.py b/pyiron_workflow/node.py index 66762d15..b213cf06 100644 --- a/pyiron_workflow/node.py +++ b/pyiron_workflow/node.py @@ -331,7 +331,7 @@ def __init__( parent: Optional[Composite] = None, overwrite_save: bool = False, run_after_init: bool = False, - storage_backend: Literal["h5io", "tinybase", "pickle"] | None = "h5io", + storage_backend: Literal["h5io", "tinybase", "pickle"] | None = "pickle", save_after_run: bool = False, **kwargs, ): @@ -384,7 +384,7 @@ def _after_node_setup( self.delete_storage() do_load = False else: - do_load = sys.version_info >= (3, 11) and self.storage.has_contents + do_load = sys.version_info >= (3, 11) and self.any_storage_has_contents if do_load and run_after_init: raise ValueError( diff --git a/tests/unit/test_node.py b/tests/unit/test_node.py index a93a98e5..17a92227 100644 --- a/tests/unit/test_node.py +++ b/tests/unit/test_node.py @@ -497,6 +497,43 @@ def test_storage(self): hard_input.delete_storage() self.n1.delete_storage() + @unittest.skipIf(sys.version_info < (3, 11), "Storage will only work in 3.11+") + def test_storage_compatibility(self): + try: + self.n1.storage_backend = "tinybase" + self.n1.inputs.x = 42 + self.n1.save() + new_n1 = ANode(label=self.n1.label, storage_backend="pickle") + self.assertEqual( + new_n1.inputs.x.value, + self.n1.inputs.x.value, + msg="Even though the new node has a different storage backend, it " + "should still _load_ the data saved with a different backend. To " + "really avoid loading, delete or move the existing save file, or " + "give your new node a different label." + ) + new_n1() + new_n1.save() # With a different backend now + + tiny_n1 = ANode(label=self.n1.label, storage_backend="tinybase") + self.assertIs( + tiny_n1.outputs.y.value, + NOT_DATA, + msg="By explicitly specifying a particular backend, we expect to " + "recover that backend's save-file, even if it is outdated" + ) + + pick_n1 = ANode(label=self.n1.label, storage_backend="pickle") + self.assertEqual( + pick_n1.outputs.y.value, + new_n1.outputs.y.value, + msg="If we specify the more-recently-saved backend, we expect to load " + "the corresponding save file, where output exists" + ) + finally: + self.n1.delete_storage() + + @unittest.skipIf(sys.version_info < (3, 11), "Storage will only work in 3.11+") def test_save_after_run(self): for backend in Node.allowed_backends():