py-modelrunner 0.16.0__tar.gz → 0.17.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/PKG-INFO +5 -5
  2. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/README.md +4 -4
  3. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/conf.py +0 -1
  4. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/user_manual.rst +3 -2
  5. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/io_hdf.py +1 -1
  6. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/io_json.py +1 -1
  7. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/io_yaml.py +1 -1
  8. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/model_storage_output.py +1 -1
  9. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/script_function.py +1 -1
  10. py_modelrunner-0.17.1/modelrunner/.gitignore +1 -0
  11. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/_version.py +2 -2
  12. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/model.py +14 -8
  13. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/results.py +36 -7
  14. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/run/__main__.py +7 -0
  15. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/run/job.py +16 -10
  16. py_modelrunner-0.17.1/modelrunner/run/templates/background.jinja +27 -0
  17. py_modelrunner-0.17.1/modelrunner/run/templates/foreground.jinja +26 -0
  18. py-modelrunner-0.16.0/modelrunner/run/templates/qsub.template → py_modelrunner-0.17.1/modelrunner/run/templates/qsub.jinja +8 -8
  19. py-modelrunner-0.16.0/modelrunner/run/templates/srun.template → py_modelrunner-0.17.1/modelrunner/run/templates/srun.jinja +8 -8
  20. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/memory.py +15 -9
  21. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/group.py +42 -8
  22. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/tools.py +4 -1
  23. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/PKG-INFO +5 -5
  24. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/SOURCES.txt +7 -4
  25. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/pyproject.toml +5 -2
  26. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/test_compatibility.py +2 -2
  27. py_modelrunner-0.17.1/tests/run/scripts/fail.py +3 -0
  28. py_modelrunner-0.17.1/tests/run/scripts/print.py +3 -0
  29. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/run/test_job.py +43 -4
  30. py_modelrunner-0.17.1/tests/run/test_run_main.py +79 -0
  31. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/storage/test_group.py +15 -0
  32. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/storage/test_storage_generic.py +8 -0
  33. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/storage/test_trajectory.py +2 -2
  34. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_model.py +27 -27
  35. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_results.py +5 -5
  36. py-modelrunner-0.16.0/modelrunner/run/templates/background.template +0 -29
  37. py-modelrunner-0.16.0/modelrunner/run/templates/foreground.template +0 -29
  38. py-modelrunner-0.16.0/tests/run/test_run_main.py +0 -26
  39. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.github/workflows/build.yml +0 -0
  40. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.github/workflows/check_docs.yml +0 -0
  41. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.github/workflows/coverage_report.yml +0 -0
  42. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.github/workflows/release_pip.yml +0 -0
  43. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.gitignore +0 -0
  44. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/.readthedocs.yaml +0 -0
  45. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/LICENSE +0 -0
  46. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/Makefile +0 -0
  47. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/build_all.sh +0 -0
  48. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/requirements.txt +0 -0
  49. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/.gitignore +0 -0
  50. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/examples.rst +0 -0
  51. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/index.rst +0 -0
  52. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/installation.rst +0 -0
  53. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/parse_examples.py +0 -0
  54. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/run_autodoc.py +0 -0
  55. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/docs/source/sphinx_simplify_typehints.py +0 -0
  56. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/model_class.py +0 -0
  57. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/model_decorator.py +0 -0
  58. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/model_function.py +0 -0
  59. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/read_many.py +0 -0
  60. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/script_custom_func.py +0 -0
  61. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/script_many_classes.py +0 -0
  62. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/script_minimal.py +0 -0
  63. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/script_model_class.py +0 -0
  64. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/submit_function.py +0 -0
  65. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/submit_many_func.py +0 -0
  66. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/submit_many_iter.py +0 -0
  67. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/examples/submit_shebang.py +0 -0
  68. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/__init__.py +0 -0
  69. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/__main__.py +0 -0
  70. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/compatibility/__init__.py +0 -0
  71. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/compatibility/triage.py +0 -0
  72. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/compatibility/version0.py +0 -0
  73. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/compatibility/version1.py +0 -0
  74. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/config.py +0 -0
  75. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/parameters.py +0 -0
  76. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/py.typed +0 -0
  77. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/run/__init__.py +0 -0
  78. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/__init__.py +0 -0
  79. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/access_modes.py +0 -0
  80. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/attributes.py +0 -0
  81. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/__init__.py +0 -0
  82. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/hdf.py +0 -0
  83. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/json.py +0 -0
  84. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/text_base.py +0 -0
  85. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/utils.py +0 -0
  86. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/yaml.py +0 -0
  87. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/backend/zarr.py +0 -0
  88. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/base.py +0 -0
  89. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/trajectory.py +0 -0
  90. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/storage/utils.py +0 -0
  91. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/modelrunner/utils.py +0 -0
  92. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/dependency_links.txt +0 -0
  93. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/not-zip-safe +0 -0
  94. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/requires.txt +0 -0
  95. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/py_modelrunner.egg-info/top_level.txt +0 -0
  96. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/requirements.txt +0 -0
  97. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/requirements_full.txt +0 -0
  98. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/create_compatibility_files.py +0 -0
  99. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/format_code.sh +0 -0
  100. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/performance_hdf5_zarr.py +0 -0
  101. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/run_tests.py +0 -0
  102. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_all.sh +0 -0
  103. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_codestyle.sh +0 -0
  104. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_coverage.sh +0 -0
  105. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_parallel.sh +0 -0
  106. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_run.sh +0 -0
  107. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/scripts/tests_types.sh +0 -0
  108. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/setup.cfg +0 -0
  109. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/0/result.hdf +0 -0
  110. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/0/result.json +0 -0
  111. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/0/result.pkl +0 -0
  112. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/0/result.yaml +0 -0
  113. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array.json +0 -0
  114. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array.pkl +0 -0
  115. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array.yaml +0 -0
  116. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array.zarr +0 -0
  117. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array_col.json +0 -0
  118. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array_col.pkl +0 -0
  119. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array_col.yaml +0 -0
  120. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/array_col.zarr +0 -0
  121. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/dict.json +0 -0
  122. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/dict.pkl +0 -0
  123. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/dict.yaml +0 -0
  124. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/dict.zarr +0 -0
  125. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/object.json +0 -0
  126. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/object.pkl +0 -0
  127. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/object.yaml +0 -0
  128. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/1/object.zarr +0 -0
  129. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array.hdf +0 -0
  130. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array.json +0 -0
  131. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array.pkl +0 -0
  132. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array.yaml +0 -0
  133. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array.zip +0 -0
  134. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array_col.hdf +0 -0
  135. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array_col.json +0 -0
  136. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array_col.pkl +0 -0
  137. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array_col.yaml +0 -0
  138. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/array_col.zip +0 -0
  139. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/dict.hdf +0 -0
  140. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/dict.json +0 -0
  141. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/dict.pkl +0 -0
  142. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/dict.yaml +0 -0
  143. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/dict.zip +0 -0
  144. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/nested.hdf +0 -0
  145. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/nested.json +0 -0
  146. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/nested.pkl +0 -0
  147. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/nested.yaml +0 -0
  148. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/compatibility/2/nested.zip +0 -0
  149. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/conftest.py +0 -0
  150. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/helpers/__init__.py +0 -0
  151. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/helpers/asserts.py +0 -0
  152. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/helpers/storage.py +0 -0
  153. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/requirements.txt +0 -0
  154. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/run/scripts/function.py +0 -0
  155. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/run/scripts/script.py +0 -0
  156. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/empty.py +0 -0
  157. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/function.py +0 -0
  158. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/function_main.py +0 -0
  159. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/function_marked.py +0 -0
  160. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/make_model.py +0 -0
  161. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/make_model_class.py +0 -0
  162. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/scripts/make_model_marked.py +0 -0
  163. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/storage/test_storage_special.py +0 -0
  164. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/storage/test_utils.py +0 -0
  165. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_config.py +0 -0
  166. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_examples.py +0 -0
  167. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_main.py +0 -0
  168. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_parameters.py +0 -0
  169. {py-modelrunner-0.16.0 → py_modelrunner-0.17.1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-modelrunner
3
- Version: 0.16.0
3
+ Version: 0.17.1
4
4
  Summary: Python classes for organizing (HPC) simulations
5
5
  Author-email: David Zwicker <david.zwicker@ds.mpg.de>
6
6
  License: MIT
@@ -226,7 +226,7 @@ def multiply(a=1, b=2):
226
226
  return a * b
227
227
 
228
228
  if __name__ == "__main__":
229
- submit_job(__file__, parameters={'a': 2}, output="data.hdf5", method="foreground")
229
+ submit_job(__file__, parameters={"a": 2}, output="data.hdf5", method="foreground")
230
230
  ```
231
231
  Here, the `output` argument specifies a file to which the results are written, while
232
232
  `method` chooses how the script is submitted.
@@ -243,7 +243,7 @@ def multiply(a=1, b=2):
243
243
 
244
244
  if __name__ == "__main__":
245
245
  for a in range(5):
246
- submit_job(__file__, parameters={'a': a}, output=f"data_{a}.hdf5", method="foreground")
246
+ submit_job(__file__, parameters={"a": a}, output=f"data_{a}.hdf5", method="foreground")
247
247
  ```
248
248
 
249
249
  Note that the safe-guard `if __name__ == "__main__"` is absolutely crucial to ensure that
@@ -261,7 +261,7 @@ def multiply(a=1, b=2):
261
261
  return a * b
262
262
 
263
263
  if __name__ == "__main__":
264
- submit_jobs(__file__, parameters={'a': range(5)}, output_folder="data", method="foreground")
264
+ submit_jobs(__file__, parameters={"a": range(5)}, output_folder="data", method="foreground")
265
265
  ```
266
266
 
267
267
  Finally, the packages also offers a method to submit a model script to the cluster using
@@ -301,7 +301,7 @@ For instance, the data from the multiple jobs ran above can be collected using
301
301
  from modelrunner import ResultCollection
302
302
 
303
303
  results = ResultCollection.from_folder(".", pattern="data_*.hdf5")
304
- print(results.dataframe)
304
+ print(results.to_dataframe())
305
305
  ```
306
306
 
307
307
  This example should print all results using a pandas Dataframe, where each row
@@ -184,7 +184,7 @@ def multiply(a=1, b=2):
184
184
  return a * b
185
185
 
186
186
  if __name__ == "__main__":
187
- submit_job(__file__, parameters={'a': 2}, output="data.hdf5", method="foreground")
187
+ submit_job(__file__, parameters={"a": 2}, output="data.hdf5", method="foreground")
188
188
  ```
189
189
  Here, the `output` argument specifies a file to which the results are written, while
190
190
  `method` chooses how the script is submitted.
@@ -201,7 +201,7 @@ def multiply(a=1, b=2):
201
201
 
202
202
  if __name__ == "__main__":
203
203
  for a in range(5):
204
- submit_job(__file__, parameters={'a': a}, output=f"data_{a}.hdf5", method="foreground")
204
+ submit_job(__file__, parameters={"a": a}, output=f"data_{a}.hdf5", method="foreground")
205
205
  ```
206
206
 
207
207
  Note that the safe-guard `if __name__ == "__main__"` is absolutely crucial to ensure that
@@ -219,7 +219,7 @@ def multiply(a=1, b=2):
219
219
  return a * b
220
220
 
221
221
  if __name__ == "__main__":
222
- submit_jobs(__file__, parameters={'a': range(5)}, output_folder="data", method="foreground")
222
+ submit_jobs(__file__, parameters={"a": range(5)}, output_folder="data", method="foreground")
223
223
  ```
224
224
 
225
225
  Finally, the packages also offers a method to submit a model script to the cluster using
@@ -259,7 +259,7 @@ For instance, the data from the multiple jobs ran above can be collected using
259
259
  from modelrunner import ResultCollection
260
260
 
261
261
  results = ResultCollection.from_folder(".", pattern="data_*.hdf5")
262
- print(results.dataframe)
262
+ print(results.to_dataframe())
263
263
  ```
264
264
 
265
265
  This example should print all results using a pandas Dataframe, where each row
@@ -240,7 +240,6 @@ napoleon_custom_sections = None
240
240
  intersphinx_mapping = {
241
241
  "h5py": ("https://docs.h5py.org/en/latest", None),
242
242
  "matplotlib": ("https://matplotlib.org/stable", None),
243
- "modelrunner": ("https://py-modelrunner.readthedocs.io/en/latest", None),
244
243
  "napari": ("https://napari.org/", None),
245
244
  "numba": ("https://numba.pydata.org/numba-doc/latest/", None),
246
245
  "numpy": ("https://numpy.org/doc/stable", None),
@@ -43,11 +43,12 @@ The main requirements for the storage classes were
43
43
  - *Usability*: The user should not need to think about how data is stored in different files
44
44
  - *Flexibility*: We want a general interface to write data in multiple file formats (YAML, HDF, zarr, ...)
45
45
  - *Stability*: Future versions of the package should be able to read older files even when the internal definitions of file formats change
46
- - *Extensibility*: States should be subclasses to implement more complicated requirements (e.g., particular serialization)
46
+ - *Modularity*: Different parts of the package (like :mod:`~modelrunner.storage`, :mod:`~modelrunner.parameters`, and :mod:`~modelrunner.run`) should be rather independent of each other, so they can be used in isolation
47
+ - *Extensibility*: Models should be easy to subclass to implement more complicated requirements (e.g., additional parameters)
47
48
  - *Self-explainability*: The files should in principle contain all information to reconstruct the data, even if the `py-modelrunner` package is no longer available.
48
49
  - *Efficiency*: The files should only store necessary information.
49
50
 
50
51
  The last point results in particular constraints if we want to store temporal simulation results.
51
52
  In most cases, there are are some data that are kept fixed for the simulation (describing physical parameters) and others that evolve with time.
52
53
  We denote by `attributes` the parameters that are kept fixed and by `data` the data that varies over time.
53
- The state classes are already prepared to deal with such data, in conjuction with the :mod:`~modelrunner.storage.trajectory` module.
54
+ The :mod:`~modelrunner.storage.trajectory` module deals with such data.
@@ -22,4 +22,4 @@ if __name__ == "__main__":
22
22
 
23
23
  # write result from file
24
24
  read = Result.from_file("test.hdf")
25
- print(read.parameters, "–– start + [0..length-1] =", read.data)
25
+ print(read.parameters, "–– start + [0..length-1] =", read.result)
@@ -18,4 +18,4 @@ if __name__ == "__main__":
18
18
  result.to_file("test.json")
19
19
 
20
20
  read = Result.from_file("test.json")
21
- print(read.parameters, "–– a * b =", read.data)
21
+ print(read.parameters, "–– a * b =", read.result)
@@ -18,4 +18,4 @@ if __name__ == "__main__":
18
18
  result.to_file("test.yaml")
19
19
 
20
20
  read = Result.from_file("test.yaml")
21
- print(read.parameters, "–– a * b =", read.data)
21
+ print(read.parameters, "–– a * b =", read.result)
@@ -24,4 +24,4 @@ with tempfile.NamedTemporaryFile(suffix=".yaml") as fp:
24
24
  # read the file and check whether all the data is there
25
25
  with open_storage(fp.name) as storage:
26
26
  print("Stored data:", storage["data"])
27
- print("Model result:", storage["result"].data)
27
+ print("Model result:", storage["result"].result)
@@ -15,4 +15,4 @@ def multiply(a: float = 1, b: float = 2):
15
15
 
16
16
  if __name__ == "__main__":
17
17
  result = run_function_with_cmd_args(multiply)
18
- print(result.data)
18
+ print(result.result)
@@ -0,0 +1 @@
1
+ /_version.py
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.16.0'
16
- __version_tuple__ = version_tuple = (0, 16, 0)
15
+ __version__ = version = '0.17.1'
16
+ __version_tuple__ = version_tuple = (0, 17, 1)
@@ -64,7 +64,11 @@ class ModelBase(Parameterized, metaclass=ABCMeta):
64
64
  :meth:`~Parameterized.get_parameters` or displayed by calling
65
65
  :meth:`~Parameterized.show_parameters`.
66
66
  output (str):
67
- Path where the output file will be written.
67
+ Path where the output file will be written. The output will be written
68
+ using :mod:`~modelrunner.storage` and might contain two groups: `result`
69
+ to which the final result of the model is written, and `data`, which
70
+ can contain extra information that is written using
71
+ :attr:`~ModelBase.storage`.
68
72
  mode (str or :class:`~modelrunner.storage.access_modes.ModeType`):
69
73
  The file mode with which the storage is accessed, which determines the
70
74
  allowed operations. Common options are "read", "full", "append", and
@@ -87,7 +91,9 @@ class ModelBase(Parameterized, metaclass=ABCMeta):
87
91
  if self.output is None:
88
92
  raise RuntimeError("Output file needs to be specified")
89
93
  self._storage = open_storage(self.output, mode=self.mode)
90
- return self._storage
94
+ return self._storage.create_group("data")
95
+ else:
96
+ return self._storage.open_group("data")
91
97
 
92
98
  def close(self) -> None:
93
99
  """close any opened storages"""
@@ -124,21 +130,21 @@ class ModelBase(Parameterized, metaclass=ABCMeta):
124
130
  result:
125
131
  The result data. If omitted, the model is run to obtain results
126
132
  """
133
+ from .results import Result # @Reimport
134
+
127
135
  if self.output is None:
128
136
  raise RuntimeError("Output file needs to be specified")
129
137
 
130
138
  if result is None:
131
139
  result = self.get_result()
132
- else:
133
- from .results import Result # @Reimport
134
-
135
- assert isinstance(result, Result)
140
+ elif not isinstance(result, Result):
141
+ raise TypeError(f"result has type {result.__class__} instead of `Result`")
136
142
 
137
143
  if self._storage is not None:
138
144
  # reuse the opened storage
139
- result.to_file(self.storage)
145
+ result.to_file(self._storage, loc="result")
140
146
  else:
141
- result.to_file(self.output, mode=self.mode)
147
+ result.to_file(self.output, loc="result", mode=self.mode)
142
148
 
143
149
  @classmethod
144
150
  def _prepare_argparser(cls, name: str | None = None) -> argparse.ArgumentParser:
@@ -47,7 +47,12 @@ class Result:
47
47
  """int: number indicating the version of the file format"""
48
48
 
49
49
  def __init__(
50
- self, model: ModelBase, result: Any, info: dict[str, Any] | None = None
50
+ self,
51
+ model: ModelBase,
52
+ result: Any,
53
+ *,
54
+ storage: StorageID = None,
55
+ info: dict[str, Any] | None = None,
51
56
  ):
52
57
  """
53
58
  Args:
@@ -55,18 +60,23 @@ class Result:
55
60
  The model from which the result was obtained
56
61
  result:
57
62
  The actual result
63
+ storage:
64
+ A storage containing additional data from the model run
58
65
  info (dict):
59
66
  Additional information for this result
60
67
  """
61
68
  if not isinstance(model, ModelBase):
62
69
  raise TypeError("The model should be of type `ModelBase`")
63
- self.model = model
64
70
  self.result = result
71
+ self.model = model
72
+ self.storage = storage
65
73
  self.info: Attrs = {} if info is None else info
66
74
 
67
75
  @property
68
76
  def data(self):
69
77
  """direct access to the underlying state data"""
78
+ # deprecated on 2024-04-13
79
+ warnings.warn("`.data` attribute was renamed to `.result`", DeprecationWarning)
70
80
  return self.result
71
81
 
72
82
  @classmethod
@@ -74,7 +84,9 @@ class Result:
74
84
  cls,
75
85
  model_data: dict[str, Any],
76
86
  result,
87
+ *,
77
88
  model: ModelBase | None = None,
89
+ storage: StorageID = None,
78
90
  info: dict[str, Any] | None = None,
79
91
  ) -> Result:
80
92
  """create result from data
@@ -86,6 +98,8 @@ class Result:
86
98
  The actual result data
87
99
  model (:class:`ModelBase`):
88
100
  The model from which the result was obtained
101
+ storage:
102
+ A storage containing additional data from the model run
89
103
  info (dict):
90
104
  Additional information for this result
91
105
 
@@ -103,7 +117,7 @@ class Result:
103
117
  model.name = model_data.get("name")
104
118
  model.description = model_data.get("description")
105
119
 
106
- return cls(model, result, info)
120
+ return cls(model, result, storage=storage, info=info)
107
121
 
108
122
  @property
109
123
  def parameters(self) -> dict[str, Any]:
@@ -119,13 +133,20 @@ class Result:
119
133
  ):
120
134
  """load object from a file
121
135
 
136
+ This function loads the results from a hierachical storage. It also attempts to
137
+ read information about the model that was used to create this result and
138
+ additional data that might have been stored in a
139
+ :attr:`~modelrunner.results.Result.storage` while the model was running.
140
+
122
141
  Args:
123
142
  store (str or :class:`zarr.Store`):
124
143
  Path or instance describing the storage, which is either a file path or
125
144
  a :class:`zarr.Storage`.
126
- key (str):
127
- Name of the node in which the data was stored. This applies to some
128
- hierarchical storage formats.
145
+ loc:
146
+ The location where the result is stored in the storage. This should
147
+ rarely be modified.
148
+ model (:class:`~modelrunner.model.ModelBase`):
149
+ The model which lead to this result
129
150
  """
130
151
  if isinstance(storage, (str, Path)):
131
152
  # check whether the file was written with an old format version
@@ -141,10 +162,15 @@ class Result:
141
162
  format_version = attrs.pop("format_version", None)
142
163
  if format_version == cls._format_version:
143
164
  # current version of storing results
165
+ if "data" in storage_obj:
166
+ data_storage = open_storage(storage, loc="data", mode="read")
167
+ else:
168
+ data_storage = None
144
169
  return cls.from_data(
145
170
  model_data=attrs.get("model", {}),
146
171
  result=storage_obj.read_item(loc, use_class=False),
147
172
  model=model,
173
+ storage=data_storage,
148
174
  info=attrs.pop("info", {}), # load additional info,
149
175
  )
150
176
 
@@ -154,7 +180,10 @@ class Result:
154
180
  def to_file(
155
181
  self, storage: StorageID, loc: str = "result", *, mode: ModeType = "insert"
156
182
  ) -> None:
157
- """write this object to a file
183
+ """write the results to a file
184
+
185
+ Note that this does only write the actual `results` but omits additional data
186
+ that might have been stored in a storage that is associated with the results.
158
187
 
159
188
  Args:
160
189
  storage (:class:`StorageBase` or :class:`StorageGroup`):
@@ -27,6 +27,12 @@ def main():
27
27
  )
