blissdata 0.3.4__py3-none-any.whl

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 (67) hide show
  1. blissdata/__init__.py +17 -0
  2. blissdata/beacon/__init__.py +1 -0
  3. blissdata/beacon/_base.py +106 -0
  4. blissdata/beacon/config.py +24 -0
  5. blissdata/beacon/data.py +45 -0
  6. blissdata/beacon/files.py +141 -0
  7. blissdata/client.py +62 -0
  8. blissdata/common/__init__.py +15 -0
  9. blissdata/common/utils.py +159 -0
  10. blissdata/data/__init__.py +15 -0
  11. blissdata/data/events/__init__.py +22 -0
  12. blissdata/data/events/channel.py +149 -0
  13. blissdata/data/events/lima.py +478 -0
  14. blissdata/data/events/node.py +28 -0
  15. blissdata/data/events/scan.py +49 -0
  16. blissdata/data/events/walk.py +42 -0
  17. blissdata/data/expiration.py +42 -0
  18. blissdata/data/lima_image.py +465 -0
  19. blissdata/data/node.py +1619 -0
  20. blissdata/data/nodes/__init__.py +11 -0
  21. blissdata/data/nodes/channel.py +394 -0
  22. blissdata/data/nodes/dataset.py +82 -0
  23. blissdata/data/nodes/dataset_collection.py +12 -0
  24. blissdata/data/nodes/lima.py +422 -0
  25. blissdata/data/nodes/node_ref_channel.py +34 -0
  26. blissdata/data/nodes/proposal.py +12 -0
  27. blissdata/data/nodes/scan.py +192 -0
  28. blissdata/data/nodes/scan_group.py +12 -0
  29. blissdata/data/remote_node.py +204 -0
  30. blissdata/data/scan.py +666 -0
  31. blissdata/h5api/__init__.py +1 -0
  32. blissdata/h5api/abstract.py +97 -0
  33. blissdata/h5api/dynamic_hdf5.py +153 -0
  34. blissdata/h5api/file_arguments.py +28 -0
  35. blissdata/h5api/static_hdf5.py +139 -0
  36. blissdata/h5api/utils/__init__.py +0 -0
  37. blissdata/h5api/utils/bliss.py +138 -0
  38. blissdata/h5api/utils/hdf5.py +280 -0
  39. blissdata/h5api/utils/hdf5_retry.py +98 -0
  40. blissdata/h5api/utils/lima.py +286 -0
  41. blissdata/h5api/utils/types.py +13 -0
  42. blissdata/redis/__init__.py +12 -0
  43. blissdata/redis/caching.py +390 -0
  44. blissdata/redis/connection.py +169 -0
  45. blissdata/redis/manager.py +164 -0
  46. blissdata/redis/proxy.py +971 -0
  47. blissdata/redis/scripting.py +24 -0
  48. blissdata/settings.py +1174 -0
  49. blissdata/streaming.py +819 -0
  50. blissdata/streaming_events.py +355 -0
  51. blissdata/tests/__init__.py +0 -0
  52. blissdata/tests/beacon/__init__.py +0 -0
  53. blissdata/tests/beacon/test_config.py +26 -0
  54. blissdata/tests/beacon/test_data.py +50 -0
  55. blissdata/tests/beacon/test_files.py +80 -0
  56. blissdata/tests/conftest.py +0 -0
  57. blissdata/tests/h5api/__init__.py +0 -0
  58. blissdata/tests/h5api/scanner.py +390 -0
  59. blissdata/tests/h5api/test_dynamic_files.py +309 -0
  60. blissdata/tests/h5api/test_static_files.py +169 -0
  61. blissdata/tests/redis/__init__.py +0 -0
  62. blissdata/tests/redis/manager.py +23 -0
  63. blissdata-0.3.4.dist-info/LICENSE +165 -0
  64. blissdata-0.3.4.dist-info/METADATA +79 -0
  65. blissdata-0.3.4.dist-info/RECORD +67 -0
  66. blissdata-0.3.4.dist-info/WHEEL +5 -0
  67. blissdata-0.3.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,97 @@
