From 927d204f1d42ec095f2c522057ff6fd5cbfbd5a7 Mon Sep 17 00:00:00 2001 From: "Samantha C. Niemoeller" Date: Mon, 9 Feb 2026 13:37:00 -0800 Subject: [PATCH 1/3] check string dtypes --- src/nisarqa/validate/sanity_checks.py | 214 ++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 15 deletions(-) diff --git a/src/nisarqa/validate/sanity_checks.py b/src/nisarqa/validate/sanity_checks.py index f6d66901..8208ad3e 100644 --- a/src/nisarqa/validate/sanity_checks.py +++ b/src/nisarqa/validate/sanity_checks.py @@ -11,6 +11,28 @@ objects_to_skip = nisarqa.get_all(name=__name__) +def _log_if_bad_string_value(val: str | list[str], ds_name: str) -> bool: + + log = nisarqa.get_logger() + + if isinstance(val, str): + val = [val] + + for v in val: + if v.upper() in ( + "", + "0", + "['0']", + "['']", + "['' '' '' '' '']", + "NONE", + "(NOT SPECIFIED)", + ): + log.error( + f"Value is {val!r}, which is not valid." + f" Path: {ds_name}" + ) + def dataset_sanity_checks(product: nisarqa.NisarProduct) -> None: """ @@ -23,12 +45,186 @@ def dataset_sanity_checks(product: nisarqa.NisarProduct) -> None: """ with h5py.File(product.filepath, "r") as f: + check_string_type_metadata(h5_file=f) + identification_sanity_checks( id_group=f[product.identification_path], product_type=product.product_type, ) +def check_string_type_metadata(h5_file: h5py.File) -> None: + + log = nisarqa.get_logger() + + def check_string_type(path: str) -> None: + """Check if string datasets or attributes have placeholder values.""" + + # Check dataset + + # `complex64` is a special data type; it is neither a HDF5 group nor + # dataset, so we just need to skip it. + if path.endswith("complex64"): + return + + ds = h5_file[path] + if isinstance(ds, h5py.Group): + + # TODO - check attributes for None/Empty and/or if string then correct string type + + return + + # Ensure dataset's values were populated with + if ds.size is None: + assert isinstance(ds[()], h5py.Empty) + log.error(f"Dataset's value is {ds[()]}. Dataset: {ds.name}") + + + dtype = ds.dtype + string_info = h5py.check_string_dtype(dtype) + + if string_info is None: + # dataset is not a string dtype. (It could be int, float, etc.) + + # TODO - check attributes for None/Empty and/or if string then correct string type + pass + else: + # dataset is a string type + if string_info.length is None: + # Variable length string + log.error( + "Dataset is a variable-length string, should be" + f" fixed-length byte string. Dataset: {ds.name}" + ) + elif string_info.length == 0: + # empty string + log.error(f"Dataset is the empty string. Dataset: {ds.name}") + else: + assert string_info.length > 0 + + ds_val = nisarqa.byte_string_to_python_str(ds[()]) + if isinstance(ds_val, str): + ds_val = [ds_val] + for val in ds_val: + _log_if_bad_string_value(val=val, ds_name=ds.name) + + # TODO - check attributes for None/Empty and/or if string then correct string type + + h5_file.visit(check_string_type) + + +import h5py +import numpy as np + +def check_string_type_metadata(h5_file: h5py.File) -> None: + """ + Iterate through an HDF5 file to validate that all groups and datasets, + including their attributes, are populated and use correct string types. + + Parameters + ---------- + h5_file : h5py.File + The opened HDF5 file object to be inspected. + + Returns + ------- + None + """ + log = nisarqa.get_logger() + + def _validate_string_logic(name: str, dtype: h5py.Datatype, value_provider, label: str) -> None: + """ + Unified logic to validate HDF5 string types and content. + + Parameters + ---------- + name : str + Path or name of the object. + dtype : h5py.Datatype + The HDF5 datatype to check. + value_provider : callable + A function/lambda that returns the actual value when called. + Used to avoid reading data unless the type check passes. + label : str + Context label for logging (e.g., "Dataset" or "Attribute"). + """ + + string_info = h5py.check_string_dtype(dtype) + if string_info is None: + # object is not a string dtype. (It could be int, float, etc.) + return + + if string_info.length is None: + log.error(f"{label} is variable-length string; should be fixed-length. Path: {name}") + elif string_info.length == 0: + log.error(f"{label} is an empty string. Path: {name}") + else: + # ONLY for strings do we read the value to check content. + # Fixed-length strings are usually small metadata fields. + raw_val = value_provider() + ds_val = nisarqa.byte_string_to_python_str(raw_val) + + vals = [ds_val] if isinstance(ds_val, (str, bytes)) else ds_val + for val in vals: + _log_if_bad_string_value(val=val, ds_name=name) + + def _check_attributes(item_name: str, item: h5py.HLObject) -> None: + """Check all attributes of a specific HDF5 object.""" + for attr_name, attr_val in item.attrs.items(): + if attr_val is None or isinstance(attr_val, h5py.Empty): + log.error(f"Attribute '{attr_name}' is empty. Path: {item_name}") + continue + + attr_id = item.attrs.get_id(attr_name) + _validate_string_logic( + name=f"{item_name} -> {attr_name}", + dtype=attr_id.dtype, + value_provider=lambda: attr_val, # Attributes are small; safe to pass + label="Attribute" + ) + + def visitor_func(path: str) -> None: + """Visitor function for h5py.visit.""" + + # `complex64` is a special data type; it is neither a HDF5 group nor + # dataset, so we just need to skip it. + if path.endswith("complex64"): + return + + obj = h5_file[path] + + # 1. Always check attributes (This is safe for large datasets) + _check_attributes(path, obj) + + # 2. Dataset-specific validation + if isinstance(obj, h5py.Dataset): + # Check if dataset is a 'null' space (Empty) without reading data. + # This occurs is when a dataset is written with a Python value of `None`, + # although there could be other causes. + # h5py datasets with no data have a shape of None or use the Empty class + if obj.shape is None: + log.error(f"Dataset has a null (Empty) space. Dataset: {obj.name}") + return + + # Check if storage was actually allocated (0 bytes means uninitialized/empty) + if obj.id.get_storage_size() == 0: + log.error(f"Dataset has no allocated storage (empty). Dataset: {obj.name}") + return + + # 3. String Type/Content Check + # _validate_string_logic only calls the lambda if it confirms it's a string dtype + _validate_string_logic( + name=obj.name, + dtype=obj.dtype, + value_provider=lambda: obj[()], # Only called if dtype is string + label="Dataset" + ) + + # Check root, then visit + _check_attributes("/", h5_file) + h5_file.visit(visitor_func) + + def identification_sanity_checks( id_group: h5py.Group, product_type: str ) -> None: @@ -443,25 +639,13 @@ def _verify_data_is_in_list( if _dataset_exists(ds_name): data = _get_string_dataset(ds_name=ds_name) if data is not None: - # TODO: Use a regex for more flexible pattern matching. - if data in ( - "", - "0", - "['0']", - "['']", - "['' '' '' '' '']", - "None", - "(NOT SPECIFIED)", - ): - log.error( - f"Dataset value is {data!r}, which is not a valid value." - f" Dataset: {_full_path(ds_name)}" - ) + ds_full_path = _full_path(ds_name) + if _log_if_bad_string_value(val=data, ds_name=ds_full_path): passes = False else: log.warning( f"Dataset value is {data!r}, but it has not be automatically" - f" verified during checks. Dataset: {_full_path(ds_name)}" + f" verified during checks. Dataset: {ds_full_path}" ) else: passes = False From ca45d05ad8fe7d4ce66a83112ca4a50aa8fee731 Mon Sep 17 00:00:00 2001 From: "Samantha C. Niemoeller" Date: Tue, 10 Feb 2026 15:11:21 -0800 Subject: [PATCH 2/3] add general validation of datasets and attributes per NISAR conventions --- .../stats_h5_writer/metrics_writer.py | 28 +++ src/nisarqa/validate/sanity_checks.py | 232 ++++++++++-------- 2 files changed, 159 insertions(+), 101 deletions(-) diff --git a/src/nisarqa/processing/stats_h5_writer/metrics_writer.py b/src/nisarqa/processing/stats_h5_writer/metrics_writer.py index 3c92c804..2aff5f67 100644 --- a/src/nisarqa/processing/stats_h5_writer/metrics_writer.py +++ b/src/nisarqa/processing/stats_h5_writer/metrics_writer.py @@ -591,6 +591,34 @@ def get_stats_name_descr(stat: str, component: str | None) -> tuple[str, str]: ), ) +def get_list_of_real_stats_names() -> list[str]: + """ + Return a list of the names of all statistics for real-valued datasets. + + These names are per NISAR conventions, for min/max/mean/std statistics. + """ + + stat_names = [] + for stat in ("min", "max", "mean", "std"): + s, _ = get_stats_name_descr(stat, component=None) + stat_names.append(s) + return stat_names + + +def get_list_of_imag_stats_names() -> list[str]: + """ + Return a list of the names of all statistics for complex-valued datasets. + + These names are per NISAR conventions, for min/max/mean/std statistics. + """ + + stat_names = [] + for comp in ("real", "imag"): + for stat in ("min", "max", "mean", "std"): + s, _ = get_stats_name_descr(stat, component=comp) + stat_names.append(s) + return stat_names + def copy_non_insar_imagery_metrics( product: nisarqa.NonInsarProduct, stats_h5: h5py.File diff --git a/src/nisarqa/validate/sanity_checks.py b/src/nisarqa/validate/sanity_checks.py index 8208ad3e..1f86ab8b 100644 --- a/src/nisarqa/validate/sanity_checks.py +++ b/src/nisarqa/validate/sanity_checks.py @@ -1,7 +1,9 @@ from __future__ import annotations import re -from collections.abc import Container +from collections.abc import Callable, Container +import copy +from typing import Any import h5py import numpy as np @@ -11,14 +13,27 @@ objects_to_skip = nisarqa.get_all(name=__name__) -def _log_if_bad_string_value(val: str | list[str], ds_name: str) -> bool: +def _log_if_bad_string_value(val: str | list[str], path: str) -> bool: + """ + Log an error if value is a known invalid string. + Parameters + ---------- + val : str or list of str + Value to be checked. + path : str + Path to the dataset (and/or attribute) containing `val` to be used + for logging. If `val` is the value of an attribute, suggest + providing the dataset's path with the attribute name. + """ log = nisarqa.get_logger() - if isinstance(val, str): - val = [val] + values = copy.deepcopy(val) - for v in val: + if isinstance(values, str): + values = [values] + + for v in values: if v.upper() in ( "", "0", @@ -29,8 +44,8 @@ def _log_if_bad_string_value(val: str | list[str], ds_name: str) -> bool: "(NOT SPECIFIED)", ): log.error( - f"Value is {val!r}, which is not valid." - f" Path: {ds_name}" + f"Value is {val!r}, which is not valid for nominal NISAR data." + f" Path: {path}" ) @@ -45,7 +60,7 @@ def dataset_sanity_checks(product: nisarqa.NisarProduct) -> None: """ with h5py.File(product.filepath, "r") as f: - check_string_type_metadata(h5_file=f) + check_metadata_conventions(h5_file=f) identification_sanity_checks( id_group=f[product.identification_path], @@ -53,86 +68,49 @@ def dataset_sanity_checks(product: nisarqa.NisarProduct) -> None: ) -def check_string_type_metadata(h5_file: h5py.File) -> None: - - log = nisarqa.get_logger() - - def check_string_type(path: str) -> None: - """Check if string datasets or attributes have placeholder values.""" - - # Check dataset - - # `complex64` is a special data type; it is neither a HDF5 group nor - # dataset, so we just need to skip it. - if path.endswith("complex64"): - return - - ds = h5_file[path] - if isinstance(ds, h5py.Group): - - # TODO - check attributes for None/Empty and/or if string then correct string type - - return - - # Ensure dataset's values were populated with - if ds.size is None: - assert isinstance(ds[()], h5py.Empty) - log.error(f"Dataset's value is {ds[()]}. Dataset: {ds.name}") - - - dtype = ds.dtype - string_info = h5py.check_string_dtype(dtype) - - if string_info is None: - # dataset is not a string dtype. (It could be int, float, etc.) - - # TODO - check attributes for None/Empty and/or if string then correct string type - pass - else: - # dataset is a string type - if string_info.length is None: - # Variable length string - log.error( - "Dataset is a variable-length string, should be" - f" fixed-length byte string. Dataset: {ds.name}" - ) - elif string_info.length == 0: - # empty string - log.error(f"Dataset is the empty string. Dataset: {ds.name}") - else: - assert string_info.length > 0 - - ds_val = nisarqa.byte_string_to_python_str(ds[()]) - if isinstance(ds_val, str): - ds_val = [ds_val] - for val in ds_val: - _log_if_bad_string_value(val=val, ds_name=ds.name) - - # TODO - check attributes for None/Empty and/or if string then correct string type - - h5_file.visit(check_string_type) - - -import h5py -import numpy as np - -def check_string_type_metadata(h5_file: h5py.File) -> None: +def check_metadata_conventions(h5_file: h5py.File) -> None: """ + Check that all datasets and attributes meet certain NISAR conventions. + Iterate through an HDF5 file to validate that all groups and datasets, - including their attributes, are populated and use correct string types. + including their attributes, meet certain NISAR conventions: + 1) populated (not empty) + 2) if string, that they are not variable-length strings and that + they are not populated with known placeholder values. + 3) if an attribute is numeric and is in given set of names, + that its dtype corresponds to its dataset's dtype. + + Any issues discovered are logged as errors. Parameters ---------- h5_file : h5py.File The opened HDF5 file object to be inspected. - Returns - ------- - None + Notes + ----- + This function is general for all NISAR product types. It does not have + special handling for specific datasets in specific products. + + This function does not compare dtypes against the dtypes denoted in the XML + product specifications. For that functionality, please use the XML Checker. """ log = nisarqa.get_logger() - def _validate_string_logic(name: str, dtype: h5py.Datatype, value_provider, label: str) -> None: + # Construct list of attributes whose dtypes should exactly-match the + # dtype of the dataset/group they are attached to. + exact_dtype_match = nisarqa.get_list_of_real_stats_names() + exact_dtype_match += ["_FillValue", "valid_min", "valid_max"] + + # Construct list of attributes whose dtypes should be half-precision of the + # dtype of the dataset/group they are attached to. + half_precision_match = nisarqa.get_list_of_imag_stats_names() + + def _validate_string_logic( + name: str, + dtype_: h5py.Datatype, + value_provider: Callable[[], Any], + label: str) -> None: """ Unified logic to validate HDF5 string types and content. @@ -140,22 +118,25 @@ def _validate_string_logic(name: str, dtype: h5py.Datatype, value_provider, labe ---------- name : str Path or name of the object. - dtype : h5py.Datatype + dtype_ : h5py.Datatype The HDF5 datatype to check. value_provider : callable - A function/lambda that returns the actual value when called. - Used to avoid reading data unless the type check passes. + A function/lambda that returns the actual value of when called. + Used to avoid reading data unless value has a string type. label : str Context label for logging (e.g., "Dataset" or "Attribute"). """ - string_info = h5py.check_string_dtype(dtype) + string_info = h5py.check_string_dtype(dtype_) if string_info is None: # object is not a string dtype. (It could be int, float, etc.) return if string_info.length is None: - log.error(f"{label} is variable-length string; should be fixed-length. Path: {name}") + log.error( + f"{label} is variable-length string; should be fixed-length." + f" Path: {name}" + ) elif string_info.length == 0: log.error(f"{label} is an empty string. Path: {name}") else: @@ -166,7 +147,7 @@ def _validate_string_logic(name: str, dtype: h5py.Datatype, value_provider, labe vals = [ds_val] if isinstance(ds_val, (str, bytes)) else ds_val for val in vals: - _log_if_bad_string_value(val=val, ds_name=name) + _log_if_bad_string_value(val=val, path=name) def _check_attributes(item_name: str, item: h5py.HLObject) -> None: """Check all attributes of a specific HDF5 object.""" @@ -176,18 +157,55 @@ def _check_attributes(item_name: str, item: h5py.HLObject) -> None: continue attr_id = item.attrs.get_id(attr_name) - _validate_string_logic( - name=f"{item_name} -> {attr_name}", - dtype=attr_id.dtype, - value_provider=lambda: attr_val, # Attributes are small; safe to pass - label="Attribute" - ) + + string_info = h5py.check_string_dtype(attr_id.dtype) + + if string_info is None: + # object is not a string dtype. (It could be int, float, etc.) + # Validate that that dtype of the attribute matches the dtype + # of the dataset. + + # Check attributes which should have an exact dtype match + if attr_name in exact_dtype_match: + if attr_id.dtype != item.dtype: + log.error( + f"Attribute has dtype {attr_id.dtype}, which does" + f" not match its dataset's dtype of {item.dtype}." + f" Path: {item_name} -> {attr_name}" + ) + + # Check attributes which should use a half-precision dtype + if attr_name in half_precision_match: + + incorrect_c32 = (nisarqa.is_complex32(item) and attr_id.dtype != np.float16) + incorrect_c64 = (item.dtype == np.complex64 and attr_id.dtype != np.float32) + if incorrect_c32 or incorrect_c64: + log.error( + f"Attribute has dtype {attr_id.dtype}, which does" + f" not match the half-precision of its dataset's" + f" dtype of {item.dtype}." + f" Path: {item_name} -> {attr_name}" + ) + else: + log.error( + f"Attribute is meant for a complex-valued dataset," + " but is attached to a non-complex-valued dataset" + f" Path: {item_name} -> {attr_name}" + ) + + else: + _validate_string_logic( + name=f"{item_name} -> {attr_name}", + dtype_=attr_id.dtype, + value_provider=lambda: attr_val, + label="Attribute" + ) + def visitor_func(path: str) -> None: """Visitor function for h5py.visit.""" - # `complex64` is a special data type; it is neither a HDF5 group nor - # dataset, so we just need to skip it. + # The `complex64` HDF5 object is neither a HDF5 group nor dataset, skip. if path.endswith("complex64"): return @@ -198,28 +216,40 @@ def visitor_func(path: str) -> None: # 2. Dataset-specific validation if isinstance(obj, h5py.Dataset): + + # 2a.For all datasets (numeric, string, etc.) check if the dataset + # was populated with some value. (aka not an empty/null dataset) + # Check if dataset is a 'null' space (Empty) without reading data. - # This occurs is when a dataset is written with a Python value of `None`, - # although there could be other causes. - # h5py datasets with no data have a shape of None or use the Empty class + # This occurs is when a dataset is written with a Python value of + # `None` (there could be other causes). h5py Datasets with no data + # have a shape of None or use the Empty class. if obj.shape is None: - log.error(f"Dataset has a null (Empty) space. Dataset: {obj.name}") + msg = f"Dataset has a null (Empty) space. Dataset: {obj.name}" + log.error(msg) return - # Check if storage was actually allocated (0 bytes means uninitialized/empty) + # Check if storage was allocated (0 bytes means uninitialized/empty) if obj.id.get_storage_size() == 0: - log.error(f"Dataset has no allocated storage (empty). Dataset: {obj.name}") + log.error( + "Dataset has no allocated storage (empty)." + f" Dataset: {obj.name}" + ) return - # 3. String Type/Content Check - # _validate_string_logic only calls the lambda if it confirms it's a string dtype + # 2b. String Type/Content Check _validate_string_logic( name=obj.name, - dtype=obj.dtype, + dtype_=obj.dtype, value_provider=lambda: obj[()], # Only called if dtype is string label="Dataset" ) + # 2c. Numeric Type/Content Check + # Numeric datasets will need to be individually validated + # via other sections in QA (XML Checker, qa_reports, Metadata LUT + # checks, etc.) + # Check root, then visit _check_attributes("/", h5_file) h5_file.visit(visitor_func) @@ -640,7 +670,7 @@ def _verify_data_is_in_list( data = _get_string_dataset(ds_name=ds_name) if data is not None: ds_full_path = _full_path(ds_name) - if _log_if_bad_string_value(val=data, ds_name=ds_full_path): + if _log_if_bad_string_value(val=data, path=ds_full_path): passes = False else: log.warning( From 138a67ca1643896b7bad598c6792237099d8ca10 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:16:54 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../stats_h5_writer/metrics_writer.py | 5 +-- src/nisarqa/validate/sanity_checks.py | 34 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/nisarqa/processing/stats_h5_writer/metrics_writer.py b/src/nisarqa/processing/stats_h5_writer/metrics_writer.py index 2aff5f67..cd1233be 100644 --- a/src/nisarqa/processing/stats_h5_writer/metrics_writer.py +++ b/src/nisarqa/processing/stats_h5_writer/metrics_writer.py @@ -591,10 +591,11 @@ def get_stats_name_descr(stat: str, component: str | None) -> tuple[str, str]: ), ) + def get_list_of_real_stats_names() -> list[str]: """ Return a list of the names of all statistics for real-valued datasets. - + These names are per NISAR conventions, for min/max/mean/std statistics. """ @@ -608,7 +609,7 @@ def get_list_of_real_stats_names() -> list[str]: def get_list_of_imag_stats_names() -> list[str]: """ Return a list of the names of all statistics for complex-valued datasets. - + These names are per NISAR conventions, for min/max/mean/std statistics. """ diff --git a/src/nisarqa/validate/sanity_checks.py b/src/nisarqa/validate/sanity_checks.py index 1f86ab8b..06885f42 100644 --- a/src/nisarqa/validate/sanity_checks.py +++ b/src/nisarqa/validate/sanity_checks.py @@ -13,6 +13,7 @@ objects_to_skip = nisarqa.get_all(name=__name__) + def _log_if_bad_string_value(val: str | list[str], path: str) -> bool: """ Log an error if value is a known invalid string. @@ -72,7 +73,7 @@ def check_metadata_conventions(h5_file: h5py.File) -> None: """ Check that all datasets and attributes meet certain NISAR conventions. - Iterate through an HDF5 file to validate that all groups and datasets, + Iterate through an HDF5 file to validate that all groups and datasets, including their attributes, meet certain NISAR conventions: 1) populated (not empty) 2) if string, that they are not variable-length strings and that @@ -110,7 +111,8 @@ def _validate_string_logic( name: str, dtype_: h5py.Datatype, value_provider: Callable[[], Any], - label: str) -> None: + label: str, + ) -> None: """ Unified logic to validate HDF5 string types and content. @@ -144,7 +146,7 @@ def _validate_string_logic( # Fixed-length strings are usually small metadata fields. raw_val = value_provider() ds_val = nisarqa.byte_string_to_python_str(raw_val) - + vals = [ds_val] if isinstance(ds_val, (str, bytes)) else ds_val for val in vals: _log_if_bad_string_value(val=val, path=name) @@ -153,7 +155,9 @@ def _check_attributes(item_name: str, item: h5py.HLObject) -> None: """Check all attributes of a specific HDF5 object.""" for attr_name, attr_val in item.attrs.items(): if attr_val is None or isinstance(attr_val, h5py.Empty): - log.error(f"Attribute '{attr_name}' is empty. Path: {item_name}") + log.error( + f"Attribute '{attr_name}' is empty. Path: {item_name}" + ) continue attr_id = item.attrs.get_id(attr_name) @@ -177,9 +181,15 @@ def _check_attributes(item_name: str, item: h5py.HLObject) -> None: # Check attributes which should use a half-precision dtype if attr_name in half_precision_match: - incorrect_c32 = (nisarqa.is_complex32(item) and attr_id.dtype != np.float16) - incorrect_c64 = (item.dtype == np.complex64 and attr_id.dtype != np.float32) - if incorrect_c32 or incorrect_c64: + incorrect_c32 = ( + nisarqa.is_complex32(item) + and attr_id.dtype != np.float16 + ) + incorrect_c64 = ( + item.dtype == np.complex64 + and attr_id.dtype != np.float32 + ) + if incorrect_c32 or incorrect_c64: log.error( f"Attribute has dtype {attr_id.dtype}, which does" f" not match the half-precision of its dataset's" @@ -198,10 +208,9 @@ def _check_attributes(item_name: str, item: h5py.HLObject) -> None: name=f"{item_name} -> {attr_name}", dtype_=attr_id.dtype, value_provider=lambda: attr_val, - label="Attribute" + label="Attribute", ) - def visitor_func(path: str) -> None: """Visitor function for h5py.visit.""" @@ -210,7 +219,7 @@ def visitor_func(path: str) -> None: return obj = h5_file[path] - + # 1. Always check attributes (This is safe for large datasets) _check_attributes(path, obj) @@ -241,8 +250,9 @@ def visitor_func(path: str) -> None: _validate_string_logic( name=obj.name, dtype_=obj.dtype, - value_provider=lambda: obj[()], # Only called if dtype is string - label="Dataset" + # Only called if dtype is string + value_provider=lambda: obj[()], + label="Dataset", ) # 2c. Numeric Type/Content Check