8000 Remodel the `eln_mapper` tool to use the NeXusTree by lukaspie · Pull Request #607 · FAIRmat-NFDI/pynxtools · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Remodel the eln_mapper tool to use the NeXusTree #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 57 additions & 10 deletions src/pynxtools/dataconverter/nexus_tree.py
10000
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@
"""

from functools import lru_cache, reduce
from typing import Any, List, Literal, Optional, Set, Tuple, Union
from typing import Any, List, Dict, Literal, Optional, Set, Tuple, Union

import lxml.etree as ET
from anytree.node.nodemixin import NodeMixin

from pynxtools import get_definitions_url
from pynxtools.dataconverter.helpers import (
get_all_parents_for,
get_nxdl_root_and_path,
Expand All @@ -45,6 +46,7 @@
get_nx_namefit,
is_name_type,
)
from pynxtools import NX_DOC_BASES

NexusType = Literal[
"NX_BINARY",
Expand Down Expand Up @@ -148,6 +150,8 @@ class NexusNode(NodeMixin):
parent_of: List["NexusNode"]:
The inverse of the above `is_a`. In the example case
`DATA` `parent_of` `my_data`.
nxdl_base: str
Base of the NXDL file where the XML element for this node is defined
"""

name: str
Expand All @@ -158,6 +162,7 @@ class NexusNode(NodeMixin):
inheritance: List[ET._Element]
is_a: List["NexusNode"]
parent_of: List["NexusNode"]
nxdl_base: str

def _set_optionality(self):
"""
Expand Down Expand Up @@ -190,12 +195,14 @@ def __init__(
variadic: Optional[bool] = None,
parent: Optional["NexusNode"] = None,
inheritance: Optional[List[Any]] = None,
nxdl_base: Optional[str] = None,
) -> None:
super().__init__()
self.name = name
self.type = type
self.name_type = name_type
self.optionality = optionality
self.nxdl_base = nxdl_base
self.variadic = is_variadic(self.name, self.name_type)
if variadic is not None:
self.variadic = variadic
Expand Down Expand Up @@ -430,7 +437,7 @@ def required_fields_and_attrs_names(

return req_children

def get_docstring(self, depth: Optional[int] = None) -> List[str]:
def get_docstring(self, depth: Optional[int] = None) -> Dict[str, str]:
"""
Gets the docstrings of the current node and its parents up to a certain depth.

Expand All @@ -449,14 +456,48 @@ def get_docstring(self, depth: Optional[int] = None) -> List[str]:
if depth is not None and depth < 0:
raise ValueError("Depth must be a positive integer or None")

docstrings = []
docstrings = {}
for elem in self.inheritance[:depth][::-1]:
doc = elem.find("nx:doc", namespaces=namespaces)

if doc is not None:
docstrings.append(doc.text)
name = elem.attrib.get("name")
if not name:
name = elem.attrib["type"][2:].upper()
docstrings[name] = doc.text

return docstrings

def get_link(self) -> str:
"""
Get documentation url
"""

anchor_segments = [self.type]
current_node = self

while True:
if not current_node:
break

segment = current_node.name
anchor_segments.append(current_node.name.replace("_", "-")) # type: ignore[arg-t 10000 ype]
current_node = current_node.parent

definitions_url = get_definitions_url()
doc_base = NX_DOC_BASES.get(
definitions_url, "https://manual.nexusformat.org/classes"
)
nx_file = self.nxdl_base.split("/definitions/")[-1].split(".nxdl.xml")[0]

# add the name of the base file at the end, drop the appdef name
anchor_segments = anchor_segments[:-1]
anchor_segments += [self.nxdl_base.split("/")[-1].split(".nxdl.xml")[0].lower()] # type: ignore[list-item]

anchor = "-".join([name.lower() for name in reversed(anchor_segments)])

return f"{doc_base}/{nx_file}.html#{anchor}"

