Skip to content

Commit f44628b

Browse files
authored
Implement XYData publish/read (#43)
* Implement XYData publish/read Signed-off-by: Michael Johansen <michael.johansen@emerson.com> * Re-lock after merge from main Signed-off-by: Michael Johansen <michael.johansen@emerson.com> --------- Signed-off-by: Michael Johansen <michael.johansen@emerson.com>
1 parent f2589ac commit f44628b

File tree

6 files changed

+121
-16
lines changed

6 files changed

+121
-16
lines changed

poetry.lock

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protobuf = {version=">=4.21"}
4040
ni-datamonikers-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
4141
ni-measurements-data-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
4242
ni-measurements-metadata-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
43-
ni-protobuf-types = { version = ">=0.1.0.dev3", allow-prereleases = true }
43+
ni-protobuf-types = { version = ">=1.0.1.dev0", allow-prereleases = true }
4444
hightime = { version = ">=0.3.0.dev0", allow-prereleases = true }
4545

4646
[tool.poetry.group.dev.dependencies]

src/ni/datastore/data/_grpc_conversion.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@
4444
I16AnalogWaveform,
4545
I16ComplexWaveform,
4646
)
47+
from ni.protobuf.types.xydata_conversion import (
48+
float64_xydata_from_protobuf,
49+
float64_xydata_to_protobuf,
50+
)
4751
from ni.protobuf.types.xydata_pb2 import DoubleXYData
4852
from nitypes.complex import ComplexInt32DType
4953
from nitypes.scalar import Scalar
5054
from nitypes.vector import Vector
5155
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
56+
from nitypes.xy_data import XYData
5257

5358
_logger = logging.getLogger(__name__)
5459

