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.

Files changed (108) hide show
  1. imas/__init__.py +34 -0
  2. imas/__main__.py +17 -0
  3. imas/_util.py +260 -0
  4. imas/_version.py +16 -0
  5. imas/assets/IDSDef2MDSpreTree.xsl +161 -0
  6. imas/assets/IDS_fake_toplevel.xml +318 -0
  7. imas/assets/IDS_minimal.xml +37 -0
  8. imas/assets/IDS_minimal_2.xml +37 -0
  9. imas/assets/IDS_minimal_struct_array.xml +43 -0
  10. imas/assets/IDS_minimal_types.xml +169 -0
  11. imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
  12. imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
  13. imas/assets/README.md +5 -0
  14. imas/assets/core_profiles.ids +2438 -0
  15. imas/assets/equilibrium.ids +3172 -0
  16. imas/backends/__init__.py +12 -0
  17. imas/backends/db_entry_impl.py +122 -0
  18. imas/backends/imas_core/__init__.py +4 -0
  19. imas/backends/imas_core/al_context.py +391 -0
  20. imas/backends/imas_core/db_entry_al.py +405 -0
  21. imas/backends/imas_core/db_entry_helpers.py +200 -0
  22. imas/backends/imas_core/imas_interface.py +216 -0
  23. imas/backends/imas_core/mdsplus_model.py +367 -0
  24. imas/backends/imas_core/uda_support.py +56 -0
  25. imas/backends/netcdf/__init__.py +4 -0
  26. imas/backends/netcdf/db_entry_nc.py +176 -0
  27. imas/backends/netcdf/ids2nc.py +306 -0
  28. imas/backends/netcdf/nc2ids.py +302 -0
  29. imas/backends/netcdf/nc_metadata.py +357 -0
  30. imas/backends/netcdf/nc_validate.py +53 -0
  31. imas/command/cli.py +239 -0
  32. imas/command/db_analysis.py +290 -0
  33. imas/command/helpers.py +45 -0
  34. imas/command/timer.py +77 -0
  35. imas/db_entry.py +802 -0
  36. imas/dd_helpers.py +300 -0
  37. imas/dd_zip.py +297 -0
  38. imas/exception.py +107 -0
  39. imas/ids_base.py +149 -0
  40. imas/ids_convert.py +920 -0
  41. imas/ids_coordinates.py +400 -0
  42. imas/ids_data_type.py +115 -0
  43. imas/ids_defs.py +171 -0
  44. imas/ids_factory.py +108 -0
  45. imas/ids_identifiers.py +125 -0
  46. imas/ids_metadata.py +307 -0
  47. imas/ids_path.py +254 -0
  48. imas/ids_primitive.py +510 -0
  49. imas/ids_struct_array.py +233 -0
  50. imas/ids_structure.py +331 -0
  51. imas/ids_toplevel.py +381 -0
  52. imas/setup_logging.py +64 -0
  53. imas/test/test_all_dd_versions.py +28 -0
  54. imas/test/test_cli.py +44 -0
  55. imas/test/test_dbentry.py +104 -0
  56. imas/test/test_dd_helpers.py +134 -0
  57. imas/test/test_dd_zip.py +29 -0
  58. imas/test/test_deepcopy.py +25 -0
  59. imas/test/test_exception.py +12 -0
  60. imas/test/test_get_sample.py +388 -0
  61. imas/test/test_hash.py +188 -0
  62. imas/test/test_helpers.py +354 -0
  63. imas/test/test_identifiers.py +105 -0
  64. imas/test/test_ids2nc.py +166 -0
  65. imas/test/test_ids_ascii_data.py +39 -0
  66. imas/test/test_ids_convert.py +415 -0
  67. imas/test/test_ids_coordinates.py +165 -0
  68. imas/test/test_ids_data_type.py +40 -0
  69. imas/test/test_ids_factory.py +35 -0
  70. imas/test/test_ids_metadata.py +66 -0
  71. imas/test/test_ids_mixin.py +20 -0
  72. imas/test/test_ids_path.py +192 -0
  73. imas/test/test_ids_primitive.py +109 -0
  74. imas/test/test_ids_struct_array.py +89 -0
  75. imas/test/test_ids_structure.py +70 -0
  76. imas/test/test_ids_toplevel.py +55 -0
  77. imas/test/test_ids_validate.py +343 -0
  78. imas/test/test_latest_dd_autofill.py +74 -0
  79. imas/test/test_latest_dd_manual.py +38 -0
  80. imas/test/test_latest_dd_resample.py +102 -0
  81. imas/test/test_lazy_loading.py +201 -0
  82. imas/test/test_list_occurrences.py +82 -0
  83. imas/test/test_minimal.py +37 -0
  84. imas/test/test_minimal_conversion.py +44 -0
  85. imas/test/test_minimal_io.py +21 -0
  86. imas/test/test_minimal_struct_array_io.py +60 -0
  87. imas/test/test_minimal_types.py +436 -0
  88. imas/test/test_minimal_types_io.py +70 -0
  89. imas/test/test_minimal_types_io_automatic.py +66 -0
  90. imas/test/test_nbc_change.py +408 -0
  91. imas/test/test_nc_autofill.py +13 -0
  92. imas/test/test_nc_entry.py +28 -0
  93. imas/test/test_nc_metadata.py +153 -0
  94. imas/test/test_nc_validation.py +197 -0
  95. imas/test/test_snippets.py +25 -0
  96. imas/test/test_static_ids.py +39 -0
  97. imas/test/test_str_1d.py +99 -0
  98. imas/test/test_time_slicing.py +182 -0
  99. imas/test/test_uda_support.py +12 -0
  100. imas/test/test_util.py +209 -0
  101. imas/training.py +45 -0
  102. imas/util.py +526 -0
  103. imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
  104. imas_python-0.1.dev1618.dist-info/METADATA +288 -0
  105. imas_python-0.1.dev1618.dist-info/RECORD +108 -0
  106. imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
  107. imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
  108. imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
