ewoksdata 2.3.0rc3__tar.gz → 2.3.0rc4__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 (49) hide show
  1. {ewoksdata-2.3.0rc3/src/ewoksdata.egg-info → ewoksdata-2.3.0rc4}/PKG-INFO +1 -1
  2. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/pyproject.toml +1 -1
  3. ewoksdata-2.3.0rc4/src/ewoksdata/data/hdf5/overwrite.py +102 -0
  4. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/utils.py +14 -1
  5. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_dataset_writer.py +67 -37
  6. ewoksdata-2.3.0rc4/src/ewoksdata/tests/unit/test_overwrite_hdf5.py +143 -0
  7. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4/src/ewoksdata.egg-info}/PKG-INFO +1 -1
  8. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata.egg-info/SOURCES.txt +2 -0
  9. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/LICENSE.md +0 -0
  10. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/README.md +0 -0
  11. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/setup.cfg +0 -0
  12. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/__init__.py +0 -0
  13. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/__init__.py +0 -0
  14. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/bliss.py +0 -0
  15. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/blissdata/__init__.py +0 -0
  16. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/blissdata/_lima.py +0 -0
  17. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/blissdata/blissdatav0.py +0 -0
  18. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/blissdata/blissdatav1.py +0 -0
  19. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/blissdata/blissdatav2.py +0 -0
  20. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/contextiterator.py +0 -0
  21. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/__init__.py +0 -0
  22. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/config.py +0 -0
  23. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/__init__.py +0 -0
  24. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/_base.py +0 -0
  25. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/_non_sequential.py +0 -0
  26. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/_non_sequential_stack.py +0 -0
  27. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/_sequential.py +0 -0
  28. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/dataset_writer/_sequential_stack.py +0 -0
  29. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/mirror.py +0 -0
  30. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/hdf5/types.py +0 -0
  31. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/nexus.py +0 -0
  32. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/data/url.py +0 -0
  33. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/__init__.py +0 -0
  34. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/integration/__init__.py +0 -0
  35. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/integration/conftest.py +0 -0
  36. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/integration/test_blissdata.py +0 -0
  37. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/__init__.py +0 -0
  38. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/conftest.py +0 -0
  39. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/data/__init__.py +0 -0
  40. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/data/bliss_scans.py +0 -0
  41. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_contextiterator.py +0 -0
  42. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_data_bliss.py +0 -0
  43. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_data_hdf5.py +0 -0
  44. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_data_nexus.py +0 -0
  45. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_find_lima_scans.py +0 -0
  46. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata/tests/unit/test_mirror_hdf5.py +0 -0
  47. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata.egg-info/dependency_links.txt +0 -0
  48. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata.egg-info/requires.txt +0 -0
  49. {ewoksdata-2.3.0rc3 → ewoksdata-2.3.0rc4}/src/ewoksdata.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ewoksdata
3
- Version: 2.3.0rc3
3
+ Version: 2.3.0rc4
4
4
  Summary: Ewoks tasks for data access
5
5
  Author-email: ESRF <dau-pydev@esrf.fr>
6
6
  License: # MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ewoksdata"
7
- version = "2.3.0rc3"
7
+ version = "2.3.0rc4"
8
8
  authors = [{ name = "ESRF", email = "dau-pydev@esrf.fr" }]
9
9
  description = "Ewoks tasks for data access"
10
10
  readme = { file = "README.md", content-type = "text/markdown" }
