Skip to content

Commit

Permalink
initial attempt at nameType in validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaspie committed Mar 7, 2025
1 parent 760e267 commit 9f5dcfe
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 9 deletions.
35 changes: 30 additions & 5 deletions src/pynxtools/dataconverter/nexus_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,18 @@ class NexusNode(NodeMixin):
The name of the node.
type (Literal["group", "field", "attribute", "choice"]):
The type of the node, e.g., xml tag in the nxdl file.
name_type (Optional["specified", "any", "partial"]):
The nameType of the node.
Defaults to "specified".
optionality (Literal["required", "recommended", "optional"], optional):
The optionality of the node.
This is automatically set on init (in the respective subclasses)
based on the values found in the nxdl file.
Defaults to "required".
variadic (bool):
True if the node name is variadic and can be matched against multiple names.
This is set automatically on init and will be True if the name contains
any uppercase characets and False otherwise.
This is set automatically on init and will be True if the `nameTYPE` is "any"
or "partial" and False otherwise.
Defaults to False.
inheritance (List[InstanceOf[ET._Element]]):
The inheritance chain of the node.
Expand All @@ -145,6 +148,7 @@ class NexusNode(NodeMixin):

name: str
type: Literal["group", "field", "attribute", "choice"]
name_type: Optional[Literal["specified", "any", "partial"]] = "specified"
optionality: Literal["required", "recommended", "optional"] = "required"
variadic: bool = False
inheritance: List[ET._Element]
Expand Down Expand Up @@ -177,6 +181,7 @@ def __init__(
self,
name: str,
type: Literal["group", "field", "attribute", "choice"],
name_type: Optional[Literal["specified", "any", "partial"]] = "specified",
optionality: Literal["required", "recommended", "optional"] = "required",
variadic: Optional[bool] = None,
parent: Optional["NexusNode"] = None,
Expand All @@ -185,8 +190,9 @@ def __init__(
super().__init__()
self.name = name
self.type = type
self.name_type = name_type
self.optionality = optionality
self.variadic = contains_uppercase(self.name)
self.variadic = self._is_variadic(self.name, self.name_type)
if variadic is not None:
self.variadic = variadic
if inheritance is not None:
Expand All @@ -197,6 +203,14 @@ def __init__(
self.is_a = []
self.parent_of = []

def _is_variadic(self, name: str, name_type: str) -> bool:
"""
Determine if a name is variadic based on its nameType.
"""
if name:
return True if name_type in ("any", "partial") else False
return True

def _construct_inheritance_chain_from_parent(self):
"""
Builds the inheritance chain of the current node based on the parent node.
Expand Down Expand Up @@ -525,21 +539,31 @@ def add_node_from(self, xml_elem: ET._Element) -> Optional["NexusNode"]:
"""
default_optionality = "required" if is_appdef(xml_elem) else "optional"
tag = remove_namespace_from_tag(xml_elem.tag)

name_type = xml_elem.attrib.get("nameType", "specified")

if tag in ("field", "attribute"):
name = xml_elem.attrib.get("name")

current_elem = NexusEntity(
parent=self,
name=name,
name_type=name_type,
type=tag,
optionality=default_optionality,
)
elif tag == "group":
name = xml_elem.attrib.get("name", xml_elem.attrib["type"][2:].upper())
name = xml_elem.attrib.get("name")
if not name:
name = xml_elem.attrib["type"][2:].upper()
name_type = None

inheritance_chain = self._build_inheritance_chain(xml_elem)
current_elem = NexusGroup(
parent=self,
type=tag,
name=name,
name_type=name_type,
nx_class=xml_elem.attrib["type"],
inheritance=inheritance_chain,
optionality=default_optionality,
Expand All @@ -548,7 +572,7 @@ def add_node_from(self, xml_elem: ET._Element) -> Optional["NexusNode"]:
current_elem = NexusChoice(
parent=self,
name=xml_elem.attrib["name"],
variadic=contains_uppercase(xml_elem.attrib["name"]),
name_type=name_type,
optionality=default_optionality,
)
else:
Expand Down Expand Up @@ -910,6 +934,7 @@ def add_children_to(parent: NexusNode, xml_elem: ET._Element) -> None:
name=appdef_xml_root.attrib["name"],
nx_class="NXroot",
type="group",
name_type="specified",
optionality="required",
variadic=False,
parent=None,
Expand Down
24 changes: 20 additions & 4 deletions src/pynxtools/dataconverter/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,14 @@ def split_class_and_name_of(name: str) -> Tuple[Optional[str], str]:
), f"{name_match.group(2)}{'' if prefix is None else prefix}"


def best_namefit_of(name: str, keys: Iterable[str]) -> Optional[str]:
def best_namefit_of(name: str, keys: Iterable[str], name_type: str) -> Optional[str]:
"""
Get the best namefit of `name` in `keys`.
Args:
name (str): The name to fit against the keys.
keys (Iterable[str]): The keys to fit `name` against.
name_type (str): nameType of the concept being fitted
Returns:
Optional[str]: The best fitting key. None if no fit was found.
Expand All @@ -155,9 +156,14 @@ def best_namefit_of(name: str, keys: Iterable[str]) -> Optional[str]:
if nx_name is not None and nx_name in keys:
return nx_name

name_any = True if name_type == "any" else False
name_partial = True if name_type == "partial" else False

best_match, score = max(
map(lambda x: (x, get_nx_namefit(name2fit, x)), keys), key=lambda x: x[1]
map(lambda x: (x, get_nx_namefit(name2fit, x, name_any, name_partial)), keys),
key=lambda x: x[1],
)
print(nx_name, name2fit, best_match, score)
if score < 0:
return None

Expand Down Expand Up @@ -188,6 +194,9 @@ def validate_dict_against(

def get_variations_of(node: NexusNode, keys: Mapping[str, Any]) -> List[str]:
if not node.variadic:
print(node.name, node.name_type, node.variadic)
if hasattr(node, "nx_class"):
print(f"{convert_nexus_to_caps(node.nx_class)}[{node.name}]") # in keys
if node.name in keys:
return [node.name]
elif (
Expand All @@ -198,14 +207,21 @@ def get_variations_of(node: NexusNode, keys: Mapping[str, Any]) -> List[str]:

variations = []
for key in keys:
# print(key)
nx_name, name2fit = split_class_and_name_of(key)
if node.type == "attribute":
# Remove the starting @ from attributes
name2fit = name2fit[1:] if name2fit.startswith("@") else name2fit
if nx_name is not None and nx_name != node.name:
continue
name_any = True if node.name_type == "any" else False
name_partial = True if node.name_type == "partial" else False

# if nx_name and "ENTRY" in nx_name:
# print(nx_name, name2fit, key)

if (
get_nx_namefit(name2fit, node.name) >= 0
get_nx_namefit(name2fit, node.name, name_any, name_partial) >= 0
and key not in node.parent.get_all_direct_children_names()
):
variations.append(key)
Expand Down Expand Up @@ -511,7 +527,7 @@ def is_documented(key: str, node: NexusNode) -> bool:

for name in key[1:].replace("@", "").split("/"):
children = node.get_all_direct_children_names()
best_name = best_namefit_of(name, children)
best_name = best_namefit_of(name, children, node.name_type)
if best_name is None:
return False

Expand Down

0 comments on commit 9f5dcfe

Please sign in to comment.