ni.datastore 0.1.0.dev2__tar.gz → 0.1.0.dev4__tar.gz

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.
Files changed (33) hide show
  1. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/PKG-INFO +5 -5
  2. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/pyproject.toml +5 -5
  3. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_data_store_client.py +77 -15
  4. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_grpc_conversion.py +11 -5
  5. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/_published_condition.py +6 -6
  6. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/_published_measurement.py +43 -23
  7. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/_step.py +17 -10
  8. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/_test_result.py +44 -22
  9. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_metadata_store_client.py +63 -18
  10. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_hardware_item.py +22 -5
  11. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_operator.py +22 -5
  12. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_software_item.py +22 -5
  13. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_test.py +22 -5
  14. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_test_adapter.py +27 -10
  15. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_test_description.py +22 -5
  16. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_test_station.py +22 -5
  17. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_uut.py +32 -8
  18. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_uut_instance.py +22 -5
  19. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/LICENSE +0 -0
  20. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/README.md +0 -0
  21. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/__init__.py +0 -0
  22. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/__init__.py +0 -0
  23. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/__init__.py +0 -0
  24. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/_types/py.typed +0 -0
  25. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/data/py.typed +0 -0
  26. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/__init__.py +0 -0
  27. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_grpc_conversion.py +0 -0
  28. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/__init__.py +0 -0
  29. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_alias.py +0 -0
  30. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/_extension_schema.py +0 -0
  31. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/_types/py.typed +0 -0
  32. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/metadata/py.typed +0 -0
  33. {ni_datastore-0.1.0.dev2 → ni_datastore-0.1.0.dev4}/src/ni/datastore/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ni.datastore
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev4
4
4
  Summary: APIs for publishing and retrieving data from the NI Measurement Data Store
5
5
  License: MIT
6
6
  Keywords: datastore
@@ -24,10 +24,10 @@ Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
26
  Requires-Dist: hightime (>=0.3.0.dev0)
27
- Requires-Dist: ni-datamonikers-v1-client (>=0.1.0.dev0)
28
- Requires-Dist: ni-measurements-data-v1-client (>=0.1.0.dev0)
29
- Requires-Dist: ni-measurements-metadata-v1-client (>=0.1.0.dev0)
30
- Requires-Dist: ni-protobuf-types (>=0.1.0.dev3)
27
+ Requires-Dist: ni-datamonikers-v1-client (>=0.1.0.dev1)
28
+ Requires-Dist: ni-measurements-data-v1-client (>=0.1.0.dev2)
29
+ Requires-Dist: ni-measurements-metadata-v1-client (>=0.1.0.dev2)
30
+ Requires-Dist: ni-protobuf-types (>=1.0.1.dev0)
31
31
  Requires-Dist: protobuf (>=4.21)
32
32
  Description-Content-Type: text/markdown
33
33
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ni.datastore"
3
- version = "0.1.0-dev2"
3
+ version = "0.1.0.dev4"
4
4
  license = "MIT"
5
5
  description = "APIs for publishing and retrieving data from the NI Measurement Data Store"
6
6
  authors = [{name = "NI", email = "opensource@ni.com"}]
@@ -37,10 +37,10 @@ requires-poetry = '>=2.1,<3.0'
37
37
  [tool.poetry.dependencies]
38
38
  python = "^3.9"
39
39
  protobuf = {version=">=4.21"}
40
- ni-datamonikers-v1-client = { version = ">=0.1.0.dev0", allow-prereleases = true }
41
- ni-measurements-data-v1-client = { version = ">=0.1.0.dev0", allow-prereleases = true }
42
- ni-measurements-metadata-v1-client = { version = ">=0.1.0.dev0", allow-prereleases = true }
43
- ni-protobuf-types = { version = ">=0.1.0.dev3", allow-prereleases = true }
40
+ ni-datamonikers-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
41
+ ni-measurements-data-v1-client = { version = ">=0.1.0.dev2", allow-prereleases = true }
42
+ ni-measurements-metadata-v1-client = { version = ">=0.1.0.dev2", allow-prereleases = true }
43
+ ni-protobuf-types = { version = ">=1.0.1.dev0", allow-prereleases = true }
44
44
  hightime = { version = ">=0.3.0.dev0", allow-prereleases = true }
45
45
 
46
46
  [tool.poetry.group.dev.dependencies]
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- from collections.abc import Iterable
6
+ import sys
7
+ from collections.abc import Iterable, Sequence
7
8
  from threading import Lock
8
- from typing import Type, TypeVar, overload
9
+ from types import TracebackType
10
+ from typing import TYPE_CHECKING, Type, TypeVar, overload
9
11
  from urllib.parse import urlparse
10
12
 
11
13
  import hightime as ht
@@ -42,12 +44,19 @@ from ni.measurements.data.v1.data_store_service_pb2 import (
42
44
  QueryConditionsRequest,
43
45
  QueryMeasurementsRequest,
44
46
  QueryStepsRequest,
47
+ QueryTestResultsRequest,
45
48
  )
46
49
  from ni.protobuf.types.precision_timestamp_conversion import (
47
50
  hightime_datetime_to_protobuf,
48
51
  )
49
52
  from ni_grpc_extensions.channelpool import GrpcChannelPool
50
53
 
54
+ if TYPE_CHECKING:
55
+ if sys.version_info >= (3, 11):
56
+ from typing import Self
57
+ else:
58
+ from typing_extensions import Self
59
+
51
60
  TRead = TypeVar("TRead")
52
61
 
53
62
  _logger = logging.getLogger(__name__)
@@ -57,6 +66,7 @@ class DataStoreClient:
57
66
  """Data store client for publishing and reading data."""
58
67
 
59
68
  __slots__ = (
69
+ "_closed",
60
70
  "_discovery_client",
61
71
  "_grpc_channel",
62
72
  "_grpc_channel_pool",
@@ -66,6 +76,9 @@ class DataStoreClient:
66
76
  "_moniker_clients_lock",
67
77
  )
68
78
 
79
+ _DATA_STORE_CLIENT_CLOSED_ERROR = "This DataStoreClient has been closed. Create a new DataStoreClient for further interaction with the data store."
80
+
81
+ _closed: bool
69
82
  _discovery_client: DiscoveryClient | None
70
83
  _grpc_channel: Channel | None
71
84
  _grpc_channel_pool: GrpcChannelPool | None
@@ -102,6 +115,35 @@ class DataStoreClient:
102
115
  self._data_store_client_lock = Lock()
103
116
  self._moniker_clients_lock = Lock()
104
117
 
118
+ self._closed = False
119
+
120
+ def __enter__(self) -> Self:
121
+ """Enter the runtime context of the data store client."""
122
+ return self
123
+
124
+ def __exit__(
125
+ self,
126
+ exc_type: type[BaseException] | None,
127
+ exc_val: BaseException | None,
128
+ traceback: TracebackType | None,
129
+ ) -> None:
130
+ """Exit the runtime context of the data store client."""
131
+ self.close()
132
+
133
+ def close(self) -> None:
134
+ """Close the data store client and clean up resources that it owns."""
135
+ self._closed = True
136
+
137
+ with self._data_store_client_lock:
138
+ if self._data_store_client is not None:
139
+ self._data_store_client.close()
140
+ self._data_store_client = None
141
+
142
+ with self._moniker_clients_lock:
143
+ for _, moniker_client in self._moniker_clients_by_service_location.items():
144
+ moniker_client.close()
145
+ self._moniker_clients_by_service_location.clear()
146
+
105
147
  def publish_condition(
106
148
  self,
107
149
  condition_name: str,
@@ -174,7 +216,7 @@ class DataStoreClient:
174
216
  hardware_item_ids: Iterable[str] = tuple(),
175
217
  test_adapter_ids: Iterable[str] = tuple(),
176
218
  software_item_ids: Iterable[str] = tuple(),
177
- ) -> Iterable[PublishedMeasurement]:
219
+ ) -> Sequence[PublishedMeasurement]:
178
220
  """Publish a batch of N values of a measurement to the data store."""
179
221
  publish_request = PublishMeasurementBatchRequest(
180
222
  measurement_name=measurement_name,
@@ -253,7 +295,7 @@ class DataStoreClient:
253
295
  get_response = self._get_data_store_client().get_test_result(get_request)
254
296
  return TestResult.from_protobuf(get_response.test_result)
255
297
 
256
- def query_conditions(self, odata_query: str) -> Iterable[PublishedCondition]:
298
+ def query_conditions(self, odata_query: str = "") -> Sequence[PublishedCondition]:
257
299
  """Query conditions from the data store."""
258
300
  query_request = QueryConditionsRequest(odata_query=odata_query)
259
301
  query_response = self._get_data_store_client().query_conditions(query_request)
@@ -262,7 +304,7 @@ class DataStoreClient:
262
304
  for published_condition in query_response.published_conditions
263
305
  ]
264
306
 
265
- def query_measurements(self, odata_query: str) -> Iterable[PublishedMeasurement]:
307
+ def query_measurements(self, odata_query: str = "") -> Sequence[PublishedMeasurement]:
266
308
  """Query measurements from the data store."""
267
309
  query_request = QueryMeasurementsRequest(odata_query=odata_query)
268
310
  query_response = self._get_data_store_client().query_measurements(query_request)
@@ -271,32 +313,52 @@ class DataStoreClient:
271
313
  for published_measurement in query_response.published_measurements
272
314
  ]
273
315
 
274
- def query_steps(self, odata_query: str) -> Iterable[Step]:
316
+ def query_test_results(self, odata_query: str = "") -> Sequence[TestResult]:
317
+ """Query test results from the data store."""
318
+ query_request = QueryTestResultsRequest(odata_query=odata_query)
319
+ query_response = self._get_data_store_client().query_test_results(query_request)
320
+ return [
321
+ TestResult.from_protobuf(test_result) for test_result in query_response.test_results
322
+ ]
323
+
324
+ def query_steps(self, odata_query: str = "") -> Sequence[Step]:
275
325
  """Query steps from the data store."""
276
326
  query_request = QueryStepsRequest(odata_query=odata_query)
277
327
  query_response = self._get_data_store_client().query_steps(query_request)
278
328
  return [Step.from_protobuf(step) for step in query_response.steps]
279
329
 
280
330
  def _get_data_store_client(self) -> DataStoreServiceClient:
331
+ if self._closed:
332
+ raise RuntimeError(self._DATA_STORE_CLIENT_CLOSED_ERROR)
333
+
281
334
  if self._data_store_client is None:
282
335
  with self._data_store_client_lock:
283
336
  if self._data_store_client is None:
284
- self._data_store_client = DataStoreServiceClient(
285
- discovery_client=self._discovery_client,
286
- grpc_channel=self._grpc_channel,
287
- grpc_channel_pool=self._grpc_channel_pool,
288
- )
337
+ self._data_store_client = self._instantiate_data_store_client()
289
338
  return self._data_store_client
290
339
 
340
+ def _instantiate_data_store_client(self) -> DataStoreServiceClient:
341
+ return DataStoreServiceClient(
342
+ discovery_client=self._discovery_client,
343
+ grpc_channel=self._grpc_channel,
344
+ grpc_channel_pool=self._grpc_channel_pool,
345
+ )
346
+
291
347
  def _get_moniker_client(self, service_location: str) -> MonikerClient:
348
+ if self._closed:
349
+ raise RuntimeError(self._DATA_STORE_CLIENT_CLOSED_ERROR)
350
+
292
351
  parsed_service_location = urlparse(service_location).netloc
293
352
  if parsed_service_location not in self._moniker_clients_by_service_location:
294
353
  with self._moniker_clients_lock:
295
354
  if parsed_service_location not in self._moniker_clients_by_service_location:
296
355
  self._moniker_clients_by_service_location[parsed_service_location] = (
297
- MonikerClient(
298
- service_location=parsed_service_location,
299
- grpc_channel_pool=self._grpc_channel_pool,
300
- )
356
+ self._instantiate_moniker_client(parsed_service_location)
301
357
  )
302
358
  return self._moniker_clients_by_service_location[parsed_service_location]
359
+
360
+ def _instantiate_moniker_client(self, parsed_service_location: str) -> MonikerClient:
361
+ return MonikerClient(
362
+ service_location=parsed_service_location,
363
+ grpc_channel_pool=self._grpc_channel_pool,
364
+ )
@@ -44,11 +44,16 @@ from ni.protobuf.types.waveform_pb2 import (
44
44
  I16AnalogWaveform,
45
45
  I16ComplexWaveform,
46
46
  )
47
+ from ni.protobuf.types.xydata_conversion import (
48
+ float64_xydata_from_protobuf,
49
+ float64_xydata_to_protobuf,
50
+ )
47
51
  from ni.protobuf.types.xydata_pb2 import DoubleXYData
48
52
  from nitypes.complex import ComplexInt32DType
49
53
  from nitypes.scalar import Scalar
50
54
  from nitypes.vector import Vector
51
55
  from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
56
+ from nitypes.xy_data import XYData
52
57
 
53
58
  _logger = logging.getLogger(__name__)
54
59
 
@@ -137,6 +142,11 @@ def populate_publish_measurement_request_value(
137
142
  raise TypeError(f"Unsupported Spectrum dtype: {value.dtype}")
138
143
  elif isinstance(value, DigitalWaveform):
139
144
  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}")
140
150
  elif isinstance(value, Iterable):
141
151
  if not value:
142
152
  raise ValueError("Cannot publish an empty Iterable.")
@@ -152,7 +162,6 @@ def populate_publish_measurement_request_value(
152
162
  raise TypeError(
153
163
  f"Unsupported measurement value type: {type(value)}. Please consult the documentation."
154
164
  )
155
- # TODO: Implement conversion from proper XYData type
156
165
 
157
166
 
158
167
  def populate_publish_measurement_batch_request_values(
@@ -208,10 +217,7 @@ def unpack_and_convert_from_protobuf_any(read_value: Any) -> object:
208
217
  elif value_type == DoubleXYData.DESCRIPTOR.full_name:
209
218
  xydata = DoubleXYData()
210
219
  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)
215
221
  elif value_type == VectorProto.DESCRIPTOR.full_name:
216
222
  vector = VectorProto()
217
223
  read_value.Unpack(vector)
@@ -13,7 +13,7 @@ class PublishedCondition:
13
13
 
14
14
  __slots__ = (
15
15
  "moniker",
16
- "published_condition_id",
16
+ "id",
17
17
  "condition_name",
18
18
  "condition_type",
19
19
  "step_id",
@@ -24,7 +24,7 @@ class PublishedCondition:
24
24
  self,
25
25
  *,
26
26
  moniker: Moniker | None = None,
27
- published_condition_id: str = "",
27
+ id: str = "",
28
28
  condition_name: str = "",
29
29
  condition_type: str = "",
30
30
  step_id: str = "",
@@ -32,7 +32,7 @@ class PublishedCondition:
32
32
  ) -> None:
33
33
  """Initialize a PublishedCondition instance."""
34
34
  self.moniker = moniker
35
- self.published_condition_id = published_condition_id
35
+ self.id = id
36
36
  self.condition_name = condition_name
37
37
  self.condition_type = condition_type
38
38
  self.step_id = step_id
@@ -47,7 +47,7 @@ class PublishedCondition:
47
47
  if published_condition_proto.HasField("moniker")
48
48
  else None
49
49
  ),
50
- published_condition_id=published_condition_proto.published_condition_id,
50
+ id=published_condition_proto.id,
51
51
  condition_name=published_condition_proto.condition_name,
52
52
  condition_type=published_condition_proto.condition_type,
53
53
  step_id=published_condition_proto.step_id,
@@ -58,7 +58,7 @@ class PublishedCondition:
58
58
  """Convert this PublishedCondition instance to a protobuf PublishedCondition message."""
