Repository: pyiron/executorlib
Pull request #601: Create cache directory
Author: login: jan-janssen
Created: 2025-03-09T16:24:41Z | Merged: 2025-03-23T06:46:41Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Enhanced the internal caching process by reorganizing how task-related data is stored.
  - Tasks are now grouped into dedicated directories, improving the reliability and efficiency of cache management.
  - Cache data retrieval has been updated to reflect the new nested directory structure, enhancing organization and access.
  - Improvements streamline operations and enhance performance, while public interfaces remain unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/executorlib/cache/queue_spawner.py b/executorlib/cache/queue_spawner.py
index 86f93a22..e7c1eeb8 100644
--- a/executorlib/cache/queue_spawner.py
+++ b/executorlib/cache/queue_spawner.py
@@ -68,7 +68,9 @@ def execute_with_pysqa(
             if k in resource_dict:
                 del resource_dict[k]
         if "job_name" not in resource_dict:
-            resource_dict["job_name"] = "pysqa"
+            resource_dict["job_name"] = os.path.basename(
+                os.path.dirname(os.path.abspath(cwd))
+            )
         submit_kwargs.update(resource_dict)
         queue_id = qa.submit_job(**submit_kwargs)
         dump(file_name=file_name, data_dict={"queue_id": queue_id})
diff --git a/executorlib/cache/shared.py b/executorlib/cache/shared.py
index d7eea67c..7e15764c 100644
--- a/executorlib/cache/shared.py
+++ b/executorlib/cache/shared.py
@@ -106,8 +106,13 @@ def execute_tasks_h5(
                 resource_dict=task_resource_dict,
             )
             if task_key not in memory_dict:
-                if task_key + ".h5out" not in os.listdir(cache_directory):
-                    file_name = os.path.join(cache_directory, task_key + ".h5in")
+                if not (
+                    task_key in os.listdir(cache_directory)
+                    and "cache.h5out"
+                    in os.listdir(os.path.join(cache_directory, task_key))
+                ):
+                    os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
+                    file_name = os.path.join(cache_directory, task_key, "cache.h5in")
                     dump(file_name=file_name, data_dict=data_dict)
                     if not disable_dependencies:
                         task_dependent_lst = [
@@ -131,10 +136,10 @@ def execute_tasks_h5(
                         resource_dict=task_resource_dict,
                         config_directory=pysqa_config_directory,
                         backend=backend,
-                        cache_directory=cache_directory,
+                        cache_directory=os.path.join(cache_directory, task_key),
                     )
                 file_name_dict[task_key] = os.path.join(
-                    cache_directory, task_key + ".h5out"
+                    cache_directory, task_key, "cache.h5out"
                 )
                 memory_dict[task_key] = task_dict["future"]
             future_queue.task_done()
@@ -190,7 +195,7 @@ def _check_task_output(
         Future: The updated future object.
 
     """
-    file_name = os.path.join(cache_directory, task_key + ".h5out")
+    file_name = os.path.join(cache_directory, task_key, "cache.h5out")
     if not os.path.exists(file_name):
         return future_obj
     exec_flag, result = get_output(file_name=file_name)
diff --git a/executorlib/interactive/shared.py b/executorlib/interactive/shared.py
index 1c39aaad..86fa62dd 100644
--- a/executorlib/interactive/shared.py
+++ b/executorlib/interactive/shared.py
@@ -148,9 +148,12 @@ def _execute_task_with_cache(
         fn_kwargs=task_dict["kwargs"],
         resource_dict=task_dict.get("resource_dict", {}),
     )
-    os.makedirs(cache_directory, exist_ok=True)
-    file_name = os.path.join(cache_directory, task_key + ".h5out")
-    if task_key + ".h5out" not in os.listdir(cache_directory):
+    os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
+    file_name = os.path.join(cache_directory, task_key, "cache.h5out")
+    if not (
+        task_key in os.listdir(cache_directory)
+        and "cache.h5out" in os.listdir(os.path.join(cache_directory, task_key))
+    ):
         f = task_dict.pop("future")
         if f.set_running_or_notify_cancel():
             try:
diff --git a/executorlib/standalone/hdf.py b/executorlib/standalone/hdf.py
index 9fb1b744..aa765555 100644
--- a/executorlib/standalone/hdf.py
+++ b/executorlib/standalone/hdf.py
@@ -104,13 +104,16 @@ def get_queue_id(file_name: Optional[str]) -> Optional[int]:
 
 def get_cache_data(cache_directory: str) -> list[dict]:
     file_lst = []
-    for file_name in os.listdir(cache_directory):
-        with h5py.File(os.path.join(cache_directory, file_name), "r") as hdf:
-            file_content_dict = {
-                key: cloudpickle.loads(np.void(hdf["/" + key]))
-                for key in group_dict.values()
-                if key in hdf
-            }
-        file_content_dict["filename"] = file_name
-        file_lst.append(file_content_dict)
+    for task_key in os.listdir(cache_directory):
+        file_name = os.path.join(cache_directory, task_key, "cache.h5out")
+        os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
+        if os.path.exists(file_name):
+            with h5py.File(file_name, "r") as hdf:
+                file_content_dict = {
+                    key: cloudpickle.loads(np.void(hdf["/" + key]))
+                    for key in group_dict.values()
+                    if key in hdf
+                }
+            file_content_dict["filename"] = file_name
+            file_lst.append(file_content_dict)
     return file_lst
diff --git a/tests/test_cache_shared.py b/tests/test_cache_shared.py
index 76c62dfb..46cbb718 100644
--- a/tests/test_cache_shared.py
+++ b/tests/test_cache_shared.py
@@ -31,7 +31,8 @@ def test_execute_function_mixed(self):
             fn_args=[1],
             fn_kwargs={"b": 2},
         )
-        file_name = os.path.join(cache_directory, task_key + ".h5in")
+        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
+        os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
         dump(file_name=file_name, data_dict=data_dict)
         backend_execute_task_in_file(file_name=file_name)
         future_obj = Future()
@@ -41,11 +42,11 @@ def test_execute_function_mixed(self):
         self.assertTrue(future_obj.done())
         self.assertEqual(future_obj.result(), 3)
         self.assertTrue(
-            get_runtime(file_name=os.path.join(cache_directory, task_key + ".h5out"))
+            get_runtime(file_name=os.path.join(cache_directory, task_key, "cache.h5out"))
             > 0.0
         )
         future_file_obj = FutureItem(
-            file_name=os.path.join(cache_directory, task_key + ".h5out")
+            file_name=os.path.join(cache_directory, task_key, "cache.h5out")
         )
         self.assertTrue(future_file_obj.done())
         self.assertEqual(future_file_obj.result(), 3)
@@ -58,7 +59,8 @@ def test_execute_function_args(self):
             fn_args=[1, 2],
             fn_kwargs={},
         )
-        file_name = os.path.join(cache_directory, task_key + ".h5in")
+        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
+        os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
         dump(file_name=file_name, data_dict=data_dict)
         backend_execute_task_in_file(file_name=file_name)
         future_obj = Future()
@@ -68,11 +70,11 @@ def test_execute_function_args(self):
         self.assertTrue(future_obj.done())
         self.assertEqual(future_obj.result(), 3)
         self.assertTrue(
-            get_runtime(file_name=os.path.join(cache_directory, task_key + ".h5out"))
+            get_runtime(file_name=os.path.join(cache_directory, task_key, "cache.h5out"))
             > 0.0
         )
         future_file_obj = FutureItem(
-            file_name=os.path.join(cache_directory, task_key + ".h5out")
+            file_name=os.path.join(cache_directory, task_key, "cache.h5out")
         )
         self.assertTrue(future_file_obj.done())
         self.assertEqual(future_file_obj.result(), 3)
@@ -85,7 +87,8 @@ def test_execute_function_kwargs(self):
             fn_args=[],
             fn_kwargs={"a": 1, "b": 2},
         )
-        file_name = os.path.join(cache_directory, task_key + ".h5in")
+        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
+        os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
         dump(file_name=file_name, data_dict=data_dict)
         backend_execute_task_in_file(file_name=file_name)
         future_obj = Future()
@@ -95,11 +98,11 @@ def test_execute_function_kwargs(self):
         self.assertTrue(future_obj.done())
         self.assertEqual(future_obj.result(), 3)
         self.assertTrue(
-            get_runtime(file_name=os.path.join(cache_directory, task_key + ".h5out"))
+            get_runtime(file_name=os.path.join(cache_directory, task_key, "cache.h5out"))
             > 0.0
         )
         future_file_obj = FutureItem(
-            file_name=os.path.join(cache_directory, task_key + ".h5out")
+            file_name=os.path.join(cache_directory, task_key, "cache.h5out")
         )
         self.assertTrue(future_file_obj.done())
         self.assertEqual(future_file_obj.result(), 3)


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>executorlib/cache/shared.py (2)</summary><blockquote>

`114-114`: **Add a comment explaining the new directory structure**

The code creates a new directory structure for caching, where each task has its own subdirectory. It would be helpful to add a comment explaining this new structure and its purpose.


```diff
+    # Create a dedicated subdirectory for each task to better organize cache files
     os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
```

---

`142-142`: **Add backwards compatibility for old cache files**

Since this change modifies the location of cache files, users upgrading from a previous version might experience issues with existing cache files.


Consider adding backwards compatibility to check for files in the old location as well:

```diff
file_name_dict[task_key] = os.path.join(
    cache_directory, task_key, "cache.h5out"
)
+# Add code to handle existing cache files for backward compatibility
+if not os.path.exists(file_name_dict[task_key]) and os.path.exists(os.path.join(cache_directory, task_key + ".h5out")):
+    # Either migrate old files to new structure or add logic to check old locations
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 15abfd4188b73c12f11d9b04c61173272d49a263 and 34b40b11289331dcae823c3d32aabc0b4536f48d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `executorlib/cache/shared.py` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (14)</summary>

* GitHub Check: unittest_openmpi (ubuntu-latest, 3.11)
* GitHub Check: unittest_openmpi (ubuntu-latest, 3.12)
* GitHub Check: unittest_openmpi (ubuntu-latest, 3.13)
* GitHub Check: unittest_mpich (ubuntu-latest, 3.11)
* GitHub Check: unittest_openmpi (macos-latest, 3.13)
* GitHub Check: unittest_mpich (ubuntu-latest, 3.12)
* GitHub Check: unittest_win
* GitHub Check: unittest_old
* GitHub Check: unittest_mpich (ubuntu-latest, 3.13)
* GitHub Check: unittest_mpich (macos-latest, 3.13)
* GitHub Check: unittest_flux_openmpi
* GitHub Check: unittest_flux_mpich
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-mpich.yml)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-openmpi.yml)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T16:28:54Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>executorlib/standalone/hdf.py (2)</summary><blockquote>

`105-117`: **Consider handling backward compatibility with the old cache structure.**

The change from a flat file structure to a nested directory structure might cause problems with existing cache data. Consider adding a migration path or fallback mechanism to handle both formats.


```diff
def get_cache_data(cache_directory: str) -> list[dict]:
    file_lst = []
+    # Handle new directory-based cache structure
    for task_key in os.listdir(cache_directory):
+        task_path = os.path.join(cache_directory, task_key)
+        if os.path.isdir(task_path):
+            file_name = os.path.join(task_path, "cache.h5in")
+            if os.path.isfile(file_name):
+                try:
+                    with h5py.File(file_name, "r") as hdf:
+                        file_content_dict = {
+                            key: cloudpickle.loads(np.void(hdf["/" + key]))
+                            for key in group_dict.values()
+                            if key in hdf
+                        }
+                        file_content_dict["filename"] = file_name
+                        file_lst.append(file_content_dict)
+                except (IOError, OSError):
+                    continue
+        # Handle old flat cache structure for backward compatibility
+        elif task_key.endswith('.h5in') and os.path.isfile(task_path):
+            try:
+                with h5py.File(task_path, "r") as hdf:
+                    file_content_dict = {
+                        key: cloudpickle.loads(np.void(hdf["/" + key]))
+                        for key in group_dict.values()
+                        if key in hdf
+                    }
+                    file_content_dict["filename"] = task_path
+                    file_lst.append(file_content_dict)
+            except (IOError, OSError):
+                continue
-        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
-        with h5py.File(file_name, "r") as hdf:
-            file_content_dict = {
-                key: cloudpickle.loads(np.void(hdf["/" + key]))
-                for key in group_dict.values()
-                if key in hdf
-            }
-        file_content_dict["filename"] = file_name
-        file_lst.append(file_content_dict)
    return file_lst
```

---

`105-105`: **Add documentation explaining the new cache directory structure.**

The function's docstring should be updated to explain the new directory-based cache structure, helping users understand how cache data is now organized.


```diff
def get_cache_data(cache_directory: str) -> list[dict]:
+    """
+    Retrieve cache data from the nested directory structure.
+    
+    Each task has its own subdirectory named after the task_key within the cache_directory.
+    Inside each task's subdirectory, there is a 'cache.h5in' file containing the task data.
+    
+    Args:
+        cache_directory (str): The base directory for the cache
+        
+    Returns:
+        list[dict]: A list of dictionaries containing the cache data for each task
+    """
    file_lst = []
    for task_key in os.listdir(cache_directory):
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 34b40b11289331dcae823c3d32aabc0b4536f48d and eceb2a62d1027a45be55856fe41e6203921dd140.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `executorlib/cache/shared.py` (3 hunks)
* `executorlib/standalone/hdf.py` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* executorlib/cache/shared.py

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: unittest_openmpi (ubuntu-latest, 3.11)
* GitHub Check: unittest_openmpi (ubuntu-latest, 3.12)
* GitHub Check: unittest_win
* GitHub Check: unittest_openmpi (ubuntu-latest, 3.13)
* GitHub Check: unittest_openmpi (macos-latest, 3.13)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-mpich.yml)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-openmpi.yml)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T16:47:08Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>executorlib/standalone/hdf.py (1)</summary><blockquote>

`107-118`: _⚠️ Potential issue_

**Add error handling for directory and file access.**

The code now assumes that all entries in the cache directory are valid subdirectories and that each contains a "cache.h5out" file. This could lead to errors if:
1. Any entry in the cache directory is a file rather than a directory
2. Insufficient permissions to access directories or files

Additionally, the `os.makedirs()` call is unnecessary since this function is only reading data, not creating files.



```diff
def get_cache_data(cache_directory: str) -> list[dict]:
    file_lst = []
    for task_key in os.listdir(cache_directory):
+        task_dir = os.path.join(cache_directory, task_key)
+        if not os.path.isdir(task_dir):
+            continue
        file_name = os.path.join(cache_directory, task_key, "cache.h5out")
-        os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
        if os.path.exists(file_name):
+            try:
-            with h5py.File(file_name, "r") as hdf:
+                with h5py.File(file_name, "r") as hdf:
                    file_content_dict = {
                        key: cloudpickle.loads(np.void(hdf["/" + key]))
                        for key in group_dict.values()
                        if key in hdf
                    }
+                file_content_dict["filename"] = file_name
+                file_lst.append(file_content_dict)
+            except (IOError, OSError) as e:
+                # Log or handle the error as appropriate for your application
+                continue
-            file_content_dict["filename"] = file_name
-            file_lst.append(file_content_dict)
    return file_lst
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>executorlib/interactive/shared.py (1)</summary><blockquote>

`151-156`: **Simplified code suggestion for cache file existence check.**

The current implementation creates the directory structure and then performs two separate checks to verify if a cache file exists. This can be simplified by using `os.path.exists()` directly on the constructed file path.


```diff
    os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
    file_name = os.path.join(cache_directory, task_key, "cache.h5out")
-   if not (
-       task_key in os.listdir(cache_directory)
-       and "cache.h5out" in os.listdir(os.path.join(cache_directory, task_key))
-   ):
+   if not os.path.exists(file_name):
```

</blockquote></details>
<details>
<summary>tests/test_cache_shared.py (1)</summary><blockquote>

`45-46`: **Consider extracting the file path creation logic to a helper method.**

The same file path construction pattern is repeated across multiple test methods. Consider extracting this into a helper method to reduce duplication and make future path format changes easier.


```python
def get_output_file_path(cache_directory, task_key):
    return os.path.join(cache_directory, task_key, "cache.h5out")
```

Then use it in your tests:
```python
self.assertTrue(
    get_runtime(file_name=get_output_file_path(cache_directory, task_key))
    > 0.0
)
```


Also applies to: 73-75, 101-103

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between eceb2a62d1027a45be55856fe41e6203921dd140 and e3598cee33319f5021a0a859b89852ac4acaafba.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `executorlib/interactive/shared.py` (1 hunks)
* `executorlib/standalone/hdf.py` (1 hunks)
* `tests/test_cache_shared.py` (6 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: unittest_win
* GitHub Check: unittest_openmpi (macos-latest, 3.13)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-mpich.yml)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-openmpi.yml)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>tests/test_cache_shared.py (1)</summary>

`34-36`: **LGTM on test changes!**

The test file modifications correctly update the path structure to match the new nested directory approach for caching. This ensures tests will properly validate the updated implementation.




Also applies to: 62-64, 90-92

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T17:02:23Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>executorlib/cache/queue_spawner.py (1)</summary><blockquote>

`71-73`: **Good improvement to dynamically name jobs based on directory context.**

The change from a static job name to one derived from the parent directory structure is a nice enhancement that aligns with the PR's objective of improving cache directory management. This will make jobs more identifiable in the queuing system and provide better context when viewing queue status.

However, consider adding a fallback value in case the directory structure doesn't yield a suitable name (e.g., if the path is unusual or empty).


```diff
resource_dict["job_name"] = os.path.basename(
    os.path.dirname(os.path.abspath(cwd))
) or "pysqa"  # Fallback if basename is empty
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e3598cee33319f5021a0a859b89852ac4acaafba and 11955c018beb3e10b7d4d5032fcf1e721b93a06d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `executorlib/cache/queue_spawner.py` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: unittest_win
* GitHub Check: unittest_openmpi (macos-latest, 3.13)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-mpich.yml)
* GitHub Check: benchmark (ubuntu-latest, 3.13, .ci_support/environment-openmpi.yml)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T17:27:28Z

Review comments:
diff_hunk: @@ -106,8 +106,13 @@ def execute_tasks_h5(
                 resource_dict=task_resource_dict,
             )
             if task_key not in memory_dict:
-                if task_key + ".h5out" not in os.listdir(cache_directory):
-                    file_name = os.path.join(cache_directory, task_key + ".h5in")
+                if task_key not in os.listdir(
+                    cache_directory
+                ) and "cache.h5out" not in os.listdir(
+                    os.path.join(cache_directory, task_key)
+                ):
+                    os.makedirs(os.path.join(cache_directory, task_key), exist_ok=True)
+                    file_name = os.path.join(cache_directory, task_key, "cache.h5in")
path: executorlib/cache/shared.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**The file structure change is inconsistent with _check_task_output function**

The code changes introduce a new directory structure for caching, where each task has its own subdirectory. While these lines correctly implement the new structure, the `_check_task_output` function (line 198) still looks for output files in the old location.


You need to update the `_check_task_output` function to work with the new directory structure:

```diff
def _check_task_output(
    task_key: str, future_obj: Future, cache_directory: str
) -> Future:
    """
    Check the output of a task and set the result of the future object if available.

    Args:
        task_key (str): The key of the task.
        future_obj (Future): The future object associated with the task.
        cache_directory (str): The directory where the HDF5 files are stored.

    Returns:
        Future: The updated future object.

    """
-    file_name = os.path.join(cache_directory, task_key + ".h5out")
+    file_name = os.path.join(cache_directory, task_key, "cache.h5out")
    if not os.path.exists(file_name):
        return future_obj
    exec_flag, result = get_output(file_name=file_name)
    if exec_flag:
        future_obj.set_result(result)
    return future_obj
```


Also applies to: 139-139, 142-142

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-09T16:28:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,8 +106,13 @@
                 resource_dict=task_resource_dict,
             )
             if task_key not in memory_dict:
-                if task_key + ".h5out" not in os.listdir(cache_directory):
-                    file_name = os.path.join(cache_directory, task_key + ".h5in")
+                if task_key not in os.listdir(
+                    cache_directory
+                ) and "cache.h5out" not in os.listdir(
+                    os.path.join(cache_directory, task_key)
+                ):
path: executorlib/cache/shared.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix potential FileNotFoundError in directory check**

The condition to check if a task needs to be executed has a potential issue. If `task_key` doesn't exist in `cache_directory`, trying to access `os.listdir(os.path.join(cache_directory, task_key))` will raise a `FileNotFoundError` because the directory doesn't exist yet.


Replace the current check with:

```diff
-if task_key not in os.listdir(
-    cache_directory
-) and "cache.h5out" not in os.listdir(
-    os.path.join(cache_directory, task_key)
-):
+if not os.path.exists(os.path.join(cache_directory, task_key)) or not os.path.exists(os.path.join(cache_directory, task_key, "cache.h5out")):
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
                if not os.path.exists(os.path.join(cache_directory, task_key)) or not os.path.exists(os.path.join(cache_directory, task_key, "cache.h5out")):
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-09T16:28:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -104,8 +104,9 @@ def get_queue_id(file_name: Optional[str]) -> Optional[int]:
 
 def get_cache_data(cache_directory: str) -> list[dict]:
     file_lst = []
-    for file_name in os.listdir(cache_directory):
-        with h5py.File(os.path.join(cache_directory, file_name), "r") as hdf:
+    for task_key in os.listdir(cache_directory):
+        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
+        with h5py.File(file_name, "r") as hdf:
path: executorlib/standalone/hdf.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling for directory and file access.**

The code now assumes that all entries in the cache directory are valid subdirectories and that each contains a "cache.h5in" file. This could lead to errors if:
1. Any entry in the cache directory is a file rather than a directory
2. The "cache.h5in" file doesn't exist in a subdirectory
3. Insufficient permissions to access directories or files


```diff
def get_cache_data(cache_directory: str) -> list[dict]:
    file_lst = []
    for task_key in os.listdir(cache_directory):
+        task_dir = os.path.join(cache_directory, task_key)
+        if not os.path.isdir(task_dir):
+            continue
+        file_name = os.path.join(task_dir, "cache.h5in")
+        if not os.path.isfile(file_name):
+            continue
+        try:
-        file_name = os.path.join(cache_directory, task_key, "cache.h5in")
-        with h5py.File(file_name, "r") as hdf:
+            with h5py.File(file_name, "r") as hdf:
                file_content_dict = {
                    key: cloudpickle.loads(np.void(hdf["/" + key]))
                    for key in group_dict.values()
                    if key in hdf
                }
+                file_content_dict["filename"] = file_name
+                file_lst.append(file_content_dict)
+        except (IOError, OSError) as e:
+            # Log or handle the error as appropriate for your application
+            continue
-        file_content_dict["filename"] = file_name
-        file_lst.append(file_content_dict)
    return file_lst
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
def get_cache_data(cache_directory: str) -> list[dict]:
    file_lst = []
    for task_key in os.listdir(cache_directory):
        task_dir = os.path.join(cache_directory, task_key)
        if not os.path.isdir(task_dir):
            continue
        file_name = os.path.join(task_dir, "cache.h5in")
        if not os.path.isfile(file_name):
            continue
        try:
            with h5py.File(file_name, "r") as hdf:
                file_content_dict = {
                    key: cloudpickle.loads(np.void(hdf["/" + key]))
                    for key in group_dict.values()
                    if key in hdf
                }
                file_content_dict["filename"] = file_name
                file_lst.append(file_content_dict)
        except (IOError, OSError) as e:
            # Log or handle the error as appropriate for your application
            continue
    return file_lst
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-09T16:47:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-09T16:24:48Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The changes enhance the management of task files within the cache directory by modifying the `execute_tasks_h5` function to check for the existence of a task key directory and a specific output file within it ("cache.h5out"). If neither is found, a new subdirectory is created, and the input and output file paths are updated to reflect this nested structure. Similar modifications are made in the `get_cache_data` function and the `_execute_task_with_cache` function, transitioning to a nested directory structure for cache files. Additionally, the `execute_with_pysqa` function now dynamically assigns the job name based on the current directory context.

## Changes

| File Path                           | Change Summary                                                                                                                                             |
|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| executorlib/cache/shared.py         | Modified `execute_tasks_h5`: checks for task key directory and `"cache.h5out"` before creating a subdirectory. Updated input (`cache.h5in`) and output (`cache.h5out`) file paths, and adjusted the cache directory passed to `execute_function`. |
| executorlib/standalone/hdf.py       | Updated `get_cache_data`: iterates over `task_key` directories instead of flat file names, constructing file paths for `cache.h5out` within these directories. Logic adjusted to include the source filename in the returned list. |
| executorlib/interactive/shared.py    | Modified `_execute_task_with_cache`: creates a subdirectory for each task key and updates file paths for input and output files to `"cache.h5in"` and `"cache.h5out"` respectively. Updated existence checks for cache files. |
| tests/test_cache_shared.py          | Updated test methods to reflect new nested directory structure for input and output files, ensuring subdirectories are created and paths are correctly referenced in assertions. |
| executorlib/cache/queue_spawner.py  | Modified `execute_with_pysqa`: dynamically assigns `job_name` based on the current working directory instead of a static value. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant Caller as execute_tasks_h5
    participant FS as File System
    participant Executor as execute_function

    Caller->>FS: Check if task_key directory exists
    Caller->>FS: Check if "cache.h5out" exists in subdirectory
    alt Not found
        FS-->>Caller: Subdirectory does not exist
        Caller->>FS: Create subdirectory (task_key)
        Caller->>Caller: Update input file path to "cache_directory/task_key/cache.h5in"
        Caller->>Caller: Update output file path to "cache_directory/task_key/cache.h5out"
        Caller->>Executor: Execute function with new cache directory "cache_directory/task_key"
    else Found
        Caller->>Executor: Execute function with existing cache directory
    end
```

## Possibly related PRs
- pyiron/executorlib#580: The changes in the main PR, which involve modifications to the `execute_tasks_h5` function for managing task files in a nested directory structure, are related to the changes in the retrieved PR that also involve the `execute_tasks_h5` function, specifically in how threading is handled during its execution. Both PRs affect the same function and its execution context.

## Poem
> I hopped on through lines of code,  
> Nesting files in a snug abode.  
> A clever twist to cache my way,  
> Making order in the fray.  
> From bunny ears, a joyful cheer—code leaps high this day! 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=pyiron/executorlib&utm_content=601):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-09T17:01:15Z
body: ## [Codecov](https://app.codecov.io/gh/pyiron/executorlib/pull/601?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyiron) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 96.40%. Comparing base [(`4f9ef5f`)](https://app.codecov.io/gh/pyiron/executorlib/commit/4f9ef5fb9005188f0b6cc87ef7c3e7fc6006f3e0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyiron) to head [(`11955c0`)](https://app.codecov.io/gh/pyiron/executorlib/commit/11955c018beb3e10b7d4d5032fcf1e721b93a06d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyiron).
> Report is 10 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #601      +/-   ##
==========================================
+ Coverage   96.39%   96.40%   +0.01%     
==========================================
  Files          28       28              
  Lines        1248     1252       +4     
==========================================
+ Hits         1203     1207       +4     
  Misses         45       45              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pyiron/executorlib/pull/601?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyiron).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyiron).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE