Skip to content

Commit dc70e25

Browse files
dixonjoelJoel Dixon
andauthored
Wrap additional gRPC types to remove them from our API (#65)
* Add ErrorInformation wrapper * Add the Moniker wrapper class * Add Outcome wrapper * Update test file style * Add AliasTargetType wrapper * Unit tests for wrapper classes * Fix ni-python-styleguide errors * Remove GrpcMoniker (was for backwards compatibility) * Fix mypy errors * Fix nps errors * PR feedback * Fix notebooks with respect to Outcome --------- Co-authored-by: Joel Dixon <joel.dixon@emerson.com>
1 parent b9f27f9 commit dc70e25

21 files changed

+1061
-96
lines changed

examples/notebooks/query/publish_sample_data.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,9 @@
286286
" # Publish measurement using Scalar values\n",
287287
" # Insert some random failures\n",
288288
" random_number = random.random()\n",
289-
" outcome = Outcome.OUTCOME_PASSED if random_number > 0.4 else Outcome.OUTCOME_FAILED\n",
289+
" outcome = Outcome.PASSED if random_number > 0.4 else Outcome.FAILED\n",
290290
" data_store_client.publish_measurement(measurement_name, Scalar(value, unit), step_id, outcome=outcome)\n",
291-
" print (f\" 🧪 Created measurement '{measurement_name}' - {'✅ Passed' if outcome == Outcome.OUTCOME_PASSED else '❌ Failed'}\")\n",
291+
" print (f\" 🧪 Created measurement '{measurement_name}' - {'✅ Passed' if outcome == Outcome.PASSED else '❌ Failed'}\")\n",
292292
" total_measurements += 1\n",
293293
" \n",
294294
" # Add test conditions for each power supply step\n",
@@ -392,7 +392,7 @@
392392
" \n",
393393
" # Publish measurement using Scalar values\n",
394394
" random_number = random.random()\n",
395-
" outcome = Outcome.OUTCOME_PASSED if random_number > 0.4 else Outcome.OUTCOME_FAILED\n",
395+
" outcome = Outcome.PASSED if random_number > 0.4 else Outcome.FAILED\n",
396396
" data_store_client.publish_measurement(measurement_name, Scalar(value, unit), step_id, outcome=outcome)\n",
397397
" amplifier_measurements += 1\n",
398398
"\n",
@@ -452,7 +452,7 @@
452452
" \n",
453453
" # Publish measurement using Scalar values\n",
454454
" random_number = random.random()\n",
455-
" outcome = Outcome.OUTCOME_PASSED if random_number > 0.4 else Outcome.OUTCOME_FAILED\n",
455+
" outcome = Outcome.PASSED if random_number > 0.4 else Outcome.FAILED\n",
456456
" data_store_client.publish_measurement(measurement_name, Scalar(value, unit), step_id, outcome=outcome)\n",
457457
" \n",
458458
" # Add test conditions for the quick verification\n",

examples/notebooks/query/query_measurements.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@
8686
"\n",
8787
"def print_measurement_with_outcome(measurement) -> str:\n",
8888
" name = measurement.name or \"Unnamed Measurement\"\n",
89-
" passed = measurement.outcome == Outcome.OUTCOME_PASSED\n",
90-
" failed = measurement.outcome == Outcome.OUTCOME_FAILED\n",
89+
" passed = measurement.outcome == Outcome.PASSED\n",
90+
" failed = measurement.outcome == Outcome.FAILED\n",
9191
" return f\"{name} - {'✅ (Passed)' if passed else '❌ (Failed)' if failed else '❓ (Unknown)'}\"\n",
9292
"\n",
9393
"print(\"\\n=== Voltage Measurements ===\")\n",

src/ni/datastore/data/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"""Public API for accessing the NI Data Store."""
22

3-
from ni.datamonikers.v1.data_moniker_pb2 import Moniker
43
from ni.datastore.data._data_store_client import DataStoreClient
4+
from ni.datastore.data._types._error_information import ErrorInformation
5+
from ni.datastore.data._types._moniker import Moniker
6+
from ni.datastore.data._types._outcome import Outcome
57
from ni.datastore.data._types._published_condition import PublishedCondition
68
from ni.datastore.data._types._published_measurement import PublishedMeasurement
79
from ni.datastore.data._types._step import Step
810
from ni.datastore.data._types._test_result import TestResult
9-
from ni.measurements.data.v1.data_store_pb2 import ErrorInformation, Outcome
1011

1112
__all__ = [
1213
"DataStoreClient",

src/ni/datastore/data/_data_store_client.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import hightime as ht
1414
from grpc import Channel
1515
from ni.datamonikers.v1.client import MonikerClient
16-
from ni.datamonikers.v1.data_moniker_pb2 import Moniker
1716
from ni.datastore.data._grpc_conversion import (
1817
get_publish_measurement_timestamp,
1918
populate_publish_condition_batch_request_values,
@@ -22,16 +21,15 @@
2221
populate_publish_measurement_request_value,
2322
unpack_and_convert_from_protobuf_any,
2423
)
24+
from ni.datastore.data._types._error_information import ErrorInformation
25+
from ni.datastore.data._types._moniker import Moniker
26+
from ni.datastore.data._types._outcome import Outcome
2527
from ni.datastore.data._types._published_condition import PublishedCondition
2628
from ni.datastore.data._types._published_measurement import PublishedMeasurement
2729
from ni.datastore.data._types._step import Step
2830
from ni.datastore.data._types._test_result import TestResult
2931
from ni.measurementlink.discovery.v1.client import DiscoveryClient
3032
from ni.measurements.data.v1.client import DataStoreClient as DataStoreServiceClient
31-
from ni.measurements.data.v1.data_store_pb2 import (
32-
ErrorInformation,
33-
Outcome,
34-
)
3533
from ni.measurements.data.v1.data_store_service_pb2 import (
3634
CreateStepRequest,
3735
CreateTestResultRequest,
@@ -227,7 +225,7 @@ def publish_measurement(
227225
value: object, # More strongly typed Union[bool, AnalogWaveform] can be used if needed
228226
step_id: str,
229227
timestamp: ht.datetime | None = None,
230-
outcome: Outcome.ValueType = Outcome.OUTCOME_UNSPECIFIED,
228+
outcome: Outcome = Outcome.UNSPECIFIED,
231229
error_information: ErrorInformation | None = None,
232230
hardware_item_ids: Iterable[str] = tuple(),
233231
test_adapter_ids: Iterable[str] = tuple(),
@@ -291,8 +289,10 @@ def publish_measurement(
291289
publish_request = PublishMeasurementRequest(
292290
measurement_name=measurement_name,
293291
step_id=step_id,
294-
outcome=outcome,
295-
error_information=error_information,
292+
outcome=outcome.to_protobuf(),
293+
error_information=(
294+
error_information.to_protobuf() if error_information is not None else None
295+
),
296296
hardware_item_ids=hardware_item_ids,
297297
test_adapter_ids=test_adapter_ids,
298298
software_item_ids=software_item_ids,
@@ -311,7 +311,7 @@ def publish_measurement_batch(
311311
values: object,
312312
step_id: str,
313313
timestamps: Iterable[ht.datetime] = tuple(),
314-
outcomes: Iterable[Outcome.ValueType] = tuple(),
314+
outcomes: Iterable[Outcome] = tuple(),
315315
error_information: Iterable[ErrorInformation] = tuple(),
316316
hardware_item_ids: Iterable[str] = tuple(),
317317
test_adapter_ids: Iterable[str] = tuple(),
@@ -372,8 +372,10 @@ def publish_measurement_batch(
372372
measurement_name=measurement_name,
373373
step_id=step_id,
374374
timestamps=[hightime_datetime_to_protobuf(ts) for ts in timestamps],
375-
outcomes=outcomes,
376-
error_information=error_information,
375+
outcomes=[outcome.to_protobuf() for outcome in outcomes],
376+
error_information=(
377+
[ei.to_protobuf() for ei in (error_information or [])] if error_information else []
378+
),
377379
hardware_item_ids=hardware_item_ids,
378380
test_adapter_ids=test_adapter_ids,
379381
software_item_ids=software_item_ids,
@@ -407,7 +409,7 @@ def read_data(
407409
408410
Args:
409411
moniker_source: The source from which to read data. Can be:
410-
- A Moniker directly
412+
- A Moniker (wrapper type) directly
411413
- A PublishedMeasurement (uses its moniker)
412414
- A PublishedCondition (uses its moniker)
413415
@@ -428,19 +430,25 @@ def read_data(
428430
TypeError: If expected_type is provided and the actual data type
429431
doesn't match.
430432
"""
433+
from ni.datamonikers.v1.data_moniker_pb2 import Moniker as MonikerProto
434+
435+
moniker_proto: MonikerProto
436+
431437
if isinstance(moniker_source, Moniker):
432-
moniker = moniker_source
438+
moniker_proto = moniker_source.to_protobuf()
433439
elif isinstance(moniker_source, PublishedMeasurement):
434440
if moniker_source.moniker is None:
435441
raise ValueError("PublishedMeasurement must have a Moniker to read data")
436-
moniker = moniker_source.moniker
442+
moniker_proto = moniker_source.moniker.to_protobuf()
437443
elif isinstance(moniker_source, PublishedCondition):
438444
if moniker_source.moniker is None:
439445
raise ValueError("PublishedCondition must have a Moniker to read data")
440-
moniker = moniker_source.moniker
446+
moniker_proto = moniker_source.moniker.to_protobuf()
447+
else:
448+
raise TypeError(f"Unsupported moniker_source type: {type(moniker_source)}")
441449

442-
moniker_client = self._get_moniker_client(moniker.service_location)
443-
read_result = moniker_client.read_from_moniker(moniker)
450+
moniker_client = self._get_moniker_client(moniker_proto.service_location)
451+
read_result = moniker_client.read_from_moniker(moniker_proto)
444452
converted_data = unpack_and_convert_from_protobuf_any(read_result.value)
445453
if expected_type is not None and not isinstance(converted_data, expected_type):
446454
raise TypeError(f"Expected type {expected_type}, got {type(converted_data)}")
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Error Information data type for the Data Store Client."""
2+
3+
from __future__ import annotations
4+
5+
from ni.measurements.data.v1.data_store_pb2 import (
6+
ErrorInformation as ErrorInformationProto,
7+
)
8+
9+
10+
class ErrorInformation:
11+
"""Represents error or exception information in case of measurement failure.
12+
13+
An ErrorInformation contains an error code, descriptive message, and
14+
source information to help identify and diagnose issues during measurement
15+
execution.
16+
"""
17+
18+
__slots__ = (
19+
"error_code",
20+
"message",
21+
"source",
22+
)
23+
24+
def __init__(
25+
self,
26+
*,
27+
error_code: int = 0,
28+
message: str = "",
29+
source: str = "",
30+
) -> None:
31+
"""Initialize an ErrorInformation instance.
32+
33+
Args:
34+
error_code: The numeric error code associated with the error.
35+
message: A descriptive message explaining the error.
36+
source: The source or location where the error occurred.
37+
"""
38+
self.error_code = error_code
39+
self.message = message
40+
self.source = source
41+
42+
@staticmethod
43+
def from_protobuf(
44+
error_information_proto: ErrorInformationProto,
45+
) -> "ErrorInformation":
46+
"""Create an ErrorInformation instance from a protobuf ErrorInformation message."""
47+
return ErrorInformation(
48+
error_code=error_information_proto.error_code,
49+
message=error_information_proto.message,
50+
source=error_information_proto.source,
51+
)
52+
53+
def to_protobuf(self) -> ErrorInformationProto:
54+
"""Convert this ErrorInformation instance to a protobuf ErrorInformation message."""
55+
return ErrorInformationProto(
56+
error_code=self.error_code,
57+
message=self.message,
58+
source=self.source,
59+
)
60+
61+
def __eq__(self, other: object) -> bool:
62+
"""Determine equality."""
63+
if not isinstance(other, ErrorInformation):
64+
return NotImplemented
65+
return (
66+
self.error_code == other.error_code
67+
and self.message == other.message
68+
and self.source == other.source
69+
)
70+
71+
def __str__(self) -> str:
72+
"""Return a string representation of the ErrorInformation."""
73+
return str(self.to_protobuf())
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Moniker data type for the Data Store Client."""
2+
3+
from __future__ import annotations
4+
5+
from ni.datamonikers.v1.data_moniker_pb2 import Moniker as MonikerProto
6+
7+
8+
class Moniker:
9+
"""Represents a data moniker for retrieving published data.
10+
11+
A moniker provides the necessary information to locate and retrieve data
12+
from the data store, including the service location, data source, and
13+
data instance identifiers.
14+
"""
15+
16+
__slots__ = (
17+
"service_location",
18+
"data_source",
19+
"data_instance",
20+
)
21+
22+
def __init__(
23+
self,
24+
*,
25+
service_location: str = "",
26+
data_source: str = "",
27+
data_instance: int = 0,
28+
) -> None:
29+
"""Initialize a Moniker instance.
30+
31+
Args:
32+
service_location: The location of the service that stores the data.
33+
data_source: The identifier for the data source.
34+
data_instance: The instance number of the data.
35+
"""
36+
self.service_location = service_location
37+
self.data_source = data_source
38+
self.data_instance = data_instance
39+
40+
@staticmethod
41+
def from_protobuf(moniker_proto: MonikerProto) -> "Moniker":
42+
"""Create a Moniker instance from a protobuf Moniker message."""
43+
return Moniker(
44+
service_location=moniker_proto.service_location,
45+
data_source=moniker_proto.data_source,
46+
data_instance=moniker_proto.data_instance,
47+
)
48+
49+
def to_protobuf(self) -> MonikerProto:
50+
"""Convert this Moniker instance to a protobuf Moniker message."""
51+
return MonikerProto(
52+
service_location=self.service_location,
53+
data_source=self.data_source,
54+
data_instance=self.data_instance,
55+
)
56+
57+
def __eq__(self, other: object) -> bool:
58+
"""Determine equality."""
59+
if not isinstance(other, Moniker):
60+
return NotImplemented
61+
return (
62+
self.service_location == other.service_location
63+
and self.data_source == other.data_source
64+
and self.data_instance == other.data_instance
65+
)
66+
67+
def __str__(self) -> str:
68+
"""Return a string representation of the Moniker."""
69+
return str(self.to_protobuf())
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Outcome data type for the Data Store Client."""
2+
3+
from __future__ import annotations
4+
5+
from enum import IntEnum
6+
7+
from ni.measurements.data.v1.data_store_pb2 import Outcome as OutcomeProto
8+
9+
10+
class Outcome(IntEnum):
11+
"""Represents the outcome of a measurement or test operation.
12+
13+
The Outcome enum indicates whether a measurement or test passed, failed,
14+
or had an indeterminate result.
15+
"""
16+
17+
UNSPECIFIED = OutcomeProto.OUTCOME_UNSPECIFIED
18+
"""The outcome is not specified or unknown."""
19+
20+
PASSED = OutcomeProto.OUTCOME_PASSED
21+
"""The measurement or test passed successfully."""
22+
23+
FAILED = OutcomeProto.OUTCOME_FAILED
24+
"""The measurement or test failed."""
25+
26+
INDETERMINATE = OutcomeProto.OUTCOME_INDETERMINATE
27+
"""The measurement or test result is indeterminate or inconclusive."""
28+
29+
@classmethod
30+
def from_protobuf(cls, outcome_proto: OutcomeProto.ValueType) -> "Outcome":
31+
"""Create an Outcome instance from a protobuf Outcome value.
32+
33+
Args:
34+
outcome_proto: The protobuf Outcome value.
35+
36+
Returns:
37+
The corresponding Outcome enum value.
38+
39+
Raises:
40+
ValueError: If the protobuf value doesn't correspond to a known Outcome.
41+
"""
42+
try:
43+
return cls(outcome_proto)
44+
except ValueError as e:
45+
raise ValueError(f"Unknown outcome value: {outcome_proto}") from e
46+
47+
def to_protobuf(self) -> OutcomeProto.ValueType:
48+
"""Convert this Outcome instance to a protobuf Outcome value.
49+
50+
Returns:
51+
The corresponding protobuf Outcome value.
52+
"""
53+
return OutcomeProto.ValueType(self.value)

src/ni/datastore/data/_types/_published_condition.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from ni.datamonikers.v1.data_moniker_pb2 import Moniker
5+
from ni.datastore.data._types._moniker import Moniker
66
from ni.measurements.data.v1.data_store_pb2 import (
77
PublishedCondition as PublishedConditionProto,
88
)
@@ -61,7 +61,7 @@ def from_protobuf(published_condition_proto: PublishedConditionProto) -> "Publis
6161
"""Create a PublishedCondition instance from a protobuf PublishedCondition message."""
6262
return PublishedCondition(
6363
moniker=(
64-
published_condition_proto.moniker
64+
Moniker.from_protobuf(published_condition_proto.moniker)
6565
if published_condition_proto.HasField("moniker")
6666
else None
6767
),
@@ -75,7 +75,7 @@ def from_protobuf(published_condition_proto: PublishedConditionProto) -> "Publis
7575
def to_protobuf(self) -> PublishedConditionProto:
7676
"""Convert this PublishedCondition instance to a protobuf PublishedCondition message."""
7777
return PublishedConditionProto(
78-
moniker=self.moniker,
78+
moniker=self.moniker.to_protobuf() if self.moniker is not None else None,
7979
id=self.id,
8080
name=self.name,
8181
type=self.type,

0 commit comments

Comments
 (0)