From 63ffe5f73e5cfe7ded6609c234385dc31e7c3f01 Mon Sep 17 00:00:00 2001 From: Andi Gerken <andi.gerken@gmail.com> Date: Mon, 27 Sep 2021 09:59:53 +0000 Subject: [PATCH] Fixed save_as for windows pipeline. In the process, the save_as function hat to be changed, so that it closes the file object before copying it to some location. This is not ideal but a work around after many trials to fix it properly. An issue #19 for this was opened. --- .gitlab-ci.yml | 9 +++++++++ README.md | 9 +++++---- examples/example_basic.py | 8 +++----- examples/example_readme.py | 6 ++---- pyproject.toml | 5 +---- src/robofish/io/file.py | 18 +++++++++++++---- tests/robofish/io/test_file.py | 37 ++++++++++++---------------------- 7 files changed, 47 insertions(+), 45 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d579b9a..ee7cb61 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - package - test - deploy + - trigger .centos: tags: [linux, docker] @@ -78,3 +79,11 @@ deploy to production: - package script: - ./ci/deploy.py --production + + +trigger fish_models: + stage: trigger + only: + - master + trigger: + project: bioroboticslab/fish_models \ No newline at end of file diff --git a/README.md b/README.md index 7a6a60f..bb9b352 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ import robofish.io import numpy as np # Create a new robofish io file -f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0) +# Mode "w" means that the file should be opened with write access. +f = robofish.io.File(path, "w", world_size_cm=[100, 100], frequency_hz=25.0) f.attrs["experiment_setup"] = "This is a simple example with made up data." # Create a new robot entity with 10 timesteps. @@ -61,11 +62,11 @@ print( f"Poses Shape:\t{f.entity_poses_rad.shape}.\t" + "Representing(entities, timesteps, pose dimensions (x, y, ori)" ) -print(f"The actions of one Fish, (timesteps, (speed, turn)):\n{fish.actions_speeds_turns}") +print( + f"The actions of one Fish, (timesteps, (speed, turn)):\n{fish.actions_speeds_turns}" +) print(f"Fish positions with orientations:\n{fish.poses_rad}") -# Save the file -f.save_as("example.hdf5") ``` ### Evaluation diff --git a/examples/example_basic.py b/examples/example_basic.py index 951d934..f37d0cc 100755 --- a/examples/example_basic.py +++ b/examples/example_basic.py @@ -3,8 +3,9 @@ import numpy as np def create_example_file(path): - # Create a new io file object with a 100x100cm world - f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0) + # Create a new io file object with a 100x100cm world. + # Mode "w" means that the file should be opened with write access. + f = robofish.io.File(path, "w", world_size_cm=[100, 100], frequency_hz=25.0) # create a robofish with 1000 timesteps. If we would not give a name, the name would be generated to be robot_1. robofish_timesteps = 1000 @@ -22,9 +23,6 @@ def create_example_file(path): # This would throw an exception if the file was invalid f.validate() - # Save file validates aswell - f.save_as(path) - # Closing and opening files (just for demonstration). When opening with r+, we can read and write afterwards. f.close() f = robofish.io.File(path, "r+") diff --git a/examples/example_readme.py b/examples/example_readme.py index 8b21f01..7b2e22c 100644 --- a/examples/example_readme.py +++ b/examples/example_readme.py @@ -4,7 +4,8 @@ import numpy as np def create_example_file(path): # Create a new robofish io file - f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0) + # Mode "w" means that the file should be opened with write access. + f = robofish.io.File(path, "w", world_size_cm=[100, 100], frequency_hz=25.0) f.attrs["experiment_setup"] = "This is a simple example with made up data." # Create a new robot entity with 10 timesteps. @@ -40,9 +41,6 @@ def create_example_file(path): ) print(f"Fish positions with orientations:\n{fish.poses_rad}") - # Save the file - f.save_as(path) - if __name__ == "__main__": path = create_example_file("example.hdf5") diff --git a/pyproject.toml b/pyproject.toml index c3be3e3..81f6e80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,4 @@ extend-ignore = "E203" [tool.black] line-length = 88 -include ='\.py' - -[tool.pytest.ini_options] -filterwarnings = ["error"] \ No newline at end of file +include ='\.py' \ No newline at end of file diff --git a/src/robofish/io/file.py b/src/robofish/io/file.py index f3d8ec4..1427241 100644 --- a/src/robofish/io/file.py +++ b/src/robofish/io/file.py @@ -30,6 +30,7 @@ import tempfile import uuid import deprecation import types +import warnings import matplotlib as mpl import matplotlib.pyplot as plt @@ -181,12 +182,13 @@ class File(h5py.File): self.validate() super().__exit__(type, value, traceback) - def save_as(self, path: Union[str, Path], strict_validate: bool = True): + def save_as(self, path: Union[str, Path], strict_validate: bool = True, no_warning: bool=False): """Save a copy of the file Args: path: path to a io file as a string or path object. If no path is specified, the last known path (from loading or saving) is used. strict_validate: optional boolean, if the file should be strictly validated, before saving. The default is True. + no_warning: optional boolean, to remove the warning from the function. Returns: The file itself, so something like f = robofish.io.File().save_as("file.hdf5") works """ @@ -199,9 +201,15 @@ class File(h5py.File): path = Path(path).resolve() path.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(Path(self.filename).resolve(), path) - - return self + filename = self.filename + self.flush() + self.close() + shutil.copyfile(filename, path) + if not no_warning: + warnings.warn( + "The 'save_as' function closes the file currently to be able to store it. If you want to use the file after saving it, please reload the file. The save_as function can be avoided by opening the correct file directly. If you want to get rid of this warning use 'save_as(..., no_warning=True)'" + ) + return None def create_sampling( self, @@ -267,6 +275,8 @@ class File(h5py.File): @property def default_sampling(self): + if not "samplings" in self: + print("Wassss?", self) if "default" in self["samplings"].attrs: return self["samplings"].attrs["default"] return None diff --git a/tests/robofish/io/test_file.py b/tests/robofish/io/test_file.py index 1c8605b..97e2aa1 100644 --- a/tests/robofish/io/test_file.py +++ b/tests/robofish/io/test_file.py @@ -11,10 +11,6 @@ import logging LOGGER = logging.getLogger(__name__) valid_file_path = utils.full_path(__file__, "../../resources/valid.hdf5") -created_by_test_path = utils.full_path(__file__, "../../resources/created_by_test.hdf5") -created_by_test_path_2 = utils.full_path( - __file__, "../../resources/created_by_test_2.hdf5" -) def test_constructor(): @@ -27,10 +23,9 @@ def test_context(): pass -def test_new_file_w_path(): - sf = robofish.io.File( - created_by_test_path_2, "w", world_size_cm=[100, 100], frequency_hz=25 - ) +def test_new_file_w_path(tmp_path): + f = tmp_path / "file.hdf5" + sf = robofish.io.File(f, "w", world_size_cm=[100, 100], frequency_hz=25) sf.create_entity("fish") sf.validate() @@ -193,36 +188,30 @@ def test_File_without_path_or_worldsize(): sf = robofish.io.File() -def test_loading_saving(): +def test_loading_saving(tmp_path): + f = tmp_path / "file.hdf5" sf = test_multiple_entities() - assert not created_by_test_path.exists() - sf.save_as(created_by_test_path) - assert created_by_test_path.exists() + assert not f.exists() + sf.save_as(f, no_warning=True) + assert f.exists() # After saving, the file should still be accessible and valid + # This was previously possible without reloading the file. + # It had to change because of windows complications. See issue #19 + sf = robofish.io.File(f, "r+") sf.validate() # Open the file again and add another entity - sf = robofish.io.File(created_by_test_path, "r+") + + print("Recovered file after saving:\n", sf) entity = sf.create_entity("fish", positions=np.ones((100, 2))) sf.entity_poses entity.poses - -def test_validate_created_file_after_reloading(): - sf = robofish.io.File(created_by_test_path) sf.validate() -# Cleanup test. The z in the name makes sure, that it is executed last in main -def test_z_cleanup(): - """This cleans up after all tests and removes all test artifacts""" - for f in [created_by_test_path, created_by_test_path_2]: - if f.exists(): - f.unlink() - - if __name__ == "__main__": # Find all functions in this module and execute them all_functions = inspect.getmembers(sys.modules[__name__], inspect.isfunction) -- GitLab