Skip to content

Commit 4ae37e4

Browse files
authored
waveform: Add __eq__ and __repr__ magic methods (#11)
* waveform: Implement __eq__ * waveform: Implement repr and corresponding constructor arguments * tests: Remove an xfail
1 parent 0d515bc commit 4ae37e4

File tree

6 files changed

+385
-47
lines changed

6 files changed

+385
-47
lines changed

src/nitypes/waveform/_analog_waveform.py

Lines changed: 119 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import datetime as dt
44
import sys
55
import warnings
6-
from collections.abc import Sequence
6+
from collections.abc import Mapping, Sequence
77
from typing import Any, Generic, SupportsIndex, TypeVar, Union, cast, overload
88

99
import hightime as ht
@@ -17,6 +17,7 @@
1717
CHANNEL_NAME,
1818
UNIT_DESCRIPTION,
1919
ExtendedPropertyDictionary,
20+
ExtendedPropertyValue,
2021
)
2122
from nitypes.waveform._scaling import NO_SCALING, ScaleMode
2223
from nitypes.waveform._timing import BaseTiming, PrecisionTiming, Timing, convert_timing
@@ -83,6 +84,9 @@ def from_array_1d(
8384
copy: bool = ...,
8485
start_index: SupportsIndex | None = ...,
8586
sample_count: SupportsIndex | None = ...,
87+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
88+
timing: Timing | PrecisionTiming | None = ...,
89+
scale_mode: ScaleMode | None = ...,
8690
) -> AnalogWaveform[_ScalarType]: ...
8791

8892
@overload
@@ -94,6 +98,9 @@ def from_array_1d(
9498
copy: bool = ...,
9599
start_index: SupportsIndex | None = ...,
96100
sample_count: SupportsIndex | None = ...,
101+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
102+
timing: Timing | PrecisionTiming | None = ...,
103+
scale_mode: ScaleMode | None = ...,
97104
) -> AnalogWaveform[_ScalarType]: ...
98105

99106
@overload
@@ -105,6 +112,9 @@ def from_array_1d(
105112
copy: bool = ...,
106113
start_index: SupportsIndex | None = ...,
107114
sample_count: SupportsIndex | None = ...,
115+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
116+
timing: Timing | PrecisionTiming | None = ...,
117+
scale_mode: ScaleMode | None = ...,
108118
) -> AnalogWaveform[Any]: ...
109119

