From 78f896a9487474cb0c9ede4ae587127151cb876d Mon Sep 17 00:00:00 2001 From: Maarten Sebregts Date: Wed, 14 Jan 2026 10:16:27 +0100 Subject: [PATCH 1/2] Defer loading the default DD definitions When constructing a DBEntry, a corresponding IDSFactory is created as well. Previously, constructing an IDSFactory would eagerly load the associated Data Dictionary XML files. In some use cases (e.g. `imas print` or visualization libraries like IMAS-Paraview). A default DBEntry is constructed, but all IDSs are read with `autoconvert=False`. If these IDSs are in an older version of the Data Dictionary, we would previously unnecessarily parse two DD definitions. This was visible to users in the logging output, for example with `imas print`: ``` $ imas print imas:ascii?path=imas/assets core_profiles [...] INFO Parsing data dictionary version 4.1.0 [...] INFO Parsing data dictionary version 3.39.0 [...] ``` This commit defers loading the default Data Dictionary definitions until they are really needed. In the `imas print` scenario above, this will skip loading the DD 4.1.0 definitions: saving a bit of time and potential confusion for users. The most visible side effect of this change is in interactive Python sessions: ```python >>> import imas >>> factory = imas.IDSFactory() >>> cp = factory.core_profiles() [...] INFO Parsing data dictionary version 4.1.0 ``` Before this change, the INFO log message would appear immediately after constructing the `IDSFactory`. Now, the XML file is only parsed when we need it for constructing the `core_profiles` IDS. N.B. Also fix a couple of type warnings from `ty` in touched files. --- imas/backends/db_entry_impl.py | 2 +- imas/db_entry.py | 6 +++--- imas/ids_factory.py | 19 ++++++++++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/imas/backends/db_entry_impl.py b/imas/backends/db_entry_impl.py index df1e463..0c1b2cd 100644 --- a/imas/backends/db_entry_impl.py +++ b/imas/backends/db_entry_impl.py @@ -78,7 +78,7 @@ def get( destination: IDSToplevel, lazy: bool, nbc_map: Optional[NBCPathMap], - ) -> None: + ) -> IDSToplevel: """Implement DBEntry.get/get_slice/get_sample. Load data from the data source. Args: diff --git a/imas/db_entry.py b/imas/db_entry.py index 471a50a..5a47064 100644 --- a/imas/db_entry.py +++ b/imas/db_entry.py @@ -160,7 +160,7 @@ def __init__( legacy = True except TypeError as exc2: raise TypeError( - f"Incorrect arguments to {__class__.__name__}.__init__(): " + "Incorrect arguments to DBEntry.__init__(): " f"{exc1.args[0]}, {exc2.args[0]}" ) from None @@ -561,7 +561,7 @@ def _get( raise RuntimeError("Database entry is not open.") if lazy and destination: raise ValueError("Cannot supply a destination IDS when lazy loading.") - if not self._ids_factory.exists(ids_name): + if autoconvert and not self._ids_factory.exists(ids_name): raise IDSNameError(ids_name, self._ids_factory) # Note: this will raise an exception when the ids/occurrence is not filled: @@ -577,7 +577,7 @@ def _get( ids_name, occurrence, ) - elif dd_version != self.dd_version and dd_version not in dd_xml_versions(): + elif dd_version not in dd_xml_versions() and dd_version != self.dd_version: # We don't know the DD version that this IDS was written with if ignore_unknown_dd_version: # User chooses to ignore this problem, load as if it was stored with diff --git a/imas/ids_factory.py b/imas/ids_factory.py index b840d8a..7e76661 100644 --- a/imas/ids_factory.py +++ b/imas/ids_factory.py @@ -41,6 +41,17 @@ def __init__( version: DD version string, e.g. "3.38.1". xml_path: XML file containing data dictionary definition. """ + if version is None and xml_path is None: + # Defer loading the DD definitions until we really need them + self.__deferred_init = True + else: + # If a specific version or xml_path is requested, we still load immediately + # so any exceptions are raise when creating the IDSfactory + self.__do_init(version, xml_path) + self.__deferred_init = False + + def __do_init(self, version: str | None, xml_path: str | pathlib.Path | None): + """Actual initalization logic""" self._xml_path = xml_path self._etree = dd_zip.dd_etree(version, xml_path) self._ids_elements = { @@ -71,10 +82,16 @@ def __dir__(self) -> Iterable[str]: return sorted(set(object.__dir__(self)).union(self._ids_elements)) def __getattr__(self, name: str) -> Any: + # Actually initialize when we deferred it before + if self.__deferred_init: + self.__do_init(None, None) + self.__deferred_init = False + return getattr(self, name) + # Check if the name matches any IDS and return a 'constructor' for it if name in self._ids_elements: # Note: returning a partial to mimic AL HLI, e.g. factory.core_profiles() return partial(IDSToplevel, self, self._ids_elements[name]) - raise AttributeError(f"{type(self)!r} object has no attribute {name!r}") + raise AttributeError(f"'IDSFactory' has no attribute {name!r}") def __iter__(self) -> Iterator[str]: """Iterate over the IDS names defined by the loaded Data Dictionary""" From cceb4f1598ee2b6944bc0a831915496161a445d5 Mon Sep 17 00:00:00 2001 From: Maarten Sebregts <110895564+maarten-ic@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:26:13 +0100 Subject: [PATCH 2/2] Update imas/ids_factory.py Co-authored-by: Mike Sanders <61876761+mikesndrs@users.noreply.github.com> --- imas/ids_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imas/ids_factory.py b/imas/ids_factory.py index 7e76661..5a8209d 100644 --- a/imas/ids_factory.py +++ b/imas/ids_factory.py @@ -51,7 +51,7 @@ def __init__( self.__deferred_init = False def __do_init(self, version: str | None, xml_path: str | pathlib.Path | None): - """Actual initalization logic""" + """Actual initialization logic""" self._xml_path = xml_path self._etree = dd_zip.dd_etree(version, xml_path) self._ids_elements = {