59
59
  return PublishedConditionProto(
60
60
  moniker=self.moniker,
61
- published_condition_id=self.published_condition_id,
61
+ id=self.id,
62
62
  condition_name=self.condition_name,
63
63
  condition_type=self.condition_type,
64
64
  step_id=self.step_id,
@@ -71,7 +71,7 @@ class PublishedCondition:
71
71
  return NotImplemented
72
72
  return (
73
73
  self.moniker == other.moniker
74
- and self.published_condition_id == other.published_condition_id
74
+ and self.id == other.id
75
75
  and self.condition_name == other.condition_name
76
76
  and self.condition_type == other.condition_type
77
77
  and self.step_id == other.step_id
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Iterable
5
+ from typing import Iterable, MutableSequence
6
6
 
7
7
  import hightime as ht
8
8
  from ni.datamonikers.v1.data_moniker_pb2 import Moniker
@@ -23,13 +23,13 @@ class PublishedMeasurement:
23
23
 
24
24
  __slots__ = (
25
25
  "moniker",
26
- "published_conditions",
27
- "published_measurement_id",
26
+ "_published_conditions",
27
+ "id",
28
28
  "test_result_id",
29
29
  "step_id",
30
- "software_item_ids",
31
- "hardware_item_ids",
32
- "test_adapter_ids",
30
+ "_software_item_ids",
31
+ "_hardware_item_ids",
32
+ "_test_adapter_ids",
33
33
  "measurement_name",
34
34
  "data_type",
35
35
  "measurement_notes",
@@ -40,12 +40,32 @@ class PublishedMeasurement:
40
40
  "error_information",
41
41
  )
42
42
 
43
+ @property
44
+ def published_conditions(self) -> MutableSequence[PublishedCondition]:
45
+ """The published conditions associated with the published measurement."""
46
+ return self._published_conditions
47
+
48
+ @property
49
+ def software_item_ids(self) -> MutableSequence[str]:
50
+ """The software item IDs associated with the published measurement."""
51
+ return self._software_item_ids
52
+
53
+ @property
54
+ def hardware_item_ids(self) -> MutableSequence[str]:
55
+ """The hardware item IDs associated with the published measurement."""
56
+ return self._hardware_item_ids
57
+
58
+ @property
59
+ def test_adapter_ids(self) -> MutableSequence[str]:
60
+ """The test adapter IDs associated with the published measurement."""
61
+ return self._test_adapter_ids
62
+
43
63
  def __init__(
44
64
  self,
45
65
  *,
46
66
  moniker: Moniker | None = None,
47
67
  published_conditions: Iterable[PublishedCondition] | None = None,
48
- published_measurement_id: str = "",
68
+ id: str = "",
49
69
  test_result_id: str = "",
50
70
  step_id: str = "",
51
71
  software_item_ids: Iterable[str] | None = None,
@@ -62,20 +82,20 @@ class PublishedMeasurement:
62
82
  ) -> None:
63
83
  """Initialize a PublishedMeasurement instance."""
64
84
  self.moniker = moniker
65
- self.published_conditions: Iterable[PublishedCondition] = (
66
- published_conditions if published_conditions is not None else []
85
+ self._published_conditions: MutableSequence[PublishedCondition] = (
86
+ list(published_conditions) if published_conditions is not None else []
67
87
  )
68
- self.published_measurement_id = published_measurement_id
88
+ self.id = id
69
89
  self.test_result_id = test_result_id
70
90
  self.step_id = step_id
71
- self.software_item_ids: Iterable[str] = (
72
- software_item_ids if software_item_ids is not None else []
91
+ self._software_item_ids: MutableSequence[str] = (
92
+ list(software_item_ids) if software_item_ids is not None else []
73
93
  )
74
- self.hardware_item_ids: Iterable[str] = (
75
- hardware_item_ids if hardware_item_ids is not None else []
94
+ self._hardware_item_ids: MutableSequence[str] = (
95
+ list(hardware_item_ids) if hardware_item_ids is not None else []
76
96
  )
77
- self.test_adapter_ids: Iterable[str] = (
78
- test_adapter_ids if test_adapter_ids is not None else []
97
+ self._test_adapter_ids: MutableSequence[str] = (
98
+ list(test_adapter_ids) if test_adapter_ids is not None else []
79
99
  )
80
100
  self.measurement_name = measurement_name
81
101
  self.data_type = data_type
@@ -101,7 +121,7 @@ class PublishedMeasurement:
101
121
  PublishedCondition.from_protobuf(cond)
102
122
  for cond in published_measurement_proto.published_conditions
103
123
  ],
104
- published_measurement_id=published_measurement_proto.published_measurement_id,
124
+ id=published_measurement_proto.id,
105
125
  test_result_id=published_measurement_proto.test_result_id,
106
126
  step_id=published_measurement_proto.step_id,
107
127
  software_item_ids=published_measurement_proto.software_item_ids,
@@ -136,7 +156,7 @@ class PublishedMeasurement:
136
156
  published_conditions=[
137
157
  condition.to_protobuf() for condition in self.published_conditions
138
158
  ],
139
- published_measurement_id=self.published_measurement_id,
159
+ id=self.id,
140
160
  test_result_id=self.test_result_id,
141
161
  step_id=self.step_id,
142
162
  software_item_ids=self.software_item_ids,
@@ -166,13 +186,13 @@ class PublishedMeasurement:
166
186
  return NotImplemented
167
187
  return (
168
188
  self.moniker == other.moniker
169
- and list(self.published_conditions) == list(other.published_conditions)
170
- and self.published_measurement_id == other.published_measurement_id
189
+ and self.published_conditions == other.published_conditions
190
+ and self.id == other.id
171
191
  and self.test_result_id == other.test_result_id
172
192
  and self.step_id == other.step_id
173
- and list(self.software_item_ids) == list(other.software_item_ids)
174
- and list(self.hardware_item_ids) == list(other.hardware_item_ids)
175
- and list(self.test_adapter_ids) == list(other.test_adapter_ids)
193
+ and self.software_item_ids == other.software_item_ids
194
+ and self.hardware_item_ids == other.hardware_item_ids
195
+ and self.test_adapter_ids == other.test_adapter_ids
176
196
  and self.measurement_name == other.measurement_name
177
197
  and self.data_type == other.data_type
178
198
  and self.measurement_notes == other.measurement_notes
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import MutableMapping
5
+ from typing import Mapping, MutableMapping
6
6
 
7
7
  import hightime as ht
8
8
  from ni.datastore.metadata._grpc_conversion import (
@@ -22,7 +22,7 @@ class Step:
22
22
  """Information about a step into which measurements and conditions are published."""
23
23
 
24
24
  __slots__ = (
25
- "step_id",
25
+ "id",
26
26
  "parent_step_id",
27
27
  "test_result_id",
28
28
  "test_id",
@@ -32,7 +32,7 @@ class Step:
32
32
  "_start_date_time",
33
33
  "_end_date_time",
34
34
  "link",
35
- "extensions",
35
+ "_extensions",
36
36
  "schema_id",
37
37
  )
38
38
 
@@ -46,10 +46,15 @@ class Step:
46
46
  """Get the end date and time of the step execution."""
47
47
  return self._end_date_time
48
48
 
49
+ @property
50
+ def extensions(self) -> MutableMapping[str, str]:
51
+ """The extensions of the step."""
52
+ return self._extensions
53
+
49
54
  def __init__(
50
55
  self,
51
56
  *,
52
- step_id: str = "",
57
+ id: str = "",
53
58
  parent_step_id: str = "",
54
59
  test_result_id: str = "",
55
60
  test_id: str = "",
@@ -57,11 +62,11 @@ class Step:
57
62
  step_type: str = "",
58
63
  notes: str = "",
59
64
  link: str = "",
60
- extensions: MutableMapping[str, str] | None = None,
65
+ extensions: Mapping[str, str] | None = None,
61
66
  schema_id: str = "",
62
67
  ) -> None:
63
68
  """Initialize a Step instance."""
64
- self.step_id = step_id
69
+ self.id = id
65
70
  self.parent_step_id = parent_step_id
66
71
  self.test_result_id = test_result_id
67
72
  self.test_id = test_id
@@ -69,7 +74,9 @@ class Step:
69
74
  self.step_type = step_type
70
75
  self.notes = notes
71
76
  self.link = link
72
- self.extensions: MutableMapping[str, str] = extensions if extensions is not None else {}
77
+ self._extensions: MutableMapping[str, str] = (
78
+ dict(extensions) if extensions is not None else {}
79
+ )
73
80
  self.schema_id = schema_id
74
81
 
75
82
  self._start_date_time: ht.datetime | None = None
@@ -79,7 +86,7 @@ class Step:
79
86
  def from_protobuf(step_proto: StepProto) -> "Step":
80
87
  """Create a Step instance from a protobuf Step message."""
81
88
  step = Step(
82
- step_id=step_proto.step_id,
89
+ id=step_proto.id,
83
90
  parent_step_id=step_proto.parent_step_id,
84
91
  test_result_id=step_proto.test_result_id,
85
92
  test_id=step_proto.test_id,
@@ -105,7 +112,7 @@ class Step:
105
112
  def to_protobuf(self) -> StepProto:
106
113
  """Convert this Step to a protobuf Step message."""
107
114
  step_proto = StepProto(
108
- step_id=self.step_id,
115
+ id=self.id,
109
116
  parent_step_id=self.parent_step_id,
110
117
  test_result_id=self.test_result_id,
111
118
  test_id=self.test_id,
@@ -131,7 +138,7 @@ class Step:
131
138
  if not isinstance(other, Step):
132
139
  return NotImplemented
133
140
  return (
134
- self.step_id == other.step_id
141
+ self.id == other.id
135
142
  and self.parent_step_id == other.parent_step_id
136
143
  and self.test_result_id == other.test_result_id
137
144
  and self.test_id == other.test_id