110120
@staticmethod
@@ -115,6 +125,9 @@ def from_array_1d(
115125
copy: bool = True,
116126
start_index: SupportsIndex | None = 0,
117127
sample_count: SupportsIndex | None = None,
128+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = None,
129+
timing: Timing | PrecisionTiming | None = None,
130+
scale_mode: ScaleMode | None = None,
118131
) -> AnalogWaveform[_ScalarType]:
119132
"""Construct an analog waveform from a one-dimensional array or sequence.
120133
@@ -125,6 +138,9 @@ def from_array_1d(
125138
copy: Specifies whether to copy the array or save a reference to it.
126139
start_index: The sample index at which the analog waveform data begins.
127140
sample_count: The number of samples in the analog waveform.
141+
extended_properties: The extended properties of the analog waveform.
142+
timing: The timing information of the analog waveform.
143+
scale_mode: The scale mode of the analog waveform.
128144
129145
Returns:
130146
An analog waveform containing the specified data.
@@ -143,9 +159,12 @@ def from_array_1d(
143159
raise invalid_arg_type("input array", "one-dimensional array or sequence", array)
144160

145161
return AnalogWaveform(
146-
_data=np.asarray(array, dtype, copy=copy),
162+
raw_data=np.asarray(array, dtype, copy=copy),
147163
start_index=start_index,
148164
sample_count=sample_count,
165+
extended_properties=extended_properties,
166+
timing=timing,
167+
scale_mode=scale_mode,
149168
)
150169

151170
@overload
@@ -157,6 +176,9 @@ def from_array_2d(
157176
copy: bool = ...,
158177
start_index: SupportsIndex | None = ...,
159178
sample_count: SupportsIndex | None = ...,
179+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
180+
timing: Timing | PrecisionTiming | None = ...,
181+
scale_mode: ScaleMode | None = ...,
160182
) -> list[AnalogWaveform[_ScalarType]]: ...
161183

162184
@overload
@@ -168,6 +190,9 @@ def from_array_2d(
168190
copy: bool = ...,
169191
start_index: SupportsIndex | None = ...,
170192
sample_count: SupportsIndex | None = ...,
193+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
194+
timing: Timing | PrecisionTiming | None = ...,
195+
scale_mode: ScaleMode | None = ...,
171196
) -> list[AnalogWaveform[_ScalarType]]: ...
172197

173198
@overload
@@ -179,6 +204,9 @@ def from_array_2d(
179204
copy: bool = ...,
180205
start_index: SupportsIndex | None = ...,
181206
sample_count: SupportsIndex | None = ...,
207+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
208+
timing: Timing | PrecisionTiming | None = ...,
209+
scale_mode: ScaleMode | None = ...,
182210
) -> list[AnalogWaveform[Any]]: ...
183211

184212
@staticmethod
@@ -189,6 +217,9 @@ def from_array_2d(
189217
copy: bool = True,
190218
start_index: SupportsIndex | None = 0,
191219
sample_count: SupportsIndex | None = None,
220+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = None,
221+
timing: Timing | PrecisionTiming | None = None,
222+
scale_mode: ScaleMode | None = None,
192223
) -> list[AnalogWaveform[_ScalarType]]:
193224
"""Construct a list of analog waveforms from a two-dimensional array or nested sequence.
194225
@@ -199,9 +230,16 @@ def from_array_2d(
199230
copy: Specifies whether to copy the array or save a reference to it.
200231
start_index: The sample index at which the analog waveform data begins.
201232
sample_count: The number of samples in the analog waveform.
233+
extended_properties: The extended properties of the analog waveform.
234+
timing: The timing information of the analog waveform.
235+
scale_mode: The scale mode of the analog waveform.
202236
203237
Returns:
204238
A list containing an analog waveform for each row of the specified data.
239+
240+
When constructing multiple analog waveforms, the same extended properties, timing
241+
information, and scale mode are applied to all analog waveforms. Consider assigning
242+
these properties after construction.
205243
"""
206244
if isinstance(array, np.ndarray):
207245
if array.ndim != 2:
@@ -218,9 +256,12 @@ def from_array_2d(
218256

219257
return [
220258
AnalogWaveform(
221-
_data=np.asarray(array[i], dtype, copy=copy),
259+
raw_data=np.asarray(array[i], dtype, copy=copy),
222260
start_index=start_index,
223261
sample_count=sample_count,
262+
extended_properties=extended_properties,
263+
timing=timing,
264+
scale_mode=scale_mode,
224265
)
225266
for i in range(len(array))
226267
]
@@ -251,9 +292,12 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
251292
sample_count: SupportsIndex | None = ...,
252293
dtype: None = ...,
253294
*,
295+
raw_data: None = ...,
254296
start_index: SupportsIndex | None = ...,
255297
capacity: SupportsIndex | None = ...,
256-
_data: None = ...,
298+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
299+
timing: Timing | PrecisionTiming | None = ...,
300+
scale_mode: ScaleMode | None = ...,
257301
) -> None: ...
258302

259303
@overload
@@ -262,9 +306,12 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
262306
sample_count: SupportsIndex | None = ...,
263307
dtype: type[_ScalarType_co] | np.dtype[_ScalarType_co] = ...,
264308
*,
309+
raw_data: None = ...,
265310
start_index: SupportsIndex | None = ...,
266311
capacity: SupportsIndex | None = ...,
267-
_data: None = ...,
312+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
313+
timing: Timing | PrecisionTiming | None = ...,
314+
scale_mode: ScaleMode | None = ...,
268315
) -> None: ...
269316

270317
@overload
@@ -273,9 +320,12 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
273320
sample_count: SupportsIndex | None = ...,
274321
dtype: None = ...,
275322
*,
323+
raw_data: npt.NDArray[_ScalarType_co] | None = ...,
276324
start_index: SupportsIndex | None = ...,
277325
capacity: SupportsIndex | None = ...,
278-
_data: npt.NDArray[_ScalarType_co] | None = ...,
326+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
327+
timing: Timing | PrecisionTiming | None = ...,
328+
scale_mode: ScaleMode | None = ...,
279329
) -> None: ...
280330

281331
@overload
@@ -284,45 +334,71 @@ def __init__( # noqa: D107 - Missing docstring in __init__ (auto-generated noqa
284334
sample_count: SupportsIndex | None = ...,
285335
dtype: npt.DTypeLike = ...,
286336
*,
337+
raw_data: npt.NDArray[Any] | None = ...,
287338
start_index: SupportsIndex | None = ...,
288339
capacity: SupportsIndex | None = ...,
289-
_data: npt.NDArray[Any] | None = ...,
340+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = ...,
341+
timing: Timing | PrecisionTiming | None = ...,
342+
scale_mode: ScaleMode | None = ...,
290343
) -> None: ...
291344

292345
def __init__(
293346
self,
294347
sample_count: SupportsIndex | None = None,
295348
dtype: npt.DTypeLike = None,
296349
*,
350+
raw_data: npt.NDArray[_ScalarType_co] | None = None,
297351
start_index: SupportsIndex | None = None,
298352
capacity: SupportsIndex | None = None,
299-
_data: npt.NDArray[_ScalarType_co] | None = None,
353+
extended_properties: Mapping[str, ExtendedPropertyValue] | None = None,
354+
timing: Timing | PrecisionTiming | None = None,
355+
scale_mode: ScaleMode | None = None,
300356
) -> None:
301357
"""Construct an analog waveform.
302358
303359
Args:
304360
sample_count: The number of samples in the analog waveform.
305361
dtype: The NumPy data type for the analog waveform data. If not specified, the data
306362
type defaults to np.float64.
363+
raw_data: A NumPy ndarray to use for sample storage. The analog waveform takes ownership
364+
of this array. If not specified, an ndarray is created based on the specified dtype,
365+
start index, sample count, and capacity.
307366
start_index: The sample index at which the analog waveform data begins.
308367
sample_count: The number of samples in the analog waveform.
309368
capacity: The number of samples to allocate. Pre-allocating a larger buffer optimizes
310369
appending samples to the waveform.
370+
extended_properties: The extended properties of the analog waveform.
371+
timing: The timing information of the analog waveform.
372+
scale_mode: The scale mode of the analog waveform.
311373
312374
Returns:
313375
An analog waveform.
314-
315-
Arguments that are prefixed with an underscore are internal implementation details and are
316-
subject to change.
317376
"""
318-
if _data is None:
377+
if raw_data is None:
319378
self._init_with_new_array(
320379
sample_count, dtype, start_index=start_index, capacity=capacity
321380
)
322-
else:
381+
elif isinstance(raw_data, np.ndarray):
323382
self._init_with_provided_array(
324-
_data, dtype, start_index=start_index, sample_count=sample_count, capacity=capacity
383+
raw_data,
384+
dtype,
385+
start_index=start_index,
386+
sample_count=sample_count,
387+
capacity=capacity,
325388
)
389+
else:
390+
raise invalid_arg_type("raw data", "NumPy ndarray", raw_data)
391+
392+
self._extended_properties = ExtendedPropertyDictionary(extended_properties)
393+
394+
if timing is None:
395+
timing = Timing.empty
396+
self._timing = timing
397+
self._converted_timing_cache = {}
398+
399+
if scale_mode is None:
400+
scale_mode = NO_SCALING
401+
self._scale_mode = scale_mode
326402

327403
def _init_with_new_array(
328404
self,
@@ -357,10 +433,6 @@ def _init_with_new_array(
357433
self._data = np.zeros(capacity, dtype)
358434
self._start_index = start_index
359435
self._sample_count = sample_count
360-
self._extended_properties = ExtendedPropertyDictionary()
361-
self._timing = Timing.empty
362-
self._converted_timing_cache = {}
363-
self._scale_mode = NO_SCALING
364436

365437
def _init_with_provided_array(
366438
self,
@@ -414,10 +486,6 @@ def _init_with_provided_array(
414486
self._data = data
415487
self._start_index = start_index
416488
self._sample_count = sample_count
417-
self._extended_properties = ExtendedPropertyDictionary()
418-
self._timing = Timing.empty
419-
self._converted_timing_cache = {}
420-
self._scale_mode = NO_SCALING
421489

422490
@property
423491
def raw_data(self) -> npt.NDArray[_ScalarType_co]:
@@ -784,3 +852,32 @@ def _increase_capacity(self, amount: int) -> None:
784852
new_capacity = self._start_index + self._sample_count + amount
785853
if new_capacity > self.capacity:
786854
self.capacity = new_capacity
855+
856+
def __eq__(self, value: object, /) -> bool:
857+
"""Return self==value."""
858+
if not isinstance(value, self.__class__):
859+
return NotImplemented
860+
return (
861+
self.dtype == value.dtype
862+
and np.array_equal(self.raw_data, value.raw_data)
863+
and self._extended_properties == value._extended_properties
864+
and self._timing == value._timing
865+
and self._scale_mode == value._scale_mode
866+
)
867+
868+
def __repr__(self) -> str:
869+
"""Return repr(self)."""
870+
args = [f"{self._sample_count}"]
871+
if self.dtype != np.float64:
872+
args.append(f"{self.dtype.name}")
873+
# start_index and capacity are not shown because they are allocation details. raw_data hides
874+
# the unused data before start_index and after start_index+sample_count.
875+
if self._sample_count > 0:
876+
args.append(f"raw_data={self.raw_data!r}")
877+
if self._extended_properties:
878+
args.append(f"extended_properties={self._extended_properties._properties!r}")
879+
if self._timing is not Timing.empty and self._timing is not PrecisionTiming.empty:
880+
args.append(f"timing={self._timing!r}")
881+
if self._scale_mode is not NO_SCALING:
882+
args.append(f"scale_mode={self._scale_mode}")
883+
return f"{self.__class__.__module__}.{self.__class__.__name__}({', '.join(args)})"
Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import operator
4+
from collections.abc import Mapping
45
from typing import Iterator, MutableMapping, Union
56

67
from nitypes._typing import TypeAlias
@@ -18,40 +19,40 @@
1819
class ExtendedPropertyDictionary(MutableMapping[str, ExtendedPropertyValue]):
1920
"""A dictionary of extended properties."""
2021

21-
def __init__(self) -> None:
22+
def __init__(self, properties: Mapping[str, ExtendedPropertyValue] | None = None, /) -> None:
2223
"""Construct an ExtendedPropertyDictionary."""
2324
self._properties: dict[str, ExtendedPropertyValue] = {}
25+
if properties is not None:
26+
self._properties.update(properties)
2427

25-
def __len__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
26-
self,
27-
) -> int:
28+
def __len__(self) -> int:
29+
"""Return len(self)."""
2830
return len(self._properties)
2931

30-
def __iter__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
31-
self,
32-
) -> Iterator[str]:
32+
def __iter__(self) -> Iterator[str]:
33+
"""Implement iter(self)."""
3334
return iter(self._properties)
3435

35-
def __contains__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
36-
self, x: object, /
37-
) -> bool:
38-
return operator.contains(self._properties, x)
36+
def __contains__(self, value: object, /) -> bool:
37+
"""Implement value in self."""
38+
return operator.contains(self._properties, value)
3939

40-
def __getitem__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
41-
self, key: str, /
42-
) -> ExtendedPropertyValue:
40+
def __getitem__(self, key: str, /) -> ExtendedPropertyValue:
41+
"""Get self[key]."""
4342
return operator.getitem(self._properties, key)
4443

45-
def __setitem__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
46-
self, key: str, value: ExtendedPropertyValue, /
47-
) -> None:
44+
def __setitem__(self, key: str, value: ExtendedPropertyValue, /) -> None:
45+
"""Set self[key] to value."""
4846
operator.setitem(self._properties, key, value)
4947

50-
def __delitem__( # noqa: D105 - Missing docstring in magic method (auto-generated noqa)
51-
self, key: str, /
52-
) -> None:
48+
def __delitem__(self, key: str, /) -> None:
49+
"""Delete self[key]."""
5350
operator.delitem(self._properties, key)
5451

5552
def _merge(self, other: ExtendedPropertyDictionary) -> None:
5653
for key, value in other.items():
5754
self._properties.setdefault(key, value)
55+
56+
def __repr__(self) -> str:
57+
"""Return repr(self)."""
58+
return f"{self.__class__.__module__}.{self.__class__.__name__}({self._properties!r})"

0 commit comments

Comments
 (0)