Source code for h5col.references

"""HDF5 object-reference backend for H5Col.

All object-reference creation and resolution in H5Col goes through this module,
so the on-disk reference representation can be changed in one place.

.. warning::

   H5Col mandates the unified ``H5T_STD_REF`` datatype (HDF5 1.12+) and forbids
   the deprecated ``H5T_STD_REF_OBJ``. h5py (as of 3.16) cannot create
   ``H5T_STD_REF``, so this backend currently writes ``H5T_STD_REF_OBJ``. This is
   a documented deviation (see ``docs/DEVIATIONS.md`` D1). The read side accepts
   either representation. A conformant backend can replace this module without
   changing any caller.
"""

from __future__ import annotations

from collections.abc import Iterable
from typing import Any

import h5py
import numpy as np
import numpy.typing as npt

from .exceptions import ObjectReferenceError

# h5py object types that can be referenced.
_Referable = Any  # h5py.Group | h5py.Dataset | h5py.Datatype
_Location = Any  # h5py.File | h5py.Group


[docs] def ref_dtype() -> np.dtype: """Return the NumPy dtype used to store object references.""" return h5py.ref_dtype
[docs] def is_reference_dtype(dtype: Any) -> bool: """Return True if *dtype* is an HDF5 object/region reference dtype.""" return h5py.check_ref_dtype(dtype) is not None
[docs] def make_ref(obj: _Referable) -> h5py.Reference: """Create an object reference to an open HDF5 object. Raises ------ ObjectReferenceError If *obj* exposes no ``ref`` (it is not a referable HDF5 object). """ try: return obj.ref except AttributeError as exc: raise ObjectReferenceError( f"cannot create an object reference to {obj!r}" ) from exc
[docs] def is_null_ref(ref: Any) -> bool: """Return True if *ref* is a null object reference.""" return not bool(ref)
[docs] def write_ref_attr(target: _Referable, name: str, obj: _Referable) -> None: """Write a scalar object-reference attribute onto *target*.""" target.attrs.create(name, make_ref(obj), dtype=ref_dtype())
[docs] def write_ref_array_attr( target: _Referable, name: str, objs: Iterable[_Referable] ) -> None: """Write a 1-D object-reference array attribute onto *target*.""" refs: npt.NDArray[Any] = np.array([make_ref(o) for o in objs], dtype=ref_dtype()) target.attrs.create(name, refs)
[docs] def append_ref_to_array_attr(target: _Referable, name: str, obj: _Referable) -> None: """Append a reference to *obj* to a 1-D reference-array attribute on *target*. Creates the attribute when absent. HDF5 attributes cannot be resized in place, so an existing attribute is rewritten with the extended array. The new reference is created *before* the old attribute is touched, and the old array is restored if the rewrite fails, so a failed append cannot silently drop the existing references. (A hard crash between the delete and the create can still lose the attribute — HDF5 offers no atomic attribute rewrite.) """ new_ref = make_ref(obj) old: list[Any] = [] if name in target.attrs: old = list(target.attrs[name]) del target.attrs[name] try: target.attrs.create(name, np.array([*old, new_ref], dtype=ref_dtype())) except BaseException: if old: try: target.attrs.create(name, np.array(old, dtype=ref_dtype())) except Exception: pass # the original failure is the error that matters raise
[docs] def resolve(where: _Location, ref: Any) -> _Referable: """Dereference *ref* relative to file/group *where*. Raises :class:`ObjectReferenceError` for a null reference or a reference that does not resolve. """ if is_null_ref(ref): raise ObjectReferenceError("cannot resolve a null object reference") try: return where[ref] except (KeyError, ValueError, TypeError) as exc: raise ObjectReferenceError(f"reference does not resolve: {ref!r}") from exc