@@ -0,0 +1,102 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import uuid
5
+ from collections import defaultdict
6
+ from typing import Dict
7
+ from typing import List
8
+ from typing import Union
9
+
10
+ from silx.io import h5py_utils
11
+ from silx.io.url import DataUrl
12
+
13
+ from ..url import as_dataurl
14
+
15
+
16
+ def ensure_items_do_not_exist(
17
+ *urls: Union[str, DataUrl], delete_existing: bool = False
18
+ ) -> None:
19
+ """Make sure the HDF5 items (groups, datasets or links) do not exist.
20
+
21
+ Existing items are deleted when :code:`delete_existing` is :code:`True`.
22
+ A file is removed when its root group is deleted or nothing remains,
23
+ and repacked otherwise.
24
+
25
+ :raises FileExistsError: when items exist and :code:`delete_existing` is :code:`False`.
26
+ :raises ValueError: when an item to delete is in another file (external link).
27
+ """
28
+ data_paths: Dict[str, List[str]] = defaultdict(list)
29
+ for url in urls:
30
+ url = as_dataurl(url)
31
+ filename = url.file_path()
32
+ parts = [s for s in (url.data_path() or "").split("/") if s]
33
+ data_paths[filename].append("/".join(parts) or "/")
34
+ for filename, file_data_paths in data_paths.items():
35
+ _ensure_items_do_not_exist(filename, file_data_paths, delete_existing)
36
+
37
+
38
+ def _ensure_items_do_not_exist(
39
+ filename: str, data_paths: List[str], delete_existing: bool
40
+ ) -> None:
41
+ """:code:`ensure_items_do_not_exist` for the items of one file."""
42
+ if not os.path.exists(filename):
43
+ return
44
+ if "/" in data_paths:
45
+ if not delete_existing:
46
+ raise FileExistsError(f"HDF5 file {filename!r} already exists")
47
+ os.remove(filename)
48
+ return
49
+
50
+ mode = "a" if delete_existing else "r"
51
+ with h5py_utils.File(filename, mode=mode) as root:
52
+ existing = [data_path for data_path in data_paths if data_path in root]
53
+ if not existing:
54
+ return
55
+ if not delete_existing:
56
+ raise FileExistsError(
57
+ f"HDF5 items already exist in {filename!r}: {existing}"
58
+ )
59
+
60
+ real_filename = os.path.realpath(root.filename)
61
+ for data_path in existing:
62
+ parent_path = data_path.rpartition("/")[0]
63
+ if not parent_path:
64
+ continue
65
+ parent_filename = os.path.realpath(root[parent_path].file.filename)
66
+ if parent_filename != real_filename:
67
+ raise ValueError(
68
+ f"HDF5 item {data_path!r} of {filename!r} is in another file"
69
+ )
70
+
71
+ for data_path in existing:
72
+ # Can be gone already when a parent was deleted
73
+ if data_path in root:
74
+ del root[data_path]
75
+ remove_file = len(root) == 0
76
+
77
+ if remove_file:
78
+ os.remove(filename)
79
+ else:
80
+ # Deleting does not free the space in the file
81
+ _repack(filename)
82
+
83
+
84
+ def _repack(filename: str) -> None:
85
+ """Rewrite the file without its unused space."""
86
+ dirname = os.path.dirname(os.path.abspath(filename))
87
+ with tempfile.TemporaryDirectory(dir=dirname) as tmp_dirname:
88
+ tmp_filename = os.path.join(tmp_dirname, os.path.basename(filename))
89
+ with h5py_utils.File(filename, mode="r") as src:
90
+ with h5py_utils.File(tmp_filename, mode="w") as dst:
91
+ # The root cannot be copied onto the root of another file. Copy it
92
+ # in one call to a temporary group so that hard links between items
93
+ # stay shared. Deleting that group leaves its header and link index
94
+ # unused: about 1 KB plus 9 bytes per root item, not data.
95
+ tmp_name = f"_repack_{uuid.uuid4().hex}"
96
+ src.copy(src, dst, name=tmp_name)
97
+ for name in list(dst[tmp_name]):
98
+ dst.move(f"{tmp_name}/{name}", name)
99
+ del dst[tmp_name]
100
+ dst.attrs.update(src.attrs)
101
+ shutil.copymode(filename, tmp_filename)
102
+ os.replace(tmp_filename, filename)
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from numbers import Number
2
3
  from typing import List
3
4
  from typing import Mapping
@@ -16,6 +17,18 @@ def is_data(data):
16
17
 
17
18
 
18
19
  def data_from_storage(data, remove_numpy=True):
