Skip to content

Commit 79245bf

Browse files
maarten-icolivhoenen
authored andcommitted
Add a RuntimeError in some cases when using the UDA backend (#99)
1 parent 982034d commit 79245bf

File tree

3 files changed

+194
-15
lines changed

3 files changed

+194
-15
lines changed

docs/source/multi-dd.rst

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -276,27 +276,110 @@ You need to explicitly convert the data, which you can do as follows:
276276
entry.put(imas.convert_ids(equilibrium, entry.dd_version))
277277
278278
279+
.. _`UDA backend and DD versions`:
279280

280-
.. _`DD background`:
281+
UDA backend caching and Data Dictionary versions
282+
------------------------------------------------
281283

282-
Background information
283-
----------------------
284+
If you try to load data from a different Data Dictionary version with the UDA backend,
285+
you may see the following error:
286+
287+
.. code-block:: text
288+
289+
The Data Dictionary version of the data (3.38.1) is different from the Data
290+
Dictionary version of the DBEntry (3.42.0). This is not supported when using the
291+
UDA backend.
292+
293+
There are three possible workarounds. The first two require passing an additional option
294+
in the IMAS UDA URI: please see the `imas-core documentation
295+
<https://imas-core.readthedocs.io/en/stable/user_guide/backends_guide.html#query-keys-specific-for-the-uda-backend>`__
296+
for more details on these URI options.
297+
298+
1. Use UDA fetch to bypass the cache problem. You can do this by appending ``&fetch=1``
299+
to the URI when you create the :py:class:`~imas.db_entry.DBEntry`.
300+
301+
Note that this will download the entire IDS files from the remote server, this may
302+
not be desired if you only want to read a single time slice.
303+
2. Disable the UDA cache. You can do this by appending ``&cache_mode=none`` to the URI
304+
when you create the :py:class:`~imas.db_entry.DBEntry`.
305+
306+
Note that this may make the ``get()`` (a lot) slower, since a separate request needs
307+
to be sent to the remote UDA server for every data variable. However, this may still
308+
be the best performing option if you are only interested in a subset of all the data
309+
in an IDS (and use :ref:`lazy loading`).
310+
3. Explicitly provide the data dictionary version when you create the
311+
:py:class:`~imas.db_entry.DBEntry`, setting it to match the Data Dictionary version
312+
of the data you want to load. To obtain the version of the data on the remote server
313+
from the field `ids_properties.put_version.data_dictionary` via a _lazy_ ``get()``
314+
with ``autoconvert=False`` option and using the ``&cache_mode=none`` query in the URI.
315+
316+
Note that you may need to call ``imas.convert_ids`` to convert the IDS to your
317+
desired Data Dictionary version.
318+
319+
All three possible workarounds are shown in the examples below:
320+
321+
.. md-tab-set::
322+
323+
.. md-tab-item:: Original code
324+
325+
.. code-block:: python
326+
327+
import imas
328+
329+
URI = (
330+
"imas://uda.iter.org:56565/uda?backend=hdf5"
331+
"&path=/work/imas/shared/imasdb/ITER/3/121013/50"
332+
)
333+
with imas.DBEntry(URI, "r") as entry:
334+
cp = entry.get("core_profiles")
284335
285-
Since IMAS-Python needs to have access to multiple DD versions it was chosen to
286-
bundle these with the code at build-time, in setup.py. If a git clone of the
287-
Data Dictionary succeeds, the setup tools automatically download saxon and
288-
generate ``IDSDef.xml`` for each of the tagged versions in the DD git
289-
repository. These are then gathered into ``IDSDef.zip``, which is
290-
distributed inside the IMAS-Python package.
336+
.. md-tab-item:: 1. Use UDA fetch
291337

292-
To update the set of data dictionaries new versions can be added to the zipfile.
293-
A reinstall of the package will ensure that all available versions are included
294-
in IMAS-Python. Additionally an explicit path to an XML file can be specified, which
295-
is useful for development.
338+
.. code-block:: python
296339
297-
Automated tests have been provided that check the loading of all of the DD
298-
versions tagged in the data-dictionary git repository.
340+
import imas
299341
342+
URI = (
343+
"imas://uda.iter.org:56565/uda?backend=hdf5"
344+
"&path=/work/imas/shared/imasdb/ITER/3/121013/50&fetch=1"
345+
)
346+
with imas.DBEntry(URI, "r") as entry:
347+
cp = entry.get("core_profiles")
348+
349+
.. md-tab-item:: 2. Disable the UDA cache
350+
351+
.. code-block:: python
352+
353+
import imas
354+
355+
URI = (
356+
"imas://uda.iter.org:56565/uda?backend=hdf5"
357+
"&path=/work/imas/shared/imasdb/ITER/3/121013/50&cache_mode=none"
358+
)
359+
with imas.DBEntry(URI, "r") as entry:
360+
cp = entry.get("core_profiles")
361+
362+
.. md-tab-item:: 3. Explicitly provide the DD version
363+
364+
.. code-block:: python
365+
366+
import imas
367+
368+
URI = (
369+
"imas://uda.iter.org:56565/uda?backend=hdf5"
370+
"&path=/work/imas/shared/imasdb/ITER/3/121013/50"
371+
)
372+
with imas.DBEntry(URI, "r", dd_version="3.38.1") as entry:
373+
cp = entry.get("core_profiles")
374+
375+
# Optional: convert the IDS to your desired DD version
376+
cp = imas.convert_ids(cp, "3.42.0")
377+
378+
379+
.. _`DD background`:
380+
381+
Background information
382+
----------------------
300383

301384
Data Dictionary definitions
302385
'''''''''''''''''''''''''''

imas/backends/imas_core/db_entry_al.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,22 @@ def read_dd_version(self, ids_name: str, occurrence: int) -> str:
256256
raise DataEntryException(
257257
f"IDS {ids_name!r}, occurrence {occurrence} is empty."
258258
)
259+
260+
# UDA caching doesn't play well when the DD version of the on-disk IDS doesn't
261+
# match the DD version of this DBEntry. See GH#97
262+
if self.backend == "uda" and dd_version != self._ids_factory.dd_version:
263+
cache_mode = self._querydict.get("cache_mode")
264+
fetch = self._querydict.get("fetch")
265+
if cache_mode != "none" and fetch not in ("1", "true"):
266+
raise RuntimeError(
267+
f"The Data Dictionary version of the data ({dd_version}) is "
268+
"different from the Data Dictionary version of the DBEntry "
269+
f"({self._ids_factory.dd_version}). This is not supported when "
270+
f"using the UDA backend. See {imas.PUBLISHED_DOCUMENTATION_ROOT}"
271+
"multi-dd.html#uda-backend-caching-and-data-dictionary-versions "
272+
"for more details and workarounds."
273+
)
274+
259275
return dd_version
260276

261277
def put(self, ids: IDSToplevel, occurrence: int, is_slice: bool) -> None:

imas/test/test_dbentry_uda.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from pathlib import Path
2+
import os
3+
from unittest.mock import patch
4+
5+
import pytest
6+
from packaging.version import Version
7+
8+
from imas import DBEntry
9+
from imas.ids_defs import READ_OP
10+
11+
12+
@pytest.fixture
13+
def mock_read_data():
14+
return {
15+
"ids_properties/homogeneous_time": 1,
16+
"ids_properties/version_put/data_dictionary": "4.0.0",
17+
}
18+
19+
20+
@pytest.fixture
21+
def mock_ll_interface(mock_read_data):
22+
"""Mock the IMAS lowlevel interface so we can still test the our UDA-specific logic.
23+
24+
Since we don't have a public UDA server available to test against, this is the
25+
next-best thing.
26+
"""
27+
with patch("imas.backends.imas_core.db_entry_al.ll_interface") as mock_ll_interface:
28+
mock_ll_interface.begin_dataentry_action.return_value = (0, 0)
29+
mock_ll_interface.begin_global_action.return_value = (0, 0)
30+
mock_ll_interface.begin_arraystruct_action.return_value = (0, 0, 0)
31+
mock_ll_interface.close_pulse.return_value = 0
32+
mock_ll_interface._al_version = Version("5.6.0")
33+
34+
def read_data(ctx, fieldPath, pyTimebasePath, ualDataType, dim):
35+
return 0, mock_read_data.get(fieldPath)
36+
37+
mock_ll_interface.read_data.side_effect = read_data
38+
39+
# Also patch in al_context.py:
40+
with patch(
41+
"imas.backends.imas_core.al_context.ll_interface", mock_ll_interface
42+
):
43+
yield mock_ll_interface
44+
45+
46+
def test_uda_idsdef_path(mock_ll_interface):
47+
# Check that IDSDEF_PATH env variable is set for the UDA backend
48+
with DBEntry("imas:uda?mock", "r", dd_version="4.0.0"):
49+
assert "IDSDEF_PATH" in os.environ
50+
path1 = Path(os.environ["IDSDEF_PATH"])
51+
assert path1.exists()
52+
with DBEntry("imas:uda?mock", "r", dd_version="3.42.0"):
53+
assert "IDSDEF_PATH" in os.environ
54+
path2 = Path(os.environ["IDSDEF_PATH"])
55+
assert path2.exists()
56+
assert path1 != path2
57+
58+
59+
def test_uda_datapath(mock_ll_interface):
60+
# Check that datapath is set when requesting the dd version
61+
with DBEntry("imas:uda?mock", "r", dd_version="4.0.0") as entry:
62+
mock_ll_interface.begin_global_action.assert_not_called()
63+
entry.get("mhd", lazy=True)
64+
# pulseCtx=0, dataobjectname="mhd", rwmode=READ_OP, datapath="ids_properties"
65+
mock_ll_interface.begin_global_action.assert_called_with(
66+
0, "mhd", READ_OP, "ids_properties"
67+
)
68+
69+
70+
def test_uda_version_mismatch_exception(mock_ll_interface):
71+
# Check that we get an exception when versions mismatch
72+
with pytest.raises(RuntimeError, match="Data Dictionary version"):
73+
DBEntry("imas:uda?path=mock", "r", dd_version="4.1.0").get("mhd")
74+
# No exceptions when using cache_mode=none
75+
DBEntry("imas:uda?path=mock&cache_mode=none", "r", dd_version="4.1.0").get("mhd")
76+
# Or when using fetch
77+
DBEntry("imas:uda?path=mock&fetch=true", "r", dd_version="4.1.0").get("mhd")
78+
DBEntry("imas:uda?path=mock&fetch=1", "r", dd_version="4.1.0").get("mhd")
79+
# Or when using the exact same DD version
80+
DBEntry("imas:uda?path=mock", "r", dd_version="4.0.0").get("mhd")

0 commit comments

Comments
 (0)