28
28
 
29
29
  parser.add_argument("-o", "--output", help="Path to output file", metavar="PATH")
30
+ parser.add_argument(
31
+ "-l",
32
+ "--log_folder",
33
+ help="Path for log files (skip for no logging)",
34
+ metavar="PATH",
35
+ )
30
36
  parser.add_argument(
31
37
  "--overwrite",
32
38
  nargs="?",
@@ -52,6 +58,7 @@ def main():
52
58
  output=args.output,
53
59
  name=args.name,
54
60
  parameters=args.parameters,
61
+ log_folder=args.log_folder,
55
62
  method=args.method,
56
63
  template=args.template,
57
64
  overwrite_strategy=args.overwrite,
@@ -11,7 +11,7 @@ import itertools
11
11
  import json
12
12
  import logging
13
13
  import os
14
- import pipes
14
+ import shlex
15
15
  import subprocess as sp
16
16
  import sys
17
17
  import warnings
@@ -26,7 +26,7 @@ from ..parameters import Parameter
26
26
 
27
27
  def escape_string(obj) -> str:
28
28
  """escape a string for the command line"""
29
- return pipes.quote(str(obj))
29
+ return shlex.quote(str(obj))
30
30
 
31
31
 
32
32
  def ensure_directory_exists(folder):
@@ -130,7 +130,7 @@ def submit_job(
130
130
  parameters: str | dict[str, Any] | None = None,
131
131
  config: str | dict[str, Any] | None = None,
132
132
  *,
133
- log_folder: str | Path = "logs",
133
+ log_folder: str | Path | None = None,
134
134
  method: str = "auto",
135
135
  use_modelrunner: bool = True,
136
136
  template: str | Path | None = None,
@@ -153,7 +153,10 @@ def submit_job(
153
153
  Configuration for the job, which determines how the job is run. Can be either
154
154
  a python dictionary or a string containing a JSON-encoded dictionary.
155
155
  log_folder (str of :class:`~pathlib.Path`):
156
- Path to the logging folder
156
+ Path to the logging folder. If omitted, the default of the template is used,
157
+ which typically sends data to stdout for local scripts (which is thus
158
+ captured and returned by this function) or writes log files to the current
159
+ working directory for remote jobs.
157
160
  method (str):
158
161
  Specifies the submission method. Currently `background`, `foreground`,
159
162
  'srun', and `qsub` are supported. The special value `auto` reads the method
@@ -169,7 +172,9 @@ def submit_job(
169
172
  `error`, `warn_skip`, `silent_skip`, `overwrite`, and `silent_overwrite`.
170
173
 
171
174
  Returns:
172
- tuple: The result `(stdout, stderr)` of the submission call
175
+ tuple: The result `(stdout, stderr)` of the submission call. These two strings
176
+ can contain the output from the actual scripts that is run when `log_folder`
177
+ is `None`.
173
178
  """
174
179
  from jinja2 import Template
175
180
 
@@ -191,7 +196,7 @@ def submit_job(
191
196
 
192
197
  # load the correct template
193
198
  if template is None:
194
- template_path = Path(__file__).parent / "templates" / (method + ".template")
199
+ template_path = Path(__file__).parent / "templates" / (method + ".jinja")
195
200
  else:
196
201
  template_path = Path(template)
197
202
  logger.info("Load template `%s`", template_path)
@@ -199,15 +204,16 @@ def submit_job(
199
204
  script_template = fp.read()
200
205
 
201
206
  # prepare submission script
202
- ensure_directory_exists(log_folder)
203
207
  script_args: dict[str, Any] = {
204
208
  "PACKAGE_PATH": Path(__file__).parents[2],
205
- "LOG_FOLDER": log_folder,
206
209
  "JOB_NAME": name,
207
210
  "MODEL_FILE": escape_string(script),
208
211
  "USE_MODELRUNNER": use_modelrunner,
209
212
  "CONFIG": configuration,
210
213
  }
214
+ if log_folder is not None:
215
+ ensure_directory_exists(log_folder)
216
+ script_args["LOG_FOLDER"] = log_folder
211
217
 
212
218
  # add the parameters to the job arguments
213
219
  job_args = []
@@ -244,9 +250,9 @@ def submit_job(
244
250
 
245
251
  # check whether output points to a directory or whether this should be a file
246
252
  if output.is_dir():
247
- script_args["OUTPUT_FOLDER"] = pipes.quote(str(output))
253
+ script_args["OUTPUT_FOLDER"] = shlex.quote(str(output))
248
254
  else:
249
- script_args["OUTPUT_FOLDER"] = pipes.quote(str(output.parent))
255
+ script_args["OUTPUT_FOLDER"] = shlex.quote(str(output.parent))
250
256
  job_args.append(f"--output {escape_string(output)}")
251
257
 
252
258
  else:
@@ -0,0 +1,27 @@
1
+ #!/bin/bash -l
2
+
3
+ export PYTHONPATH={{ PACKAGE_PATH }}:$PYTHONPATH
4
+
5
+ {% if CONFIG.num_threads is number %}
6
+ # set the number of threads to use
7
+ export MKL_NUM_THREADS={{ CONFIG.num_threads }}
8
+ export NUMBA_NUM_THREADS={{ CONFIG.num_threads }}
9
+ export NUMEXPR_NUM_THREADS={{ CONFIG.num_threads }}
10
+ export OMP_NUM_THREADS={{ CONFIG.num_threads }}
11
+ export OPENBLAS_NUM_THREADS={{ CONFIG.num_threads }}
12
+ {% endif %}
13
+
14
+ {% if OUTPUT_FOLDER is defined and OUTPUT_FOLDER %}
15
+ mkdir -p {{ OUTPUT_FOLDER }}
16
+ {% endif %}
17
+
18
+ # Run the program in the background
19
+ nohup {{ CONFIG.python_bin }} \
20
+ {% if USE_MODELRUNNER is defined and USE_MODELRUNNER %} -m modelrunner {% endif %} \
21
+ {{ MODEL_FILE }} \
22
+ {{ JOB_ARGS }} \
23
+ {% if LOG_FOLDER is defined -%}
24
+ > {{ LOG_FOLDER }}/{{ JOB_NAME }}.out.txt \
25
+ 2> {{ LOG_FOLDER }}/{{ JOB_NAME }}.err.txt \
26
+ {% endif -%}
27
+ &
@@ -0,0 +1,26 @@
1
+ #!/bin/bash -l
2
+
3
+ export PYTHONPATH={{ PACKAGE_PATH }}:$PYTHONPATH
4
+
5
+ {% if CONFIG.num_threads is number %}
6
+ # set the number of threads to use
7
+ export MKL_NUM_THREADS={{ CONFIG.num_threads }}
8
+ export NUMBA_NUM_THREADS={{ CONFIG.num_threads }}
9
+ export NUMEXPR_NUM_THREADS={{ CONFIG.num_threads }}
10
+ export OMP_NUM_THREADS={{ CONFIG.num_threads }}
11
+ export OPENBLAS_NUM_THREADS={{ CONFIG.num_threads }}
12
+ {% endif %}
13
+
14
+ {% if OUTPUT_FOLDER is defined and OUTPUT_FOLDER %}
15
+ mkdir -p {{ OUTPUT_FOLDER }}
16
+ {% endif %}
17
+
18
+ # Run the program
19
+ {{ CONFIG.python_bin }} \
20
+ {% if USE_MODELRUNNER is defined and USE_MODELRUNNER %} -m modelrunner {% endif %} \
21
+ {{ MODEL_FILE }} \
22
+ {{ JOB_ARGS }} \
23
+ {% if LOG_FOLDER is defined -%}
24
+ > {{ LOG_FOLDER }}/{{ JOB_NAME }}.out.txt \
25
+ 2> {{ LOG_FOLDER }}/{{ JOB_NAME }}.err.txt \
26
+ {% endif -%}
@@ -18,13 +18,13 @@
18
18
  hostname
19
19
  echo $JOB_ID
20
20
 
21
- {% if CONFIG['num_threads'] is number %}
21
+ {% if CONFIG.num_threads is number %}
22
22
  # set the number of threads to use
23
- export MKL_NUM_THREADS={{ CONFIG['num_threads'] }}
24
- export NUMBA_NUM_THREADS={{ CONFIG['num_threads'] }}
25
- export NUMEXPR_NUM_THREADS={{ CONFIG['num_threads'] }}
26
- export OMP_NUM_THREADS={{ CONFIG['num_threads'] }}
27
- export OPENBLAS_NUM_THREADS={{ CONFIG['num_threads'] }}
23
+ export MKL_NUM_THREADS={{ CONFIG.num_threads }}
24
+ export NUMBA_NUM_THREADS={{ CONFIG.num_threads }}
25
+ export NUMEXPR_NUM_THREADS={{ CONFIG.num_threads }}
26
+ export OMP_NUM_THREADS={{ CONFIG.num_threads }}
27
+ export OPENBLAS_NUM_THREADS={{ CONFIG.num_threads }}
28
28
  {% endif %}
29
29
 
30
30
  {% if OUTPUT_FOLDER is defined and OUTPUT_FOLDER %}
@@ -33,7 +33,7 @@ mkdir -p {{ OUTPUT_FOLDER }}
33
33
 
34
34
  # Run the program
35
35
  {% if USE_MODELRUNNER is defined and USE_MODELRUNNER %}
36
- {{ CONFIG['python_bin'] }} -m modelrunner {{ MODEL_FILE }} {{ JOB_ARGS }}
36
+ {{ CONFIG.python_bin }} -m modelrunner {{ MODEL_FILE }} {{ JOB_ARGS }}
37
37
  {% else %}
38
- {{ CONFIG['python_bin'] }} {{ MODEL_FILE }} {{ JOB_ARGS }}
38
+ {{ CONFIG.python_bin }} {{ MODEL_FILE }} {{ JOB_ARGS }}
39
39
  {% endif %}
@@ -16,13 +16,13 @@
16
16
  hostname
17
17
  echo $JOB_ID
18
18
 
19
- {% if CONFIG['num_threads'] is number %}
19
+ {% if CONFIG.num_threads is number %}
20
20
  # set the number of threads to use
21
- export MKL_NUM_THREADS={{ CONFIG['num_threads'] }}
22
- export NUMBA_NUM_THREADS={{ CONFIG['num_threads'] }}
23
- export NUMEXPR_NUM_THREADS={{ CONFIG['num_threads'] }}
24
- export OMP_NUM_THREADS={{ CONFIG['num_threads'] }}
25
- export OPENBLAS_NUM_THREADS={{ CONFIG['num_threads'] }}
21
+ export MKL_NUM_THREADS={{ CONFIG.num_threads }}
22
+ export NUMBA_NUM_THREADS={{ CONFIG.num_threads }}
23
+ export NUMEXPR_NUM_THREADS={{ CONFIG.num_threads }}
24
+ export OMP_NUM_THREADS={{ CONFIG.num_threads }}
25
+ export OPENBLAS_NUM_THREADS={{ CONFIG.num_threads }}
26
26
  {% endif %}
27
27
 
28
28
  {% if OUTPUT_FOLDER is defined and OUTPUT_FOLDER %}
@@ -31,7 +31,7 @@ mkdir -p {{ OUTPUT_FOLDER }}
31
31
 
32
32
  # Run the program
33
33
  {% if USE_MODELRUNNER is defined and USE_MODELRUNNER %}
34
- {{ CONFIG['python_bin'] }} -m modelrunner {{ MODEL_FILE }} {{ JOB_ARGS }}
34
+ {{ CONFIG.python_bin }} -m modelrunner {{ MODEL_FILE }} {{ JOB_ARGS }}
35
35
  {% else %}
36
- {{ CONFIG['python_bin'] }} {{ MODEL_FILE }} {{ JOB_ARGS }}
36
+ {{ CONFIG.python_bin }} {{ MODEL_FILE }} {{ JOB_ARGS }}
37
37
  {% endif %}
@@ -56,17 +56,20 @@ class MemoryStorage(StorageBase):
56
56
  (group, str):
57
57
  A tuple consisting of the parent group and the name of the current item
58
58
  """
59
- value = self._data
59
+ if check_write and not self.is_group(loc[:-1]):
60
+ raise TypeError(f"Location `/{'/'.join(loc[:-1])}` is not a group")
61
+
62
+ parent = self._data
60
63
  for part in loc[:-1]:
61
64
  try:
62
- value = value[part]
65
+ parent = parent[part]
63
66
  except KeyError:
64
- if isinstance(value, dict):
65
- value[part] = {}
66
- value = value[part]
67
+ if isinstance(parent, dict):
68
+ parent[part] = {}
69
+ parent = parent[part]
67
70
  else:
68
71
  raise TypeError(f"Cannot add item to `/{'/'.join(loc)}`")
69
- if not isinstance(value, dict):
72
+ if not isinstance(parent, dict):
70
73
  raise TypeError(f"Cannot add item to `/{'/'.join(loc)}`")
71
74
 
72
75
  try:
@@ -74,14 +77,17 @@ class MemoryStorage(StorageBase):
74
77
  except IndexError:
75
78
  raise KeyError(f"Location `/{'/'.join(loc)}` has no parent")
76
79
 
77
- if check_write and not self.mode.overwrite and name in value:
80
+ if check_write and not self.mode.overwrite and name in parent:
78
81
  raise AccessError(f"Overwriting `/{'/'.join(loc)}` disabled")
79
- return value, name
82
+ return parent, name
80
83
 
81
84
  def __getitem__(self, loc: Sequence[str]) -> Any:
82
85
  if loc:
83
86
  parent, name = self._get_parent(loc)
84
- return parent[name]
87
+ try:
88
+ return parent[name]
89
+ except KeyError as e:
90
+ raise KeyError(f"Item `{name}` not in group {parent.keys()}") from e
85
91
  else:
86
92
  return self._data
87
93