20
+ """Deprecated: values restored from ewoks persistent storage no longer need to be
21
+ converted."""
22
+ warnings.warn(
23
+ "'data_from_storage' is deprecated. Values restored from ewoks persistent "
24
+ "storage keep their python type since ewokscore 5.1.0rc4.",
25
+ DeprecationWarning,
26
+ stacklevel=2,
27
+ )
28
+ return _data_from_storage(data, remove_numpy=remove_numpy)
29
+
30
+
31
+ def _data_from_storage(data, remove_numpy=True):
19
32
  if isinstance(data, numpy.ndarray):
20
33
  if not remove_numpy:
21
34
  return data
@@ -25,7 +38,7 @@ def data_from_storage(data, remove_numpy=True):
25
38
  return data.tolist()
26
39
  elif isinstance(data, Mapping):
27
40
  return {
28
- k: data_from_storage(v, remove_numpy=remove_numpy)
41
+ k: _data_from_storage(v, remove_numpy=remove_numpy)
29
42
  for k, v in data.items()
30
43
  if not k.startswith("@")
31
44
  }
@@ -191,9 +191,19 @@ def test_concurrent_reader(tmp_path):
191
191
 
192
192
  mp_context = multiprocessing.get_context("spawn")
193
193
  start_event = mp_context.Event()
194
+ done_event = mp_context.Event()
195
+ nread = mp_context.Value("i", 0)
194
196
  reader_process = mp_context.Process(
195
197
  target=_read_hdf5_file,
196
- args=(hdf5_filename, dataset_name, npoints, read_timestamps, start_event),
198
+ args=(
199
+ hdf5_filename,
200
+ dataset_name,
201
+ npoints,
202
+ read_timestamps,
203
+ start_event,
204
+ done_event,
205
+ nread,
206
+ ),
197
207
  )
198
208
  reader_process.start()
199
209
 
@@ -201,80 +211,100 @@ def test_concurrent_reader(tmp_path):
201
211
 
202
212
  # Run write loop
203
213
  try:
204
- _write_hdf5_file(hdf5_filename, dataset_name, npoints, write_timestamps)
214
+ _write_hdf5_file(hdf5_filename, dataset_name, npoints, write_timestamps, nread)
205
215
  except Exception:
206
216
  reader_process.terminate()
207
217
  raise
208
218
 
209
- # Wait for read loop to finish
219
+ # Do not rely on the exit code: with gevent monkey-patching (bliss
220
+ # integration tests in the same session), once any subprocess was started,
221
+ # gevent collects the exit code of every child process that terminates.
222
+ # The exit code of the reader is then no longer available to
223
+ # multiprocessing, so `reader_process.exitcode` stays `None`.
210
224
  try:
211
- reader_process.join(timeout=20)
212
- if reader_process.exitcode is None:
213
- raise TimeoutError("Reader process did not terminate within the timeout.")
214
- elif reader_process.exitcode != 0:
215
- raise RuntimeError(
216
- f"Reader process terminated with exit code {reader_process.exitcode}."
217
- )
218
- except Exception:
219
- reader_process.terminate()
220
- raise
225
+ assert done_event.wait(timeout=20), "Reader did not finish within the timeout."
226
+ finally:
227
+ reader_process.join(timeout=5)
228
+ if reader_process.is_alive():
229
+ reader_process.terminate()
221
230
 
222
- # Check that reading and writing happened concurrently
223
- with open(read_timestamps, "r") as eventfile:
224
- read_events = [int(line.strip()) for line in eventfile.readlines()]
225
- with open(write_timestamps, "r") as eventfile:
226
- write_events = [int(line.strip()) for line in eventfile.readlines()]
231
+ read_events = _load_events(read_timestamps)
232
+ write_events = _load_events(write_timestamps)
227
233
 
228
- assert len(read_events) == npoints
229
- assert len(write_events) == npoints
234
+ assert [value for _, value in read_events] == list(range(npoints))
235
+ assert [value for _, value in write_events] == list(range(npoints))
230
236
 
