diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 034091f82..1a14e2804 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -36,7 +36,7 @@ jobs: - name: Install nomad if: "${{ matrix.python_version != '3.8' && matrix.python_version != '3.12'}}" run: | - uv pip install nomad-lab@git+https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR.git + uv pip install nomad-lab@git+https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR.git@Sprint_Nomad_BaseSection - name: Install pynx run: | uv pip install ".[dev]" diff --git a/src/pynxtools/nexus/nexus.py b/src/pynxtools/nexus/nexus.py index c4f06d4fe..679d4f94d 100644 --- a/src/pynxtools/nexus/nexus.py +++ b/src/pynxtools/nexus/nexus.py @@ -5,8 +5,7 @@ import os import sys from functools import lru_cache - -from typing import Optional, Union, List, Any +from typing import Any, List, Optional, Union import click import h5py @@ -16,6 +15,7 @@ from pynxtools.definitions.dev_tools.utils.nxdl_utils import ( add_base_classes, check_attr_name_nxdl, + decode_or_not, get_best_child, get_hdf_info_parent, get_local_name_from_xml, @@ -29,7 +29,6 @@ try_find_units, walk_elist, write_doc_string, - decode_or_not, ) @@ -378,6 +377,8 @@ def get_inherited_hdf_nodes( # let us start with the given definition file if hdf_node is None: raise ValueError("hdf_node must not be None") + if nx_name == "NO NXentry found": + return (None, [], []) elist = [] # type: ignore[var-annotated] add_base_classes(elist, nx_name, elem) nxdl_elem_path = [elist[0]] diff --git a/src/pynxtools/nomad/parser.py b/src/pynxtools/nomad/parser.py index 2e65c6686..ea3dd3c3e 100644 --- a/src/pynxtools/nomad/parser.py +++ b/src/pynxtools/nomad/parser.py @@ -24,7 +24,8 @@ try: from ase.data import chemical_symbols from nomad.atomutils import Formula - from nomad.datamodel import EntryArchive + from nomad.datamodel import EntryArchive, EntryMetadata + from nomad.datamodel.data import EntryData from nomad.datamodel.results import Material, Results from nomad.metainfo import MSection from nomad.metainfo.util import MQuantity, MSubSectionList, resolve_variadic_name @@ -39,23 +40,8 @@ import pynxtools.nomad.schema as nexus_schema from pynxtools.nexus.nexus import HandleNexus - -__REPLACEMENT_FOR_NX = "BS" -__REPLACEMENT_LEN = len(__REPLACEMENT_FOR_NX) - - -def _rename_nx_to_nomad(name: str) -> Optional[str]: - """ - Rename the NXDL name to NOMAD. - For example: NXdata -> BSdata, - except NXobject -> NXobject - """ - if name == "NXobject": - return name - if name is not None: - if name.startswith("NX"): - return name.replace("NX", __REPLACEMENT_FOR_NX) - return name +from pynxtools.nomad.utils import __REPLACEMENT_FOR_NX +from pynxtools.nomad.utils import __rename_nx_for_nomad as rename_nx_for_nomad def _to_group_name(nx_node: ET.Element): @@ -63,9 +49,7 @@ def _to_group_name(nx_node: ET.Element): Normalise the given group name """ # assuming always upper() is incorrect, e.g. NXem_msr is a specific one not EM_MSR! - grp_nm = nx_node.attrib.get( - "name", nx_node.attrib["type"][__REPLACEMENT_LEN:].upper() - ) + grp_nm = nx_node.attrib.get("name", nx_node.attrib["type"][2:].upper()) return grp_nm @@ -109,6 +93,8 @@ def _to_section( # no need to change section for quantities and attributes return current + nomad_def_name = rename_nx_for_nomad(nomad_def_name, is_group=True) + # for groups, get the definition from the package new_def = current.m_def.all_sub_sections[nomad_def_name] @@ -233,9 +219,7 @@ def _populate_data( raise Warning( "setting attribute attempt before creating quantity" ) - current.m_set_quantity_attribute( - quantity.name, attr_name, attr_value - ) + quantity.m_set_attribute(attr_name, attr_value) except Exception as e: self._logger.warning( "error while setting attribute", @@ -307,28 +291,16 @@ def _populate_data( # may need to check if the given unit is in the allowable list try: current.m_set(metainfo_def, field) - current.m_set_quantity_attribute( - data_instance_name, "m_nx_data_path", hdf_node.name - ) - current.m_set_quantity_attribute( - data_instance_name, "m_nx_data_file", self.nxs_fname - ) + field.m_set_attribute("m_nx_data_path", hdf_node.name) + field.m_set_attribute("m_nx_data_file", self.nxs_fname) if field_stats is not None: # TODO _add_additional_attributes function has created these nx_data_* # attributes speculatively already so if the field_stats is None # this will cause unpopulated attributes in the GUI - current.m_set_quantity_attribute( - data_instance_name, "nx_data_mean", field_stats[0] - ) - current.m_set_quantity_attribute( - data_instance_name, "nx_data_var", field_stats[1] - ) - current.m_set_quantity_attribute( - data_instance_name, "nx_data_min", field_stats[2] - ) - current.m_set_quantity_attribute( - data_instance_name, "nx_data_max", field_stats[3] - ) + field.m_set_attribute("nx_data_mean", field_stats[0]) + field.m_set_attribute("nx_data_var", field_stats[1]) + field.m_set_attribute("nx_data_min", field_stats[2]) + field.m_set_attribute("nx_data_max", field_stats[3]) except Exception as e: self._logger.warning( "error while setting field", @@ -349,7 +321,8 @@ def __nexus_populate(self, params: dict, attr=None): # pylint: disable=W0613 hdf_path: str = hdf_info["hdf_path"] hdf_node = hdf_info["hdf_node"] if nx_def is not None: - nx_def = _rename_nx_to_nomad(nx_def) + nx_def = rename_nx_for_nomad(nx_def) + if nx_path is None: return @@ -489,8 +462,9 @@ def parse( child_archives: Dict[str, EntryArchive] = None, ) -> None: self.archive = archive - self.archive.m_create(nexus_schema.NeXus) # type: ignore # pylint: disable=no-member - self.nx_root = self.archive.nexus + self.nx_root = nexus_schema.NeXus() # type: ignore # pylint: disable=no-member + + self.archive.data = self.nx_root self._logger = logger if logger else get_logger(__name__) self._clear_class_refs() @@ -500,14 +474,10 @@ def parse( # TODO: domain experiment could also be registered if archive.metadata is None: - return + archive.metadata = EntryMetadata() # Normalise experiment type - app_def: str = "" - for var in dir(archive.nexus): - if getattr(archive.nexus, var, None) is not None: - app_def = var - break + app_def = str(self.nx_root).split("(")[1].split(")")[0].split(",")[0] if archive.metadata.entry_type is None: archive.metadata.entry_type = app_def archive.metadata.domain = "nexus" diff --git a/src/pynxtools/nomad/schema.py b/src/pynxtools/nomad/schema.py index 5fbbb6ac8..98dd86f85 100644 --- a/src/pynxtools/nomad/schema.py +++ b/src/pynxtools/nomad/schema.py @@ -31,7 +31,8 @@ try: from nomad import utils - from nomad.datamodel import EntryArchive + from nomad.datamodel import EntryArchive, EntryMetadata + from nomad.datamodel.data import EntryData from nomad.datamodel.metainfo.basesections import ( BaseSection, Component, @@ -73,7 +74,7 @@ from pynxtools import get_definitions_url from pynxtools.definitions.dev_tools.utils.nxdl_utils import get_nexus_definitions_path -from pynxtools.nomad.utils import __REPLACEMENT_FOR_NX, __rename_nx_to_nomad +from pynxtools.nomad.utils import __REPLACEMENT_FOR_NX, __rename_nx_for_nomad # __URL_REGEXP from # https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url @@ -82,6 +83,7 @@ r"(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+" r'(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))' ) + # noinspection HttpUrlsUsage __XML_NAMESPACES = {"nx": "http://definition.nexusformat.org/nxdl/3.1"} @@ -93,11 +95,11 @@ __logger = get_logger(__name__) __BASESECTIONS_MAP: Dict[str, Any] = { - "BSfabrication": [Instrument], - "BSsample": [CompositeSystem], - "BSsample_component": [Component], - "BSidentifier": [EntityReference], - # "BSobject": BaseSection, + __rename_nx_for_nomad("NXfabrication"): [Instrument], + __rename_nx_for_nomad("NXsample"): [CompositeSystem], + __rename_nx_for_nomad("NXsample_component"): [Component], + __rename_nx_for_nomad("NXidentifier"): [EntityReference], + # "object": BaseSection, } @@ -293,8 +295,6 @@ def __to_section(name: str, **kwargs) -> Section: class nexus definition. """ - # name = __rename_nx_to_nomad(name) - if name in __section_definitions: section = __section_definitions[name] section.more.update(**kwargs) @@ -372,7 +372,7 @@ def __create_attributes(xml_node: ET.Element, definition: Union[Section, Quantit todo: account for more attributes of attribute, e.g., default, minOccurs """ for attribute in xml_node.findall("nx:attribute", __XML_NAMESPACES): - name = attribute.get("name") + "__attribute" + name = __rename_nx_for_nomad(attribute.get("name"), is_attribute=True) nx_enum = __get_enumeration(attribute) if nx_enum: @@ -465,7 +465,8 @@ def __create_field(xml_node: ET.Element, container: Section) -> Quantity: # name assert "name" in xml_attrs, "Expecting name to be present" - name = xml_attrs["name"] + "__field" + + name = __rename_nx_for_nomad(xml_attrs["name"], is_field=True) # type nx_type = xml_attrs.get("type", "NX_CHAR") @@ -548,10 +549,11 @@ def __create_group(xml_node: ET.Element, root_section: Section): xml_attrs = group.attrib assert "type" in xml_attrs, "Expecting type to be present" - nx_type = __rename_nx_to_nomad(xml_attrs["type"]) + nx_type = __rename_nx_for_nomad(xml_attrs["type"]) nx_name = xml_attrs.get("name", nx_type) - group_section = Section(validate=VALIDATE, nx_kind="group", name=nx_name) + section_name = __rename_nx_for_nomad(nx_name, is_group=True) + group_section = Section(validate=VALIDATE, nx_kind="group", name=section_name) __attach_base_section(group_section, root_section, __to_section(nx_type)) __add_common_properties(group, group_section) @@ -559,10 +561,11 @@ def __create_group(xml_node: ET.Element, root_section: Section): nx_name = xml_attrs.get( "name", nx_type.replace(__REPLACEMENT_FOR_NX, "").upper() ) + subsection_name = __rename_nx_for_nomad(nx_name, is_group=True) group_subsection = SubSection( section_def=group_section, nx_kind="group", - name=nx_name, + name=subsection_name, repeats=__if_repeats(nx_name, xml_attrs.get("maxOccurs", "0")), variable=__if_template(nx_name), ) @@ -604,7 +607,7 @@ def __create_class_section(xml_node: ET.Element) -> Section: nx_type = xml_attrs["type"] nx_category = xml_attrs["category"] - nx_name = __rename_nx_to_nomad(nx_name) + nx_name = __rename_nx_for_nomad(nx_name) class_section: Section = __to_section( nx_name, nx_kind=nx_type, nx_category=nx_category ) @@ -612,7 +615,7 @@ def __create_class_section(xml_node: ET.Element) -> Section: nomad_base_sec_cls = __BASESECTIONS_MAP.get(nx_name, [BaseSection]) if "extends" in xml_attrs: - nx_base_sec = __to_section(__rename_nx_to_nomad(xml_attrs["extends"])) + nx_base_sec = __to_section(__rename_nx_for_nomad(xml_attrs["extends"])) class_section.base_sections = [nx_base_sec] + [ cls.m_def for cls in nomad_base_sec_cls ] @@ -779,7 +782,9 @@ def init_nexus_metainfo(): # We take the application definitions and create a common parent section that allows # to include nexus in an EntryArchive. - nexus_section = Section(validate=VALIDATE, name=__GROUPING_NAME) + nexus_section = Section( + validate=VALIDATE, name=__GROUPING_NAME, label=__GROUPING_NAME + ) # try: # load_nexus_schema('') @@ -791,10 +796,6 @@ def init_nexus_metainfo(): # pass nexus_metainfo_package = __create_package_from_nxdl_directories(nexus_section) - EntryArchive.nexus = SubSection(name="nexus", section_def=nexus_section) - EntryArchive.nexus.init_metainfo() - EntryArchive.m_def.sub_sections.append(EntryArchive.nexus) - nexus_metainfo_package.section_definitions.append(nexus_section) # We need to initialize the metainfo definitions. This is usually done automatically, @@ -813,6 +814,9 @@ def init_nexus_metainfo(): sections.append(section) for section in sections: + # TODO: add when quantities with mixed use_full_storage are supported by GUI + # if not (str(section).startswith("nexus.")): + # continue __add_additional_attributes(section) for quantity in section.quantities: __add_additional_attributes(quantity) @@ -827,16 +831,20 @@ def init_nexus_metainfo(): init_nexus_metainfo() -def normalize_BSfabrication(self, archive, logger): - """Normalizer for BSfabrication section.""" - current_cls = __section_definitions["BSfabrication"].section_cls +def normalize_fabrication(self, archive, logger): + """Normalizer for fabrication section.""" + current_cls = __section_definitions[ + __rename_nx_for_nomad("NXfabrication") + ].section_cls super(current_cls, self).normalize(archive, logger) self.lab_id = "Hello" -def normalize_BSsample_component(self, archive, logger): - """Normalizer for BSsample_component section.""" - current_cls = __section_definitions["BSsample_component"].section_cls +def normalize_sample_component(self, archive, logger): + """Normalizer for sample_component section.""" + current_cls = __section_definitions[ + __rename_nx_for_nomad("NXsample_component") + ].section_cls if self.name__field: self.name = self.name__field if self.mass__field: @@ -845,24 +853,31 @@ def normalize_BSsample_component(self, archive, logger): super(current_cls, self).normalize(archive, logger) -def normalize_BSsample(self, archive, logger): - """Normalizer for BSsample section.""" - current_cls = __section_definitions["BSsample"].section_cls +def normalize_sample(self, archive, logger): + """Normalizer for sample section.""" + current_cls = __section_definitions[__rename_nx_for_nomad("NXsample")].section_cls if self.name__field: self.name = self.name__field - # one could also copy local ids to BSidentifier for search purposes + # one could also copy local ids to identifier for search purposes super(current_cls, self).normalize(archive, logger) -def normalize_BSidentifier(self, archive, logger): - """Normalizer for BSidentifier section.""" +def normalize_identifier(self, archive, logger): + """Normalizer for identifier section.""" def create_Entity(lab_id, archive, f_name): + # TODO: use this instead of BasicEln() when use_full_storage is properly supported by the GUI + # entitySec = Entity() + # entitySec.lab_id = lab_id + # entity = EntryArchive ( + # data = entitySec, + # m_context=archive.m_context, + # metadata=EntryMetadata(entry_type = "identifier"), #upload_id=archive.m_context.upload_id, + # ) + # with archive.m_context.raw_file(f_name, 'w') as f_obj: + # json.dump(entity.m_to_dict(with_meta=True), f_obj) entity = BasicEln() entity.lab_id = lab_id - entity.entity = Entity() - entity.entity.lab_id = lab_id - with archive.m_context.raw_file(f_name, "w") as f_obj: json.dump( {"data": entity.m_to_dict(with_meta=True, include_derived=True)}, @@ -880,7 +895,9 @@ def get_entry_reference(archive, f_name): return f"/entries/{entry_id}/archive#/data" - current_cls = __section_definitions["BSidentifier"].section_cls + current_cls = __section_definitions[ + __rename_nx_for_nomad("NXidentifier") + ].section_cls # super(current_cls, self).normalize(archive, logger) if self.identifier__field: logger.info(f"{self.identifier__field} - identifier received") @@ -896,10 +913,10 @@ def get_entry_reference(archive, f_name): __NORMALIZER_MAP: Dict[str, Any] = { - "BSfabrication": normalize_BSfabrication, - "BSsample": normalize_BSsample, - "BSsample_component": normalize_BSsample_component, - "BSidentifier": normalize_BSidentifier, + __rename_nx_for_nomad("NXfabrication"): normalize_fabrication, + __rename_nx_for_nomad("NXsample"): normalize_sample, + __rename_nx_for_nomad("NXsample_component"): normalize_sample_component, + __rename_nx_for_nomad("NXidentifier"): normalize_identifier, } # Handling nomad BaseSection and other inherited Section from BaseSection diff --git a/src/pynxtools/nomad/utils.py b/src/pynxtools/nomad/utils.py index 203e52bc7..794a94e60 100644 --- a/src/pynxtools/nomad/utils.py +++ b/src/pynxtools/nomad/utils.py @@ -18,18 +18,67 @@ from typing import Optional -__REPLACEMENT_FOR_NX = "BS" +__REPLACEMENT_FOR_NX = "" +# This is a list of NeXus group names that are not allowed because they are defined as quantities in the BaseSection class. +UNALLOWED_GROUP_NAMES = {"name", "datetime", "lab_id", "description"} -def __rename_nx_to_nomad(name: str) -> Optional[str]: + +def __rename_classes_in_nomad(nx_name: str) -> Optional[str]: + """ + Modify group names that conflict with NOMAD due to being defined as quantities + in the BaseSection class by appending '__group' to those names. + + Some quantities names names are reserved in the BaseSection class (or even higher up in metainfo), + and thus require renaming to avoid collisions. + + Args: + nx_name (str): The original group name. + + Returns: + Optional[str]: The modified group name with '__group' appended if it's in + UNALLOWED_GROUP_NAMES, or the original name if no change is needed. + """ + return nx_name + "__group" if nx_name in UNALLOWED_GROUP_NAMES else nx_name + + +def __rename_nx_for_nomad( + name: str, + is_group: bool = False, + is_field: bool = False, + is_attribute: bool = False, +) -> Optional[str]: """ - Rename the NXDL name to NOMAD. - For example: NXdata -> BSdata, - except NXobject -> NXobject + Rename NXDL names for compatibility with NOMAD, applying specific rules + based on the type of the NeXus concept. (group, field, or attribute). + + - NXobject is unchanged. + - NX-prefixed names (e.g., NXdata) are renamed by replacing 'NX' with a custom string. + - Group names are passed to __rename_classes_in_nomad(), and the result is capitalized. + - Fields and attributes have '__field' or '__attribute' appended, respectively. + + Args: + name (str): The NXDL name. + is_group (bool): Whether the name represents a group. + is_field (bool): Whether the name represents a field. + is_attribute (bool): Whether the name represents an attribute. + + Returns: + Optional[str]: The renamed NXDL name, with group names capitalized, + or None if input is invalid. """ if name == "NXobject": return name - if name is not None: - if name.startswith("NX"): - return name.replace("NX", __REPLACEMENT_FOR_NX) + + if name and name.startswith("NX"): + name = __REPLACEMENT_FOR_NX + name[2:] + name = name[0].upper() + name[1:] + + if is_group: + name = __rename_classes_in_nomad(name) + elif is_field: + name += "__field" + elif is_attribute: + name += "__attribute" + return name diff --git a/tests/data/nomad/NXlauetof.hdf5 b/tests/data/nomad/NXlauetof.hdf5 new file mode 100644 index 000000000..40b524d6e Binary files /dev/null and b/tests/data/nomad/NXlauetof.hdf5 differ diff --git a/tests/nexus/test_nexus.py b/tests/nexus/test_nexus.py index dee2aad11..6656ee666 100644 --- a/tests/nexus/test_nexus.py +++ b/tests/nexus/test_nexus.py @@ -19,10 +19,11 @@ import logging import os -import pytest -import numpy as np import lxml.etree as ET +import numpy as np +import pytest + from pynxtools.definitions.dev_tools.utils.nxdl_utils import ( get_inherited_nodes, get_node_at_nxdl_path, @@ -30,8 +31,7 @@ get_nx_classes, get_nx_units, ) -from pynxtools.nexus.nexus import decode_if_string -from pynxtools.nexus.nexus import HandleNexus +from pynxtools.nexus.nexus import HandleNexus, decode_if_string logger = logging.getLogger(__name__) diff --git a/tests/nomad/test_metainfo_schema.py b/tests/nomad/test_metainfo_schema.py new file mode 100644 index 000000000..5a4a2903c --- /dev/null +++ b/tests/nomad/test_metainfo_schema.py @@ -0,0 +1,117 @@ +"""This is a code that performs several tests on nexus tool""" + +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest + +try: + from nomad.metainfo import Section +except ImportError: + pytest.skip("nomad not installed", allow_module_level=True) + +from typing import Any + +from pynxtools.nomad.schema import nexus_metainfo_package +from pynxtools.nomad.utils import __rename_nx_for_nomad as rename_nx_for_nomad + + +@pytest.mark.parametrize( + "path,value", + [ + pytest.param("name", "nexus"), + pytest.param("NXobject.name", "NXobject"), + pytest.param(rename_nx_for_nomad("NXentry") + ".nx_kind", "group"), + pytest.param(rename_nx_for_nomad("NXdetector") + ".real_time__field", "*"), + pytest.param(rename_nx_for_nomad("NXentry") + ".DATA.nx_optional", True), + pytest.param(rename_nx_for_nomad("NXentry") + ".DATA.nx_kind", "group"), + pytest.param(rename_nx_for_nomad("NXentry") + ".DATA.nx_optional", True), + pytest.param( + rename_nx_for_nomad("NXdetector") + ".real_time__field.name", + "real_time__field", + ), + pytest.param( + rename_nx_for_nomad("NXdetector") + ".real_time__field.nx_type", "NX_NUMBER" + ), + pytest.param( + rename_nx_for_nomad("NXdetector") + ".real_time__field.nx_units", "NX_TIME" + ), + pytest.param(rename_nx_for_nomad("NXarpes") + ".ENTRY.DATA.nx_optional", False), + pytest.param(rename_nx_for_nomad("NXentry") + ".nx_category", "base"), + pytest.param( + rename_nx_for_nomad("NXdispersion_table") + + ".refractive_index__field.nx_type", + "NX_COMPLEX", + ), + pytest.param( + rename_nx_for_nomad("NXdispersive_material") + + ".ENTRY.dispersion_x." + + "DISPERSION_TABLE.refractive_index__field.nx_type", + "NX_COMPLEX", + ), + pytest.param(rename_nx_for_nomad("NXapm") + ".nx_category", "application"), + ], +) +def test_assert_nexus_metainfo(path: str, value: Any): + """ + Test the existence of nexus metainfo + + pytest.param('NXdispersive_material.inner_section_definitions[0].sub_sections[1].sub_section.inner_section_definitions[0].quantities[4].more["nx_type"] + """ + current = nexus_metainfo_package + for name in path.split("."): + elements: list = [] + if name.endswith("__field"): + subelement_list = getattr(current, "quantities", None) + if subelement_list: + elements += subelement_list + else: + subelement_list = getattr(current, "section_definitions", None) + if subelement_list: + elements += subelement_list + subelement_list = getattr(current, "sub_sections", None) + if subelement_list: + elements += subelement_list + subelement_list = getattr(current, "attributes", None) + if subelement_list: + elements += subelement_list + subelement_list = current.m_contents() + if subelement_list: + elements += subelement_list + for content in elements: + if getattr(content, "name", None) == name: + current = content # type: ignore + if getattr(current, "sub_section", None): + current = current.section_definition + break + else: + current = getattr(current, name, None) + if current is None: + assert False, f"{path} does not exist" + + if value == "*": + assert current is not None, f"{path} does not exist" + elif value is None: + assert current is None, f"{path} does exist" + else: + assert current == value, f"{path} has wrong value" + + if isinstance(current, Section): + assert current.nx_kind is not None + for base_section in current.all_base_sections: + assert base_section.nx_kind == current.nx_kind diff --git a/tests/nomad/test_parsing.py b/tests/nomad/test_parsing.py index bcf3afaae..8a71f9af3 100644 --- a/tests/nomad/test_parsing.py +++ b/tests/nomad/test_parsing.py @@ -1,4 +1,4 @@ -"""This is a code that performs several tests on nexus tool""" +"""This tests that the nexus parsing still works.""" # # Copyright The NOMAD Authors. @@ -18,106 +18,22 @@ # limitations under the License. # +import os + import pytest try: from nomad.datamodel import EntryArchive - from nomad.metainfo import Section from nomad.units import ureg from nomad.utils import get_logger except ImportError: pytest.skip("nomad not installed", allow_module_level=True) - from typing import Any from pynxtools.nomad.parser import NexusParser from pynxtools.nomad.schema import nexus_metainfo_package - -__REPLACEMENT_FOR_NX = "BS" - - -@pytest.mark.parametrize( - "path,value", - [ - pytest.param("name", "nexus"), - pytest.param("NXobject.name", "NXobject"), - pytest.param(f"{__REPLACEMENT_FOR_NX}entry.nx_kind", "group"), - pytest.param(f"{__REPLACEMENT_FOR_NX}detector.real_time__field", "*"), - pytest.param(f"{__REPLACEMENT_FOR_NX}entry.DATA.nx_optional", True), - pytest.param(f"{__REPLACEMENT_FOR_NX}entry.DATA.nx_kind", "group"), - pytest.param(f"{__REPLACEMENT_FOR_NX}entry.DATA.nx_optional", True), - pytest.param( - f"{__REPLACEMENT_FOR_NX}detector.real_time__field.name", "real_time__field" - ), - pytest.param( - f"{__REPLACEMENT_FOR_NX}detector.real_time__field.nx_type", "NX_NUMBER" - ), - pytest.param( - f"{__REPLACEMENT_FOR_NX}detector.real_time__field.nx_units", "NX_TIME" - ), - pytest.param(f"{__REPLACEMENT_FOR_NX}arpes.ENTRY.DATA.nx_optional", False), - pytest.param(f"{__REPLACEMENT_FOR_NX}entry.nx_category", "base"), - pytest.param( - f"{__REPLACEMENT_FOR_NX}dispersion_table.refractive_index__field.nx_type", - "NX_COMPLEX", - ), - pytest.param( - f"{__REPLACEMENT_FOR_NX}dispersive_material.ENTRY.dispersion_x." - "DISPERSION_TABLE.refractive_index__field.nx_type", - "NX_COMPLEX", - ), - pytest.param(f"{__REPLACEMENT_FOR_NX}apm.nx_category", "application"), - ], -) -def test_assert_nexus_metainfo(path: str, value: Any): - """ - Test the existence of nexus metainfo - - pytest.param('NXdispersive_material.inner_section_definitions[0].sub_sections[1].sub_section.inner_section_definitions[0].quantities[4].more["nx_type"] - """ - current = nexus_metainfo_package - for name in path.split("."): - elements: list = [] - if name.endswith("__field"): - subelement_list = getattr(current, "quantities", None) - if subelement_list: - elements += subelement_list - else: - subelement_list = getattr(current, "section_definitions", None) - if subelement_list: - elements += subelement_list - subelement_list = getattr(current, "sub_sections", None) - if subelement_list: - elements += subelement_list - subelement_list = getattr(current, "attributes", None) - if subelement_list: - elements += subelement_list - subelement_list = current.m_contents() - if subelement_list: - elements += subelement_list - for content in elements: - if getattr(content, "name", None) == name: - current = content # type: ignore - if getattr(current, "sub_section", None): - current = current.section_definition - break - else: - current = getattr(current, name, None) - if current is None: - assert False, f"{path} does not exist" - - if value == "*": - assert current is not None, f"{path} does not exist" - elif value is None: - assert current is None, f"{path} does exist" - else: - assert current == value, f"{path} has wrong value" - - if isinstance(current, Section): - assert current.nx_kind is not None - for base_section in current.all_base_sections: - assert base_section.nx_kind == current.nx_kind +from pynxtools.nomad.utils import __rename_nx_for_nomad as rename_nx_for_nomad def test_nexus_example(): @@ -125,7 +41,7 @@ def test_nexus_example(): example_data = "src/pynxtools/data/201805_WSe2_arpes.nxs" NexusParser().parse(example_data, archive, get_logger(__name__)) - arpes_obj = getattr(archive.nexus, f"{__REPLACEMENT_FOR_NX}arpes") + arpes_obj = getattr(archive.data, rename_nx_for_nomad("NXarpes")) assert arpes_obj.ENTRY[0].SAMPLE[0].pressure__field == ureg.Quantity( "3.27e-10*millibar" @@ -169,3 +85,18 @@ def test_same_name_field_and_group(): example_data = "tests/data/parser/SiO2onSi.ellips.nxs" NexusParser().parse(example_data, archive, get_logger(__name__)) archive.m_to_dict(with_out_meta=True) + + +def test_nexus_example_with_renamed_groups(): + archive = EntryArchive() + + lauetof_data = os.path.join( + os.path.dirname(__file__), "../data/nomad/NXlauetof.hdf5" + ) + NexusParser().parse(lauetof_data, archive, get_logger(__name__)) + lauetof_obj = getattr(archive.data, rename_nx_for_nomad("NXlauetof")) + + assert lauetof_obj.entry.name__group.time_of_flight__field == ureg.Quantity( + "1.0*second" + ) + assert lauetof_obj.entry.sample.name__field == "SAMPLE-CHAR-DATA"