h5pack 1.1.0__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.
h5pack/.DS_Store ADDED
Binary file
h5pack/__init__.py ADDED
File without changes
h5pack/cli/__init__.py ADDED
File without changes
h5pack/cli/checksum.py ADDED
@@ -0,0 +1,126 @@
1
+ import os
2
+ from time import perf_counter
3
+ from argparse import Namespace
4
+ from ..core.io import (
5
+ add_extension,
6
+ get_dir_files
7
+ )
8
+ from ..core.guards import is_file_with_ext
9
+ from ..core.display import (
10
+ exit_error,
11
+ exit_warning,
12
+ print_warning,
13
+ print_error,
14
+ )
15
+ from ..core.utils import (
16
+ get_file_checksum,
17
+ time_to_str
18
+ )
19
+
20
+
21
+ def cmd_checksum(args: Namespace) -> None:
22
+ """Calculates or verifies checksums associated with `.h5` files.
23
+
24
+ Args:
25
+ args (Namespace): User input arguments provided through the console.
26
+ """
27
+ # Check input type
28
+ if is_file_with_ext(file=args.input, ext=".sha256"): # Verify
29
+ if args.save:
30
+ print_warning("--save ignored for .sha256 input")
31
+
32
+ # Read lines and check they contain only two elements
33
+ root_dir = os.path.dirname(args.input)
34
+ print(f"Using root folder '{os.path.abspath(root_dir)}'")
35
+ print(f"Verifying checksum in '{args.input}' ...")
36
+
37
+ start_time = perf_counter()
38
+
39
+ with open(args.input, "r") as f:
40
+ for line_idx, line in enumerate(f):
41
+ h5_filename, saved_checksum = line.split("\t")
42
+ h5_file = os.path.join(root_dir, h5_filename)
43
+ saved_checksum = saved_checksum.rstrip("\n")
44
+
45
+ if not is_file_with_ext(h5_file, ext=".h5"):
46
+ exit_error(
47
+ f"Invalid file '{h5_file}' reference in '{args.input}'"
48
+ f" (line {line_idx + 1})"
49
+ )
50
+
51
+ checksum = get_file_checksum(h5_file, hash="sha256")
52
+
53
+ if saved_checksum == checksum:
54
+ print(
55
+ f"{h5_filename}\t{saved_checksum} [OK]"
56
+ )
57
+
58
+ else:
59
+ print_error(
60
+ f"{h5_filename} [MISMATCH]"
61
+ f"\n - Saved: {saved_checksum}"
62
+ f"\n - Calculated: {checksum}"
63
+ )
64
+
65
+ end_time = perf_counter()
66
+ elapsed_time_repr = time_to_str(end_time - start_time, abbrev=False)
67
+ print(f"Checksum verification completed in {elapsed_time_repr}")
68
+
69
+ else: # Calculate
70
+ if is_file_with_ext(args.input, ext=".h5"):
71
+ all_files = [args.input]
72
+
73
+ elif os.path.isdir(args.input):
74
+ all_files = get_dir_files(
75
+ args.input,
76
+ ext=".h5",
77
+ recursive=args.recursive
78
+ )
79
+
80
+ else:
81
+ exit_error(
82
+ "Input must be an .h5 file or a folder containing .h5 files"
83
+ )
84
+
85
+ if len(all_files) == 0:
86
+ if not args.recursive:
87
+ exit_warning(
88
+ f"0 .h5 files found in in '{args.input}'. Use "
89
+ "--recursive/-r if you intended to perform a recursive "
90
+ "search"
91
+ )
92
+
93
+ else:
94
+ exit_warning(f"0 .h5 files found in '{args.input}'")
95
+
96
+ print(f"Calculating checksum for .h5 files in '{args.input}' ...")
97
+
98
+ if args.save:
99
+ checksum_file = add_extension(args.save, ext=".sha256")
100
+
101
+ with open(add_extension(args.save, ext=".sha256"), "w") as f:
102
+ start_time = perf_counter()
103
+
104
+ for file in all_files:
105
+ checksum = get_file_checksum(file, hash="sha256")
106
+ checksum_repr = f"{os.path.basename(file)}\t{checksum}"
107
+ f.write(f"{checksum_repr}\n")
108
+ print(checksum_repr)
109
+
110
+ end_time = perf_counter()
111
+ elapsed_time_repr = time_to_str(end_time - start_time)
112
+ print(
113
+ f"Checksum calculation completed in {elapsed_time_repr} "
114
+ f"and saved to '{checksum_file}'"
115
+ )
116
+
117
+ else:
118
+ start_time = perf_counter()
119
+
120
+ for file in all_files:
121
+ checksum = get_file_checksum(file, hash="sha256")
122
+ print(f"{os.path.basename(file)}\t{checksum}")
123
+
124
+ end_time = perf_counter()
125
+ elapsed_time_repr = time_to_str(end_time - start_time)
126
+ print(f"Checksum calculation completed in {elapsed_time_repr}")
h5pack/cli/info.py ADDED
@@ -0,0 +1,83 @@
1
+ import os
2
+ import h5py
3
+ from argparse import Namespace
4
+ from ..core.guards import is_file_with_ext
5
+ from ..core.display import (
6
+ exit_error,
7
+ print_warning
8
+ )
9
+
10
+
11
+ def cmd_info(args: Namespace) -> None:
12
+ """Inspects a `.h5` file generated with `h5pack`.
13
+
14
+ Args:
15
+ args (Namespace): Input user arguments provided through the console.
16
+ """
17
+ # Check file exists
18
+ if not is_file_with_ext(args.input, ext=".h5"):
19
+ exit_error(f"Invalid input file '{args.input}'")
20
+
21
+ # Print info
22
+ print(f"Input file: '{args.input}'" )
23
+
24
+ with h5py.File(args.input, "r") as h5_file:
25
+ # Check if producer is h5pack, otherwise will not be correctly parsed
26
+ if not h5_file.attrs.get("producer", "").startswith("h5pack"):
27
+ exit_error(
28
+ "This file was not created using h5pack, so it may not be "
29
+ "formatted as expected and cannot be reliably parsed"
30
+ )
31
+
32
+ if len(h5_file.attrs) > 0:
33
+ print("File attribute(s):")
34
+ key_ljust = max([len(k) for k in h5_file.attrs]) + 6
35
+
36
+ for k, v in h5_file.attrs.items():
37
+ print(f" - {k}:".ljust(key_ljust) + f"{v}")
38
+
39
+ # Get data group level attributes
40
+ for data_group_name, data_group_data in h5_file.items():
41
+ print(f"Data group '{data_group_name}':")
42
+
43
+ if len(data_group_data.attrs) > 0:
44
+ key_ljust = max([len(k) for k in data_group_data.attrs]) + 6
45
+
46
+ print(f"'{data_group_name}' attribute(s):")
47
+
48
+ for k, v in data_group_data.items():
49
+ print(f" - {k}:".ljust(key_ljust) + f"{v}")
50
+
51
+ # Get dataset level info
52
+ for dataset_name, dataset_data in data_group_data.items():
53
+ if len(dataset_data.attrs) > 0:
54
+ key_ljust = max([len(k) for k in dataset_data.attrs]) + 8
55
+ print(f" - '{dataset_name}' attribute(s):")
56
+
57
+ for k, v in dataset_data.attrs.items():
58
+ print(f" - {k}:".ljust(key_ljust) + f"{v}")
59
+
60
+ print(f" - '{dataset_name}' data attribute(s):")
61
+ print(f" - shape: {dataset_data.shape}")
62
+ print(f" - dtype: {dataset_data.dtype}")
63
+
64
+ # If virtual, check paths are accesible and report broken paths
65
+ if h5_file.attrs.get("is_virtual") is not None:
66
+ print("Virtual dataset sources:")
67
+
68
+ for data_group_name, data_group_data in h5_file.items():
69
+ for dataset_name, dataset_data in data_group_data.items():
70
+ plist = dataset_data.id.get_create_plist()
71
+
72
+ print(f" - '{dataset_name}' source(s):")
73
+
74
+ for file_idx in range(plist.get_virtual_count()):
75
+ file = plist.get_virtual_filename(file_idx)
76
+
77
+ if not os.path.isfile(file):
78
+ print_warning(
79
+ f" - {file_idx}: {file} [NOT FOUND]"
80
+ )
81
+
82
+ else:
83
+ print(f" - {file_idx}: {file} [OK]")
h5pack/cli/main.py ADDED
@@ -0,0 +1,234 @@
1
+ import sys
2
+ import argparse
3
+ from datetime import datetime
4
+ from importlib.metadata import version
5
+ from .checksum import cmd_checksum
6
+ from .info import cmd_info
7
+ from .pack import cmd_pack
8
+ from .unpack import cmd_unpack
9
+ from .virtual import cmd_virtual
10
+
11
+
12
+ def get_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ description="pack/unpack/inspect hierarchical data format datasets",
15
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
16
+ allow_abbrev=False
17
+ )
18
+ subparser = parser.add_subparsers(dest="action")
19
+
20
+ # Pack parser
21
+ pack_parser = subparser.add_parser(
22
+ "pack",
23
+ description="pack data into HDF5 dataset files",
24
+ help="pack data into HDF5 dataset files",
25
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
26
+ allow_abbrev=False
27
+ )
28
+ pack_parser.add_argument(
29
+ "-c", "--config",
30
+ type=str,
31
+ default="h5pack.yaml",
32
+ help=".yaml configuration file containing dataset specifications"
33
+ )
34
+ pack_parser.add_argument(
35
+ "-o", "--output",
36
+ type=str,
37
+ required=True,
38
+ help="output HDF5 partition file(s) with .h5 extension"
39
+ )
40
+ pack_partitions_parser = pack_parser.add_mutually_exclusive_group()
41
+ pack_partitions_parser.add_argument(
42
+ "-p", "--partitions",
43
+ type=int,
44
+ default=1,
45
+ help="number of partitions to create"
46
+ )
47
+ pack_parser.add_argument(
48
+ "-f", "--files-per-partition",
49
+ type=int,
50
+ help="number of files per partition"
51
+ )
52
+ pack_parser.add_argument(
53
+ "-d", "--dataset",
54
+ type=str,
55
+ required=True,
56
+ help="name of the dataset to generate"
57
+ )
58
+ pack_parser.add_argument(
59
+ "--create-virtual",
60
+ action="store_true",
61
+ help="create a virtual layout when two or more partitions are created"
62
+ )
63
+ pack_parser.add_argument(
64
+ "--skip-validation",
65
+ action="store_true",
66
+ help="skip validating files before generating the partition(s)"
67
+ )
68
+ pack_parser.add_argument(
69
+ "--skip-checksum",
70
+ action="store_true",
71
+ help="skip generating the checksum file"
72
+ )
73
+ pack_parser.add_argument(
74
+ "--overwrite",
75
+ action="store_true",
76
+ help="allow overwriting existing files"
77
+ )
78
+ pack_parser.add_argument(
79
+ "-w", "--workers",
80
+ type=int,
81
+ default=1,
82
+ help="number of workers (0 means 1 worker per core)"
83
+ )
84
+ pack_parser.add_argument(
85
+ "-u", "--unattended",
86
+ action="store_true",
87
+ help="unattended mode (no user prompts)"
88
+ )
89
+
90
+ # Unpack parser
91
+ unpack_parser = subparser.add_parser(
92
+ "unpack",
93
+ description="unpack HDF5 datasets into individual files",
94
+ help="unpack HDF5 datasets datasets into individual files",
95
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
96
+ allow_abbrev=False
97
+ )
98
+ unpack_parser.add_argument(
99
+ "input",
100
+ help="input .h5 file"
101
+ )
102
+ unpack_parser.add_argument(
103
+ "-o", "--output",
104
+ type=str,
105
+ help="output folder"
106
+ )
107
+
108
+ # Virtual parser
109
+ virtual_parser = subparser.add_parser(
110
+ "virtual",
111
+ description="create virtual HDF5 datasets",
112
+ help="create virtual HDF5 datasets",
113
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
114
+ allow_abbrev=False
115
+ )
116
+ virtual_parser.add_argument(
117
+ "input",
118
+ type=str,
119
+ nargs="+",
120
+ help="input .h5 file(s) or folder(s) containing .h5 file(s)"
121
+ )
122
+ virtual_parser.add_argument(
123
+ "-o", "--output",
124
+ type=str,
125
+ required=True,
126
+ help="output HDF5 file(s) with .h5 extension"
127
+ )
128
+ virtual_parser.add_argument(
129
+ "-r", "--recursive",
130
+ action="store_true",
131
+ help="search folders recursively"
132
+ )
133
+ virtual_parser.add_argument(
134
+ "-a", "--attrs",
135
+ type=str,
136
+ nargs="+",
137
+ metavar="KEY VALUE",
138
+ help="top level attributes to write as a list of 'key' 'value' pairs"
139
+ )
140
+ virtual_pattr_parser = virtual_parser.add_mutually_exclusive_group()
141
+ virtual_pattr_parser.add_argument(
142
+ "-s", "--select",
143
+ type=str,
144
+ metavar="PATTERN",
145
+ help="select pattern include matching elements from --input"
146
+ )
147
+ virtual_pattr_parser.add_argument(
148
+ "-f", "--filter",
149
+ type=str,
150
+ metavar="PATTERN",
151
+ help="filter pattern to remove matching elements from --input"
152
+ )
153
+ virtual_parser.add_argument(
154
+ "--force-abspath",
155
+ action="store_true",
156
+ help="force all embedded paths to be absolute paths"
157
+ )
158
+ virtual_parser.add_argument(
159
+ "-u", "--unattended",
160
+ action="store_true",
161
+ help="unattended mode (no user prompts)"
162
+ )
163
+
164
+ # Info parser
165
+ info_parser = subparser.add_parser(
166
+ "info",
167
+ description="inspect HDF5 datasets",
168
+ help="inspect HDF5 datasets",
169
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
170
+ allow_abbrev=False
171
+ )
172
+ info_parser.add_argument(
173
+ "input",
174
+ help="input .h5 file"
175
+ )
176
+
177
+ # Checksum parser
178
+ checksum_parser = subparser.add_parser(
179
+ "checksum",
180
+ help="create/verify virtual HDF5 datasets checksum",
181
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
182
+ allow_abbrev=False
183
+ )
184
+ checksum_parser.add_argument(
185
+ "input",
186
+ type=str,
187
+ help=".sha256 file (to verify) or file or folder (to calculate)"
188
+ )
189
+ checksum_parser.add_argument(
190
+ "--save",
191
+ type=str,
192
+ help="save calculated checksum to a .sha256 file"
193
+ )
194
+ checksum_parser.add_argument(
195
+ "-r", "--recursive",
196
+ action="store_true",
197
+ help="search folders recursively if input is a folder"
198
+ )
199
+
200
+ return parser
201
+
202
+
203
+ def main() -> int:
204
+ if (
205
+ len(sys.argv) == 1
206
+ or (len(sys.argv) == 2 and sys.argv[1] in ("--version"))
207
+ ):
208
+ print(
209
+ f"h5pack version {version('h5pack')} 2024-{datetime.now().year} "
210
+ "developed by Esteban Gómez (Speech Interaction Technology, Aalto "
211
+ "University)"
212
+ )
213
+ sys.exit(0)
214
+
215
+ parser = get_parser()
216
+ args = parser.parse_args()
217
+
218
+ if args.action == "pack":
219
+ cmd_pack(args)
220
+
221
+ elif args.action == "virtual":
222
+ cmd_virtual(args)
223
+
224
+ elif args.action == "checksum":
225
+ cmd_checksum(args)
226
+
227
+ elif args.action == "info":
228
+ cmd_info(args)
229
+
230
+ elif args.action == "unpack":
231
+ cmd_unpack(args)
232
+
233
+ else:
234
+ raise AssertionError