Skip to content

Commit 10626f6

Browse files
authored
remove unneeded code for checking attributes of nonexisting fields (#691)
1 parent b92b8b0 commit 10626f6

File tree

4 files changed

+102
-216
lines changed

4 files changed

+102
-216
lines changed

src/pynxtools/data/NXtest.nxdl.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
</group>
5353
<group type="NXdata" name="any_groupGROUP" nameType="any">
5454
<doc>A group with a name and nameType="any".</doc>
55-
<field name="any_fieldFIELD" nameType="any" type="NX_FLOAT" units="NX_ANY">
55+
<field name="any_fieldFIELD" nameType="any" optional="true" type="NX_FLOAT" units="NX_ANY">
5656
<attribute name="any_attrATTR_in_field" nameType="any"/>
5757
</field>
5858
<attribute name="any_attrATTR" nameType="any"/>

src/pynxtools/dataconverter/helpers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class ValidationProblem(Enum):
7070
MissingUnit = auto()
7171
ChoiceValidationError = auto()
7272
UnitWithoutField = auto()
73-
AttributeForNonExistingField = auto()
73+
AttributeForNonExistingConcept = auto()
7474
BrokenLink = auto()
7575
FailedNamefitting = auto()
7676
NXdataMissingSignalData = auto()
@@ -163,10 +163,10 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar
163163
logger.warning(f'Missing attribute: "{path}"')
164164
elif log_type == ValidationProblem.UnitWithoutField:
165165
logger.warning(f"Unit {path} in dataset without its field {value}.")
166-
elif log_type == ValidationProblem.AttributeForNonExistingField:
166+
elif log_type == ValidationProblem.AttributeForNonExistingConcept:
167167
logger.warning(
168-
f"There were attributes set for the field {path}, "
169-
"but the field does not exist."
168+
f"There were attributes set for the {value} {path}, "
169+
f"but the {value} does not exist."
170170
)
171171
elif log_type == ValidationProblem.BrokenLink:
172172
logger.warning(f"Broken link at {path} to {value}.")

src/pynxtools/dataconverter/validation.py

Lines changed: 44 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ def _follow_link(
594594
return resolved_keys
595595

596596
def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
597-
full_path = remove_from_not_visited(f"{prev_path}/{node.name}")
597+
full_path = f"{prev_path}/{node.name}"
598598
variants = get_variations_of(node, keys)
599599
if (
600600
not variants
@@ -605,15 +605,15 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
605605
return
606606

607607
for variant in variants:
608-
variant_path = f"{prev_path}/{variant}"
608+
variant_path = remove_from_not_visited(f"{prev_path}/{variant}")
609609

610610
if (
611611
isinstance(keys[variant], Mapping)
612612
and not all(k.startswith("@") for k in keys[variant])
613613
and not list(keys[variant].keys()) == ["compress", "strength"]
614614
):
615615
# A field should not have a dict of keys that are _not_ all attributes,
616-
# i.e. no sub-fields or sub-groups.
616+
# i.e. there should be no sub-fields or sub-groups.
617617
collector.collect_and_log(
618618
variant_path,
619619
ValidationProblem.ExpectedField,
@@ -640,8 +640,8 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
640640
)
641641
collector.collect_and_log(
642642
variant_path,
643-
ValidationProblem.AttributeForNonExistingField,
644-
None,
643+
ValidationProblem.AttributeForNonExistingConcept,
644+
"field",
645645
)
646646
return
647647
if variant not in keys or mapping.get(variant_path) is None:
@@ -661,30 +661,31 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
661661

662662
# Check unit category
663663
if node.unit is not None:
664-
unit_path = f"{variant_path}/@units"
665-
if node.unit != "NX_UNITLESS":
666-
remove_from_not_visited(unit_path)
667-
if f"{variant}@units" not in keys and (
668-
node.unit != "NX_TRANSFORMATION"
669-
or mapping.get(f"{variant_path}/@transformation_type")
670-
in ("translation", "rotation")
671-
):
664+
unit_path = remove_from_not_visited(f"{variant_path}/@units")
665+
if f"{variant}@units" not in keys and (
666+
node.unit != "NX_TRANSFORMATION"
667+
or mapping.get(f"{variant_path}/@transformation_type")
668+
in ("translation", "rotation")
669+
):
670+
if node.unit != "NX_UNITLESS":
672671
collector.collect_and_log(
673672
variant_path,
674673
ValidationProblem.MissingUnit,
675674
node.unit,
676675
)
677-
break
678676

679-
unit = keys.get(f"{variant}@units")
680-
# Special case: NX_TRANSFORMATION unit depends on `@transformation_type` attribute
681-
if (
682-
transformation_type := keys.get(f"{variant}@transformation_type")
683-
) is not None:
684-
hints = {"transformation_type": transformation_type}
685677
else:
686-
hints = {}
687-
is_valid_unit_for_node(node, unit, unit_path, hints)
678+
unit = keys.get(f"{variant}@units")
679+
# Special case: NX_TRANSFORMATION unit depends on `@transformation_type` attribute
680+
if (
681+
transformation_type := keys.get(
682+
f"{variant}@transformation_type"
683+
)
684+
) is not None:
685+
hints = {"transformation_type": transformation_type}
686+
else:
687+
hints = {}
688+
is_valid_unit_for_node(node, unit, unit_path, hints)
688689

689690
field_attributes = get_field_attributes(variant, keys)
690691
field_attributes = _follow_link(field_attributes, variant_path)
@@ -696,7 +697,7 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
696697
)
697698

698699
def handle_attribute(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
699-
full_path = remove_from_not_visited(f"{prev_path}/@{node.name}")
700+
full_path = f"{prev_path}/@{node.name}"
700701
variants = get_variations_of(node, keys)
701702

702703
if (
@@ -708,7 +709,7 @@ def handle_attribute(node: NexusNode, keys: Mapping[str, Any], prev_path: str):
708709
return
709710

710711
for variant in variants:
711-
variant_path = (
712+
variant_path = remove_from_not_visited(
712713
f"{prev_path}/{variant if variant.startswith('@') else f'@{variant}'}"
713714
)
714715
mapping[variant_path] = is_valid_data_field(
@@ -1035,177 +1036,6 @@ def find_instance_name_conflicts(mapping: MutableMapping[str, str]) -> None:
10351036
)
10361037
keys_to_remove.append(valid_key)
10371038

1038-
def check_attributes_of_nonexisting_field(
1039-
node: NexusNode,
1040-
):
1041-
"""
1042-
This method runs through the mapping dictionary and checks if there are any
1043-
attributes assigned to the fields (not groups!) which are not explicitly
1044-
present in the mapping.
1045-
If there are any found, a warning is logged and the corresponding items are
1046-
added to the list that stores all keys that shall be removed.
1047-
1048-
Args:
1049-
node (NexusNode): the tree generated from application definition.
1050-
1051-
"""
1052-
1053-
for key in mapping:
1054-
last_index = key.rfind("/")
1055-
if key[last_index + 1] == "@" and key[last_index + 1 :] != "@units":
1056-
# key is an attribute. Find a corresponding parent, check all the other
1057-
# children of this parent
1058-
# ignore units here, they are checked separately
1059-
attribute_parent_checked = False
1060-
for key_iterating in mapping:
1061-
# check if key_iterating starts with parent of the key OR any
1062-
# allowed variation of the parent of the key
1063-
flag, extra_length = startswith_with_variations(
1064-
key_iterating, key[0:last_index]
1065-
)
1066-
# the variation of the path to the parent might have different length
1067-
# than the key itself, extra_length is adjusting for that
1068-
if flag:
1069-
if len(key_iterating) == last_index + extra_length:
1070-
# the parent is a field
1071-
attribute_parent_checked = True
1072-
break
1073-
elif (key_iterating[last_index + extra_length] == "/") and (
1074-
key_iterating[last_index + extra_length + 1] != "@"
1075-
):
1076-
# the parent is a group
1077-
attribute_parent_checked = True
1078-
break
1079-
if not attribute_parent_checked:
1080-
type_of_parent_from_tree = check_type_with_tree(
1081-
node, key[0:last_index]
1082-
)
1083-
# The parent can be a group with only attributes as children; check
1084-
# in the tree built from app. def. Alternatively, parent can be not
1085-
# in the tree and then group with only attributes is indistinguishable
1086-
# from missing field. Continue without warnings or changes.
1087-
if not (
1088-
type_of_parent_from_tree == "group"
1089-
or type_of_parent_from_tree is None
1090-
):
1091-
collector.collect_and_log(
1092-
key[0:last_index],
1093-
ValidationProblem.AttributeForNonExistingField,
1094-
"attribute",
1095-
)
1096-
collector.collect_and_log(
1097-
key,
1098-
ValidationProblem.KeyToBeRemoved,
1099-
"attribute",
1100-
)
1101-
keys_to_remove.append(key)
1102-
remove_from_not_visited(key)
1103-
1104-
def check_type_with_tree(
1105-
node: NexusNode,
1106-
path: str,
1107-
) -> Optional[str]:
1108-
"""
1109-
Recursively search for the type of the object from Template
1110-
(described by path) using subtree hanging below the node.
1111-
The path should be relative to the current node.
1112-
1113-
Args:
1114-
node (NexusNode): the subtree to search in.
1115-
path (str): the string addressing the object from Mapping (the Template)
1116-
to search the type of.
1117-
1118-
Returns:
1119-
Optional str: type of the object as a string, if the object was found
1120-
in the tree, None otherwise.
1121-
"""
1122-
1123-
# already arrived to the object
1124-
if path == "":
1125-
return node.type
1126-
# searching for object among the children of the node
1127-
next_child_name_index = path[1:].find("/")
1128-
if (
1129-
next_child_name_index == -1
1130-
): # the whole path from element #1 is the child name
1131-
next_child_name_index = (
1132-
len(path) - 1
1133-
) # -1 because we count from element #1, not #0
1134-
next_child_class, next_child_name = split_class_and_name_of(
1135-
path[1 : next_child_name_index + 1]
1136-
)
1137-
if (next_child_class is not None) or (next_child_name is not None):
1138-
output = None
1139-
for child in node.children:
1140-
# regexs to separate the class and the name from full name of the child
1141-
child_class_from_node = re.sub(
1142-
r"(\@.*)*(\[.*?\])*(\(.*?\))*([a-z]\_)*(\_[a-z])*[a-z]*\s*",
1143-
"",
1144-
child.__str__(),
1145-
)
1146-
child_name_from_node = re.sub(
1147-
r"(\@.*)*(\(.*?\))*(.*\[)*(\].*)*\s*",
1148-
"",
1149-
child.__str__(),
1150-
)
1151-
if (child_class_from_node == next_child_class) or (
1152-
child_name_from_node == next_child_name
1153-
):
1154-
output_new = check_type_with_tree(
1155-
child, path[next_child_name_index + 1 :]
1156-
)
1157-
if output_new is not None:
1158-
output = output_new
1159-
return output
1160-
else:
1161-
return None
1162-
1163-
def startswith_with_variations(
1164-
large_str: str, baseline_str: str
1165-
) -> tuple[bool, int]:
1166-
"""
1167-
Recursively check if the large_str starts with baseline_str or an allowed
1168-
equivalent (i.e. .../AXISNAME[energy]/... matches .../energy/...).
1169-
1170-
Args:
1171-
large_str (str): the string to be checked.
1172-
baseline_str (str): the string used as a baseline for comparison.
1173-
1174-
Returns:
1175-
bool: True if large_str starts with baseline_str or equivalent, else False.
1176-
int: The combined length difference between baseline_str and the equivalent
1177-
part of the large_str.
1178-
"""
1179-
if len(baseline_str.split("/")) == 1:
1180-
# if baseline_str has no separators left, match already found
1181-
return (True, 0)
1182-
first_token_large_str = large_str.split("/")[1]
1183-
first_token_baseline_str = baseline_str.split("/")[1]
1184-
1185-
remaining_large_str = large_str[len(first_token_large_str) + 1 :]
1186-
remaining_baseline_str = baseline_str[len(first_token_baseline_str) + 1 :]
1187-
if first_token_large_str == first_token_baseline_str:
1188-
# exact match of n-th token
1189-
return startswith_with_variations(
1190-
remaining_large_str, remaining_baseline_str
1191-
)
1192-
match_check = re.search(r"\[.*?\]", first_token_large_str)
1193-
if match_check is None:
1194-
# tokens are different and do not contain []
1195-
return (False, 0)
1196-
variation_first_token_large = match_check.group(0)[1:-1]
1197-
if variation_first_token_large == first_token_baseline_str:
1198-
# equivalents match
1199-
extra_length_this_step = len(first_token_large_str) - len(
1200-
first_token_baseline_str
1201-
)
1202-
a, b = startswith_with_variations(
1203-
remaining_large_str, remaining_baseline_str
1204-
)
1205-
return (a, b + extra_length_this_step)
1206-
# default
1207-
return (False, 0)
1208-
12091039
def check_reserved_suffix(key: str, mapping: MutableMapping[str, Any]) -> bool:
12101040
"""
12111041
Check if an associated field exists for a key with a reserved suffix.
@@ -1369,8 +1199,6 @@ def check_reserved_prefix(
13691199
keys = _follow_link(nested_keys, "")
13701200
recurse_tree(tree, nested_keys)
13711201

1372-
check_attributes_of_nonexisting_field(tree)
1373-
13741202
for not_visited_key in not_visited:
13751203
if mapping.get(not_visited_key) is None:
13761204
# This value is not really set. Skip checking its validity.
@@ -1441,15 +1269,29 @@ def check_reserved_prefix(
14411269

14421270
if "@" in not_visited_key.rsplit("/")[-1]:
14431271
# check that parent exists
1444-
if not_visited_key.rsplit("/", 1)[0] not in mapping.keys():
1272+
parent_key = not_visited_key.rsplit("/", 1)[0]
1273+
if (parent_key := not_visited_key.rsplit("/", 1)[0]) not in mapping.keys():
14451274
# check that parent is not a group
14461275
node = add_best_matches_for(not_visited_key.rsplit("/", 1)[0], tree)
1447-
if node is None or node.type != "group":
1276+
1277+
remove_attr = False
1278+
1279+
if node is None:
14481280
collector.collect_and_log(
1449-
not_visited_key.rsplit("/", 1)[0],
1450-
ValidationProblem.AttributeForNonExistingField,
1451-
None,
1281+
parent_key,
1282+
ValidationProblem.AttributeForNonExistingConcept,
1283+
"group or field",
1284+
)
1285+
remove_attr = True
1286+
elif node.type != "group":
1287+
collector.collect_and_log(
1288+
parent_key,
1289+
ValidationProblem.AttributeForNonExistingConcept,
1290+
"field",
14521291
)
1292+
remove_attr = True
1293+
1294+
if remove_attr:
14531295
collector.collect_and_log(
14541296
not_visited_key,
14551297
ValidationProblem.KeyToBeRemoved,

0 commit comments

Comments
 (0)