plato-hdf5 2024.1.3__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.
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plato-hdf5
|
|
3
|
+
Version: 2024.1.3
|
|
4
|
+
Summary: HDF5 Persistence sub-class for CGSE
|
|
5
|
+
Author: IVS KU Leuven
|
|
6
|
+
Maintainer-email: Rik Huygen <rik.huygen@kuleuven.be>, Sara Regibo <sara.regibo@kuleuven.be>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: CGSE,Common-EGSE,hardware testing,software framework
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: cgse-common
|
|
11
|
+
Requires-Dist: h5py
|
|
12
|
+
Requires-Dist: natsort
|
|
13
|
+
Requires-Dist: plato-spw
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-mock; extra == 'test'
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "plato-hdf5"
|
|
3
|
+
version = "2024.1.3"
|
|
4
|
+
description = "HDF5 Persistence sub-class for CGSE"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "IVS KU Leuven"}
|
|
7
|
+
]
|
|
8
|
+
maintainers = [
|
|
9
|
+
{name = "Rik Huygen", email = "rik.huygen@kuleuven.be"},
|
|
10
|
+
{name = "Sara Regibo", email = "sara.regibo@kuleuven.be"}
|
|
11
|
+
]
|
|
12
|
+
readme = {"file" = "README.md", "content-type" = "text/markdown"}
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
keywords = [
|
|
16
|
+
"CGSE",
|
|
17
|
+
"Common-EGSE",
|
|
18
|
+
"hardware testing",
|
|
19
|
+
"software framework"
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"cgse-common",
|
|
23
|
+
"plato-spw",
|
|
24
|
+
"h5py",
|
|
25
|
+
"natsort",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
test = ["pytest", "pytest-mock", "pytest-cov"]
|
|
30
|
+
|
|
31
|
+
[project.entry-points."cgse.version"]
|
|
32
|
+
plato-hdf5 = 'egse.plugins'
|
|
33
|
+
|
|
34
|
+
[project.entry-points."cgse.storage.persistence"]
|
|
35
|
+
HDF5 = "egse.plugins.storage.hdf5:HDF5"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.sdist]
|
|
38
|
+
exclude = [
|
|
39
|
+
"/tests",
|
|
40
|
+
"/pytest.ini",
|
|
41
|
+
"/.gitignore",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/egse", "src/scripts"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 120
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
extend-select = ["E501"]
|
|
52
|
+
|
|
53
|
+
[build-system]
|
|
54
|
+
requires = ["hatchling"]
|
|
55
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import h5py
|
|
6
|
+
import natsort
|
|
7
|
+
import numpy as np
|
|
8
|
+
from egse.spw import DataDataPacket
|
|
9
|
+
from egse.spw import HousekeepingPacket
|
|
10
|
+
from egse.spw import OverscanDataPacket
|
|
11
|
+
from egse.spw import TimecodePacket
|
|
12
|
+
from egse.persistence import PersistenceLayer # -> circular dependency because this import loads this module as a plugin
|
|
13
|
+
|
|
14
|
+
LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HDF5(PersistenceLayer):
|
|
18
|
+
extension = "hdf5"
|
|
19
|
+
|
|
20
|
+
def __init__(self, filename, prep: dict = None):
|
|
21
|
+
"""
|
|
22
|
+
The `prep` argument needs at least the following mandatory key:value pairs:
|
|
23
|
+
|
|
24
|
+
* mode: the mode used for opening the file [default is 'r']
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
# LOGGER.debug(f"{h5py.version.hdf5_version=}")
|
|
28
|
+
self._filepath = Path(filename)
|
|
29
|
+
self._mode = prep.get("mode") or "r"
|
|
30
|
+
self._h5file: Optional[h5py.File] = None
|
|
31
|
+
|
|
32
|
+
def __enter__(self):
|
|
33
|
+
self.open(mode=self._mode)
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
37
|
+
self.close()
|
|
38
|
+
|
|
39
|
+
def open(self, mode=None):
|
|
40
|
+
self._mode = mode or self._mode
|
|
41
|
+
LOGGER.debug(f"Opening file {self._filepath} in mode '{self._mode}'")
|
|
42
|
+
self._h5file = h5py.File(self._filepath, mode=self._mode)
|
|
43
|
+
|
|
44
|
+
# File "h5py/h5f.pyx", line 554, in h5py.h5f.FileID.start_swmr_write
|
|
45
|
+
# RuntimeError: Unable to start swmr writing (file superblock version - should be at least 3)
|
|
46
|
+
# self._h5file.swmr_mode = True
|
|
47
|
+
|
|
48
|
+
def close(self):
|
|
49
|
+
self._h5file.close()
|
|
50
|
+
|
|
51
|
+
def exists(self):
|
|
52
|
+
return self._filepath.exists()
|
|
53
|
+
|
|
54
|
+
def create(self, data):
|
|
55
|
+
"""
|
|
56
|
+
Store the given data in the HDF5 file. The data argument shall be a dictionary where the
|
|
57
|
+
keys represent the group where the data shall be saved, and the value is the data to be
|
|
58
|
+
saved. When the key ends with ":ATTRS", then the value is a list of attributes to that
|
|
59
|
+
group. Values can be of different type and are processed if needed.
|
|
60
|
+
|
|
61
|
+
An example data argument:
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
"/10/timecode": tc_packet,
|
|
65
|
+
"/10/timecode:ATTRS": [("timestamp", timestamp)],
|
|
66
|
+
"/10/command/": f"{command.__name__}, {args=}",
|
|
67
|
+
"/10/register/": self.register_map.get_memory_map_as_ndarray()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
The example saves a Timecode packet in the group "/10/timecode" and attaches a timestamp
|
|
71
|
+
as an attribute called "timestamp" to the same group. It then adds a command string in
|
|
72
|
+
the "/10/command" group and finally adds a register memory map (an np.ndarray) in the group
|
|
73
|
+
"/10/register".
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
data (dict): a dictionary containing the data that needs to be saved.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
None.
|
|
80
|
+
"""
|
|
81
|
+
for key, value in data.items():
|
|
82
|
+
if key.endswith(":ATTRS"):
|
|
83
|
+
a_key = key.split(":")[0]
|
|
84
|
+
for k, v in value:
|
|
85
|
+
self._h5file[a_key].attrs[k] = v
|
|
86
|
+
if isinstance(value, TimecodePacket):
|
|
87
|
+
self._h5file[key] = value.timecode
|
|
88
|
+
if isinstance(value, HousekeepingPacket):
|
|
89
|
+
self._h5file[key] = value.packet_as_ndarray
|
|
90
|
+
if isinstance(value, HousekeepingData):
|
|
91
|
+
self._h5file[key] = value.data_as_ndarray
|
|
92
|
+
if isinstance(value, DataDataPacket):
|
|
93
|
+
self._h5file[key] = value.packet_as_ndarray
|
|
94
|
+
if isinstance(value, OverscanDataPacket):
|
|
95
|
+
self._h5file[key] = value.packet_as_ndarray
|
|
96
|
+
if isinstance(value, (str, bytearray, np.ndarray)):
|
|
97
|
+
|
|
98
|
+
# if we save a command, put it into a 'commands' group.
|
|
99
|
+
# This is a special case that is the result of issue #1461
|
|
100
|
+
|
|
101
|
+
if 'command' in key:
|
|
102
|
+
idx = key.split('/')[1]
|
|
103
|
+
if idx in self._h5file and 'commands' in self._h5file[idx]:
|
|
104
|
+
last_idx = int(sorted(self._h5file[f"/{idx}/commands"].keys())[-1])
|
|
105
|
+
key = f"/{idx}/commands/{last_idx + 1}"
|
|
106
|
+
else:
|
|
107
|
+
key = f"/{idx}/commands/0"
|
|
108
|
+
|
|
109
|
+
self._h5file[key] = value
|
|
110
|
+
|
|
111
|
+
def read(self, select=None):
|
|
112
|
+
"""
|
|
113
|
+
Read information or data from the HDF5 file.
|
|
114
|
+
|
|
115
|
+
The `select` argument can contain the following information:
|
|
116
|
+
|
|
117
|
+
* the string 'number_of_groups': request to determine the number of top groups in
|
|
118
|
+
the HDF5 file.
|
|
119
|
+
* the string 'last_top_group': request the name/key of the last item in the top group.
|
|
120
|
+
The last item is the last element of the list of keys, sorted with natural order.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
select (str or dict): specify which information should be read
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
When 'number_of_groups', return an integer, when 'last_top_group' return a string.
|
|
127
|
+
"""
|
|
128
|
+
if select == "number_of_groups":
|
|
129
|
+
return len(self._h5file.keys())
|
|
130
|
+
if select == "last_top_group":
|
|
131
|
+
keys = self._h5file.keys()
|
|
132
|
+
|
|
133
|
+
LOGGER.debug(f"{self._h5file.filename}: {keys=}")
|
|
134
|
+
|
|
135
|
+
return 0 if len(keys) == 0 else natsort.natsorted(keys)[-1]
|
|
136
|
+
|
|
137
|
+
# This following lines is a longer version of the previous two lines, keep them for
|
|
138
|
+
# debugging because I had problems and not yet sure what is the cause...
|
|
139
|
+
|
|
140
|
+
# sorted_keys = natsort.natsorted(keys)
|
|
141
|
+
# LOGGER.debug(f"{self._h5file.filename}: {sorted_keys=}")
|
|
142
|
+
# key = sorted_keys[-1]
|
|
143
|
+
# LOGGER.debug(f"{key=}")
|
|
144
|
+
# return key
|
|
145
|
+
|
|
146
|
+
def update(self, idx, data):
|
|
147
|
+
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
def delete(self, idx):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def get_filepath(self):
|
|
154
|
+
return self._filepath
|