imas-python 0.1.dev1618__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imas-python might be problematic. Click here for more details.
- imas/__init__.py +34 -0
- imas/__main__.py +17 -0
- imas/_util.py +260 -0
- imas/_version.py +16 -0
- imas/assets/IDSDef2MDSpreTree.xsl +161 -0
- imas/assets/IDS_fake_toplevel.xml +318 -0
- imas/assets/IDS_minimal.xml +37 -0
- imas/assets/IDS_minimal_2.xml +37 -0
- imas/assets/IDS_minimal_struct_array.xml +43 -0
- imas/assets/IDS_minimal_types.xml +169 -0
- imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
- imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
- imas/assets/README.md +5 -0
- imas/assets/core_profiles.ids +2438 -0
- imas/assets/equilibrium.ids +3172 -0
- imas/backends/__init__.py +12 -0
- imas/backends/db_entry_impl.py +122 -0
- imas/backends/imas_core/__init__.py +4 -0
- imas/backends/imas_core/al_context.py +391 -0
- imas/backends/imas_core/db_entry_al.py +405 -0
- imas/backends/imas_core/db_entry_helpers.py +200 -0
- imas/backends/imas_core/imas_interface.py +216 -0
- imas/backends/imas_core/mdsplus_model.py +367 -0
- imas/backends/imas_core/uda_support.py +56 -0
- imas/backends/netcdf/__init__.py +4 -0
- imas/backends/netcdf/db_entry_nc.py +176 -0
- imas/backends/netcdf/ids2nc.py +306 -0
- imas/backends/netcdf/nc2ids.py +302 -0
- imas/backends/netcdf/nc_metadata.py +357 -0
- imas/backends/netcdf/nc_validate.py +53 -0
- imas/command/cli.py +239 -0
- imas/command/db_analysis.py +290 -0
- imas/command/helpers.py +45 -0
- imas/command/timer.py +77 -0
- imas/db_entry.py +802 -0
- imas/dd_helpers.py +300 -0
- imas/dd_zip.py +297 -0
- imas/exception.py +107 -0
- imas/ids_base.py +149 -0
- imas/ids_convert.py +920 -0
- imas/ids_coordinates.py +400 -0
- imas/ids_data_type.py +115 -0
- imas/ids_defs.py +171 -0
- imas/ids_factory.py +108 -0
- imas/ids_identifiers.py +125 -0
- imas/ids_metadata.py +307 -0
- imas/ids_path.py +254 -0
- imas/ids_primitive.py +510 -0
- imas/ids_struct_array.py +233 -0
- imas/ids_structure.py +331 -0
- imas/ids_toplevel.py +381 -0
- imas/setup_logging.py +64 -0
- imas/test/test_all_dd_versions.py +28 -0
- imas/test/test_cli.py +44 -0
- imas/test/test_dbentry.py +104 -0
- imas/test/test_dd_helpers.py +134 -0
- imas/test/test_dd_zip.py +29 -0
- imas/test/test_deepcopy.py +25 -0
- imas/test/test_exception.py +12 -0
- imas/test/test_get_sample.py +388 -0
- imas/test/test_hash.py +188 -0
- imas/test/test_helpers.py +354 -0
- imas/test/test_identifiers.py +105 -0
- imas/test/test_ids2nc.py +166 -0
- imas/test/test_ids_ascii_data.py +39 -0
- imas/test/test_ids_convert.py +415 -0
- imas/test/test_ids_coordinates.py +165 -0
- imas/test/test_ids_data_type.py +40 -0
- imas/test/test_ids_factory.py +35 -0
- imas/test/test_ids_metadata.py +66 -0
- imas/test/test_ids_mixin.py +20 -0
- imas/test/test_ids_path.py +192 -0
- imas/test/test_ids_primitive.py +109 -0
- imas/test/test_ids_struct_array.py +89 -0
- imas/test/test_ids_structure.py +70 -0
- imas/test/test_ids_toplevel.py +55 -0
- imas/test/test_ids_validate.py +343 -0
- imas/test/test_latest_dd_autofill.py +74 -0
- imas/test/test_latest_dd_manual.py +38 -0
- imas/test/test_latest_dd_resample.py +102 -0
- imas/test/test_lazy_loading.py +201 -0
- imas/test/test_list_occurrences.py +82 -0
- imas/test/test_minimal.py +37 -0
- imas/test/test_minimal_conversion.py +44 -0
- imas/test/test_minimal_io.py +21 -0
- imas/test/test_minimal_struct_array_io.py +60 -0
- imas/test/test_minimal_types.py +436 -0
- imas/test/test_minimal_types_io.py +70 -0
- imas/test/test_minimal_types_io_automatic.py +66 -0
- imas/test/test_nbc_change.py +408 -0
- imas/test/test_nc_autofill.py +13 -0
- imas/test/test_nc_entry.py +28 -0
- imas/test/test_nc_metadata.py +153 -0
- imas/test/test_nc_validation.py +197 -0
- imas/test/test_snippets.py +25 -0
- imas/test/test_static_ids.py +39 -0
- imas/test/test_str_1d.py +99 -0
- imas/test/test_time_slicing.py +182 -0
- imas/test/test_uda_support.py +12 -0
- imas/test/test_util.py +209 -0
- imas/training.py +45 -0
- imas/util.py +526 -0
- imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
- imas_python-0.1.dev1618.dist-info/METADATA +288 -0
- imas_python-0.1.dev1618.dist-info/RECORD +108 -0
- imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
- imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
- imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
"""Logic for interacting with all data backends.
|
|
4
|
+
|
|
5
|
+
Currently supported backends are:
|
|
6
|
+
|
|
7
|
+
- ``imas_core``: IMAS Access Layer Core (lowlevel).
|
|
8
|
+
|
|
9
|
+
Interfaces with the AL core provided by Python package ``imas_core`` (available
|
|
10
|
+
since AL5.2). For older versions it falls back to the ``imas`` HLI module, which
|
|
11
|
+
contains the interface to ``imas_core``.
|
|
12
|
+
"""
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
import numpy
|
|
9
|
+
|
|
10
|
+
from imas.ids_convert import NBCPathMap
|
|
11
|
+
from imas.ids_factory import IDSFactory
|
|
12
|
+
from imas.ids_toplevel import IDSToplevel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class GetSliceParameters:
|
|
17
|
+
"""Helper class to store parameters to get_slice."""
|
|
18
|
+
|
|
19
|
+
time_requested: float
|
|
20
|
+
"""See :param:`imas.db_entry.DBEntry.get_slice.time_requested`."""
|
|
21
|
+
interpolation_method: int
|
|
22
|
+
"""See :param:`imas.db_entry.DBEntry.get_slice.interpolation_method`."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class GetSampleParameters:
|
|
27
|
+
"""Helper class to store parameters to get_sample."""
|
|
28
|
+
|
|
29
|
+
tmin: float
|
|
30
|
+
"""See :param:`imas.db_entry.DBEntry.get_sample.tmin`."""
|
|
31
|
+
tmax: float
|
|
32
|
+
"""See :param:`imas.db_entry.DBEntry.get_sample.tmax`."""
|
|
33
|
+
dtime: Optional[numpy.ndarray]
|
|
34
|
+
"""See :param:`imas.db_entry.DBEntry.get_sample.dtime`."""
|
|
35
|
+
interpolation_method: Optional[int]
|
|
36
|
+
"""See :param:`imas.db_entry.DBEntry.get_sample.interpolation_method`."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DBEntryImpl(ABC):
|
|
40
|
+
"""Interface for DBEntry implementations."""
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def from_uri(cls, uri: str, mode: str, factory: IDSFactory) -> "DBEntryImpl":
|
|
45
|
+
"""Open a datasource by URI."""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_pulse_run(
|
|
49
|
+
cls,
|
|
50
|
+
backend_id: int,
|
|
51
|
+
db_name: str,
|
|
52
|
+
pulse: int,
|
|
53
|
+
run: int,
|
|
54
|
+
user_name: Optional[str],
|
|
55
|
+
data_version: Optional[str],
|
|
56
|
+
mode: int,
|
|
57
|
+
options: Any,
|
|
58
|
+
factory: IDSFactory,
|
|
59
|
+
) -> "DBEntryImpl":
|
|
60
|
+
"""Open a datasource with pulse, run and other legacy arguments."""
|
|
61
|
+
raise NotImplementedError() # Only the `imas_core` backend implements this.
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def close(self, *, erase: bool = False) -> None:
|
|
65
|
+
"""Close the data source.
|
|
66
|
+
|
|
67
|
+
Keyword Args:
|
|
68
|
+
erase: The Access Layer allowed a parameter to erase data files when
|
|
69
|
+
closing. This parameter may be ignored when implementing a backend.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def get(
|
|
74
|
+
self,
|
|
75
|
+
ids_name: str,
|
|
76
|
+
occurrence: int,
|
|
77
|
+
parameters: Union[None, GetSliceParameters, GetSampleParameters],
|
|
78
|
+
destination: IDSToplevel,
|
|
79
|
+
lazy: bool,
|
|
80
|
+
nbc_map: Optional[NBCPathMap],
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Implement DBEntry.get/get_slice/get_sample. Load data from the data source.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
ids_name: Name of the IDS to load.
|
|
86
|
+
occurrence: Which occurence of the IDS to load.
|
|
87
|
+
parameters: Additional parameters for a get_slice/get_sample call.
|
|
88
|
+
destination: IDS object to store data in.
|
|
89
|
+
lazy: Use lazy loading.
|
|
90
|
+
nbc_map: NBCPathMap to use for implicit conversion. When None, no implicit
|
|
91
|
+
conversion needs to be done.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def read_dd_version(self, ids_name: str, occurrence: int) -> str:
|
|
96
|
+
"""Read data dictionary version that the requested IDS was stored with.
|
|
97
|
+
|
|
98
|
+
This method should raise a DataEntryException if the specified ids/occurrence is
|
|
99
|
+
not filled.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def put(self, ids: IDSToplevel, occurrence: int, is_slice: bool) -> None:
|
|
104
|
+
"""Implement DBEntry.put()/put_slice(): store data.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
ids: IDS to store in the data source.
|
|
108
|
+
occurrence: Which occurrence of the IDS to store to.
|
|
109
|
+
is_slice: True: put_slice(), False: put()
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def access_layer_version(self) -> str:
|
|
114
|
+
"""Get the access layer version used to store data."""
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def delete_data(self, ids_name: str, occurrence: int) -> None:
|
|
118
|
+
"""Implement DBEntry.delete_data()"""
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def list_all_occurrences(self, ids_name: str) -> List[int]:
|
|
122
|
+
"""Implement DBEntry.list_all_occurrences()"""
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
"""Object-oriented interface to the IMAS lowlevel.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import weakref
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import numpy
|
|
12
|
+
|
|
13
|
+
from imas.backends.imas_core.imas_interface import ll_interface
|
|
14
|
+
from imas.exception import LowlevelError
|
|
15
|
+
from imas.ids_defs import (
|
|
16
|
+
CLOSEST_INTERP,
|
|
17
|
+
LINEAR_INTERP,
|
|
18
|
+
PREVIOUS_INTERP,
|
|
19
|
+
UNDEFINED_INTERP,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
INTERP_MODES = (
|
|
23
|
+
CLOSEST_INTERP,
|
|
24
|
+
LINEAR_INTERP,
|
|
25
|
+
PREVIOUS_INTERP,
|
|
26
|
+
UNDEFINED_INTERP,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from imas.backends.imas_core.db_entry_al import ALDBEntryImpl
|
|
31
|
+
from imas.ids_convert import NBCPathMap
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ALContext:
|
|
38
|
+
"""Helper class that wraps Access Layer contexts.
|
|
39
|
+
|
|
40
|
+
Provides:
|
|
41
|
+
|
|
42
|
+
- Object oriented wrappers around AL lowlevel methods which require a context
|
|
43
|
+
- Context managers for creating and automatically ending AL actions
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
__slots__ = ["ctx", "__weakref__"]
|
|
47
|
+
|
|
48
|
+
def __init__(self, ctx: int) -> None:
|
|
49
|
+
"""Construct a new ALContext object
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
ctx: Context identifier returned by the AL
|
|
53
|
+
"""
|
|
54
|
+
self.ctx = ctx
|
|
55
|
+
"""Context identifier"""
|
|
56
|
+
|
|
57
|
+
def __enter__(self) -> "ALContext":
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
|
61
|
+
ll_interface.end_action(self.ctx)
|
|
62
|
+
|
|
63
|
+
def global_action(self, path: str, rwmode: int) -> "ALContext":
|
|
64
|
+
"""Begin a new global action for use in a ``with`` context.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
path: access layer path for this global action: ``<idsname>[/<occurrence>]``
|
|
68
|
+
rwmode: read-only or read-write operation mode: ``READ_OP``/``WRITE_OP``
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The created context.
|
|
72
|
+
"""
|
|
73
|
+
status, ctx = ll_interface.begin_global_action(self.ctx, path, rwmode)
|
|
74
|
+
if status != 0:
|
|
75
|
+
raise LowlevelError("global_action", status)
|
|
76
|
+
return ALContext(ctx)
|
|
77
|
+
|
|
78
|
+
def slice_action(
|
|
79
|
+
self, path: str, rwmode: int, time_requested: float, interpolation_method: int
|
|
80
|
+
) -> "ALContext":
|
|
81
|
+
"""Begin a new slice action for use in a ``with`` context.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
path: access layer path for this global action: ``<idsname>[/<occurrence>]``
|
|
85
|
+
rwmode: read-only or read-write operation mode: ``READ_OP``/``WRITE_OP``
|
|
86
|
+
time_requested: time-point requested. Use ``UNDEFINED_TIME`` for put_slice.
|
|
87
|
+
interpolation_method: interpolation method to use: ``CLOSEST_INTERP``,
|
|
88
|
+
``LINEAR_INTERP`` or ``PREVIOUS_INTERP`` for get_slice;
|
|
89
|
+
``UNDEFINED_INTERP`` for put_slice.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The created context.
|
|
93
|
+
"""
|
|
94
|
+
if interpolation_method not in INTERP_MODES:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"get_slice called with unexpected interpolation method: "
|
|
97
|
+
f"{interpolation_method}"
|
|
98
|
+
)
|
|
99
|
+
status, ctx = ll_interface.begin_slice_action(
|
|
100
|
+
self.ctx,
|
|
101
|
+
path,
|
|
102
|
+
rwmode,
|
|
103
|
+
time_requested,
|
|
104
|
+
interpolation_method,
|
|
105
|
+
)
|
|
106
|
+
if status != 0:
|
|
107
|
+
raise LowlevelError("slice_action", status)
|
|
108
|
+
return ALContext(ctx)
|
|
109
|
+
|
|
110
|
+
def timerange_action(
|
|
111
|
+
self,
|
|
112
|
+
path: str,
|
|
113
|
+
rwmode: int,
|
|
114
|
+
tmin: float,
|
|
115
|
+
tmax: float,
|
|
116
|
+
dtime: Optional[numpy.ndarray],
|
|
117
|
+
interpolation_method: int,
|
|
118
|
+
) -> "ALContext":
|
|
119
|
+
"""Begin a new timerange action for use in a ``with`` context."""
|
|
120
|
+
ctx = ll_interface.begin_timerange_action(
|
|
121
|
+
self.ctx, path, rwmode, tmin, tmax, dtime, interpolation_method
|
|
122
|
+
)
|
|
123
|
+
return ALContext(ctx)
|
|
124
|
+
|
|
125
|
+
def arraystruct_action(
|
|
126
|
+
self, path: str, timebase: str, size: int
|
|
127
|
+
) -> "ALArrayStructContext":
|
|
128
|
+
"""Begin a new arraystruct action for use in a ``with`` context.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
path: relative access layer path within this context
|
|
132
|
+
timebase: path to the timebase for this coordinate (an empty string for
|
|
133
|
+
non-dynamic array of structures)
|
|
134
|
+
size: the size of the array of structures (only relevant when writing data)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The created context.
|
|
138
|
+
"""
|
|
139
|
+
status, ctx, size = ll_interface.begin_arraystruct_action(
|
|
140
|
+
self.ctx, path, timebase, size
|
|
141
|
+
)
|
|
142
|
+
if status != 0:
|
|
143
|
+
raise LowlevelError("arraystruct_action", status)
|
|
144
|
+
return ALArrayStructContext(ctx, size)
|
|
145
|
+
|
|
146
|
+
def read_data(self, path: str, timebasepath: str, datatype: int, dim: int) -> Any:
|
|
147
|
+
"""Call ual_read_data with this context."""
|
|
148
|
+
status, data = ll_interface.read_data(
|
|
149
|
+
self.ctx, path, timebasepath, datatype, dim
|
|
150
|
+
)
|
|
151
|
+
if status != 0:
|
|
152
|
+
raise LowlevelError(f"read data at {path!r}", status)
|
|
153
|
+
return data
|
|
154
|
+
|
|
155
|
+
def delete_data(self, path: str) -> None:
|
|
156
|
+
"""Call ual_delete_data with this context."""
|
|
157
|
+
status = ll_interface.delete_data(self.ctx, path)
|
|
158
|
+
if status != 0:
|
|
159
|
+
raise LowlevelError(f"delete data at {path!r}", status)
|
|
160
|
+
|
|
161
|
+
def write_data(self, path: str, timebasepath: str, data: Any) -> None:
|
|
162
|
+
"""Call ual_write_data with this context."""
|
|
163
|
+
status = ll_interface.write_data(self.ctx, path, timebasepath, data)
|
|
164
|
+
if status != 0:
|
|
165
|
+
raise LowlevelError(f"write data at {path!r}: {status=}")
|
|
166
|
+
|
|
167
|
+
def list_all_occurrences(self, ids_name: str) -> List[int]:
|
|
168
|
+
"""List all occurrences of this IDS."""
|
|
169
|
+
status, occurrences = ll_interface.get_occurrences(self.ctx, ids_name)
|
|
170
|
+
if status != 0:
|
|
171
|
+
raise LowlevelError(f"list occurrences for {ids_name!r}", status)
|
|
172
|
+
if occurrences is not None:
|
|
173
|
+
return list(occurrences)
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
def close(self):
|
|
177
|
+
"""Close this ALContext."""
|
|
178
|
+
ll_interface.end_action(self.ctx)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ALArrayStructContext(ALContext):
|
|
182
|
+
"""Helper class that wraps contexts created through al_begin_arraystruct_action."""
|
|
183
|
+
|
|
184
|
+
# Note: slot for "ctx" is defined in ALContext, only declare /additional/ slots:
|
|
185
|
+
__slots__ = ["size", "curindex"]
|
|
186
|
+
|
|
187
|
+
def __init__(self, ctx, size):
|
|
188
|
+
"""Construct a new ALContext object
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
ctx: Context identifier returned by the AL
|
|
192
|
+
size: size of the AoS returned by the AL
|
|
193
|
+
"""
|
|
194
|
+
self.ctx = ctx
|
|
195
|
+
self.size = size
|
|
196
|
+
"""AoS size"""
|
|
197
|
+
self.curindex = 0
|
|
198
|
+
"""Current iteration index of this AoS context"""
|
|
199
|
+
|
|
200
|
+
def __enter__(self):
|
|
201
|
+
return self, self.size
|
|
202
|
+
|
|
203
|
+
def iterate_over_arraystruct(self, step: int) -> None:
|
|
204
|
+
"""Call al_iterate_over_arraystruct with this context."""
|
|
205
|
+
status = ll_interface.iterate_over_arraystruct(self.ctx, step)
|
|
206
|
+
if status != 0:
|
|
207
|
+
raise LowlevelError("iterate over arraystruct", status)
|
|
208
|
+
self.curindex += step
|
|
209
|
+
|
|
210
|
+
def iterate_to_index(self, index: int) -> None:
|
|
211
|
+
"""Call al_iterate_over_arraystruct to iterate to the provided index."""
|
|
212
|
+
step = index - self.curindex
|
|
213
|
+
if step:
|
|
214
|
+
status = ll_interface.iterate_over_arraystruct(self.ctx, step)
|
|
215
|
+
if status != 0:
|
|
216
|
+
raise LowlevelError("iterate over arraystruct", status)
|
|
217
|
+
self.curindex = index
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class LazyALContext:
|
|
221
|
+
"""Replacement for ALContext that is used during lazy loading.
|
|
222
|
+
|
|
223
|
+
This class implements ``global_action``, ``slice_action`` and ``read_data``, such
|
|
224
|
+
that it can be used as a drop-in replacement in ``imas.db_entry._get_children``
|
|
225
|
+
and only custom logic is needed for IDSStructArray there.
|
|
226
|
+
|
|
227
|
+
This class tracks:
|
|
228
|
+
|
|
229
|
+
- The ALDBEntryImpl object which was used for get() / get_slice().
|
|
230
|
+
- The context object from that ALDBEntryImpl (such that we can detect if the
|
|
231
|
+
underlying AL context was closed or replaced).
|
|
232
|
+
- Potentially a parent LazyALContext for nested contexts (looking at you,
|
|
233
|
+
arraystruct_action!).
|
|
234
|
+
- The ALContext method and arguments that we need to call on the ALContext we obtain
|
|
235
|
+
from our parent, to obtain the actual ALContext we should use for loading data.
|
|
236
|
+
- The NBC map that ``imas.db_entry._get_children`` needs when lazy loading
|
|
237
|
+
children of an IDSStructArray.
|
|
238
|
+
|
|
239
|
+
When constructing a LazyALContext, you need to supply either the ``dbentry`` and
|
|
240
|
+
``nbc_map``, or a ``parent_ctx``.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
__slots__ = [
|
|
244
|
+
"dbentry",
|
|
245
|
+
"dbentry_ctx",
|
|
246
|
+
"parent_ctx",
|
|
247
|
+
"method",
|
|
248
|
+
"args",
|
|
249
|
+
"nbc_map",
|
|
250
|
+
"time_mode",
|
|
251
|
+
"context",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
parent_ctx: Optional["LazyALContext"] = None,
|
|
257
|
+
method: Optional[Callable] = None,
|
|
258
|
+
args: Tuple = (),
|
|
259
|
+
*,
|
|
260
|
+
dbentry: Optional["ALDBEntryImpl"] = None,
|
|
261
|
+
nbc_map: Optional["NBCPathMap"] = None,
|
|
262
|
+
time_mode: Optional[int] = None,
|
|
263
|
+
) -> None:
|
|
264
|
+
self.dbentry = dbentry or (parent_ctx and parent_ctx.dbentry)
|
|
265
|
+
"""ALDBEntryImpl object that created us, or our parent."""
|
|
266
|
+
self.dbentry_ctx = self.dbentry._db_ctx
|
|
267
|
+
"""The ALContext of the ALDBEntryImpl at the time of get/get_slice."""
|
|
268
|
+
self.parent_ctx = parent_ctx
|
|
269
|
+
"""Optional parent context that provides our parent ALContext."""
|
|
270
|
+
self.method = method
|
|
271
|
+
"""Method we need to call with our parent ALContext to get our ALContext."""
|
|
272
|
+
self.args = args
|
|
273
|
+
"""Additional arguments we need to supply to self.method"""
|
|
274
|
+
self.nbc_map = nbc_map or (parent_ctx and parent_ctx.nbc_map)
|
|
275
|
+
"""NBC map for _get_children() when lazy loading IDSStructArray items."""
|
|
276
|
+
if time_mode is None and parent_ctx:
|
|
277
|
+
time_mode = parent_ctx.time_mode
|
|
278
|
+
self.time_mode = time_mode
|
|
279
|
+
"""Time mode used by the IDS being lazy loaded."""
|
|
280
|
+
self.context = None
|
|
281
|
+
"""Potential weak reference to opened context."""
|
|
282
|
+
|
|
283
|
+
def get_context(self) -> ALContext:
|
|
284
|
+
"""Create and yield the actual ALContext."""
|
|
285
|
+
if self.dbentry._db_ctx is not self.dbentry_ctx:
|
|
286
|
+
raise RuntimeError(
|
|
287
|
+
"Cannot lazy load the requested data: the data entry is no longer "
|
|
288
|
+
"available for reading. Hint: did you close() the DBEntry?"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Try to retrieve context from the cache
|
|
292
|
+
ctx = self.context and self.context() # dereference weakref.ref if it exists
|
|
293
|
+
if ctx:
|
|
294
|
+
# Close all sub-contexts still alive in the cache
|
|
295
|
+
cache = self.dbentry._lazy_ctx_cache
|
|
296
|
+
while cache and cache[-1] is not ctx:
|
|
297
|
+
cache.pop().close()
|
|
298
|
+
if not cache or cache[-1] is not ctx:
|
|
299
|
+
logger.warning(
|
|
300
|
+
"Found an empty AL context cache: This should not happen, please "
|
|
301
|
+
"report this bug to the imas-python developers."
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
return ctx
|
|
305
|
+
|
|
306
|
+
if self.parent_ctx:
|
|
307
|
+
# First convert our parent LazyALContext to an actual ALContext
|
|
308
|
+
parent = self.parent_ctx.get_context()
|
|
309
|
+
# Now we can create our ALContext:
|
|
310
|
+
ctx = self.method(parent, *self.args)
|
|
311
|
+
# Add context to the cache and store a weak reference to it
|
|
312
|
+
self.dbentry._lazy_ctx_cache.append(ctx)
|
|
313
|
+
self.context = weakref.ref(ctx)
|
|
314
|
+
return ctx
|
|
315
|
+
# Note that we do not close the ctx, that happens when it is evicted
|
|
316
|
+
# from the cache
|
|
317
|
+
|
|
318
|
+
else:
|
|
319
|
+
# Purge the cache to close open contexts from other IDSs (IMAS-5603)
|
|
320
|
+
cache = self.dbentry._lazy_ctx_cache
|
|
321
|
+
while cache:
|
|
322
|
+
cache.pop().close()
|
|
323
|
+
return self.dbentry_ctx
|
|
324
|
+
|
|
325
|
+
@contextmanager
|
|
326
|
+
def global_action(self, path: str, rwmode: int) -> Iterator["LazyALContext"]:
|
|
327
|
+
"""Lazily start a lowlevel global action, see :meth:`ALContext.global_action`"""
|
|
328
|
+
yield LazyALContext(self, ALContext.global_action, (path, rwmode))
|
|
329
|
+
|
|
330
|
+
@contextmanager
|
|
331
|
+
def slice_action(
|
|
332
|
+
self, path: str, rwmode: int, time_requested: float, interpolation_method: int
|
|
333
|
+
) -> Iterator["LazyALContext"]:
|
|
334
|
+
"""Lazily start a lowlevel slice action, see :meth:`ALContext.slice_action`"""
|
|
335
|
+
yield LazyALContext(
|
|
336
|
+
self,
|
|
337
|
+
ALContext.slice_action,
|
|
338
|
+
(path, rwmode, time_requested, interpolation_method),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
@contextmanager
|
|
342
|
+
def timerange_action(
|
|
343
|
+
self,
|
|
344
|
+
path: str,
|
|
345
|
+
rwmode: int,
|
|
346
|
+
tmin: float,
|
|
347
|
+
tmax: float,
|
|
348
|
+
dtime: Optional[numpy.ndarray],
|
|
349
|
+
interpolation_method: int,
|
|
350
|
+
) -> Iterator["LazyALContext"]:
|
|
351
|
+
"""Lazily start a lowlevel timerange action, see
|
|
352
|
+
:meth:`ALContext.timerange_action`.
|
|
353
|
+
"""
|
|
354
|
+
yield LazyALContext(
|
|
355
|
+
self,
|
|
356
|
+
ALContext.timerange_action,
|
|
357
|
+
(path, rwmode, tmin, tmax, dtime, interpolation_method),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def arraystruct_action(
|
|
361
|
+
self, path: str, timebase: str, size: int
|
|
362
|
+
) -> "LazyALArrayStructContext":
|
|
363
|
+
"""Lazily start an arraystruct action."""
|
|
364
|
+
return LazyALArrayStructContext(
|
|
365
|
+
self, ALContext.arraystruct_action, (path, timebase, size)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class LazyALArrayStructContext(LazyALContext):
|
|
370
|
+
"""Subclass for lazy array struct contexts."""
|
|
371
|
+
|
|
372
|
+
__slots__ = ()
|
|
373
|
+
# We're sure that this returns an AlArrayStructContext
|
|
374
|
+
get_context: Callable[..., ALArrayStructContext]
|
|
375
|
+
|
|
376
|
+
def iterate_to_index(self, index: int) -> "LazyALArrayStructChildContext":
|
|
377
|
+
"""Return a lazy context that can be used by the child structure at index."""
|
|
378
|
+
return LazyALArrayStructChildContext(self, None, (index,))
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class LazyALArrayStructChildContext(LazyALContext):
|
|
382
|
+
"""Subclass that allows an array of structures child structure to read data."""
|
|
383
|
+
|
|
384
|
+
__slots__ = ()
|
|
385
|
+
parent_ctx: LazyALArrayStructContext
|
|
386
|
+
|
|
387
|
+
def get_context(self) -> ALContext:
|
|
388
|
+
# Override get_context to iterate to the correct index
|
|
389
|
+
context = self.parent_ctx.get_context()
|
|
390
|
+
context.iterate_to_index(self.args[0])
|
|
391
|
+
return context
|