231
- all_events = sorted(read_events + write_events)
232
- assert read_events != all_events[: len(read_events)], (
237
+ # The reader got data before the writer finished
238
+ assert read_events[0][0] < write_events[-1][0], (
233
239
  "reads and writes are not interleaved"
234
240
  )
235
241
 
236
242
 
237
243
  class _DatasetWriter(dataset_writer.DatasetWriter):
238
- """Testing concurrent reading and writing always ends up
239
- being flaky. So we introduce a sleep after flushing
240
- to give the reader plenty of time to fetch new data.
241
- """
244
+ """Waits after each flush until the reader has read all flushed points."""
245
+
246
+ def __init__(self, *args, nread, **kwargs) -> None:
247
+ super().__init__(*args, **kwargs)
248
+ self._nread = nread
242
249
 
243
250
  def _flush_hdf5(self) -> None:
244
251
  print("flush", flush=True)
245
252
  super()._flush_hdf5()
246
- time.sleep(0.2)
253
+ # Timing alone does not guarantee interleaving on a loaded machine
254
+ nflushed = self.dataset.shape[0]
255
+ t0 = time.monotonic()
256
+ while self._nread.value < nflushed and time.monotonic() - t0 < 10:
257
+ time.sleep(0.01)
247
258
 
248
259
 
249
- def _write_hdf5_file(hdf5_filename, dataset_name, npoints, event_filename):
250
- data = numpy.zeros((500, 500), dtype=numpy.int64)
260
+ def _write_hdf5_file(hdf5_filename, dataset_name, npoints, event_filename, nread):
251
261
  with open(event_filename, "a") as eventfile:
252
262
  with h5py.File(hdf5_filename, mode="w") as h5file:
253
- with _DatasetWriter(h5file, dataset_name) as writer:
263
+ with _DatasetWriter(
264
+ h5file, dataset_name, flush_period=0.1, nread=nread
265
+ ) as writer:
254
266
  for i in range(npoints):
255
- writer.add_point(data)
256
- eventfile.write(f"{time.perf_counter_ns()}\n")
267
+ writer.add_point(numpy.full((500, 500), i, dtype=numpy.int64))
268
+ eventfile.write(f"{time.perf_counter_ns()} {i}\n")
257
269
  eventfile.flush()
258
270
  print(f"write {i}", flush=True)
271
+ # Ensure flushes happen before the writer exits
272
+ time.sleep(0.02)
259
273
 
260
274
 
261
- def _read_hdf5_file(hdf5_filename, dataset_name, npoints, event_filename, start_event):
275
+ def _read_hdf5_file(
276
+ hdf5_filename,
277
+ dataset_name,
278
+ npoints,
279
+ event_filename,
280
+ start_event,
281
+ done_event,
282
+ nread,
283
+ ):
262
284
  i = 0
263
285
  with open(event_filename, "a") as eventfile:
264
286
  start_event.set()
265
287
  while i < npoints:
266
- for _ in _iter_data(hdf5_filename, dataset_name):
267
- eventfile.write(f"{time.perf_counter_ns()}\n")
288
+ for value in _iter_data(hdf5_filename, dataset_name, start_index=i):
289
+ eventfile.write(f"{time.perf_counter_ns()} {value}\n")
268
290
  eventfile.flush()
269
291
  print(f"read {i}", flush=True)
270
292
  i += 1
293
+ nread.value = i
294
+ print("reader finished", flush=True)
295
+ done_event.set()
271
296
 
272
297
 
273
298
  @h5py_utils.retry()
274
299
  def _iter_data(hdf5_filename, dataset_name, start_index=0):
275
300
  with h5py_utils.File(hdf5_filename) as h5file:
276
- for _ in h5file[dataset_name][start_index:]:
277
- yield None
301
+ for value in h5file[dataset_name][start_index:, 0, 0]:
302
+ yield int(value)
303
+
304
+
305
+ def _load_events(filename):
306
+ with open(filename, "r") as eventfile:
307
+ return [tuple(map(int, line.split())) for line in eventfile]
278
308
 
279
309
 
280
310
  def test_stack_dataset_writer_multi_chunk_dim0(tmp_path):
@@ -0,0 +1,143 @@
1
+ import os
2
+ import stat
3
+
4
+ import h5py
5
+ import numpy
6
+ import pytest
7
+
8
+ from ...data.hdf5.overwrite import ensure_items_do_not_exist
9
+
10
+
11
+ def test_ensure_items_do_not_exist_nested(tmp_path):
12
+ filename = tmp_path / "test.h5"
13
+ with h5py.File(filename, "w") as f:
14
+ f["1.1/fit/data"] = numpy.zeros((1000, 1000))
15
+ f.create_group("1.1/norm")
16
+ size_before = os.path.getsize(filename)
17
+
18
+ ensure_items_do_not_exist(
19
+ f"{filename}::/1.1/fit", f"{filename}::/1.1/missing", delete_existing=True
20
+ )
21
+
22
+ with h5py.File(filename, "r") as f:
23
+ assert set(f["1.1"]) == {"norm"}
24
+ assert os.path.getsize(filename) < size_before / 10
25
+
26
+
27
+ def test_ensure_items_do_not_exist_some_top_level(tmp_path):
28
+ filename = tmp_path / "test.h5"
29
+ with h5py.File(filename, "w") as f:
30
+ f.attrs["creator"] = "test"
31
+ f["1.1/data"] = numpy.zeros((1000, 1000))
32
+ f["2.1/data"] = numpy.zeros(10)
33
+ f["2.1/hard"] = f["2.1/data"]
34
+ f["3.1/hard"] = f["2.1/data"]
35
+ f["3.1/soft"] = h5py.SoftLink("/2.1/data")
36
+ f["3.1/external"] = h5py.ExternalLink("other.h5", "/data")
37
+ os.chmod(filename, 0o644)
38
+ size_before = os.path.getsize(filename)
39
+
40
+ ensure_items_do_not_exist(f"{filename}::/1.1", delete_existing=True)
41
+
42
+ with h5py.File(filename, "r") as f:
43
+ assert set(f) == {"2.1", "3.1"}
44
+ assert f.attrs["creator"] == "test"
45
+ assert f["2.1/hard"].id == f["2.1/data"].id
46
+ assert f["3.1/hard"].id == f["2.1/data"].id
47
+ assert f.get("3.1/soft", getlink=True).path == "/2.1/data"
48
+ assert f.get("3.1/external", getlink=True).filename == "other.h5"
49
+ assert os.path.getsize(filename) < size_before / 10
50
+ assert stat.S_IMODE(os.stat(filename).st_mode) == 0o644
51
+ assert os.listdir(tmp_path) == ["test.h5"]
52
+
53
+
54
+ def test_ensure_items_do_not_exist_all_top_level(tmp_path):
55
+ filename = tmp_path / "test.h5"
56
+ with h5py.File(filename, "w") as f:
57
+ f.create_group("1.1")
58
+ f.create_group("2.1")
59
+
60
+ ensure_items_do_not_exist(
61
+ f"{filename}::/1.1", f"{filename}::/2.1", delete_existing=True
62
+ )
63
+
64
+ assert not filename.exists()
65
+
66
+
67
+ def test_ensure_items_do_not_exist_missing(tmp_path):
68
+ filename = tmp_path / "test.h5"
69
+ ensure_items_do_not_exist(f"{filename}::/1.1")
70
+ ensure_items_do_not_exist(f"{filename}::/1.1", delete_existing=True)
71
+ assert not filename.exists()
72
+
73
+
74
+ def test_ensure_items_do_not_exist_root(tmp_path):
75
+ filename = tmp_path / "test.h5"
76
+ with h5py.File(filename, "w") as f:
77
+ f.create_group("1.1")
78
+
79
+ with pytest.raises(FileExistsError):
80
+ ensure_items_do_not_exist(f"{filename}::/")
81
+ assert filename.exists()
82
+
83
+ ensure_items_do_not_exist(
84
+ f"{filename}::/1.1", f"{filename}::/", delete_existing=True
85
+ )
86
+ assert not filename.exists()
87
+
88
+
89
+ def test_ensure_items_do_not_exist_raise(tmp_path):
90
+ filename = tmp_path / "test.h5"
91
+ with h5py.File(filename, "w") as f:
92
+ f["1.1/data"] = numpy.zeros(10)
93
+ f.create_group("2.1")
94
+
95
+ ensure_items_do_not_exist(f"{filename}::/1.1/missing")
96
+ with pytest.raises(FileExistsError):
97
+ ensure_items_do_not_exist(f"{filename}::/1.1/missing", f"{filename}::/2.1")
98
+
99
+ with h5py.File(filename, "r") as f:
100
+ assert set(f) == {"1.1", "2.1"}
101
+
102
+
103
+ def test_ensure_items_do_not_exist_datasets_and_links(tmp_path):
104
+ filename = tmp_path / "test.h5"
105
+ with h5py.File(filename, "w") as f:
106
+ f["1.1/data"] = numpy.zeros(10)
107
+ f["1.1/other"] = numpy.zeros(10)
108
+ f["1.1/dangling"] = h5py.SoftLink("/missing")
109
+
110
+ ensure_items_do_not_exist(
111
+ f"{filename}::/1.1/data", f"{filename}::/1.1/dangling", delete_existing=True
112
+ )
113
+
114
+ with h5py.File(filename, "r") as f:
115
+ assert set(f["1.1"]) == {"other"}
116
+
117
+
118
+ def test_ensure_items_do_not_exist_external(tmp_path):
119
+ filename = tmp_path / "test.h5"
120
+ external_filename = tmp_path / "external.h5"
121
+ with h5py.File(external_filename, "w") as f:
122
+ f["group/data"] = numpy.zeros(10)
123
+ with h5py.File(filename, "w") as f:
124
+ f["1.1/data"] = numpy.zeros(10)
125
+ f["1.1/external"] = h5py.ExternalLink(str(external_filename), "/group")
126
+
127
+ with pytest.raises(ValueError):
128
+ ensure_items_do_not_exist(
129
+ f"{filename}::/1.1/data",
130
+ f"{filename}::/1.1/external/data",
131
+ delete_existing=True,
132
+ )
133
+
134
+ with h5py.File(external_filename, "r") as f:
135
+ assert "group/data" in f
136
+ with h5py.File(filename, "r") as f:
137
+ assert "1.1/data" in f
138
+
139
+ ensure_items_do_not_exist(f"{filename}::/1.1/external", delete_existing=True)
140
+ with h5py.File(filename, "r") as f:
141
+ assert set(f["1.1"]) == {"data"}
142
+ with h5py.File(external_filename, "r") as f:
143
+ assert "group/data" in f
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ewoksdata
3
- Version: 2.3.0rc3
3
+ Version: 2.3.0rc4
4
4
  Summary: Ewoks tasks for data access
5
5
  Author-email: ESRF <dau-pydev@esrf.fr>
6
6
  License: # MIT License
@@ -21,6 +21,7 @@ src/ewoksdata/data/blissdata/blissdatav2.py
21
21
  src/ewoksdata/data/hdf5/__init__.py
22
22
  src/ewoksdata/data/hdf5/config.py
23
23
  src/ewoksdata/data/hdf5/mirror.py
24
+ src/ewoksdata/data/hdf5/overwrite.py
24
25
  src/ewoksdata/data/hdf5/types.py
25
26
  src/ewoksdata/data/hdf5/dataset_writer/__init__.py
26
27
  src/ewoksdata/data/hdf5/dataset_writer/_base.py
@@ -41,5 +42,6 @@ src/ewoksdata/tests/unit/test_data_nexus.py
41
42
  src/ewoksdata/tests/unit/test_dataset_writer.py
42
43
  src/ewoksdata/tests/unit/test_find_lima_scans.py
43
44
  src/ewoksdata/tests/unit/test_mirror_hdf5.py
45
+ src/ewoksdata/tests/unit/test_overwrite_hdf5.py
44
46
  src/ewoksdata/tests/unit/data/__init__.py
45
47
  src/ewoksdata/tests/unit/data/bliss_scans.py
File without changes
File without changes
File without changes