h5t 0.2.0__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.
h5t-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 binado
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
h5t-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: h5t
3
+ Version: 0.2.0
4
+ Summary: Load HDF5 groups as detached, typed Python records.
5
+ Keywords: hdf5,h5py,schema,pydantic,typing,validation
6
+ Author: binado
7
+ Author-email: binado <bernardopveronese@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: h5py>=3.10
23
+ Requires-Dist: numpy>=1.26
24
+ Requires-Dist: pydantic>=2.4,<3
25
+ Requires-Python: >=3.11
26
+ Project-URL: Homepage, https://github.com/binado/h5t
27
+ Project-URL: Repository, https://github.com/binado/h5t
28
+ Project-URL: Issues, https://github.com/binado/h5t/issues
29
+ Project-URL: Changelog, https://github.com/binado/h5t/blob/main/CHANGELOG.md
30
+ Description-Content-Type: text/markdown
31
+
32
+ # h5t
33
+
34
+ [![PyPI](https://img.shields.io/pypi/v/h5t.svg)](https://pypi.org/project/h5t/)
35
+ [![Python versions](https://img.shields.io/pypi/pyversions/h5t.svg)](https://pypi.org/project/h5t/)
36
+ [![CI](https://github.com/binado/h5t/actions/workflows/ci.yml/badge.svg)](https://github.com/binado/h5t/actions/workflows/ci.yml)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/binado/h5t/blob/main/LICENSE)
38
+
39
+ `h5t` loads HDF5 groups into detached, typed Python records. Group attributes and
40
+ ordinary NumPy-array fields are materialized while the file is open. Typed datasets
41
+ keep snapshot metadata and can read their payload lazily without retaining an open
42
+ file descriptor.
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install h5t # or: uv add h5t
48
+ ```
49
+
50
+ Requires Python 3.11+. `h5py`, `numpy`, and Pydantic v2 are installed automatically.
51
+
52
+ ## Example
53
+
54
+ ```python
55
+ import json
56
+ from pathlib import Path
57
+ from typing import Annotated, Any
58
+
59
+ import numpy as np
60
+
61
+ import h5t
62
+
63
+
64
+ class Measurement(h5t.Dataset, extras="forbid"):
65
+ unit: str
66
+
67
+
68
+ class Nested(h5t.Group):
69
+ label: str
70
+
71
+
72
+ class Result(h5t.Group, extras="ignore"):
73
+ version: int # implicit HDF5 attribute
74
+ title: Annotated[str, h5t.Name("name")] # renamed attribute
75
+ config: Annotated[
76
+ dict[str, Any],
77
+ h5t.Attr(converter=json.loads),
78
+ ]
79
+ array_attr: Annotated[np.ndarray, h5t.Attr()]
80
+ values: np.ndarray # eager dataset payload
81
+ measurement: Measurement # lazy detached dataset
82
+ eager_measurement: Annotated[Measurement, h5t.Eager()]
83
+ nested: Nested # recursively loaded group
84
+ note: str | None # absent becomes None
85
+ revision: int = 1 # absent uses a validated default
86
+
87
+
88
+ result = Result.from_file(Path("result.h5"), root="/")
89
+ ```
90
+
91
+ `result.attrs` and `result.measurement.attrs` are immutable mappings keyed by their
92
+ on-disk HDF5 names. Declared attributes contain their Pydantic-processed values;
93
+ undeclared attributes retained under `extras="ignore"` contain the raw h5py values.
94
+
95
+ Typed datasets expose snapshot metadata and explicit data access:
96
+
97
+ ```python
98
+ dataset = result.measurement
99
+ dataset.path, dataset.shape, dataset.dtype, dataset.ndim
100
+
101
+ complete = dataset.data # first access reads and caches an ndarray
102
+ assert dataset.read() is complete
103
+
104
+ with dataset.open() as live:
105
+ first_hundred = live[:100] # fresh file view, useful for slices
106
+ ```
107
+
108
+ Both `from_file()` and `Dataset.open()` close every handle on normal and exceptional
109
+ exits. Schema objects cannot be directly constructed, written, or serialized by h5t.
110
+
111
+ ## Field rules
112
+
113
+ | Annotation | HDF5 representation | Loading behavior |
114
+ | --- | --- | --- |
115
+ | `Group` subclass | child group | recursively materialized |
116
+ | `Dataset` or subclass | child dataset | metadata/attrs snapshot, payload lazy |
117
+ | `Annotated[DatasetSubtype, Eager()]` | child dataset | complete payload cached during loading |
118
+ | `np.ndarray` | child dataset | complete payload loaded as an ndarray |
119
+ | `Annotated[T, Attr(...)]` | attribute | converter, then Pydantic validation |
120
+ | scalar or `Literal[...]` | attribute | Pydantic validation |
121
+
122
+ `Name("stored-name")` renames any field kind. `Attr` is valid only for attributes and
123
+ `Eager` only for typed datasets. A parameterized alias such as
124
+ `numpy.typing.NDArray[np.floating]` is accepted wherever `np.ndarray` is; the dtype
125
+ parameter is not validated. Unsupported collection-shaped child annotations raise
126
+ `SchemaError`; dynamic collections are not yet supported. Declarations are compiled at
127
+ the `class` statement, so a `SchemaError` surfaces there. A schema class that names a
128
+ class defined later in its module instead compiles on first use.
129
+
130
+ Each `Group` and `Dataset` subclass accepts `extras="ignore"` (the default) or
131
+ `extras="forbid"`. A group policy applies to its immediate child and attribute
132
+ names. A typed dataset policy applies to its attributes. Nested schemas keep their own
133
+ policy, while a plain `np.ndarray` field never checks the dataset's attributes.
134
+
135
+ ## Snapshot consistency
136
+
137
+ A loaded model is a snapshot, with one deliberate exception:
138
+
139
+ - Group and dataset attributes, dataset shape/dtype/path, eager datasets, and plain
140
+ arrays reflect the file during `from_file()`.
141
+ - A lazy dataset's first `.data`/`.read()` observes the file at that later moment and
142
+ caches the resulting array permanently.
143
+ - `.open()` always opens the current file and current dataset. It may therefore observe
144
+ replacements or fail if the source was changed or deleted.
145
+
146
+ ## CLI
147
+
148
+ ```bash
149
+ h5t check result.h5 --schema mypackage.schemas:Result --root /results/latest
150
+ ```
151
+
152
+ Exit status is 0 on success, 1 for the first file/schema mismatch, and 2 for import,
153
+ declaration, usage, or I/O errors.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ uv sync
159
+ uv run pytest
160
+ uv run ty check
161
+ uv run ruff check .
162
+ ```
h5t-0.2.0/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # h5t
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/h5t.svg)](https://pypi.org/project/h5t/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/h5t.svg)](https://pypi.org/project/h5t/)
5
+ [![CI](https://github.com/binado/h5t/actions/workflows/ci.yml/badge.svg)](https://github.com/binado/h5t/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/binado/h5t/blob/main/LICENSE)
7
+
8
+ `h5t` loads HDF5 groups into detached, typed Python records. Group attributes and
9
+ ordinary NumPy-array fields are materialized while the file is open. Typed datasets
10
+ keep snapshot metadata and can read their payload lazily without retaining an open
11
+ file descriptor.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install h5t # or: uv add h5t
17
+ ```
18
+
19
+ Requires Python 3.11+. `h5py`, `numpy`, and Pydantic v2 are installed automatically.
20
+
21
+ ## Example
22
+
23
+ ```python
24
+ import json
25
+ from pathlib import Path
26
+ from typing import Annotated, Any
27
+
28
+ import numpy as np
29
+
30
+ import h5t
31
+
32
+
33
+ class Measurement(h5t.Dataset, extras="forbid"):
34
+ unit: str
35
+
36
+
37
+ class Nested(h5t.Group):
38
+ label: str
39
+
40
+
41
+ class Result(h5t.Group, extras="ignore"):
42
+ version: int # implicit HDF5 attribute
43
+ title: Annotated[str, h5t.Name("name")] # renamed attribute
44
+ config: Annotated[
45
+ dict[str, Any],
46
+ h5t.Attr(converter=json.loads),
47
+ ]
48
+ array_attr: Annotated[np.ndarray, h5t.Attr()]
49
+ values: np.ndarray # eager dataset payload
50
+ measurement: Measurement # lazy detached dataset
51
+ eager_measurement: Annotated[Measurement, h5t.Eager()]
52
+ nested: Nested # recursively loaded group
53
+ note: str | None # absent becomes None
54
+ revision: int = 1 # absent uses a validated default
55
+
56
+
57
+ result = Result.from_file(Path("result.h5"), root="/")
58
+ ```
59
+
60
+ `result.attrs` and `result.measurement.attrs` are immutable mappings keyed by their
61
+ on-disk HDF5 names. Declared attributes contain their Pydantic-processed values;
62
+ undeclared attributes retained under `extras="ignore"` contain the raw h5py values.
63
+
64
+ Typed datasets expose snapshot metadata and explicit data access:
65
+
66
+ ```python
67
+ dataset = result.measurement
68
+ dataset.path, dataset.shape, dataset.dtype, dataset.ndim
69
+
70
+ complete = dataset.data # first access reads and caches an ndarray
71
+ assert dataset.read() is complete
72
+
73
+ with dataset.open() as live:
74
+ first_hundred = live[:100] # fresh file view, useful for slices
75
+ ```
76
+
77
+ Both `from_file()` and `Dataset.open()` close every handle on normal and exceptional
78
+ exits. Schema objects cannot be directly constructed, written, or serialized by h5t.
79
+
80
+ ## Field rules
81
+
82
+ | Annotation | HDF5 representation | Loading behavior |
83
+ | --- | --- | --- |
84
+ | `Group` subclass | child group | recursively materialized |
85
+ | `Dataset` or subclass | child dataset | metadata/attrs snapshot, payload lazy |
86
+ | `Annotated[DatasetSubtype, Eager()]` | child dataset | complete payload cached during loading |
87
+ | `np.ndarray` | child dataset | complete payload loaded as an ndarray |
88
+ | `Annotated[T, Attr(...)]` | attribute | converter, then Pydantic validation |
89
+ | scalar or `Literal[...]` | attribute | Pydantic validation |
90
+
91
+ `Name("stored-name")` renames any field kind. `Attr` is valid only for attributes and
92
+ `Eager` only for typed datasets. A parameterized alias such as
93
+ `numpy.typing.NDArray[np.floating]` is accepted wherever `np.ndarray` is; the dtype
94
+ parameter is not validated. Unsupported collection-shaped child annotations raise
95
+ `SchemaError`; dynamic collections are not yet supported. Declarations are compiled at
96
+ the `class` statement, so a `SchemaError` surfaces there. A schema class that names a
97
+ class defined later in its module instead compiles on first use.
98
+
99
+ Each `Group` and `Dataset` subclass accepts `extras="ignore"` (the default) or
100
+ `extras="forbid"`. A group policy applies to its immediate child and attribute
101
+ names. A typed dataset policy applies to its attributes. Nested schemas keep their own
102
+ policy, while a plain `np.ndarray` field never checks the dataset's attributes.
103
+
104
+ ## Snapshot consistency
105
+
106
+ A loaded model is a snapshot, with one deliberate exception:
107
+
108
+ - Group and dataset attributes, dataset shape/dtype/path, eager datasets, and plain
109
+ arrays reflect the file during `from_file()`.
110
+ - A lazy dataset's first `.data`/`.read()` observes the file at that later moment and
111
+ caches the resulting array permanently.
112
+ - `.open()` always opens the current file and current dataset. It may therefore observe
113
+ replacements or fail if the source was changed or deleted.
114
+
115
+ ## CLI
116
+
117
+ ```bash
118
+ h5t check result.h5 --schema mypackage.schemas:Result --root /results/latest
119
+ ```
120
+
121
+ Exit status is 0 on success, 1 for the first file/schema mismatch, and 2 for import,
122
+ declaration, usage, or I/O errors.
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ uv sync
128
+ uv run pytest
129
+ uv run ty check
130
+ uv run ruff check .
131
+ ```
@@ -0,0 +1,96 @@
1
+ [project]
2
+ name = "h5t"
3
+ version = "0.2.0"
4
+ description = "Load HDF5 groups as detached, typed Python records."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ keywords = [
10
+ "hdf5",
11
+ "h5py",
12
+ "schema",
13
+ "pydantic",
14
+ "typing",
15
+ "validation",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: Science/Research",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Scientific/Engineering",
28
+ "Topic :: Software Development :: Libraries",
29
+ "Typing :: Typed",
30
+ ]
31
+ dependencies = [
32
+ "h5py>=3.10",
33
+ "numpy>=1.26",
34
+ "pydantic>=2.4,<3",
35
+ ]
36
+
37
+ [[project.authors]]
38
+ name = "binado"
39
+ email = "bernardopveronese@gmail.com"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/binado/h5t"
43
+ Repository = "https://github.com/binado/h5t"
44
+ Issues = "https://github.com/binado/h5t/issues"
45
+ Changelog = "https://github.com/binado/h5t/blob/main/CHANGELOG.md"
46
+
47
+ [project.scripts]
48
+ h5t = "h5t._cli:main"
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=8",
53
+ "hypothesis>=6",
54
+ "ty>=0.0.74",
55
+ "ruff>=0.6",
56
+ ]
57
+
58
+ [build-system]
59
+ requires = ["uv_build>=0.12.5,<0.13.0"]
60
+ build-backend = "uv_build"
61
+
62
+ [tool.ruff]
63
+ line-length = 100
64
+ target-version = "py311"
65
+
66
+ [tool.ruff.lint]
67
+ select = [
68
+ "E",
69
+ "F",
70
+ "I",
71
+ "UP",
72
+ "B",
73
+ "D",
74
+ ]
75
+ ignore = [
76
+ "D105",
77
+ "D107",
78
+ ]
79
+
80
+ [tool.ruff.lint.pydocstyle]
81
+ convention = "numpy"
82
+
83
+ [tool.ruff.lint.per-file-ignores]
84
+ "tests/**" = [
85
+ "D",
86
+ "B018",
87
+ ]
88
+
89
+ [tool.ty.src]
90
+ include = [
91
+ "src/h5t",
92
+ "tests/acceptance_example.py",
93
+ ]
94
+
95
+ [tool.pytest.ini_options]
96
+ testpaths = ["tests"]
@@ -0,0 +1,76 @@
1
+ [project]
2
+ name = "h5t"
3
+ version = "0.2.0"
4
+ description = "Load HDF5 groups as detached, typed Python records."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "binado", email = "bernardopveronese@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["hdf5", "h5py", "schema", "pydantic", "typing", "validation"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: Science/Research",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Scientific/Engineering",
24
+ "Topic :: Software Development :: Libraries",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [
28
+ "h5py>=3.10",
29
+ "numpy>=1.26",
30
+ "pydantic>=2.4,<3",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/binado/h5t"
35
+ Repository = "https://github.com/binado/h5t"
36
+ Issues = "https://github.com/binado/h5t/issues"
37
+ Changelog = "https://github.com/binado/h5t/blob/main/CHANGELOG.md"
38
+
39
+ [project.scripts]
40
+ h5t = "h5t._cli:main"
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "pytest>=8",
45
+ "hypothesis>=6",
46
+ "ty>=0.0.74",
47
+ "ruff>=0.6",
48
+ ]
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.12.5,<0.13.0"]
52
+ build-backend = "uv_build"
53
+
54
+ [tool.ruff]
55
+ line-length = 100
56
+ target-version = "py311"
57
+
58
+ [tool.ruff.lint]
59
+ select = ["E", "F", "I", "UP", "B", "D"]
60
+ ignore = [
61
+ "D105", # missing docstring in magic method
62
+ "D107", # missing docstring in __init__
63
+ ]
64
+
65
+ [tool.ruff.lint.pydocstyle]
66
+ convention = "numpy"
67
+
68
+ [tool.ruff.lint.per-file-ignores]
69
+ # B018 fires on attribute access inside pytest.raises blocks.
70
+ "tests/**" = ["D", "B018"]
71
+
72
+ [tool.ty.src]
73
+ include = ["src/h5t", "tests/acceptance_example.py"]
74
+
75
+ [tool.pytest.ini_options]
76
+ testpaths = ["tests"]
@@ -0,0 +1,20 @@
1
+ """Detached, typed records for read-only HDF5 access."""
2
+
3
+ from h5t._compile import Dataset, Group
4
+ from h5t._errors import ConversionError, H5TError, SchemaError, ValidationError
5
+ from h5t._spec import Attr, Eager, Name
6
+
7
+ __version__ = "0.2.0"
8
+
9
+ __all__ = [
10
+ "Attr",
11
+ "ConversionError",
12
+ "Dataset",
13
+ "Eager",
14
+ "Group",
15
+ "H5TError",
16
+ "Name",
17
+ "SchemaError",
18
+ "ValidationError",
19
+ "__version__",
20
+ ]
@@ -0,0 +1,66 @@
1
+ """The ``h5t check`` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import importlib
7
+ import sys
8
+ from typing import NoReturn
9
+
10
+ from h5t._compile import Group
11
+ from h5t._errors import SchemaError, ValidationError
12
+
13
+
14
+ def _fail(message: str) -> NoReturn:
15
+ print(f"error: {message}", file=sys.stderr)
16
+ raise SystemExit(2)
17
+
18
+
19
+ def _load_schema(ref: str) -> type[Group]:
20
+ module_name, separator, class_name = ref.partition(":")
21
+ if not separator or not module_name or not class_name:
22
+ _fail(f"--schema must look like pkg.module:ClassName, got {ref!r}")
23
+ try:
24
+ module = importlib.import_module(module_name)
25
+ except Exception as exc:
26
+ _fail(f"could not import module {module_name!r}: {exc}")
27
+ try:
28
+ schema = getattr(module, class_name)
29
+ except AttributeError:
30
+ _fail(f"module {module_name!r} has no attribute {class_name!r}")
31
+ if not (isinstance(schema, type) and issubclass(schema, Group)):
32
+ _fail(f"{ref!r} is not an h5t.Group schema class")
33
+ return schema
34
+
35
+
36
+ def _cmd_check(args: argparse.Namespace) -> int:
37
+ schema = _load_schema(args.schema)
38
+ try:
39
+ schema.from_file(args.file, root=args.root)
40
+ except ValidationError as exc:
41
+ print(f"invalid: {args.file} against {schema.__name__}: {exc}")
42
+ return 1
43
+ except (OSError, ValueError, TypeError, SchemaError) as exc:
44
+ _fail(f"could not check {args.file!r}: {exc}")
45
+ print(f"ok: {args.file} validates against {schema.__name__} at {args.root}")
46
+ return 0
47
+
48
+
49
+ def main(argv: list[str] | None = None) -> int:
50
+ """Run the command line interface and return its process exit code."""
51
+ parser = argparse.ArgumentParser(
52
+ prog="h5t",
53
+ description="Validate and materialize an HDF5 group schema.",
54
+ )
55
+ subparsers = parser.add_subparsers(dest="command", required=True)
56
+ check = subparsers.add_parser("check", help="check an HDF5 file against a Group schema")
57
+ check.add_argument("file", help="path to the HDF5 file")
58
+ check.add_argument("--schema", required=True, help="schema reference, e.g. pkg.schemas:Result")
59
+ check.add_argument("--root", default="/", help="absolute HDF5 group path (default: /)")
60
+ check.set_defaults(func=_cmd_check)
61
+ args = parser.parse_args(argv)
62
+ return int(args.func(args))
63
+
64
+
65
+ if __name__ == "__main__":
66
+ sys.exit(main())