@@ -137,6 +142,11 @@ def populate_publish_measurement_request_value(
137142
raise TypeError(f"Unsupported Spectrum dtype: {value.dtype}")
138143
elif isinstance(value, DigitalWaveform):
139144
publish_request.digital_waveform.CopyFrom(digital_waveform_to_protobuf(value))
145+
elif isinstance(value, XYData):
146+
if value.dtype == np.float64:
147+
publish_request.x_y_data.CopyFrom(float64_xydata_to_protobuf(value))
148+
else:
149+
raise TypeError(f"Unsupported XYData dtype: {value.dtype}")
140150
elif isinstance(value, Iterable):
141151
if not value:
142152
raise ValueError("Cannot publish an empty Iterable.")
@@ -152,7 +162,6 @@ def populate_publish_measurement_request_value(
152162
raise TypeError(
153163
f"Unsupported measurement value type: {type(value)}. Please consult the documentation."
154164
)
155-
# TODO: Implement conversion from proper XYData type
156165

157166

158167
def populate_publish_measurement_batch_request_values(
@@ -208,10 +217,7 @@ def unpack_and_convert_from_protobuf_any(read_value: Any) -> object:
208217
elif value_type == DoubleXYData.DESCRIPTOR.full_name:
209218
xydata = DoubleXYData()
210219
read_value.Unpack(xydata)
211-
_logger.warning(
212-
"DoubleXYData conversion is not yet implemented. Returning the raw protobuf object."
213-
)
214-
return xydata
220+
return float64_xydata_from_protobuf(xydata)
215221
elif value_type == VectorProto.DESCRIPTOR.full_name:
216222
vector = VectorProto()
217223
read_value.Unpack(vector)

tests/unit/data/test_grpc_conversion.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
scalar_pb2,
2222
vector_pb2,
2323
waveform_pb2,
24+
xydata_pb2,
2425
)
2526
from nitypes.complex import ComplexInt32DType
2627
from nitypes.scalar import Scalar
2728
from nitypes.vector import Vector
2829
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
30+
from nitypes.xy_data import XYData
2931

3032

3133
# ========================================================
@@ -193,6 +195,21 @@ def test___python_float64_spectrum___populate_measurement___measurement_updated_
193195
assert request.double_spectrum.frequency_increment == 10.0
194196

195197

198+
def test___python_float64_xydata___populate_measurement___measurement_updated_correctly() -> None:
199+
xydata = XYData.from_arrays_1d(
200+
[1.0, 2.0], [3.0, 4.0], np.float64, x_units="Volts", y_units="Seconds"
201+
)
202+
203+
request = PublishMeasurementRequest()
204+
populate_publish_measurement_request_value(request, xydata)
205+
206+
assert isinstance(request.x_y_data, xydata_pb2.DoubleXYData)
207+
assert list(request.x_y_data.x_data) == [1.0, 2.0]
208+
assert list(request.x_y_data.y_data) == [3.0, 4.0]
209+
assert request.x_y_data.attributes["NI_UnitDescription_X"].string_value == "Volts"
210+
assert request.x_y_data.attributes["NI_UnitDescription_Y"].string_value == "Seconds"
211+
212+
196213
# ========================================================
197214
# Populate Measurement Batch
198215
# ========================================================
@@ -322,6 +339,27 @@ def test___double_spectrum_proto___convert_from_protobuf___valid_python_spectrum
322339
assert result.frequency_increment == 10.0
323340

324341

342+
def test___xydata_proto___convert_from_protobuf___valid_python_xydata() -> None:
343+
attrs = {
344+
"NI_UnitDescription_X": attribute_value_pb2.AttributeValue(string_value="amps"),
345+
"NI_UnitDescription_Y": attribute_value_pb2.AttributeValue(string_value="seconds"),
346+
}
347+
pb_value = xydata_pb2.DoubleXYData(
348+
x_data=[1.0, 2.0],
349+
y_data=[3.0, 4.0],
350+
attributes=attrs,
351+
)
352+
packed_any = _pack_into_any(pb_value)
353+
354+
result = unpack_and_convert_from_protobuf_any(packed_any)
355+
356+
assert isinstance(result, XYData)
357+
assert list(result.x_data) == [1.0, 2.0]
358+
assert list(result.y_data) == [3.0, 4.0]
359+
assert result.x_units == "amps"
360+
assert result.y_units == "seconds"
361+
362+
325363
# ========================================================
326364
# Pack/Unpack Helpers
327365
# ========================================================

tests/unit/data/test_publish_measurement.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929
from ni.protobuf.types.vector_pb2 import Vector as VectorProto
3030
from ni.protobuf.types.waveform_conversion import float64_analog_waveform_to_protobuf
3131
from ni.protobuf.types.waveform_pb2 import DoubleAnalogWaveform
32+
from ni.protobuf.types.xydata_conversion import float64_xydata_to_protobuf
33+
from ni.protobuf.types.xydata_pb2 import DoubleXYData
3234
from nitypes.vector import Vector
3335
from nitypes.waveform import AnalogWaveform, Timing
36+
from nitypes.xy_data import XYData
3437

3538

3639
@pytest.mark.parametrize("value", [True, False])
@@ -118,6 +121,34 @@ def test___publish_analog_waveform_data___calls_data_store_service_client(
118121
assert request.test_adapter_ids == []
119122

120123

124+
def test___publish_float64_xydata___calls_data_store_service_client(
125+
data_store_client: DataStoreClient,
126+
mocked_data_store_service_client: NonCallableMock,
127+
) -> None:
128+
xydata = XYData.from_arrays_1d(
129+
x_array=[1.0, 2.0],
130+
y_array=[3.0, 4.0],
131+
dtype=np.float64,
132+
x_units="Volts",
133+
y_units="Seconds",
134+
)
135+
expected_protobuf_xydata = DoubleXYData()
136+
expected_protobuf_xydata.CopyFrom(float64_xydata_to_protobuf(xydata))
137+
published_measurement = PublishedMeasurement(published_measurement_id="response_id")
138+
expected_response = PublishMeasurementResponse(published_measurement=published_measurement)
139+
mocked_data_store_service_client.publish_measurement.return_value = expected_response
140+
141+
# Now, when client.publish_measurement calls foo.MyClass().publish(), it will use the mock
142+
result = data_store_client.publish_measurement("name", xydata, "step_id")
143+
144+
args, __ = mocked_data_store_service_client.publish_measurement.call_args
145+
request = cast(PublishMeasurementRequest, args[0]) # The PublishMeasurementRequest object
146+
assert result.published_measurement_id == "response_id"
147+
assert request.step_id == "step_id"
148+
assert request.measurement_name == "name"
149+
assert request.x_y_data == expected_protobuf_xydata
150+
151+
121152
@pytest.mark.parametrize(
122153
"value", [[1, 2, 3], [1.0, 2.0, 3.0], [True, False, True], ["one", "two", "three"]]
123154
)

tests/unit/data/test_read.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
from google.protobuf.any_pb2 import Any as gpAny
1010
from ni.datamonikers.v1.data_moniker_pb2 import Moniker, ReadFromMonikerResult
1111
from ni.datastore.data import DataStoreClient
12-
from ni.protobuf.types import array_pb2, attribute_value_pb2, vector_pb2, waveform_pb2
12+
from ni.protobuf.types import array_pb2, attribute_value_pb2, vector_pb2, waveform_pb2, xydata_pb2
1313
from nitypes.complex import ComplexInt32DType
1414
from nitypes.vector import Vector
1515
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
16+
from nitypes.xy_data import XYData
1617

1718

1819
def test___read_data___calls_moniker_client(
@@ -173,6 +174,34 @@ def test___read_vector___value_correct(
173174
assert actual_vector.units == "amps"
174175

175176

177+
def test___read_xydata___value_correct(
178+
data_store_client: DataStoreClient, mocked_moniker_client: NonCallableMock
179+
) -> None:
180+
moniker = _init_moniker()
181+
result = ReadFromMonikerResult()
182+
value_to_read = gpAny()
183+
attrs = {
184+
"NI_UnitDescription_X": attribute_value_pb2.AttributeValue(string_value="amps"),
185+
"NI_UnitDescription_Y": attribute_value_pb2.AttributeValue(string_value="seconds"),
186+
}
187+
expected_xydata = xydata_pb2.DoubleXYData(
188+
x_data=[1.0, 2.0],
189+
y_data=[3.0, 4.0],
190+
attributes=attrs,
191+
)
192+
value_to_read.Pack(expected_xydata)
193+
result.value.CopyFrom(value_to_read)
194+
mocked_moniker_client.read_from_moniker.return_value = result
195+
196+
actual_xydata = data_store_client.read_data(moniker, XYData)
197+
198+
assert isinstance(actual_xydata, XYData)
199+
assert list(actual_xydata.x_data) == [1.0, 2.0]
200+
assert list(actual_xydata.y_data) == [3.0, 4.0]
201+
assert actual_xydata.x_units == "amps"
202+
assert actual_xydata.y_units == "seconds"
203+
204+
176205
def _init_moniker() -> Moniker:
177206
moniker = Moniker()
178207
moniker.data_instance = 12

0 commit comments

Comments
 (0)