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
imas/ids_primitive.py ADDED
@@ -0,0 +1,510 @@
1
+ # This file is part of imas-python.
2
+ # You should have received the imas-python LICENSE file with this project.
3
+ """Provides the classes for IDS data nodes
4
+ """
5
+ import logging
6
+ import math
7
+ import operator
8
+ import struct
9
+ from copy import deepcopy
10
+ from numbers import Complex, Integral, Number, Real
11
+ from typing import Tuple
12
+
13
+ import numpy as np
14
+ from xxhash import xxh3_64, xxh3_64_digest
15
+
16
+ from imas.ids_base import IDSBase, IDSDoc
17
+ from imas.ids_coordinates import IDSCoordinates
18
+ from imas.ids_data_type import IDSDataType
19
+ from imas.ids_metadata import IDSMetadata
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def _binary_wrapper(op, name):
25
+ def func(self, other):
26
+ if isinstance(other, IDSPrimitive):
27
+ other = other.value
28
+ return op(self.value, other)
29
+
30
+ func.__name__ = f"__{name}__"
31
+ return func
32
+
33
+
34
+ def _reflected_binary_wrapper(op, name):
35
+ def func(self, other):
36
+ if isinstance(other, IDSPrimitive):
37
+ other = other.value
38
+ return op(other, self.value)
39
+
40
+ func.__name__ = f"__r{name}__"
41
+ return func
42
+
43
+
44
+ def _numeric_wrapper(op, name):
45
+ return (_binary_wrapper(op, name), _reflected_binary_wrapper(op, name))
46
+
47
+
48
+ def _unary_wrapper(op, name):
49
+ def func(self):
50
+ return op(self.value)
51
+
52
+ func.__name__ = f"__{name}__"
53
+ return func
54
+
55
+
56
+ class IDSPrimitive(IDSBase):
57
+ """IDS leaf node
58
+
59
+ Represents actual data. Examples are (arrays of) strings, floats, integers.
60
+ Lives entirely in-memory until 'put' into a database.
61
+ """
62
+
63
+ __doc__ = IDSDoc(__doc__)
64
+ __slots__ = ["_parent", "metadata", "__value"]
65
+
66
+ def __init__(self, parent: IDSBase, metadata: IDSMetadata):
67
+ """Initialize IDSPrimitive
68
+
69
+ Args:
70
+ parent: Parent node of this leaf
71
+ metadata: IDSMetadata that describes this IDSPrimitive
72
+ """
73
+ self._parent = parent
74
+ self.metadata = metadata
75
+
76
+ self.__value = None
77
+
78
+ @property
79
+ def _lazy(self):
80
+ """Whether this IDS Node is part of a lazy-loaded IDSToplevel"""
81
+ return self._parent._lazy
82
+
83
+ @property
84
+ def coordinates(self):
85
+ """Coordinates belonging to this quantity."""
86
+ return IDSCoordinates(self)
87
+
88
+ def __deepcopy__(self, memo):
89
+ # note: if parent needs updating it is handled by the deepcopy of our parent
90
+ # TODO: implement the statement on the previous line O_O
91
+ copy = self.__class__(self._parent, self.metadata)
92
+ copy.__value = deepcopy(self.__value, memo)
93
+ return copy
94
+
95
+ @property
96
+ def shape(self) -> Tuple[int, ...]:
97
+ """Get the shape of the contained data.
98
+
99
+ For 0D data types, the shape is always an empty tuple.
100
+ See also :external:py:func:`numpy.shape`.
101
+ """
102
+ if self.metadata.ndim == 0:
103
+ return tuple()
104
+ if self.__value is None:
105
+ return (0,) * self.metadata.ndim
106
+ return np.shape(self.__value)
107
+
108
+ @property
109
+ def size(self) -> int:
110
+ """Get the size of stored data (number of elements stored).
111
+
112
+ For 0D data types, the size is always 1 (even when set to the default).
113
+ For 1+D data types, the size is the number of elements stored, see
114
+ :external:py:attr:`numpy.ndarray.size`.
115
+ """
116
+ if self.metadata.ndim == 0:
117
+ return 1
118
+ if self.__value is None:
119
+ return 0
120
+ if self.metadata.data_type == IDSDataType.STR:
121
+ return len(self.__value)
122
+ # self.__value must be a numpy array
123
+ return self.__value.size
124
+
125
+ @property
126
+ def has_value(self) -> bool:
127
+ """True if a value is defined here that is not the default"""
128
+ if self.__value is None: # No value set
129
+ return False
130
+ return self.size > 0 # Default for ndarray and STR_1D types is size == 0
131
+
132
+ def __len__(self) -> int:
133
+ if self.metadata.ndim == 0:
134
+ raise TypeError(f"IDS data node of type {self.data_type} has no len()")
135
+ if self.__value is None:
136
+ return 0
137
+ return len(self.__value)
138
+
139
+ @property
140
+ def _default(self):
141
+ data_type = self.metadata.data_type
142
+ if self.metadata.ndim == 0:
143
+ return data_type.default
144
+ if data_type is IDSDataType.STR:
145
+ return []
146
+ return np.empty((0,) * self.metadata.ndim, dtype=data_type.numpy_dtype)
147
+
148
+ def __iter__(self):
149
+ return iter(self.value)
150
+
151
+ def __repr__(self):
152
+ empty = value_repr = ""
153
+ if self.has_value:
154
+ value_repr = f"\n{self.value.__class__.__qualname__}({self.value!r})"
155
+ else:
156
+ empty = "empty "
157
+ return f"{self._build_repr_start()}, {empty}{self.data_type})>{value_repr}"
158
+
159
+ @property
160
+ def value(self):
161
+ """Return the value of this IDSPrimitive if it is set,
162
+ otherwise return the default"""
163
+ if self.__value is None:
164
+ if self.metadata.ndim == 0:
165
+ return self._default
166
+ # 1+D data types can be modified in-place, first set before returning so,
167
+ # for example, `ids.time.value.resize(10)`` always works as expected.
168
+ self.__value = self._default
169
+ return self.__value
170
+
171
+ @value.setter
172
+ def value(self, setter_value):
173
+ # NOTE: This setter is bypassed during a get/get_slice, and self.__value is set
174
+ # directly.
175
+ if self._lazy:
176
+ raise ValueError("Lazy-loaded IDSs are read-only.")
177
+ if isinstance(setter_value, type(self)):
178
+ # No need to cast, just overwrite contained value
179
+ if (
180
+ setter_value.metadata.data_type is self.metadata.data_type
181
+ and setter_value.metadata.ndim == self.metadata.ndim
182
+ ):
183
+ self.__value = setter_value.value
184
+ # Can we cast the internal value to a valid value?
185
+ else:
186
+ self.__value = self._cast_value(setter_value.value)
187
+ else:
188
+ self.__value = self._cast_value(setter_value)
189
+ if self.metadata.ndim == 0 and self.__value == self.metadata.data_type.default:
190
+ # Unset 0D types when setting them to their magic default value
191
+ self.__value = None
192
+
193
+ # Implement special __dunder__ methods that just defer to the underlying value
194
+ __eq__ = _binary_wrapper(operator.eq, "eq")
195
+ __ne__ = _binary_wrapper(operator.ne, "ne")
196
+ __lt__ = _binary_wrapper(operator.lt, "lt")
197
+ __le__ = _binary_wrapper(operator.le, "le")
198
+ __gt__ = _binary_wrapper(operator.gt, "gt")
199
+ __ge__ = _binary_wrapper(operator.ge, "ge")
200
+ __contains__ = _binary_wrapper(operator.contains, "contains")
201
+ # Numeric methods
202
+ __add__, __radd__ = _numeric_wrapper(operator.add, "add")
203
+ __sub__, __rsub__ = _numeric_wrapper(operator.sub, "sub")
204
+ __mul__, __rmul__ = _numeric_wrapper(operator.mul, "mul")
205
+ __matmul__, __rmatmul__ = _numeric_wrapper(operator.matmul, "matmul")
206
+ __truediv__, __rtruediv__ = _numeric_wrapper(operator.truediv, "truediv")
207
+ __floordiv__, __rfloordiv__ = _numeric_wrapper(operator.floordiv, "floordiv")
208
+ __mod__, __rmod__ = _numeric_wrapper(operator.mod, "mod")
209
+ __divmod__, __rdivmod__ = _numeric_wrapper(divmod, "divmod")
210
+ # TODO: handle the optional third argument for __pow__?
211
+ __pow__, __rpow__ = _numeric_wrapper(operator.pow, "pow")
212
+ __lshift__, __rlshift__ = _numeric_wrapper(operator.lshift, "lshift")
213
+ __rshift__, __rrshift__ = _numeric_wrapper(operator.rshift, "rshift")
214
+ __and__, __rand__ = _numeric_wrapper(operator.and_, "and")
215
+ __xor__, __rxor__ = _numeric_wrapper(operator.xor, "xor")
216
+ __or__, __ror__ = _numeric_wrapper(operator.or_, "or")
217
+ # unary methods
218
+ __neg__ = _unary_wrapper(operator.neg, "neg")
219
+ __pos__ = _unary_wrapper(operator.pos, "pos")
220
+ __abs__ = _unary_wrapper(operator.abs, "abs")
221
+ __invert__ = _unary_wrapper(operator.invert, "invert")
222
+
223
+ def __getitem__(self, index):
224
+ return self.value[index]
225
+
226
+ def __setitem__(self, index, value):
227
+ self.value[index] = value
228
+
229
+ def __getattr__(self, name):
230
+ # Forward this getattr call to our actual value
231
+ if not name.startswith("_"):
232
+ return getattr(self.value, name)
233
+ raise AttributeError(f"{self.__class__} object has no attribute {name}")
234
+
235
+ def _cast_value(self, value):
236
+ """Cast a value to the correct type.
237
+
238
+ Must be implemented by subclasses.
239
+ """
240
+ raise NotImplementedError()
241
+
242
+ @property
243
+ def data_type(self):
244
+ """Combine imas ids_type and ndims to AL data_type"""
245
+ return "{!s}_{!s}D".format(self.metadata.data_type.value, self.metadata.ndim)
246
+
247
+ def _validate(self) -> None:
248
+ # Common validation logic
249
+ super()._validate()
250
+ # Validate coordinates
251
+ if self.metadata.ndim > 0 and self.has_value:
252
+ self.coordinates._validate()
253
+
254
+
255
+ _CONVERT_MSG = "Assigning incorrect type '%s' to %r, attempting automatic conversion."
256
+
257
+
258
+ def _cast_str(node, value):
259
+ """Cast a value to a string.
260
+
261
+ If value is a bytes object, decode it using UTF-8. Otherwise return str(value).
262
+ """
263
+ if not isinstance(value, str):
264
+ logger.info(_CONVERT_MSG, type(value), node)
265
+ if isinstance(value, bytes):
266
+ return value.decode("UTF-8")
267
+ return str(value)
268
+ return value
269
+
270
+
271
+ class IDSString0D(IDSPrimitive):
272
+ """IDSPrimitive specialization for STR_0D."""
273
+
274
+ __doc__ = IDSDoc(__doc__)
275
+ __slots__ = ()
276
+
277
+ _cast_value = _cast_str
278
+
279
+ def __str__(self):
280
+ return self.value
281
+
282
+ def __len__(self): # override as it is the only 0D quantity with a length
283
+ return len(self.value)
284
+
285
+ def _xxhash(self) -> bytes:
286
+ return xxh3_64_digest(self.value.encode("UTF-8"))
287
+
288
+
289
+ class IDSString1D(IDSPrimitive):
290
+ """IDSPrimitive specialization for STR_1D."""
291
+
292
+ __doc__ = IDSDoc(__doc__)
293
+ __slots__ = ()
294
+
295
+ def __getattr__(self, name):
296
+ # Forward this getattr call to our actual value
297
+ return getattr(self.value, name)
298
+
299
+ def append(self, value):
300
+ """Append a string to this list."""
301
+ self.value.append(_cast_str(self, value))
302
+
303
+ def extend(self, value):
304
+ """Extend this list with an iterable of strings."""
305
+ self.value.extend(_cast_str(self, val) for val in value)
306
+
307
+ def __setitem__(self, index, value):
308
+ if isinstance(index, slice):
309
+ value = (_cast_str(self, val) for val in value)
310
+ else:
311
+ value = _cast_str(self, value)
312
+ self.value[index] = value
313
+
314
+ def _cast_value(self, value):
315
+ if not isinstance(value, (str, bytes)):
316
+ try: # Try to iterate over value
317
+ return list(_cast_str(self, val) for val in value)
318
+ except TypeError:
319
+ pass # Just convert value to a string and store it as the only item
320
+ if isinstance(value, str):
321
+ logger.info(_CONVERT_MSG, type(value), self)
322
+ return [_cast_str(self, value)]
323
+
324
+ def _xxhash(self) -> bytes:
325
+ hsh = xxh3_64(len(self).to_bytes(8, "little"))
326
+ for s in self:
327
+ hsh.update(xxh3_64_digest(s.encode("UTF-8")))
328
+ return hsh.digest()
329
+
330
+
331
+ class IDSNumeric0D(IDSPrimitive):
332
+ """Abstract base class for INT_0D, FLT_0D and CPX_0D."""
333
+
334
+ __doc__ = IDSDoc(__doc__)
335
+ __slots__ = ()
336
+
337
+ def __array__(self, dtype=None):
338
+ return np.array(self.value, dtype=dtype)
339
+
340
+ def __str__(self):
341
+ return str(self.value)
342
+
343
+ def _cast_value(self, value):
344
+ if isinstance(value, np.ndarray) and value.ndim == 0:
345
+ value = value.item() # Unpack 0D numpy arrays
346
+ cast_value = self.metadata.data_type.python_type(value)
347
+ # nan != nan, so we need the second check to not complain when assigning a nan
348
+ if cast_value != value and not (np.isnan(cast_value) and np.isnan(value)):
349
+ logger.info(_CONVERT_MSG, type(value), self)
350
+ return cast_value
351
+
352
+
353
+ class IDSComplex0D(IDSNumeric0D, Complex):
354
+ """IDSPrimitive specialization for CPX_0D."""
355
+
356
+ __doc__ = IDSDoc(__doc__)
357
+ __slots__ = ()
358
+
359
+ def __complex__(self) -> complex:
360
+ return self.value
361
+
362
+ @property
363
+ def real(self) -> float:
364
+ return self.value.real
365
+
366
+ @property
367
+ def imag(self) -> float:
368
+ return self.value.imag
369
+
370
+ def conjugate(self) -> complex:
371
+ return self.value.conjugate()
372
+
373
+ def _xxhash(self) -> bytes:
374
+ return xxh3_64_digest(struct.pack("<dd", self.real, self.imag))
375
+
376
+
377
+ class IDSFloat0D(IDSNumeric0D, Real):
378
+ """IDSPrimitive specialization for FLT_0D."""
379
+
380
+ __doc__ = IDSDoc(__doc__)
381
+ __slots__ = ()
382
+
383
+ def __float__(self) -> float:
384
+ return self.value
385
+
386
+ def __int__(self) -> int:
387
+ return int(self.value)
388
+
389
+ def __trunc__(self) -> int:
390
+ return int(self.value)
391
+
392
+ def __floor__(self) -> int:
393
+ return math.floor(self.value)
394
+
395
+ def __ceil__(self) -> int:
396
+ return math.ceil(self.value)
397
+
398
+ def __round__(self, ndigits=None):
399
+ return round(self.value, ndigits)
400
+
401
+ def _xxhash(self) -> bytes:
402
+ return xxh3_64_digest(struct.pack("<d", self.value))
403
+
404
+
405
+ class IDSInt0D(IDSNumeric0D, Integral):
406
+ """IDSPrimitive specialization for INT_0D."""
407
+
408
+ __doc__ = IDSDoc(__doc__)
409
+ __slots__ = ()
410
+
411
+ def __int__(self):
412
+ return self.value
413
+
414
+ def __trunc__(self) -> int:
415
+ return int(self.value)
416
+
417
+ def __ceil__(self) -> int:
418
+ return self.value
419
+
420
+ def __floor__(self) -> int:
421
+ return self.value
422
+
423
+ def __round__(self, ndigits=None):
424
+ return round(self.value, ndigits)
425
+
426
+ def _xxhash(self) -> bytes:
427
+ return xxh3_64_digest(self.value.to_bytes(4, "little", signed=True))
428
+
429
+
430
+ class IDSNumericArray(IDSPrimitive, np.lib.mixins.NDArrayOperatorsMixin):
431
+ """IDSPrimitive specialization for ND numeric types (wrapping ``numpy.ndarray``)."""
432
+
433
+ __doc__ = IDSDoc(__doc__)
434
+ __slots__ = ()
435
+
436
+ # One might also consider adding the built-in list type to this
437
+ # list, to support operations like np.add(array_like, list)
438
+ _HANDLED_TYPES = (np.ndarray, Number)
439
+
440
+ def __array__(self, dtype=None):
441
+ return self.value.astype(dtype, copy=False)
442
+
443
+ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
444
+ out = kwargs.get("out", ())
445
+ for x in inputs + out:
446
+ # Only support operations with instances of _HANDLED_TYPES.
447
+ # Use ArrayLike instead of type(self) for isinstance to
448
+ # allow subclasses that don't override __array_ufunc__ to
449
+ # handle ArrayLike objects.
450
+ if not isinstance(x, self._HANDLED_TYPES + (IDSPrimitive,)):
451
+ return NotImplemented
452
+
453
+ # Defer to the implementation of the ufunc on unwrapped values.
454
+ inputs = tuple(x.value if isinstance(x, IDSPrimitive) else x for x in inputs)
455
+ if out:
456
+ kwargs["out"] = tuple(
457
+ x.value if isinstance(x, IDSPrimitive) else x for x in out
458
+ )
459
+ result = getattr(ufunc, method)(*inputs, **kwargs)
460
+
461
+ if method == "at":
462
+ # no return value
463
+ return None
464
+ else:
465
+ # one return value
466
+ return result
467
+
468
+ def __repr__(self):
469
+ # Specify that this is a numpy array, instead of a Python array As we
470
+ # know .value is a numpy array, we can remove "array" part of the
471
+ # numpy-native repr
472
+ empty = value_repr = ""
473
+ if self.has_value:
474
+ value_repr = f"\n{_fullname(self.value)}{repr(self.value)[5:]}"
475
+ else:
476
+ empty = "empty "
477
+ return f"{self._build_repr_start()}, {empty}{self.data_type})>{value_repr}"
478
+
479
+ def _cast_value(self, value):
480
+ dtype = self.metadata.data_type.numpy_dtype
481
+ value = np.asanyarray(value)
482
+ if value.dtype != dtype:
483
+ logger.info(_CONVERT_MSG, value.dtype, self)
484
+ value = np.array(value, dtype=dtype, copy=False)
485
+ if value.ndim != self.metadata.ndim:
486
+ raise ValueError(f"Trying to assign a {value.ndim}D value to {self!r}.")
487
+ return value
488
+
489
+ def _xxhash(self) -> bytes:
490
+ arr = self.value
491
+ hsh = xxh3_64(arr.ndim.to_bytes(1, "little"))
492
+ hsh.update(struct.pack("<" + arr.ndim * "q", *arr.shape))
493
+ # Ensure array is little endian, only create a copy if it is big-endian
494
+ arr = arr.astype(arr.dtype.newbyteorder("little"), copy=False)
495
+ hsh.update(arr.tobytes(order="F"))
496
+ return hsh.digest()
497
+
498
+
499
+ def _fullname(o) -> str:
500
+ """Get the full name to a type, including module name etc.
501
+
502
+ Examples:
503
+ - _fullname(np.array([1,2,3]) -> numpy.ndarray
504
+ - fullname([1,2,3]) -> list
505
+ """
506
+ class_ = o.__class__
507
+ module = class_.__module__
508
+ if module == "builtins":
509
+ return class_.__qualname__ # avoid outputs like 'builtins.str'
510
+ return module + "." + class_.__qualname__