Skip to content

Commit f2c5833

Browse files
committed
support custom attribute in validate_nexus
1 parent 96c6f8c commit f2c5833

File tree

3 files changed

+172
-22
lines changed

3 files changed

+172
-22
lines changed

src/pynxtools/dataconverter/helpers.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import logging
2222
import os
2323
import re
24-
from collections.abc import Mapping, Sequence
24+
from collections.abc import Mapping, MutableMapping, Sequence
2525
from datetime import datetime, timezone
2626
from enum import Enum, auto
2727
from functools import cache, lru_cache
@@ -154,7 +154,7 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar
154154

155155
elif log_type == ValidationProblem.InvalidEnum:
156156
logger.warning(
157-
f"The value {args[0]} at {path} should be one of the following: {value}."
157+
f"The value '{args[0]}' at {path} should be one of the following: {value}."
158158
)
159159
elif log_type == ValidationProblem.OpenEnumWithCustom:
160160
logger.info(
@@ -166,11 +166,13 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar
166166
"When a different value is used, the boolean 'custom' attribute cannot be False."
167167
)
168168
elif log_type == ValidationProblem.OpenEnumWithMissingCustom:
169-
logger.info(
169+
log_text = (
170170
f"The value '{args[0]}' at {path} does not match with the enumerated items from the open enumeration: {value}. "
171-
"When a different value is used, a boolean 'custom=True' attribute must be added. It was added here automatically."
171+
"When a different value is used, a boolean 'custom=True' attribute must be added."
172172
)
173-
173+
if args[1] is True:
174+
log_text += " It was added here automatically."
175+
logger.info(log_text)
174176
elif log_type == ValidationProblem.MissingRequiredGroup:
175177
logger.warning(f"The required group {path} hasn't been supplied.")
176178
elif log_type == ValidationProblem.MissingRequiredField:
@@ -878,6 +880,81 @@ def validate_data_value(value: Any, nxdl_type: str, path: str) -> Any:
878880
return validate_data_value(value, nxdl_type, path)
879881

880882

883+
def get_custom_attr_path(path: str) -> str:
884+
if path.split("/")[-1].startswith("@"):
885+
attr_name = path.split("/")[-1][1:] # remove "@"
886+
return f"{path}_custom"
887+
return f"{path}/@custom"
888+
889+
890+
def is_valid_enum(
891+
value: Any,
892+
nxdl_enum: list,
893+
nxdl_enum_open: bool,
894+
path: str,
895+
mapping: MutableMapping,
896+
):
897+
"""Check enumeration."""
898+
899+
if isinstance(value, dict) and set(value.keys()) == {"compress", "strength"}:
900+
value = value["compress"]
901+
902+
if nxdl_enum is not None:
903+
if (
904+
isinstance(value, np.ndarray)
905+
and isinstance(nxdl_enum, list)
906+
and isinstance(nxdl_enum[0], list)
907+
):
908+
enum_value = list(value)
909+
else:
910+
enum_value = value
911+
912+
if enum_value not in nxdl_enum:
913+
if nxdl_enum_open:
914+
custom_path = get_custom_attr_path(path)
915+
916+
if isinstance(mapping, h5py.Group):
917+
parent_path, attr_name = custom_path.rsplit("@", 1)
918+
custom_attr = mapping.get(parent_path).attrs.get(attr_name)
919+
custom_added_auto = False
920+
else:
921+
custom_attr = mapping.get(custom_path)
922+
custom_added_auto = True
923+
924+
if custom_attr == True: # noqa: E712
925+
collector.collect_and_log(
926+
path,
927+
ValidationProblem.OpenEnumWithCustom,
928+
nxdl_enum,
929+
value,
930+
)
931+
elif custom_attr == False: # noqa: E712
932+
collector.collect_and_log(
933+
path,
934+
ValidationProblem.OpenEnumWithCustomFalse,
935+
nxdl_enum,
936+
value,
937+
)
938+
939+
elif custom_attr is None:
940+
try:
941+
mapping[custom_path] = True
942+
except ValueError:
943+
# we are in the HDF5 validation, cannot set custom attribute.
944+
pass
945+
collector.collect_and_log(
946+
path,
947+
ValidationProblem.OpenEnumWithMissingCustom,
948+
nxdl_enum,
949+
value,
950+
custom_added_auto,
951+
)
952+
else:
953+
collector.collect_and_log(
954+
path, ValidationProblem.InvalidEnum, nxdl_enum, value
955+
)
956+
957+
881958
def split_class_and_name_of(name: str) -> tuple[Optional[str], str]:
882959
"""
883960
Return the class and the name of a data dict entry of the form

src/pynxtools/dataconverter/validation.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
clean_str_attr,
4242
collector,
4343
convert_nexus_to_caps,
44+
get_custom_attr_path,
4445
is_valid_data_field,
46+
is_valid_enum,
4547
split_class_and_name_of,
4648
)
4749
from pynxtools.dataconverter.nexus_tree import (
@@ -644,9 +646,14 @@ def handle_field(
644646
is_valid_data_field(
645647
clean_str_attr(dataset[()]),
646648
node.dtype,
649+
full_path,
650+
)
651+
is_valid_enum(
652+
clean_str_attr(dataset[()]),
647653
node.items,
648654
node.open_enum,
649655
full_path,
656+
data,
650657
)
651658

652659
units = dataset.attrs.get("units")
@@ -695,7 +702,12 @@ def handle_attributes(
695702
for attr_name in attrs:
696703
full_path = f"{entry_name}/{path}/@{attr_name}"
697704

698-
if attr_name in ("NX_class", "units", "target"):
705+
if attr_name in (
706+
"NX_class",
707+
"units",
708+
"target",
709+
"custom",
710+
) or attr_name.endswith("_custom"):
699711
# Ignore special attrs
700712
continue
701713

@@ -734,9 +746,15 @@ def handle_attributes(
734746
is_valid_data_field(
735747
attr_data,
736748
node.dtype,
749+
full_path,
750+
)
751+
752+
is_valid_enum(
753+
attr_data,
737754
node.items,
738755
node.open_enum,
739756
full_path,
757+
data,
740758
)
741759

742760
def validate(path: str, h5_obj: Union[h5py.Group, h5py.Dataset]):
@@ -1351,6 +1369,7 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
13511369
variant_path,
13521370
mapping,
13531371
)
1372+
remove_from_not_visited(get_custom_attr_path(variant_path))
13541373

13551374
check_reserved_suffix(variant_path, keys)
13561375
check_reserved_prefix(variant_path, get_definition(variant_path), "field")
@@ -1424,6 +1443,7 @@ def handle_attribute(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
14241443
variant_path,
14251444
mapping,
14261445
)
1446+
remove_from_not_visited(get_custom_attr_path(variant_path))
14271447
check_reserved_prefix(
14281448
variant_path, get_definition(variant_path), "attribute"
14291449
)
@@ -1662,6 +1682,7 @@ def is_documented(key: str, tree: NexusNode) -> bool:
16621682
key,
16631683
mapping,
16641684
)
1685+
remove_from_not_visited(get_custom_attr_path(key))
16651686

16661687
return True
16671688

@@ -1686,6 +1707,7 @@ def is_documented(key: str, tree: NexusNode) -> bool:
16861707
key,
16871708
mapping,
16881709
)
1710+
remove_from_not_visited(get_custom_attr_path(key))
16891711

16901712
# Check main field exists for units
16911713
if (

0 commit comments

Comments
 (0)