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