def _build_inheritance_chain(self, xml_elem: ET._Element) -> List[ET._Element]:
"""
Builds the inheritance chain based on the given xml node and the inheritance
Expand Down Expand Up @@ -559,6 +600,7 @@ def add_node_from(self, xml_elem: ET._Element) -> Optional["NexusNode"]:
name_type=name_type,
type=tag,
optionality=default_optionality,
nxdl_base=xml_elem.base,
)
elif tag == "group":
name = xml_elem.attrib.get("name")
Expand All @@ -575,13 +617,15 @@ def add_node_from(self, xml_elem: ET._Element) -> Optional["NexusNode"]:
nx_class=xml_elem.attrib["type"],
inheritance=inheritance_chain,
optionality=default_optionality,
nxdl_base=xml_elem.base,
)
elif tag == "choice":
current_elem = NexusChoice(
parent=self,
name=xml_elem.attrib["name"],
name_type=name_type,
optionality=default_optionality,
nxdl_base=xml_elem.base,
)
else:
# TODO: Tags: link
Expand Down Expand Up @@ -932,14 +976,15 @@ def populate_tree_from_parents(node: NexusNode):
populate_tree_from_parents(child_node)


def generate_tree_from(appdef: str) -> NexusNode:
def generate_tree_from(appdef: str, set_root_attr: bool = True) -> NexusNode:
"""
Generates a NexusNode tree from an application definition.
NexusNode is based on anytree nodes and anytree's functions can be used
for displaying and traversal of the tree.

Args:
appdef (str): The application definition name to generate the NexusNode tree from.
set_root_attr (bool): Whether or not to set the root attributes.

Returns:
NexusNode: The tree representing the application definition.
Expand Down Expand Up @@ -968,6 +1013,7 @@ def add_children_to(parent: NexusNode, xml_elem: ET._Element) -> None:
add_children_to(current_elem, child)

appdef_xml_root, _ = get_nxdl_root_and_path(appdef)

global namespaces
namespaces = {"nx": appdef_xml_root.nsmap[None]}

Expand All @@ -983,18 +1029,19 @@ def add_children_to(parent: NexusNode, xml_elem: ET._Element) -> None:
variadic=False,
parent=None,
inheritance=appdef_inheritance_chain,
nxdl_base=appdef_xml_root.base,
)
# Set root attributes
nx_root, _ = get_nxdl_root_and_path("NXroot")
for root_attrib in nx_root.findall("nx:attribute", namespaces=namespaces):
child = tree.add_node_from(root_attrib)
child.optionality = "optional"
if set_root_attr:
nx_root, _ = get_nxdl_root_and_path("NXroot")
for root_attrib in nx_root.findall("nx:attribute", namespaces=namespaces):
child = tree.add_node_from(root_attrib)
child.optionality = "optional"

entry = appdef_xml_root.find("nx:group[@type='NXentry']", namespaces=namespaces)
add_children_to(tree, entry)

# Add all fields and attributes from the parent appdefs
if len(appdef_inheritance_chain) > 1:
populate_tree_from_parents(tree)

return tree
8 changes: 6 additions & 2 deletions src/pynxtools/eln_mapper/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# ELN generator

This is a helper tool for generating ELN files that can be used to add metadata to the dataconverter routine.

Two types of ELN are supported (by passing the flag `eln-type`):
- **eln**: The simple ELN generator that can be used in a console or jupyter-notebook.
- **scheme_eln**: Scheme based ELN generator that can be used in NOMAD and the ELN can be used as a custom scheme in NOMAD.

- **`reader`**: The simple ELN generator that can be used in a console or jupyter-notebook, e.g. by the `pynxtools` dataconverter.
- **`schema`**: Scheme based ELN generator that can be used in NOMAD and the ELN can be used as a custom scheme in NOMAD.

Here you can find more information about the tool:

- [API documentation](https://fairmat-nfdi.github.io/pynxtools/reference/cli-api.html#generate_eln)
Loading
Loading
0