1
+ """Definition of the h5py-like Bliss Data API"""
2
+
3
+ from collections import abc
4
+ from abc import abstractmethod, abstractproperty
5
+ from typing import Iterator, Optional, Tuple
6
+ from numpy.typing import DTypeLike
7
+ from .utils import types
8
+
9
+
10
+ class Attributes(abc.Mapping):
11
+ """Node attributes."""
12
+
13
+
14
+ class Node:
15
+ """Node in the data tree."""
16
+
17
+ @abstractproperty
18
+ def name(self) -> str:
19
+ pass
20
+
21
+ @abstractproperty
22
+ def short_name(self) -> str:
23
+ pass
24
+
25
+ @abstractproperty
26
+ def attrs(self) -> Attributes:
27
+ pass
28
+
29
+ @abstractproperty
30
+ def parent(self) -> "Group":
31
+ pass
32
+
33
+ @abstractproperty
34
+ def file(self) -> "File":
35
+ pass
36
+
37
+
38
+ class Group(Node, abc.Mapping):
39
+ """Node in the data tree which contains other nodes."""
40
+
41
+ @abstractmethod
42
+ def __getitem__(self, key: str) -> Node:
43
+ pass
44
+
45
+ @abstractmethod
46
+ def __iter__(self) -> Iterator[Node]:
47
+ pass
48
+
49
+ @abstractmethod
50
+ def __len__(self) -> int:
51
+ pass
52
+
53
+
54
+ class Dataset(Node, abc.Sequence):
55
+ """Node in the data tree which contains data."""
56
+
57
+ @abstractmethod
58
+ def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
59
+ pass
60
+
61
+ @abstractmethod
62
+ def __len__(self) -> int:
63
+ pass
64
+
65
+ @abstractproperty
66
+ def dtype(self) -> DTypeLike:
67
+ pass
68
+
69
+ @abstractproperty
70
+ def shape(self) -> Tuple[int]:
71
+ pass
72
+
73
+ @abstractproperty
74
+ def size(self) -> int:
75
+ pass
76
+
77
+ @abstractproperty
78
+ def ndim(self) -> int:
79
+ pass
80
+
81
+
82
+ class File(Group):
83
+ """Root node in the data tree."""
84
+
85
+ def __enter__(self) -> "File":
86
+ return self
87
+
88
+ def __exit__(self, exception_type, exception_value, traceback) -> Optional[bool]:
89
+ self.close()
90
+
91
+ @abstractmethod
92
+ def close(self) -> None:
93
+ pass
94
+
95
+ @property
96
+ def parent(self) -> None:
97
+ return None
@@ -0,0 +1,153 @@
1
+ """Implementation of the h5py-like Bliss Data API with dynamic HDF5 files"""
2
+
3
+ from typing import Any, Iterator, Tuple, Type
4
+ from numpy.typing import DTypeLike
5
+ import h5py
6
+ from . import abstract
7
+ from .utils import hdf5
8
+ from .utils import bliss
9
+ from .utils import types
10
+ from .file_arguments import ignore_file_arguments
11
+
12
+
13
+ class Attributes(abstract.Attributes):
14
+ """Node attributes."""
15
+
16
+ def __init__(self, retry_handler: hdf5.DynamicHDF5Handler, name: str) -> None:
17
+ self.__retry_handler = retry_handler
18
+ self.__name = name
19
+ super().__init__()
20
+
21
+ def __getitem__(self, key: str) -> Any:
22
+ return self.__retry_handler.get_attr(self.__name, key)
23
+
24
+ def __iter__(self) -> Iterator[Any]:
25
+ yield from self.__retry_handler.iter_attrs(self.__name)
26
+
27
+ def __len__(self) -> int:
28
+ return self.__retry_handler.len_attrs(self.__name)
29
+
30
+
31
+ class Node:
32
+ """Node in the data tree."""
33
+
34
+ def __init__(self, retry_handler: hdf5.DynamicHDF5Handler, name: str) -> None:
35
+ self._retry_handler = retry_handler
36
+ if not name:
37
+ name = hdf5.SEP
38
+ self.__name = name
39
+ self.__attrs = None
40
+ self.__parent = None
41
+ self.__file = None
42
+ super().__init__()
43
+
44
+ @property
45
+ def name(self) -> str:
46
+ return self.__name
47
+
48
+ @property
49
+ def short_name(self) -> str:
50
+ return self.name.rpartition(hdf5.SEP)[-1]
51
+
52
+ @property
53
+ def parent(self) -> "Group":
54
+ if self.__parent is None:
55
+ parent_name = self.name.rpartition(hdf5.SEP)[0]
56
+ self.__parent = Group(self._retry_handler, parent_name)
57
+ return self.__parent
58
+
59
+ @property
60
+ def file(self) -> "File":
61
+ if self.__file is None:
62
+ self.__file = File(self._retry_handler)
63
+ return self.__file
64
+
65
+ @property
66
+ def attrs(self) -> Attributes:
67
+ if self.__attrs is None:
68
+ self.__attrs = Attributes(self._retry_handler, self.name)
69
+ return self.__attrs
70
+
71
+
72
+ class Group(Node, abstract.Group):
73
+ """Node in the data tree which contains other nodes."""
74
+
75
+ def __getitem__(self, key: str) -> Node:
76
+ if not key.startswith(hdf5.SEP):
77
+ key = f"{self.name}{hdf5.SEP}{key}"
78
+ h5py_obj = self._retry_handler.get_item(key)
79
+ if isinstance(h5py_obj, h5py.Group):
80
+ return Group(self._retry_handler, h5py_obj.name)
81
+ else:
82
+ return Dataset(self._retry_handler, h5py_obj.name)
83
+
84
+ def __iter__(self) -> Iterator[Node]:
85
+ yield from self._retry_handler.iter_item(self.name)
86
+
87
+ def __len__(self) -> int:
88
+ return self._retry_handler.len_item(self.name)
89
+
90
+
91
+ class Dataset(Node, abstract.Dataset):
92
+ """Node in the data tree which contains data."""
93
+
94
+ def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
95
+ return self._retry_handler.slice_dataset(self.name, idx)
96
+
97
+ def __iter__(self) -> Iterator[types.DataType]:
98
+ yield from self._retry_handler.iter_item(self.name)
99
+
100
+ def __len__(self) -> int:
101
+ return self._retry_handler.len_item(self.name)
102
+
103
+ @property
104
+ def dtype(self) -> DTypeLike:
105
+ return self._retry_handler.getattr_item(self.name, "dtype")
106
+
107
+ @property
108
+ def shape(self) -> Tuple[int]:
109
+ return self._retry_handler.getattr_item(self.name, "shape")
110
+
111
+ @property
112
+ def size(self) -> int:
113
+ return self._retry_handler.getattr_item(self.name, "size")
114
+
115
+ @property
116
+ def ndim(self) -> int:
117
+ return self._retry_handler.getattr_item(self.name, "ndim")
118
+
119
+
120
+ class File(Group, abstract.File):
121
+ """Root node in the data tree."""
122
+
123
+ def __init__(
124
+ self,
125
+ file: str,
126
+ hdf5_retry_handler: Type[
127
+ hdf5.DynamicHDF5Handler
128
+ ] = bliss.BlissDynamicHDF5Handler,
129
+ **openargs,
130
+ ) -> None:
131
+ if isinstance(file, str):
132
+ openargs = {
133
+ k: v
134
+ for k, v in openargs.items()
135
+ if k not in ignore_file_arguments("dynamic_hdf5")
136
+ }
137
+ file = hdf5_retry_handler(file, **openargs)
138
+ super().__init__(file, hdf5.SEP)
139
+
140
+ def close(self) -> None:
141
+ self._retry_handler.close()
142
+
143
+ @property
144
+ def parent(self) -> None:
145
+ return None
146
+
147
+ @property
148
+ def file(self) -> "File":
149
+ return self
150
+
151
+ @property
152
+ def short_name(self) -> str:
153
+ return self.name
@@ -0,0 +1,28 @@
1
+ """The `File` class in each context has specific arguments other than the `h5py.File`
2
+ arguments which are specific for that context (static HDF5, dynamic HDF5, ...).
3
+ """
4
+ from typing import Set
5
+
6
+
7
+ _CONTEXT_SPECIFIC_KEYS = {
8
+ "h5py": {"mode"},
9
+ "static_hdf5": set(),
10
+ "dynamic_hdf5": {
11
+ "hdf5_retry_handler",
12
+ "retry_timeout",
13
+ "retry_period",
14
+ "lima_names",
15
+ "instrument_name",
16
+ },
17
+ }
18
+
19
+
20
+ def ignore_file_arguments(context: str) -> Set[str]:
21
+ """Returns all keys with that should be ignore when instantiating the `File`
22
+ in a specific context"""
23
+ ignore = set()
24
+ for key, value in _CONTEXT_SPECIFIC_KEYS.items():
25
+ if key == context:
26
+ continue
27
+ ignore |= value
28
+ return ignore
@@ -0,0 +1,139 @@
1
+ """Implementation of the h5py-like Bliss Data API with static HDF5 files"""
2
+
3
+ from typing import Any, Iterator, Tuple
4
+ from numpy.typing import DTypeLike
5
+ import h5py
6
+ from silx.io import h5py_utils
7
+ from . import abstract
8
+ from .utils import hdf5
9
+ from .utils import types
10
+ from .file_arguments import ignore_file_arguments
11
+
12
+
13
+ class Attributes(abstract.Attributes):
14
+ """Node attributes."""
15
+
16
+ def __init__(self, attrs: h5py.AttributeManager) -> None:
17
+ self.__attrs = attrs
18
+ super().__init__()
19
+
20
+ def __getitem__(self, key: str) -> Any:
21
+ return self.__attrs[key]
22
+
23
+ def __iter__(self) -> Iterator[Any]:
24
+ yield from self.__attrs
25
+
26
+ def __len__(self) -> int:
27
+ return len(self.__attrs)
28
+
29
+
30
+ class Node:
31
+ """Node in the data tree."""
32
+
33
+ def __init__(self, h5py_obj: hdf5.HDF5Item) -> None:
34
+ self._h5py_obj = h5py_obj
35
+ self.__attrs = None
36
+ self.__parent = None
37
+ self.__file = None
38
+ super().__init__()
39
+
40
+ @property
41
+ def name(self) -> str:
42
+ return self._h5py_obj.name
43
+
44
+ @property
45
+ def short_name(self) -> str:
46
+ return self.name.rpartition(hdf5.SEP)[-1]
47
+
48
+ @property
49
+ def parent(self) -> "Group":
50
+ if self.__parent is None:
51
+ self.__parent = Group(self._h5py_obj.parent)
52
+ return self.__parent
53
+
54
+ @property
55
+ def file(self) -> "File":
56
+ if self.__file is None:
57
+ self.__file = File(self._h5py_obj.file)
58
+ return self.__file
59
+
60
+ @property
61
+ def attrs(self) -> Attributes:
62
+ if self.__attrs is None:
63
+ self.__attrs = Attributes(self._h5py_obj.attrs)
64
+ return self.__attrs
65
+
66
+
67
+ class Group(Node, abstract.Group):
68
+ """Node in the data tree which contains other nodes."""
69
+
70
+ def __getitem__(self, key: str) -> Node:
71
+ h5py_obj = self._h5py_obj[key]
72
+ if isinstance(h5py_obj, h5py.Group):
73
+ return Group(h5py_obj)
74
+ else:
75
+ return Dataset(h5py_obj)
76
+
77
+ def __iter__(self) -> Iterator[Node]:
78
+ yield from self._h5py_obj
79
+
80
+ def __len__(self) -> int:
81
+ return len(self._h5py_obj)
82
+
83
+
84
+ class Dataset(Node, abstract.Dataset):
85
+ """Node in the data tree which contains data."""
86
+
87
+ def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
88
+ return self._h5py_obj[idx]
89
+
90
+ def __iter__(self) -> Iterator[types.DataType]:
91
+ yield from self._h5py_obj
92
+
93
+ def __len__(self) -> int:
94
+ return len(self._h5py_obj)
95
+
96
+ @property
97
+ def dtype(self) -> DTypeLike:
98
+ return self._h5py_obj.dtype
99
+
100
+ @property
101
+ def shape(self) -> Tuple[int]:
102
+ return self._h5py_obj.shape
103
+
104
+ @property
105
+ def size(self) -> int:
106
+ return self._h5py_obj.size
107
+
108
+ @property
109
+ def ndim(self) -> int:
110
+ return self._h5py_obj.ndim
111
+
112
+
113
+ class File(Group, abstract.File):
114
+ """Root node in the data tree."""
115
+
116
+ def __init__(self, file: str, **openargs) -> None:
117
+ if isinstance(file, str):
118
+ openargs = {
119
+ k: v
120
+ for k, v in openargs.items()
121
+ if k not in ignore_file_arguments("dynamic_hdf5")
122
+ }
123
+ file = h5py_utils.File(file, **openargs)
124
+ super().__init__(file)
125
+
126
+ def close(self) -> None:
127
+ self._h5py_obj.close()
128
+
129
+ @property
130
+ def parent(self) -> None:
131
+ return None
132
+
133
+ @property
134
+ def file(self) -> "File":
135
+ return self
136
+
137
+ @property
138
+ def short_name(self) -> str:
139
+ return self.name
File without changes
@@ -0,0 +1,138 @@
1
+ import os
2
+ import re
3
+ from typing import Optional, Sequence
4
+
5
+ import h5py
6
+
7
+ from . import lima
8
+ from . import hdf5
9
+ from .hdf5_retry import RetryError
10
+
11
+
12
+ class BlissDynamicHDF5Handler(hdf5.DynamicHDF5Handler):
13
+ """Each NXentry has a writer status which can be
14
+ STARTING, RUNNING, SUCCEEDED or FAILED. This status
15
+ is used as stop criterium for iteration.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ *args,
21
+ lima_names: Sequence[str] = tuple(),
22
+ instrument_name: Optional[str] = None,
23
+ **kwargs,
24
+ ):
25
+ self._match_lima_group = [
26
+ re.compile(
27
+ f"{hdf5.SEP}[0-9]+\\.[0-9]+{hdf5.SEP}instrument{hdf5.SEP}{name}{hdf5.SEP}data"
28
+ )
29
+ for name in lima_names
30
+ ]
31
+ self._match_lima_dataset = [
32
+ re.compile(
33
+ f"{hdf5.SEP}[0-9]+\\.[0-9]+{hdf5.SEP}instrument{hdf5.SEP}{name}{hdf5.SEP}data"
34
+ )
35
+ for name in lima_names
36
+ ]
37
+ self._non_native_items = dict()
38
+ self._lima_kwargs = {"user_instrument_name": instrument_name}
39
+ super().__init__(*args, **kwargs)
40
+
41
+ def reset(self) -> None:
42
+ super().reset()
43
+ for item in self._non_native_items.values():
44
+ item.reset()
45
+
46
+ def close(self) -> None:
47
+ for item in self._non_native_items.values():
48
+ item.close()
49
+ self._non_native_items = dict()
50
+ super().close()
51
+
52
+ def _check_dataset_before_read(self, item: hdf5.HDF5Dataset):
53
+ """Check that the last source of a virtual dataset is released by the writer."""
54
+ if not hasattr(item, "is_virtual"):
55
+ return
56
+ if not item.is_virtual:
57
+ return
58
+ sources = item.virtual_sources()
59
+ if not sources:
60
+ return
61
+ _, filename, _, _ = sources[-1]
62
+ if not os.path.isabs(filename):
63
+ filename = os.path.abspath(
64
+ os.path.join(os.path.dirname(item.file.filename), filename)
65
+ )
66
+ try:
67
+ with h5py.File(filename, "r"):
68
+ pass
69
+ except Exception:
70
+ raise RetryError(f"cannot open '{filename}'")
71
+
72
+ def _get_item(self, name: str) -> hdf5.HDF5Item:
73
+ h5item = self._non_native_items.get(name)
74
+ if h5item is not None:
75
+ return h5item
76
+
77
+ try:
78
+ return super()._get_item(name)
79
+ except KeyError as e:
80
+ exception = e
81
+
82
+ if any(m.match(name) for m in self._match_lima_dataset):
83
+ dirname = os.path.dirname(self.file_obj.filename)
84
+ h5item = lima.LimaDataset(name, dirname, **self._lima_kwargs)
85
+ self._non_native_items[name] = h5item
86
+ return h5item
87
+
88
+ if any(m.match(name) for m in self._match_lima_group):
89
+ dirname = os.path.dirname(self.file_obj.filename)
90
+ h5item = lima.LimaGroup(name, dirname, **self._lima_kwargs)
91
+ self._non_native_items[name] = h5item
92
+ return h5item
93
+
94
+ raise exception
95
+
96
+ @staticmethod
97
+ def is_group(h5item: hdf5.HDF5Item):
98
+ return isinstance(h5item, (h5py.Group, h5py.File, lima.LimaGroup))
99
+
100
+ def _is_initialized(self, h5item: hdf5.HDF5Item) -> bool:
101
+ try:
102
+ if h5item.name == hdf5.SEP:
103
+ return False
104
+ scan = [s for s in h5item.name.split(hdf5.SEP) if s][0]
105
+ nxentry = self.file_obj[scan]
106
+ except AttributeError:
107
+ raise RetryError("file is closed")
108
+ if "end_time" in nxentry:
109
+ # Last dataset written to the file
110
+ return True
111
+ return self._get_writer_status(nxentry) in ("RUNNING", "SUCCEEDED", "FAILED")
112
+
113
+ def _is_finished(self, h5item: hdf5.HDF5Item) -> bool:
114
+ try:
115
+ if h5item.name == hdf5.SEP:
116
+ # Assume there could be always more scans coming
117
+ return False
118
+ scan = [s for s in h5item.name.split(hdf5.SEP) if s][0]
119
+ nxentry = self.file_obj[scan]
120
+ except AttributeError:
121
+ raise RetryError("file is closed")
122
+ return "end_time" in nxentry # Last dataset written to the file
123
+
124
+ def _get_writer_status(self, nxentry: h5py.Dataset) -> Optional[str]:
125
+ nxnote = nxentry.get("writer", None)
126
+ if nxnote is None:
127
+ # writer notes not created yet
128
+ return None
129
+ status = nxnote.get("status", None)
130
+ if status is None:
131
+ # writer status not set yet
132
+ return None
133
+ status = status[()]
134
+ try:
135
+ status = status.decode()
136
+ except AttributeError:
137
+ pass
138
+ return status