@@ -0,0 +1,233 @@
1
+ # This file is part of imas-python.
2
+ # You should have received the imas-python LICENSE file with this project.
3
+ """IDS StructArray represents an Array of Structures in the IDS tree.
4
+ """
5
+
6
+ import logging
7
+ from copy import deepcopy
8
+ from typing import Optional, Tuple
9
+
10
+ from xxhash import xxh3_64
11
+
12
+ from imas.backends.imas_core.al_context import LazyALArrayStructContext
13
+ from imas.ids_base import IDSBase, IDSDoc
14
+ from imas.ids_coordinates import IDSCoordinates
15
+ from imas.ids_identifiers import IDSIdentifier
16
+ from imas.ids_metadata import IDSMetadata
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class IDSStructArray(IDSBase):
22
+ """IDS array of structures (AoS) node
23
+
24
+ Represents a node in the IDS tree. Does not itself contain data,
25
+ but contains references to IDSStructures
26
+ """
27
+
28
+ __doc__ = IDSDoc(__doc__)
29
+ __slots__ = ["_parent", "_lazy", "metadata", "value", "_lazy_ctx"]
30
+
31
+ def __init__(self, parent: IDSBase, metadata: IDSMetadata):
32
+ """Initialize IDSStructArray from XML specification
33
+
34
+ Args:
35
+ parent: Parent structure. Can be anything, but at database write
36
+ time should be something with a path attribute
37
+ metadata: IDSMetadata describing the structure of the IDS
38
+ """
39
+ self._parent = parent
40
+ self._lazy = parent._lazy
41
+ self.metadata = metadata
42
+
43
+ # Initialize with an 0-length list or None when lazy loading
44
+ self.value = None if self._lazy else []
45
+ """"""
46
+
47
+ # Lazy loading context, only applicable when self._lazy is True
48
+ # When lazy loading, all items in self.value are None until they are requested
49
+ self._lazy_ctx: Optional[LazyALArrayStructContext] = None
50
+
51
+ @property
52
+ def coordinates(self):
53
+ """Coordinates of this array of structures."""
54
+ return IDSCoordinates(self)
55
+
56
+ def __deepcopy__(self, memo):
57
+ copy = self.__class__(self._parent, self.metadata)
58
+ for value in self.value:
59
+ value_copy = deepcopy(value, memo)
60
+ value_copy._parent = copy
61
+ copy.value.append(value_copy)
62
+ return copy
63
+
64
+ def __eq__(self, other) -> bool:
65
+ if self is other:
66
+ return True
67
+ if not isinstance(other, IDSStructArray):
68
+ return False
69
+ # Equal if same size and all contained structures are the same
70
+ return len(self) == len(other) and all(a == b for a, b in zip(self, other))
71
+
72
+ def _set_lazy_context(self, ctx: LazyALArrayStructContext) -> None:
73
+ """Called by DBEntry during a lazy get/get_slice.
74
+
75
+ Set the context that we can use for retrieving our size and children.
76
+ """
77
+ self._lazy_ctx = ctx
78
+
79
+ def _load(self, item: Optional[int]) -> None:
80
+ """When lazy loading, ensure that the requested item is loaded.
81
+
82
+ Args:
83
+ item: index of the item to load. When None, just ensure that our size is
84
+ loaded from the lowlevel.
85
+ """
86
+ assert self._lazy
87
+ if self.value is not None: # We already loaded our size
88
+ if item is None:
89
+ return
90
+ if self.value[item] is not None:
91
+ return # item is already loaded
92
+ # Load requested data from the backend
93
+ if self.value is None:
94
+ if self._lazy_ctx is None:
95
+ # Lazy context can be None when:
96
+ # 1. The element does not exist in the on-disk DD version
97
+ # 2. The element exists, but changed type compared to the on-disk DD
98
+ # In both cases we just report that we're empty
99
+ self.value = []
100
+ else:
101
+ ctx = self._lazy_ctx.get_context()
102
+ self.value = [None] * ctx.size
103
+
104
+ if item is not None:
105
+ if item < 0:
106
+ item += len(self)
107
+ if item < 0 or item >= len(self):
108
+ raise IndexError("list index out of range")
109
+ # Create the requested item
110
+ from imas.ids_structure import IDSStructure
111
+
112
+ element = self.value[item] = IDSStructure(self, self.metadata)
113
+ element._set_lazy_context(self._lazy_ctx.iterate_to_index(item))
114
+
115
+ @property
116
+ def _element_structure(self):
117
+ """Prepare an element structure JIT"""
118
+ from imas.ids_structure import IDSStructure
119
+
120
+ struct = IDSStructure(self, self.metadata)
121
+ return struct
122
+
123
+ def __getitem__(self, item):
124
+ # value is a list, so the given item should be convertable to integer
125
+ # TODO: perhaps we should allow slices as well?
126
+ list_idx = int(item)
127
+ if self._lazy:
128
+ self._load(item)
129
+ return self.value[list_idx]
130
+
131
+ def __setitem__(self, item, value):
132
+ # value is a list, so the given item should be convertable to integer
133
+ # TODO: perhaps we should allow slices as well?
134
+ if self._lazy:
135
+ raise ValueError("Lazy-loaded IDSs are read-only.")
136
+ list_idx = int(item)
137
+ if isinstance(value, (IDSIdentifier, str, int)):
138
+ self.value[list_idx]._assign_identifier(value)
139
+ else: # FIXME: check if value is of the correct class
140
+ self.value[list_idx] = value
141
+
142
+ def __len__(self) -> int:
143
+ if self._lazy:
144
+ self._load(None)
145
+ return len(self.value)
146
+
147
+ @property
148
+ def shape(self) -> Tuple[int]:
149
+ """Get the shape of the contained data.
150
+
151
+ This will always return a tuple: ``(len(self), )``.
152
+ """
153
+ if self._lazy:
154
+ self._load(None)
155
+ return (len(self.value),)
156
+
157
+ def append(self, elt):
158
+ """Append elements to the end of the array of structures.
159
+
160
+ Args:
161
+ elt: IDS structure, or list of IDS structures, to append to this array
162
+ """
163
+ if self._lazy:
164
+ raise ValueError("Lazy-loaded IDSs are read-only.")
165
+ if not isinstance(elt, list):
166
+ elements = [elt]
167
+ else:
168
+ elements = elt
169
+ for e in elements:
170
+ # Just blindly append for now
171
+ # TODO: Maybe check if user is not trying to append weird elements
172
+ e._parent = self
173
+ self.value.append(e)
174
+
175
+ def __repr__(self):
176
+ return f"{self._build_repr_start()} with {len(self)} items)>"
177
+
178
+ def resize(self, nbelt: int, keep: bool = False):
179
+ """Resize an array of structures.
180
+
181
+ Args:
182
+ nbelt: The number of elements for the targeted array of structure,
183
+ which can be smaller or bigger than the size of the current
184
+ array if it already exists.
185
+ keep: Specifies if the targeted array of structure should keep
186
+ existing data in remaining elements after resizing it.
187
+ """
188
+ if self._lazy:
189
+ raise ValueError("Lazy-loaded IDSs are read-only.")
190
+ if nbelt < 0:
191
+ raise ValueError(f"Invalid size {nbelt}: size may not be negative")
192
+ if not keep:
193
+ self.value = []
194
+ cur = len(self.value)
195
+ if nbelt > cur:
196
+ # Create new structures to fill this AoS with
197
+ from imas.ids_structure import IDSStructure
198
+
199
+ new_els = [IDSStructure(self, self.metadata) for _ in range(nbelt - cur)]
200
+ if cur:
201
+ self.value.extend(new_els)
202
+ else:
203
+ self.value = new_els
204
+ elif nbelt < cur:
205
+ self.value = self.value[:nbelt]
206
+ else: # nbelt == cur
207
+ pass # nothing to do, already correct size
208
+
209
+ @property
210
+ def has_value(self) -> bool:
211
+ """True if this struct-array has nonzero size"""
212
+ # Note self.__len__ will lazy load our size if needed
213
+ return len(self) > 0
214
+
215
+ @property
216
+ def size(self) -> int:
217
+ """Get the number of elements in this array"""
218
+ return len(self)
219
+
220
+ def _validate(self) -> None:
221
+ # Common validation logic
222
+ super()._validate()
223
+ # IDSStructArray specific: validate coordinates and child nodes
224
+ if self.has_value:
225
+ self.coordinates._validate()
226
+ for child in self:
227
+ child._validate()
228
+
229
+ def _xxhash(self) -> bytes:
230
+ hsh = xxh3_64(len(self).to_bytes(8, "little"))
231
+ for s in self:
232
+ hsh.update(s._xxhash())
233
+ return hsh.digest()
imas/ids_structure.py ADDED
@@ -0,0 +1,331 @@
1
+ # This file is part of imas-python.
2
+ # You should have received the imas-python LICENSE file with this project.
3
+ """A structure in an IDS
4
+ """
5
+
6
+ import logging
7
+ from copy import deepcopy
8
+ from types import MappingProxyType
9
+ from typing import Generator, List, Optional, Union
10
+
11
+ from xxhash import xxh3_64
12
+
13
+ from imas.backends.imas_core.al_context import LazyALContext
14
+ from imas.ids_base import IDSBase, IDSDoc
15
+ from imas.ids_identifiers import IDSIdentifier
16
+ from imas.ids_metadata import IDSDataType, IDSMetadata
17
+ from imas.ids_path import IDSPath
18
+ from imas.ids_primitive import IDSPrimitive
19
+ from imas.ids_struct_array import IDSStructArray
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class IDSStructure(IDSBase):
25
+ """IDS structure node
26
+
27
+ Represents a node in the IDS tree. Does not itself contain data,
28
+ but contains references to leaf nodes with data (IDSPrimitive) or
29
+ other node-like structures, for example other IDSStructures or
30
+ IDSStructArrays
31
+ """
32
+
33
+ __doc__ = IDSDoc(__doc__)
34
+ _children: "MappingProxyType[str, IDSMetadata]"
35
+ _lazy_context: Optional[LazyALContext]
36
+
37
+ def __init__(self, parent: IDSBase, metadata: IDSMetadata):
38
+ """Initialize IDSStructure from metadata specification
39
+
40
+ Args:
41
+ parent: Parent structure. Can be anything, but at database write
42
+ time should be something with a path attribute
43
+ metadata: IDSMetadata describing the structure of the IDS
44
+ """
45
+ # Performance hack: bypass our __setattr__ implementation during __init__:
46
+ dct = self.__dict__
47
+ dct["_parent"] = parent
48
+ # parent._lazy is undefined for IDSToplevel, but then _lazy is already set
49
+ if "_lazy" not in dct:
50
+ dct["_lazy"] = parent._lazy
51
+ dct["metadata"] = metadata
52
+
53
+ dct["_children"] = metadata._children
54
+ dct["_lazy_context"] = None
55
+
56
+ def __getattr__(self, name):
57
+ if name not in self._children:
58
+ raise AttributeError(
59
+ f"IDS structure '{self._path}' has no attribute '{name}'"
60
+ )
61
+ # Create child node
62
+ child_meta = self._children[name]
63
+ child = child_meta._node_type(self, child_meta)
64
+ self.__dict__[name] = child # bypass setattr logic below: avoid recursion
65
+ if self._lazy: # lazy load the child
66
+ from imas.backends.imas_core.db_entry_helpers import _get_child
67
+
68
+ _get_child(child, self._lazy_context)
69
+ return child
70
+
71
+ def _assign_identifier(self, value: Union[IDSIdentifier, str, int]) -> None:
72
+ identifier_enum = self.metadata.identifier_enum
73
+ if identifier_enum is None:
74
+ raise TypeError(
75
+ f"Cannot assign {value!r} to {self!r}. This structure is not an "
76
+ "identifier, or the Data Dictionary does not define the "
77
+ "identifier options."
78
+ )
79
+ if isinstance(value, IDSIdentifier):
80
+ if not isinstance(value, identifier_enum):
81
+ raise TypeError(
82
+ f"Assigning incorrect identifier. {value} is a "
83
+ f"{type(value).__name__}, was expecting a "
84
+ f"{identifier_enum.__name__}"
85
+ )
86
+ elif isinstance(value, int):
87
+ value = identifier_enum(value)
88
+ else: # value is a string
89
+ try:
90
+ value = identifier_enum[value]
91
+ except KeyError:
92
+ raise ValueError(
93
+ f"{value!r} is not a valid {identifier_enum.__name__}"
94
+ ) from None
95
+ self.name = value.name
96
+ self.index = value.index
97
+ self.description = value.description
98
+
99
+ def __setattr__(self, key, value):
100
+ """
101
+ 'Smart' setting of attributes. To be able to warn the user on imas
102
+ IDS interaction time, instead of on database put time
103
+ Only try to cast user-facing attributes, as core developers might
104
+ want to always bypass this mechanism (I know I do!)
105
+ """
106
+ # Skip logic for any value that is not a child IDS node
107
+ if key.startswith("_"):
108
+ return super().__setattr__(key, value)
109
+
110
+ # This will raise an attribute error when there is no child named 'key', fine?
111
+ attr = getattr(self, key)
112
+
113
+ if isinstance(attr, IDSStructure):
114
+ if isinstance(value, (IDSIdentifier, str, int)):
115
+ return attr._assign_identifier(value)
116
+
117
+ if not isinstance(value, IDSStructure):
118
+ raise TypeError(
119
+ f"Trying to set structure field {key} with non-structure."
120
+ )
121
+ if value.metadata.path != attr.metadata.path:
122
+ raise ValueError(
123
+ f"Trying to set structure field {attr.metadata.path} "
124
+ f"with a non-matching structure {value.metadata.path}."
125
+ )
126
+ super().__setattr__(key, value)
127
+ value._parent = self
128
+
129
+ elif isinstance(attr, IDSStructArray):
130
+ if not isinstance(value, IDSStructArray):
131
+ raise TypeError(
132
+ f"Trying to set struct array field {key} with non-struct-array."
133
+ )
134
+ if value.metadata.path != attr.metadata.path:
135
+ raise ValueError(
136
+ f"Trying to set struct array field {attr.metadata.path} "
137
+ f"with a non-matching struct array {value.metadata.path}."
138
+ )
139
+ super().__setattr__(key, value)
140
+ value._parent = self
141
+
142
+ else:
143
+ attr.value = value
144
+
145
+ def __deepcopy__(self, memo):
146
+ if self._lazy:
147
+ raise NotImplementedError(
148
+ "deepcopy is not implemented for lazy-loaded IDSs."
149
+ )
150
+ copy = self.__class__(self._parent, self.metadata)
151
+ for child in self._children:
152
+ if child in self.__dict__:
153
+ child_copy = deepcopy(getattr(self, child), memo)
154
+ setattr(copy, child, child_copy)
155
+ return copy
156
+
157
+ def __dir__(self) -> List[str]:
158
+ return sorted(set(object.__dir__(self)).union(self._children))
159
+
160
+ def __eq__(self, other) -> bool:
161
+ if self is other:
162
+ return True
163
+ if not isinstance(other, IDSStructure):
164
+ return False if isinstance(other, IDSBase) else NotImplemented
165
+ from imas.util import idsdiffgen # local import to avoid circular import
166
+
167
+ for _ in idsdiffgen(self, other):
168
+ return False # Not equal if there is any difference
169
+ return True # Equal when there are no differences
170
+
171
+ def _set_lazy_context(self, ctx: LazyALContext) -> None:
172
+ """Called by DBEntry during a lazy get/get_slice.
173
+
174
+ Set the context that we can use for retrieving our children.
175
+ """
176
+ self._lazy_context = ctx
177
+
178
+ @property
179
+ def _dd_parent(self) -> IDSBase:
180
+ if self.metadata.data_type is IDSDataType.STRUCT_ARRAY:
181
+ return self._parent._parent
182
+ return self._parent
183
+
184
+ @property
185
+ def has_value(self) -> bool:
186
+ """True if any of the children has a non-default value"""
187
+ if self._lazy:
188
+ raise NotImplementedError(
189
+ "`has_value` is not implemented for lazy-loaded structures."
190
+ )
191
+ for _ in self.iter_nonempty_():
192
+ return True
193
+ return False
194
+
195
+ def iter_nonempty_(self, *, accept_lazy=False) -> Generator[IDSBase, None, None]:
196
+ """Iterate over all child nodes with non-default value.
197
+
198
+ Note:
199
+ The name ends with an underscore so it won't clash with IDS child names.
200
+
201
+ Caution:
202
+ This method works differently for lazy-loaded IDSs.
203
+
204
+ By default, an error is raised when calling this method for lazy loaded
205
+ IDSs. You may set the keyword argument ``accept_lazy`` to ``True`` to
206
+ iterate over all `loaded` child nodes with a non-default value. See below
207
+ example for lazy-loaded IDSs.
208
+
209
+ Examples:
210
+
211
+ .. code-block:: python
212
+ :caption: ``iter_nonempty_`` for fully loaded IDSs
213
+
214
+ >>> import imas.training
215
+ >>> entry = imas.training.get_training_db_entry()
216
+ >>> cp = entry.get("core_profiles")
217
+ >>> list(cp.iter_nonempty_())
218
+ [
219
+ <IDSStructure (IDS:core_profiles, ids_properties)>,
220
+ <IDSStructArray (IDS:core_profiles, profiles_1d with 3 items)>,
221
+ <IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
222
+ numpy.ndarray([ 3.98722186, 432.93759781, 792. ])
223
+ ]
224
+
225
+ .. code-block:: python
226
+ :caption: ``iter_nonempty_`` for lazy-loaded IDSs
227
+
228
+ >>> import imas.training
229
+ >>> entry = imas.training.get_training_db_entry()
230
+ >>> cp = entry.get("core_profiles", lazy=True)
231
+ >>> list(cp.iter_nonempty_())
232
+ RuntimeError: Iterating over non-empty nodes of a lazy loaded IDS will
233
+ skip nodes that are not loaded. Set accept_lazy=True to continue.
234
+ See the documentation for more information: [...]
235
+ >>> list(cp.iter_nonempty_(accept_lazy=True))
236
+ []
237
+ >>> # An empty list because nothing is loaded. Load `time`:
238
+ >>> cp.time
239
+ <IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
240
+ numpy.ndarray([ 3.98722186, 432.93759781, 792. ])
241
+ >>> list(cp.iter_nonempty_(accept_lazy=True))
242
+ [<IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
243
+ numpy.ndarray([ 3.98722186, 432.93759781, 792. ])]
244
+
245
+ Keyword Args:
246
+ accept_lazy: Accept that this method will only yield loaded nodes of a
247
+ lazy-loaded IDS. Non-empty nodes that have not been loaded from the
248
+ backend are not iterated over. See detailed explanation above.
249
+ """
250
+ if self._lazy and not accept_lazy:
251
+ raise RuntimeError(
252
+ "Iterating over non-empty nodes of a lazy loaded IDS will skip nodes "
253
+ "that are not loaded. Set accept_lazy=True to continue. "
254
+ "See the documentation for more information: "
255
+ "https://sharepoint.iter.org/departments/POP/CM/IMDesign/"
256
+ "Code%20Documentation/imas-python-doc/generated/imas.ids_structure."
257
+ "IDSStructure.html#imas.ids_structure.IDSStructure.iter_nonempty_"
258
+ )
259
+ for child in self._children:
260
+ if child in self.__dict__:
261
+ child_node = getattr(self, child)
262
+ if ( # IDSStructure.has_value is not implemented when lazy-loaded:
263
+ self._lazy and isinstance(child_node, IDSStructure)
264
+ ) or child_node.has_value:
265
+ yield child_node
266
+
267
+ def __iter__(self):
268
+ """Iterate over this structure's children"""
269
+ return iter(map(self.__getitem__, self._children))
270
+
271
+ def __str__(self):
272
+ return '%s("%s")' % (type(self).__name__, self.metadata.name)
273
+
274
+ def __getitem__(self, key):
275
+ keyname = str(key)
276
+ if keyname in self._children:
277
+ return getattr(self, keyname)
278
+
279
+ path = IDSPath(keyname)
280
+ if len(path) == 1 and path.indices[0] is None:
281
+ raise AttributeError(f"'{self!r}' has no attribute '{keyname}'")
282
+
283
+ return path.goto(self, from_root=False)
284
+
285
+ def __repr__(self):
286
+ return f"{self._build_repr_start()})>"
287
+
288
+ def __setitem__(self, key, value):
289
+ keyname = str(key)
290
+ if keyname in self._children:
291
+ return self.__setattr__(keyname, value)
292
+
293
+ path = IDSPath(keyname)
294
+ if len(path) == 1 and path.indices[0] is None:
295
+ raise AttributeError(f"'{self!r}' has no attribute '{keyname}'")
296
+
297
+ attr = path.goto(self, from_root=False)
298
+ if isinstance(attr, IDSPrimitive):
299
+ attr.value = value
300
+ else:
301
+ # Setting an IDSStructArray or IDSStructure: delegate to the
302
+ # relevant __setitem__ of its parent
303
+ parent = attr._parent
304
+ parent[path.parts[-1]] = value
305
+
306
+ def _validate(self) -> None:
307
+ # Common validation logic
308
+ super()._validate()
309
+ # IDSStructure specific: validate child nodes
310
+ # accept_lazy=True: users are warned in IDSToplevel.validate()
311
+ for child in self.iter_nonempty_(accept_lazy=True):
312
+ child._validate()
313
+
314
+ def _xxhash(self) -> bytes:
315
+ hsh = xxh3_64()
316
+ children = sorted(self._children)
317
+
318
+ # Skip ids_properties.version_put
319
+ if self.metadata.name == "ids_properties":
320
+ if "version_put" in children: # Some old DDs don't have version_put defined
321
+ children.remove("version_put")
322
+
323
+ for childname in children:
324
+ child = self[childname]
325
+ if not child.has_value:
326
+ continue
327
+
328
+ hsh.update(childname.encode("UTF-8"))
329
+ hsh.update(child._xxhash())
330
+
331
+ return hsh.digest()