ni.datastore 0.1.0.dev1__tar.gz → 0.1.0.dev2__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 (35) hide show
  1. {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/PKG-INFO +1 -2
  2. {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/README.md +0 -1
  3. {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/pyproject.toml +1 -1
  4. ni_datastore-0.1.0.dev2/src/ni/datastore/__init__.py +1 -0
  5. ni_datastore-0.1.0.dev2/src/ni/datastore/data/__init__.py +30 -0
  6. ni_datastore-0.1.0.dev2/src/ni/datastore/data/_data_store_client.py +302 -0
  7. ni_datastore-0.1.0.dev1/src/ni/datastore/grpc_conversion.py → ni_datastore-0.1.0.dev2/src/ni/datastore/data/_grpc_conversion.py +72 -29
  8. ni_datastore-0.1.0.dev2/src/ni/datastore/data/_types/__init__.py +1 -0
  9. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/_published_measurement.py +1 -1
  10. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/_step.py +1 -1
  11. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/_test_result.py +1 -1
  12. ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/__init__.py +46 -0
  13. ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_grpc_conversion.py +39 -0
  14. ni_datastore-0.1.0.dev1/src/ni/datastore/_client.py → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_metadata_store_client.py +47 -317
  15. ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_types/__init__.py +1 -0
  16. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_hardware_item.py +1 -1
  17. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_operator.py +1 -1
  18. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_software_item.py +1 -1
  19. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_test.py +1 -1
  20. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_test_adapter.py +1 -1
  21. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_test_description.py +1 -1
  22. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_test_station.py +1 -1
  23. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_uut.py +1 -1
  24. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_uut_instance.py +1 -1
  25. ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_types/py.typed +0 -0
  26. ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/py.typed +0 -0
  27. ni_datastore-0.1.0.dev2/src/ni/datastore/py.typed +0 -0
  28. ni_datastore-0.1.0.dev1/src/ni/datastore/__init__.py +0 -66
  29. ni_datastore-0.1.0.dev1/src/ni/datastore/_types/__init__.py +0 -1
  30. {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/LICENSE +0 -0
  31. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/_published_condition.py +0 -0
  32. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/py.typed +0 -0
  33. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/py.typed +0 -0
  34. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_alias.py +0 -0
  35. {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_extension_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ni.datastore
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
4
4
  Summary: APIs for publishing and retrieving data from the NI Measurement Data Store
5
5
  License: MIT
6
6
  Keywords: datastore
@@ -34,7 +34,6 @@ Description-Content-Type: text/markdown
34
34
  # Table of Contents
35
35
 
36
36
  - [Table of Contents](#table-of-contents)
37
- - [Measurement ]
38
37
  - [About](#about)
39
38
  - [Operating System Support](#operating-system-support)
40
39
  - [Python Version Support](#python-version-support)
@@ -1,7 +1,6 @@
1
1
  # Table of Contents
2
2
 
3
3
  - [Table of Contents](#table-of-contents)
4
- - [Measurement ]
5
4
  - [About](#about)
6
5
  - [Operating System Support](#operating-system-support)
7
6
  - [Python Version Support](#python-version-support)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ni.datastore"
3
- version = "0.1.0-dev1"
3
+ version = "0.1.0-dev2"
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"}]
@@ -0,0 +1 @@
1
+ """Public API for accessing the NI Data/Metadata Stores."""
@@ -0,0 +1,30 @@
1
+ """Public API for accessing the NI Data Store."""
2
+
3
+ from ni.datamonikers.v1.data_moniker_pb2 import Moniker
4
+ from ni.datastore.data._data_store_client import DataStoreClient
5
+ from ni.datastore.data._types._published_condition import PublishedCondition
6
+ from ni.datastore.data._types._published_measurement import PublishedMeasurement
7
+ from ni.datastore.data._types._step import Step
8
+ from ni.datastore.data._types._test_result import TestResult
9
+ from ni.measurements.data.v1.data_store_pb2 import ErrorInformation, Outcome
10
+
11
+ __all__ = [
12
+ "DataStoreClient",
13
+ "ErrorInformation",
14
+ "Moniker",
15
+ "Outcome",
16
+ "PublishedCondition",
17
+ "PublishedMeasurement",
18
+ "Step",
19
+ "TestResult",
20
+ ]
21
+
22
+ # Hide that it was not defined in this top-level package
23
+ DataStoreClient.__module__ = __name__
24
+ ErrorInformation.__module__ = __name__
25
+ Moniker.__module__ = __name__
26
+ Outcome.__module__ = __name__
27
+ PublishedCondition.__module__ = __name__
28
+ PublishedMeasurement.__module__ = __name__
29
+ Step.__module__ = __name__
30
+ TestResult.__module__ = __name__
@@ -0,0 +1,302 @@
1
+ """Data store client for publishing and reading data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Iterable
7
+ from threading import Lock
8
+ from typing import Type, TypeVar, overload
9
+ from urllib.parse import urlparse
10
+
11
+ import hightime as ht
12
+ from grpc import Channel
13
+ from ni.datamonikers.v1.client import MonikerClient
14
+ from ni.datamonikers.v1.data_moniker_pb2 import Moniker
15
+ from ni.datastore.data._grpc_conversion import (
16
+ get_publish_measurement_timestamp,
17
+ populate_publish_condition_batch_request_values,
18
+ populate_publish_condition_request_value,
19
+ populate_publish_measurement_batch_request_values,
20
+ populate_publish_measurement_request_value,
21
+ unpack_and_convert_from_protobuf_any,
22
+ )
23
+ from ni.datastore.data._types._published_condition import PublishedCondition
24
+ from ni.datastore.data._types._published_measurement import PublishedMeasurement
25
+ from ni.datastore.data._types._step import Step
26
+ from ni.datastore.data._types._test_result import TestResult
27
+ from ni.measurementlink.discovery.v1.client import DiscoveryClient
28
+ from ni.measurements.data.v1.client import DataStoreClient as DataStoreServiceClient
29
+ from ni.measurements.data.v1.data_store_pb2 import (
30
+ ErrorInformation,
31
+ Outcome,
32
+ )
33
+ from ni.measurements.data.v1.data_store_service_pb2 import (
34
+ CreateStepRequest,
35
+ CreateTestResultRequest,
36
+ GetStepRequest,
37
+ GetTestResultRequest,
38
+ PublishConditionBatchRequest,
39
+ PublishConditionRequest,
40
+ PublishMeasurementBatchRequest,
41
+ PublishMeasurementRequest,
42
+ QueryConditionsRequest,
43
+ QueryMeasurementsRequest,
44
+ QueryStepsRequest,
45
+ )
46
+ from ni.protobuf.types.precision_timestamp_conversion import (
47
+ hightime_datetime_to_protobuf,
48
+ )
49
+ from ni_grpc_extensions.channelpool import GrpcChannelPool
50
+
51
+ TRead = TypeVar("TRead")
52
+
53
+ _logger = logging.getLogger(__name__)
54
+
55
+
56
+ class DataStoreClient:
57
+ """Data store client for publishing and reading data."""
58
+
59
+ __slots__ = (
60
+ "_discovery_client",
61
+ "_grpc_channel",
62
+ "_grpc_channel_pool",
63
+ "_data_store_client",
64
+ "_data_store_client_lock",
65
+ "_moniker_clients_by_service_location",
66
+ "_moniker_clients_lock",
67
+ )
68
+
69
+ _discovery_client: DiscoveryClient | None
70
+ _grpc_channel: Channel | None
71
+ _grpc_channel_pool: GrpcChannelPool | None
72
+ _data_store_client: DataStoreServiceClient | None
73
+ _moniker_clients_by_service_location: dict[str, MonikerClient]
74
+ _data_store_client_lock: Lock
75
+ _moniker_clients_lock: Lock
76
+
77
+ def __init__(
78
+ self,
79
+ discovery_client: DiscoveryClient | None = None,
80
+ grpc_channel: Channel | None = None,
81
+ grpc_channel_pool: GrpcChannelPool | None = None,
82
+ ) -> None:
83
+ """Initialize the DataStoreClient.
84
+
85
+ Args:
86
+ discovery_client: An optional discovery client (recommended).
87
+
88
+ grpc_channel: An optional data store gRPC channel. Providing this channel will bypass
89
+ discovery service resolution of the data store. (Note: Reading data from a moniker
90
+ will still always use a channel corresponding to the service location specified by
91
+ that moniker.)
92
+
93
+ grpc_channel_pool: An optional gRPC channel pool (recommended).
94
+ """
95
+ self._discovery_client = discovery_client
96
+ self._grpc_channel = grpc_channel
97
+ self._grpc_channel_pool = grpc_channel_pool
98
+
99
+ self._data_store_client = None
100
+ self._moniker_clients_by_service_location = {}
101
+
102
+ self._data_store_client_lock = Lock()
103
+ self._moniker_clients_lock = Lock()
104
+
105
+ def publish_condition(
106
+ self,
107
+ condition_name: str,
108
+ type: str,
109
+ value: object,
110
+ step_id: str,
111
+ ) -> PublishedCondition:
112
+ """Publish a condition value to the data store."""
113
+ publish_request = PublishConditionRequest(
114
+ condition_name=condition_name,
115
+ type=type,
116
+ step_id=step_id,
117
+ )
118
+ populate_publish_condition_request_value(publish_request, value)
119
+ publish_response = self._get_data_store_client().publish_condition(publish_request)
120
+ return PublishedCondition.from_protobuf(publish_response.published_condition)
121
+
122
+ def publish_condition_batch(
123
+ self, condition_name: str, type: str, values: object, step_id: str
124
+ ) -> PublishedCondition:
125
+ """Publish a batch of N values for a condition to the data store."""
126
+ publish_request = PublishConditionBatchRequest(
127
+ condition_name=condition_name,
128
+ type=type,
129
+ step_id=step_id,
130
+ )
131
+ populate_publish_condition_batch_request_values(publish_request, values)
132
+ publish_response = self._get_data_store_client().publish_condition_batch(publish_request)
133
+ return PublishedCondition.from_protobuf(publish_response.published_condition)
134
+
135
+ def publish_measurement(
136
+ self,
137
+ measurement_name: str,
138
+ value: object, # More strongly typed Union[bool, AnalogWaveform] can be used if needed
139
+ step_id: str,
140
+ timestamp: ht.datetime | None = None,
141
+ outcome: Outcome.ValueType = Outcome.OUTCOME_UNSPECIFIED,
142
+ error_information: ErrorInformation | None = None,
143
+ hardware_item_ids: Iterable[str] = tuple(),
144
+ test_adapter_ids: Iterable[str] = tuple(),
145
+ software_item_ids: Iterable[str] = tuple(),
146
+ notes: str = "",
147
+ ) -> PublishedMeasurement:
148
+ """Publish a measurement value to the data store."""
149
+ publish_request = PublishMeasurementRequest(
150
+ measurement_name=measurement_name,
151
+ step_id=step_id,
152
+ outcome=outcome,
153
+ error_information=error_information,
154
+ hardware_item_ids=hardware_item_ids,
155
+ test_adapter_ids=test_adapter_ids,
156
+ software_item_ids=software_item_ids,
157
+ notes=notes,
158
+ )
159
+ populate_publish_measurement_request_value(publish_request, value)
160
+ publish_request.timestamp.CopyFrom(
161
+ get_publish_measurement_timestamp(publish_request, timestamp)
162
+ )
163
+ publish_response = self._get_data_store_client().publish_measurement(publish_request)
164
+ return PublishedMeasurement.from_protobuf(publish_response.published_measurement)
165
+
166
+ def publish_measurement_batch(
167
+ self,
168
+ measurement_name: str,
169
+ values: object,
170
+ step_id: str,
171
+ timestamps: Iterable[ht.datetime] = tuple(),
172
+ outcomes: Iterable[Outcome.ValueType] = tuple(),
173
+ error_information: Iterable[ErrorInformation] = tuple(),
174
+ hardware_item_ids: Iterable[str] = tuple(),
175
+ test_adapter_ids: Iterable[str] = tuple(),
176
+ software_item_ids: Iterable[str] = tuple(),
177
+ ) -> Iterable[PublishedMeasurement]:
178
+ """Publish a batch of N values of a measurement to the data store."""
179
+ publish_request = PublishMeasurementBatchRequest(
180
+ measurement_name=measurement_name,
181
+ step_id=step_id,
182
+ timestamp=[hightime_datetime_to_protobuf(ts) for ts in timestamps],
183
+ outcome=outcomes,
184
+ error_information=error_information,
185
+ hardware_item_ids=hardware_item_ids,
186
+ test_adapter_ids=test_adapter_ids,
187
+ software_item_ids=software_item_ids,
188
+ )
189
+ populate_publish_measurement_batch_request_values(publish_request, values)
190
+ publish_response = self._get_data_store_client().publish_measurement_batch(publish_request)
191
+ return [
192
+ PublishedMeasurement.from_protobuf(pm) for pm in publish_response.published_measurements
193
+ ]
194
+
195
+ @overload
196
+ def read_data(
197
+ self,
198
+ moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
199
+ expected_type: Type[TRead],
200
+ ) -> TRead: ...
201
+
202
+ @overload
203
+ def read_data(
204
+ self,
205
+ moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
206
+ ) -> object: ...
207
+
208
+ def read_data(
209
+ self,
210
+ moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
211
+ expected_type: Type[TRead] | None = None,
212
+ ) -> TRead | object:
213
+ """Read data published to the data store."""
214
+ if isinstance(moniker_source, Moniker):
215
+ moniker = moniker_source
216
+ elif isinstance(moniker_source, PublishedMeasurement):
217
+ if moniker_source.moniker is None:
218
+ raise ValueError("PublishedMeasurement must have a Moniker to read data")
219
+ moniker = moniker_source.moniker
220
+ elif isinstance(moniker_source, PublishedCondition):
221
+ if moniker_source.moniker is None:
222
+ raise ValueError("PublishedCondition must have a Moniker to read data")
223
+ moniker = moniker_source.moniker
224
+
225
+ moniker_client = self._get_moniker_client(moniker.service_location)
226
+ read_result = moniker_client.read_from_moniker(moniker)
227
+ converted_data = unpack_and_convert_from_protobuf_any(read_result.value)
228
+ if expected_type is not None and not isinstance(converted_data, expected_type):
229
+ raise TypeError(f"Expected type {expected_type}, got {type(converted_data)}")
230
+ return converted_data
231
+
232
+ def create_step(self, step: Step) -> str:
233
+ """Create a step in the data store."""
234
+ create_request = CreateStepRequest(step=step.to_protobuf())
235
+ create_response = self._get_data_store_client().create_step(create_request)
236
+ return create_response.step_id
237
+
238
+ def get_step(self, step_id: str) -> Step:
239
+ """Get a step from the data store."""
240
+ get_request = GetStepRequest(step_id=step_id)
241
+ get_response = self._get_data_store_client().get_step(get_request)
242
+ return Step.from_protobuf(get_response.step)
243
+
244
+ def create_test_result(self, test_result: TestResult) -> str:
245
+ """Create a test result in the data store."""
246
+ create_request = CreateTestResultRequest(test_result=test_result.to_protobuf())
247
+ create_response = self._get_data_store_client().create_test_result(create_request)
248
+ return create_response.test_result_id
249
+
250
+ def get_test_result(self, test_result_id: str) -> TestResult:
251
+ """Get a test result from the data store."""
252
+ get_request = GetTestResultRequest(test_result_id=test_result_id)
253
+ get_response = self._get_data_store_client().get_test_result(get_request)
254
+ return TestResult.from_protobuf(get_response.test_result)
255
+
256
+ def query_conditions(self, odata_query: str) -> Iterable[PublishedCondition]:
257
+ """Query conditions from the data store."""
258
+ query_request = QueryConditionsRequest(odata_query=odata_query)
259
+ query_response = self._get_data_store_client().query_conditions(query_request)
260
+ return [
261
+ PublishedCondition.from_protobuf(published_condition)
262
+ for published_condition in query_response.published_conditions
263
+ ]
264
+
265
+ def query_measurements(self, odata_query: str) -> Iterable[PublishedMeasurement]:
266
+ """Query measurements from the data store."""
267
+ query_request = QueryMeasurementsRequest(odata_query=odata_query)
268
+ query_response = self._get_data_store_client().query_measurements(query_request)
269
+ return [
270
+ PublishedMeasurement.from_protobuf(published_measurement)
271
+ for published_measurement in query_response.published_measurements
272
+ ]
273
+
274
+ def query_steps(self, odata_query: str) -> Iterable[Step]:
275
+ """Query steps from the data store."""
276
+ query_request = QueryStepsRequest(odata_query=odata_query)
277
+ query_response = self._get_data_store_client().query_steps(query_request)
278
+ return [Step.from_protobuf(step) for step in query_response.steps]
279
+
280
+ def _get_data_store_client(self) -> DataStoreServiceClient:
281
+ if self._data_store_client is None:
282
+ with self._data_store_client_lock:
283
+ 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
+ )
289
+ return self._data_store_client
290
+
291
+ def _get_moniker_client(self, service_location: str) -> MonikerClient:
292
+ parsed_service_location = urlparse(service_location).netloc
293
+ if parsed_service_location not in self._moniker_clients_by_service_location:
294
+ with self._moniker_clients_lock:
295
+ if parsed_service_location not in self._moniker_clients_by_service_location:
296
+ 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
+ )
301
+ )
302
+ return self._moniker_clients_by_service_location[parsed_service_location]
@@ -2,19 +2,23 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import datetime as std_datetime
5
6
  import logging
6
- from typing import MutableMapping
7
+ from typing import Iterable, cast
7
8
 
9
+ import hightime as ht
8
10
  import numpy as np
9
11
  from google.protobuf.any_pb2 import Any
10
- from google.protobuf.internal.containers import MessageMap
11
12
  from ni.measurements.data.v1.data_store_service_pb2 import (
12
13
  PublishConditionBatchRequest,
13
14
  PublishConditionRequest,
14
15
  PublishMeasurementBatchRequest,
15
16
  PublishMeasurementRequest,
16
17
  )
17
- from ni.measurements.metadata.v1.metadata_store_pb2 import ExtensionValue
18
+ from ni.protobuf.types.precision_timestamp_conversion import (
19
+ hightime_datetime_to_protobuf,
20
+ )
21
+ from ni.protobuf.types.precision_timestamp_pb2 import PrecisionTimestamp
18
22
  from ni.protobuf.types.scalar_conversion import scalar_to_protobuf
19
23
  from ni.protobuf.types.vector_conversion import vector_from_protobuf, vector_to_protobuf
20
24
  from ni.protobuf.types.vector_pb2 import Vector as VectorProto
@@ -73,9 +77,19 @@ def populate_publish_condition_batch_request_values(
73
77
  publish_request: PublishConditionBatchRequest, values: object
74
78
  ) -> None:
75
79
  """Assign a value to the scalar_values vector member of PublishConditionBatchRequest."""
76
- # TODO: Determine whether we wish to support primitive types such as a list of float
77
80
  if isinstance(values, Vector):
78
81
  publish_request.scalar_values.CopyFrom(vector_to_protobuf(values))
82
+ elif isinstance(values, Iterable):
83
+ if not values:
84
+ raise ValueError("Cannot publish an empty Iterable.")
85
+ try:
86
+ vector = Vector(values)
87
+ except (TypeError, ValueError):
88
+ raise TypeError(
89
+ f"Unsupported iterable: {values}. Subtype must be bool, float, int, or string."
90
+ )
91
+
92
+ publish_request.scalar_values.CopyFrom(vector_to_protobuf(vector))
79
93
  else:
80
94
  raise TypeError(
81
95
  f"Unsupported condition values type: {type(values)}. Please consult the documentation."
@@ -123,6 +137,17 @@ def populate_publish_measurement_request_value(
123
137
  raise TypeError(f"Unsupported Spectrum dtype: {value.dtype}")
124
138
  elif isinstance(value, DigitalWaveform):
125
139
  publish_request.digital_waveform.CopyFrom(digital_waveform_to_protobuf(value))
140
+ elif isinstance(value, Iterable):
141
+ if not value:
142
+ raise ValueError("Cannot publish an empty Iterable.")
143
+ try:
144
+ vector = Vector(value)
145
+ except (TypeError, ValueError):
146
+ raise TypeError(
147
+ f"Unsupported iterable: {value}. Subtype must be bool, float, int, or string."
148
+ )
149
+
150
+ publish_request.vector.CopyFrom(vector_to_protobuf(vector))
126
151
  else:
127
152
  raise TypeError(
128
153
  f"Unsupported measurement value type: {type(value)}. Please consult the documentation."
@@ -134,9 +159,19 @@ def populate_publish_measurement_batch_request_values(
134
159
  publish_request: PublishMeasurementBatchRequest, values: object
135
160
  ) -> None:
136
161
  """Assign a value to the appropriate field of the PublishMeasurementBatchRequest object."""
137
- # TODO: Determine whether we wish to support primitive types such as a list of float
138
162
  if isinstance(values, Vector):
139
163
  publish_request.scalar_values.CopyFrom(vector_to_protobuf(values))
164
+ elif isinstance(values, Iterable):
165
+ if not values:
166
+ raise ValueError("Cannot publish an empty Iterable.")
167
+ try:
168
+ vector = Vector(values)
169
+ except (TypeError, ValueError):
170
+ raise TypeError(
171
+ f"Unsupported iterable: {values}. Subtype must be bool, float, int, or string."
172
+ )
173
+
174
+ publish_request.scalar_values.CopyFrom(vector_to_protobuf(vector))
140
175
  else:
141
176
  raise TypeError(
142
177
  f"Unsupported measurement values type: {type(values)}. Please consult the documentation."
@@ -185,29 +220,37 @@ def unpack_and_convert_from_protobuf_any(read_value: Any) -> object:
185
220
  raise TypeError(f"Unsupported data type Name: {value_type}")
186
221
 
187
222
 
188
- def populate_extension_value_message_map(
189
- destination: MessageMap[str, ExtensionValue],
190
- source: MutableMapping[str, str],
191
- ) -> None:
192
- """Populate a gRPC message map of string keys to ExtensionValue.
193
-
194
- The input is a mapping of string keys to string values.
195
- """
196
- for key, value in source.items():
197
- destination[key].string_value = value
223
+ def get_publish_measurement_timestamp(
224
+ publish_request: PublishMeasurementRequest, client_provided_timestamp: ht.datetime | None
225
+ ) -> PrecisionTimestamp:
226
+ """Determine the correct timestamp to use for publishing a measurement."""
227
+ no_client_timestamp_provided = client_provided_timestamp is None
228
+ if no_client_timestamp_provided:
229
+ publish_time = hightime_datetime_to_protobuf(ht.datetime.now(std_datetime.timezone.utc))
230
+ else:
231
+ publish_time = hightime_datetime_to_protobuf(cast(ht.datetime, client_provided_timestamp))
198
232
 
233
+ waveform_t0: PrecisionTimestamp | None = None
234
+ value_case = publish_request.WhichOneof("value")
235
+ if value_case == "double_analog_waveform":
236
+ waveform_t0 = publish_request.double_analog_waveform.t0
237
+ elif value_case == "i16_analog_waveform":
238
+ waveform_t0 = publish_request.i16_analog_waveform.t0
239
+ elif value_case == "double_complex_waveform":
240
+ waveform_t0 = publish_request.double_complex_waveform.t0
241
+ elif value_case == "i16_complex_waveform":
242
+ waveform_t0 = publish_request.i16_complex_waveform.t0
243
+ elif value_case == "digital_waveform":
244
+ waveform_t0 = publish_request.digital_waveform.t0
199
245
 
200
- def populate_from_extension_value_message_map(
201
- destination: MutableMapping[str, str],
202
- source: MessageMap[str, ExtensionValue],
203
- ) -> None:
204
- """Populate a mapping of string keys to stringvalues.
205
-
206
- The input is a gRPC message map of string keys to ExtensionValue.
207
- """
208
- for key, extension_value in source.items():
209
- value_case = extension_value.WhichOneof("metadata")
210
- if value_case == "string_value":
211
- destination[key] = extension_value.string_value
212
- else:
213
- raise TypeError(f"Unsupported ExtensionValue type for key '{key}': {value_case}")
246
+ # If an initialized waveform t0 value is present
247
+ if waveform_t0 is not None and waveform_t0 != PrecisionTimestamp():
248
+ if no_client_timestamp_provided:
249
+ # If the client did not provide a timestamp, use the waveform t0 value
250
+ publish_time = waveform_t0
251
+ elif publish_time != waveform_t0:
252
+ raise ValueError(
253
+ "The provided timestamp does not match the waveform t0. Please provide a matching timestamp or "
254
+ "omit the timestamp to use the waveform t0."
255
+ )
256
+ return publish_time
@@ -0,0 +1 @@
1
+ """Types for use in accessing the NI Data Store."""
@@ -6,7 +6,7 @@ from typing import Iterable
6
6
 
7
7
  import hightime as ht
8
8
  from ni.datamonikers.v1.data_moniker_pb2 import Moniker
9
- from ni.datastore._types._published_condition import PublishedCondition
9
+ from ni.datastore.data._types._published_condition import PublishedCondition
10
10
  from ni.measurements.data.v1.data_store_pb2 import (
11
11
  ErrorInformation,
12
12
  Outcome,
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from typing import MutableMapping
6
6
 
7
7
  import hightime as ht
8
- from ni.datastore.grpc_conversion import (
8
+ from ni.datastore.metadata._grpc_conversion import (
9
9
  populate_extension_value_message_map,
10
10
  populate_from_extension_value_message_map,
11
11
  )
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from typing import Iterable, MutableMapping
6
6
 
7
7
  import hightime as ht
8
- from ni.datastore.grpc_conversion import (
8
+ from ni.datastore.metadata._grpc_conversion import (
9
9
  populate_extension_value_message_map,
10
10
  populate_from_extension_value_message_map,
11
11
  )
@@ -0,0 +1,46 @@
1
+ """Public API for accessing the NI Metadata Store."""
2
+
3
+ from ni.datastore.metadata._metadata_store_client import MetadataStoreClient
4
+ from ni.datastore.metadata._types._alias import Alias
5
+ from ni.datastore.metadata._types._extension_schema import ExtensionSchema
6
+ from ni.datastore.metadata._types._hardware_item import HardwareItem
7
+ from ni.datastore.metadata._types._operator import Operator
8
+ from ni.datastore.metadata._types._software_item import SoftwareItem
9
+ from ni.datastore.metadata._types._test import Test
10
+ from ni.datastore.metadata._types._test_adapter import TestAdapter
11
+ from ni.datastore.metadata._types._test_description import TestDescription
12
+ from ni.datastore.metadata._types._test_station import TestStation
13
+ from ni.datastore.metadata._types._uut import Uut
14
+ from ni.datastore.metadata._types._uut_instance import UutInstance
15
+ from ni.measurements.metadata.v1.metadata_store_pb2 import AliasTargetType
16
+
17
+ __all__ = [
18
+ "Alias",
19
+ "AliasTargetType",
20
+ "ExtensionSchema",
21
+ "HardwareItem",
22
+ "MetadataStoreClient",
23
+ "Operator",
24
+ "SoftwareItem",
25
+ "Test",
26
+ "TestAdapter",
27
+ "TestDescription",
28
+ "TestStation",
29
+ "Uut",
30
+ "UutInstance",
31
+ ]
32
+
33
+ # Hide that it was not defined in this top-level package
34
+ Alias.__module__ = __name__
35
+ AliasTargetType.__module__ = __name__
36
+ ExtensionSchema.__module__ = __name__
37
+ HardwareItem.__module__ = __name__
38
+ MetadataStoreClient.__module__ = __name__
39
+ Operator.__module__ = __name__
40
+ SoftwareItem.__module__ = __name__
41
+ Test.__module__ = __name__
42
+ TestAdapter.__module__ = __name__
43
+ TestDescription.__module__ = __name__
44
+ TestStation.__module__ = __name__
45
+ Uut.__module__ = __name__
46
+ UutInstance.__module__ = __name__
@@ -0,0 +1,39 @@
1
+ """Helper methods to convert between python and protobuf types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import MutableMapping
7
+
8
+ from google.protobuf.internal.containers import MessageMap
9
+ from ni.measurements.metadata.v1.metadata_store_pb2 import ExtensionValue
10
+
11
+ _logger = logging.getLogger(__name__)
12
+
13
+
14
+ def populate_extension_value_message_map(
15
+ destination: MessageMap[str, ExtensionValue],
16
+ source: MutableMapping[str, str],
17
+ ) -> None:
18
+ """Populate a gRPC message map of string keys to ExtensionValue.
19
+
20
+ The input is a mapping of string keys to string values.
21
+ """
22
+ for key, value in source.items():
23
+ destination[key].string_value = value
24
+
25
+
26
+ def populate_from_extension_value_message_map(
27
+ destination: MutableMapping[str, str],
28
+ source: MessageMap[str, ExtensionValue],
29
+ ) -> None:
30
+ """Populate a mapping of string keys to stringvalues.
31
+
32
+ The input is a gRPC message map of string keys to ExtensionValue.
33
+ """
34
+ for key, extension_value in source.items():
35
+ value_case = extension_value.WhichOneof("metadata")
36
+ if value_case == "string_value":
37
+ destination[key] = extension_value.string_value
38
+ else:
39
+ raise TypeError(f"Unsupported ExtensionValue type for key '{key}': {value_case}")
@@ -1,60 +1,26 @@
1
- """Datastore client for publishing and reading data."""
1
+ """Metadata store client for publishing and reading metadata."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import datetime as std_datetime
6
5
  import logging
7
6
  from collections.abc import Iterable
7
+ from pathlib import Path
8
8
  from threading import Lock
9
- from typing import Type, TypeVar, cast, overload
10
- from urllib.parse import urlparse
11
9
 
12
- import hightime as ht
13
10
  from grpc import Channel
14
- from ni.datamonikers.v1.client import MonikerClient
15
- from ni.datamonikers.v1.data_moniker_pb2 import Moniker
16
- from ni.datastore._types._alias import Alias
17
- from ni.datastore._types._extension_schema import ExtensionSchema
18
- from ni.datastore._types._hardware_item import HardwareItem
19
- from ni.datastore._types._operator import Operator
20
- from ni.datastore._types._published_condition import PublishedCondition
21
- from ni.datastore._types._published_measurement import PublishedMeasurement
22
- from ni.datastore._types._software_item import SoftwareItem
23
- from ni.datastore._types._step import Step
24
- from ni.datastore._types._test import Test
25
- from ni.datastore._types._test_adapter import TestAdapter
26
- from ni.datastore._types._test_description import TestDescription
27
- from ni.datastore._types._test_result import TestResult
28
- from ni.datastore._types._test_station import TestStation
29
- from ni.datastore._types._uut import Uut
30
- from ni.datastore._types._uut_instance import UutInstance
31
- from ni.datastore.grpc_conversion import (
32
- populate_publish_condition_batch_request_values,
33
- populate_publish_condition_request_value,
34
- populate_publish_measurement_batch_request_values,
35
- populate_publish_measurement_request_value,
36
- unpack_and_convert_from_protobuf_any,
37
- )
11
+ from ni.datastore.metadata._types._alias import Alias
12
+ from ni.datastore.metadata._types._extension_schema import ExtensionSchema
13
+ from ni.datastore.metadata._types._hardware_item import HardwareItem
14
+ from ni.datastore.metadata._types._operator import Operator
15
+ from ni.datastore.metadata._types._software_item import SoftwareItem
16
+ from ni.datastore.metadata._types._test import Test
17
+ from ni.datastore.metadata._types._test_adapter import TestAdapter
18
+ from ni.datastore.metadata._types._test_description import TestDescription
19
+ from ni.datastore.metadata._types._test_station import TestStation
20
+ from ni.datastore.metadata._types._uut import Uut
21
+ from ni.datastore.metadata._types._uut_instance import UutInstance
38
22
  from ni.measurementlink.discovery.v1.client import DiscoveryClient
39
- from ni.measurements.data.v1.client import DataStoreClient
40
- from ni.measurements.data.v1.data_store_pb2 import (
41
- ErrorInformation,
42
- Outcome,
43
- )
44
- from ni.measurements.data.v1.data_store_service_pb2 import (
45
- CreateStepRequest,
46
- CreateTestResultRequest,
47
- GetStepRequest,
48
- GetTestResultRequest,
49
- PublishConditionBatchRequest,
50
- PublishConditionRequest,
51
- PublishMeasurementBatchRequest,
52
- PublishMeasurementRequest,
53
- QueryConditionsRequest,
54
- QueryMeasurementsRequest,
55
- QueryStepsRequest,
56
- )
57
- from ni.measurements.metadata.v1.client import MetadataStoreClient
23
+ from ni.measurements.metadata.v1.client import MetadataStoreClient as MetadataStoreServiceClient
58
24
  from ni.measurements.metadata.v1.metadata_store_service_pb2 import (
59
25
  CreateAliasRequest,
60
26
  CreateHardwareItemRequest,
@@ -90,41 +56,27 @@ from ni.measurements.metadata.v1.metadata_store_service_pb2 import (
90
56
  QueryUutsRequest,
91
57
  RegisterSchemaRequest,
92
58
  )
93
- from ni.protobuf.types.precision_timestamp_conversion import (
94
- hightime_datetime_to_protobuf,
95
- )
96
- from ni.protobuf.types.precision_timestamp_pb2 import PrecisionTimestamp
97
59
  from ni_grpc_extensions.channelpool import GrpcChannelPool
98
60
 
99
- TRead = TypeVar("TRead")
100
-
101
61
  _logger = logging.getLogger(__name__)
102
62
 
103
63
 
104
- class Client:
105
- """Datastore client for publishing and reading data."""
64
+ class MetadataStoreClient:
65
+ """Metadata store client for publishing and reading metadata."""
106
66
 
107
67
  __slots__ = (
108
68
  "_discovery_client",
109
69
  "_grpc_channel",
110
70
  "_grpc_channel_pool",
111
- "_data_store_client",
112
- "_data_store_client_lock",
113
71
  "_metadata_store_client",
114
72
  "_metadata_store_client_lock",
115
- "_moniker_clients_by_service_location",
116
- "_moniker_clients_lock",
117
73
  )
118
74
 
119
75
  _discovery_client: DiscoveryClient | None
120
76
  _grpc_channel: Channel | None
121
77
  _grpc_channel_pool: GrpcChannelPool | None
122
- _data_store_client: DataStoreClient | None
123
- _metadata_store_client: MetadataStoreClient | None
124
- _moniker_clients_by_service_location: dict[str, MonikerClient]
125
- _data_store_client_lock: Lock
78
+ _metadata_store_client: MetadataStoreServiceClient | None
126
79
  _metadata_store_client_lock: Lock
127
- _moniker_clients_lock: Lock
128
80
 
129
81
  def __init__(
130
82
  self,
@@ -132,15 +84,13 @@ class Client:
132
84
  grpc_channel: Channel | None = None,
133
85
  grpc_channel_pool: GrpcChannelPool | None = None,
134
86
  ) -> None:
135
- """Initialize the Client.
87
+ """Initialize the MetadataStoreClient.
136
88
 
137
89
  Args:
138
90
  discovery_client: An optional discovery client (recommended).
139
91
 
140
- grpc_channel: An optional data store gRPC channel. Providing this channel will bypass
141
- discovery service resolution of the data store. (Note: Reading data from a moniker
142
- will still always use a channel corresponding to the service location specified by
143
- that moniker.)
92
+ grpc_channel: An optional metadata store gRPC channel. Providing this channel
93
+ will bypass discovery service resolution of the metadata store.
144
94
 
145
95
  grpc_channel_pool: An optional gRPC channel pool (recommended).
146
96
  """
@@ -148,188 +98,8 @@ class Client:
148
98
  self._grpc_channel = grpc_channel
149
99
  self._grpc_channel_pool = grpc_channel_pool
150
100
 
151
- self._data_store_client = None
152
101
  self._metadata_store_client = None
153
- self._moniker_clients_by_service_location = {}
154
-
155
- self._data_store_client_lock = Lock()
156
102
  self._metadata_store_client_lock = Lock()
157
- self._moniker_clients_lock = Lock()
158
-
159
- def publish_condition(
160
- self,
161
- condition_name: str,
162
- type: str,
163
- value: object,
164
- step_id: str,
165
- ) -> PublishedCondition:
166
- """Publish a condition value to the data store."""
167
- publish_request = PublishConditionRequest(
168
- condition_name=condition_name,
169
- type=type,
170
- step_id=step_id,
171
- )
172
- populate_publish_condition_request_value(publish_request, value)
173
- publish_response = self._get_data_store_client().publish_condition(publish_request)
174
- return PublishedCondition.from_protobuf(publish_response.published_condition)
175
-
176
- def publish_condition_batch(
177
- self, condition_name: str, type: str, values: object, step_id: str
178
- ) -> PublishedCondition:
179
- """Publish a batch of N values for a condition to the data store."""
180
- publish_request = PublishConditionBatchRequest(
181
- condition_name=condition_name,
182
- type=type,
183
- step_id=step_id,
184
- )
185
- populate_publish_condition_batch_request_values(publish_request, values)
186
- publish_response = self._get_data_store_client().publish_condition_batch(publish_request)
187
- return PublishedCondition.from_protobuf(publish_response.published_condition)
188
-
189
- def publish_measurement(
190
- self,
191
- measurement_name: str,
192
- value: object, # More strongly typed Union[bool, AnalogWaveform] can be used if needed
193
- step_id: str,
194
- timestamp: ht.datetime | None = None,
195
- outcome: Outcome.ValueType = Outcome.OUTCOME_UNSPECIFIED,
196
- error_information: ErrorInformation | None = None,
197
- hardware_item_ids: Iterable[str] = tuple(),
198
- test_adapter_ids: Iterable[str] = tuple(),
199
- software_item_ids: Iterable[str] = tuple(),
200
- notes: str = "",
201
- ) -> PublishedMeasurement:
202
- """Publish a measurement value to the data store."""
203
- publish_request = PublishMeasurementRequest(
204
- measurement_name=measurement_name,
205
- step_id=step_id,
206
- outcome=outcome,
207
- error_information=error_information,
208
- hardware_item_ids=hardware_item_ids,
209
- test_adapter_ids=test_adapter_ids,
210
- software_item_ids=software_item_ids,
211
- notes=notes,
212
- )
213
- populate_publish_measurement_request_value(publish_request, value)
214
- publish_request.timestamp.CopyFrom(
215
- self._get_publish_measurement_timestamp(publish_request, timestamp)
216
- )
217
- publish_response = self._get_data_store_client().publish_measurement(publish_request)
218
- return PublishedMeasurement.from_protobuf(publish_response.published_measurement)
219
-
220
- def publish_measurement_batch(
221
- self,
222
- measurement_name: str,
223
- values: object,
224
- step_id: str,
225
- timestamps: Iterable[ht.datetime] = tuple(),
226
- outcomes: Iterable[Outcome.ValueType] = tuple(),
227
- error_information: Iterable[ErrorInformation] = tuple(),
228
- hardware_item_ids: Iterable[str] = tuple(),
229
- test_adapter_ids: Iterable[str] = tuple(),
230
- software_item_ids: Iterable[str] = tuple(),
231
- ) -> Iterable[PublishedMeasurement]:
232
- """Publish a batch of N values of a measurement to the data store."""
233
- publish_request = PublishMeasurementBatchRequest(
234
- measurement_name=measurement_name,
235
- step_id=step_id,
236
- timestamp=[hightime_datetime_to_protobuf(ts) for ts in timestamps],
237
- outcome=outcomes,
238
- error_information=error_information,
239
- hardware_item_ids=hardware_item_ids,
240
- test_adapter_ids=test_adapter_ids,
241
- software_item_ids=software_item_ids,
242
- )
243
- populate_publish_measurement_batch_request_values(publish_request, values)
244
- publish_response = self._get_data_store_client().publish_measurement_batch(publish_request)
245
- return [
246
- PublishedMeasurement.from_protobuf(pm) for pm in publish_response.published_measurements
247
- ]
248
-
249
- @overload
250
- def read_data(
251
- self,
252
- moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
253
- expected_type: Type[TRead],
254
- ) -> TRead: ...
255
-
256
- @overload
257
- def read_data(
258
- self,
259
- moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
260
- ) -> object: ...
261
-
262
- def read_data(
263
- self,
264
- moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
265
- expected_type: Type[TRead] | None = None,
266
- ) -> TRead | object:
267
- """Read data published to the data store."""
268
- if isinstance(moniker_source, Moniker):
269
- moniker = moniker_source
270
- elif isinstance(moniker_source, PublishedMeasurement):
271
- if moniker_source.moniker is None:
272
- raise ValueError("PublishedMeasurement must have a Moniker to read data")
273
- moniker = moniker_source.moniker
274
- elif isinstance(moniker_source, PublishedCondition):
275
- if moniker_source.moniker is None:
276
- raise ValueError("PublishedCondition must have a Moniker to read data")
277
- moniker = moniker_source.moniker
278
-
279
- moniker_client = self._get_moniker_client(moniker.service_location)
280
- read_result = moniker_client.read_from_moniker(moniker)
281
- converted_data = unpack_and_convert_from_protobuf_any(read_result.value)
282
- if expected_type is not None and not isinstance(converted_data, expected_type):
283
- raise TypeError(f"Expected type {expected_type}, got {type(converted_data)}")
284
- return converted_data
285
-
286
- def create_step(self, step: Step) -> str:
287
- """Create a step in the datastore."""
288
- create_request = CreateStepRequest(step=step.to_protobuf())
289
- create_response = self._get_data_store_client().create_step(create_request)
290
- return create_response.step_id
291
-
292
- def get_step(self, step_id: str) -> Step:
293
- """Get a step from the data store."""
294
- get_request = GetStepRequest(step_id=step_id)
295
- get_response = self._get_data_store_client().get_step(get_request)
296
- return Step.from_protobuf(get_response.step)
297
-
298
- def create_test_result(self, test_result: TestResult) -> str:
299
- """Create a test result in the data store."""
300
- create_request = CreateTestResultRequest(test_result=test_result.to_protobuf())
301
- create_response = self._get_data_store_client().create_test_result(create_request)
302
- return create_response.test_result_id
303
-
304
- def get_test_result(self, test_result_id: str) -> TestResult:
305
- """Get a test result from the data store."""
306
- get_request = GetTestResultRequest(test_result_id=test_result_id)
307
- get_response = self._get_data_store_client().get_test_result(get_request)
308
- return TestResult.from_protobuf(get_response.test_result)
309
-
310
- def query_conditions(self, odata_query: str) -> Iterable[PublishedCondition]:
311
- """Query conditions from the data store."""
312
- query_request = QueryConditionsRequest(odata_query=odata_query)
313
- query_response = self._get_data_store_client().query_conditions(query_request)
314
- return [
315
- PublishedCondition.from_protobuf(published_condition)
316
- for published_condition in query_response.published_conditions
317
- ]
318
-
319
- def query_measurements(self, odata_query: str) -> Iterable[PublishedMeasurement]:
320
- """Query measurements from the data store."""
321
- query_request = QueryMeasurementsRequest(odata_query=odata_query)
322
- query_response = self._get_data_store_client().query_measurements(query_request)
323
- return [
324
- PublishedMeasurement.from_protobuf(published_measurement)
325
- for published_measurement in query_response.published_measurements
326
- ]
327
-
328
- def query_steps(self, odata_query: str) -> Iterable[Step]:
329
- """Query steps from the data store."""
330
- query_request = QueryStepsRequest(odata_query=odata_query)
331
- query_response = self._get_data_store_client().query_steps(query_request)
332
- return [Step.from_protobuf(step) for step in query_response.steps]
333
103
 
334
104
  def create_uut_instance(self, uut_instance: UutInstance) -> str:
335
105
  """Create a UUT instance in the metadata store."""
@@ -510,10 +280,31 @@ class Client:
510
280
  TestAdapter.from_protobuf(test_adapter) for test_adapter in query_response.test_adapters
511
281
  ]
512
282
 
513
- # TODO: Also support providing a file path?
514
- def register_schema(self, schema: str) -> str:
515
- """Register a schema in the metadata store."""
516
- register_request = RegisterSchemaRequest(schema=schema)
283
+ def register_schema_from_file(self, schema_file_path: Path | str) -> str:
284
+ """Register a schema obtained from the specified file in the metadata store.
285
+
286
+ Args:
287
+ schema_file_path: The path at which the schema file is located
288
+
289
+ Raises:
290
+ FileNotFoundError: If the schema file does not exist.
291
+ """
292
+ if isinstance(schema_file_path, str):
293
+ schema_file_path = Path(schema_file_path)
294
+
295
+ if not schema_file_path.exists():
296
+ raise FileNotFoundError(f"Schema file not found: {schema_file_path}")
297
+
298
+ schema_contents = schema_file_path.read_text(encoding="utf-8-sig")
299
+ return self.register_schema(schema_contents=schema_contents)
300
+
301
+ def register_schema(self, schema_contents: str) -> str:
302
+ """Register a schema in the metadata store.
303
+
304
+ Args:
305
+ schema_contents: The contents of the schema to register
306
+ """
307
+ register_request = RegisterSchemaRequest(schema=schema_contents)
517
308
  register_response = self._get_metadata_store_client().register_schema(register_request)
518
309
  return register_response.schema_id
519
310
 
@@ -579,74 +370,13 @@ class Client:
579
370
  query_response = self._get_metadata_store_client().query_aliases(query_request)
580
371
  return [Alias.from_protobuf(alias) for alias in query_response.aliases]
581
372
 
582
- def _get_data_store_client(self) -> DataStoreClient:
583
- if self._data_store_client is None:
584
- with self._data_store_client_lock:
585
- if self._data_store_client is None:
586
- self._data_store_client = DataStoreClient(
587
- discovery_client=self._discovery_client,
588
- grpc_channel=self._grpc_channel,
589
- grpc_channel_pool=self._grpc_channel_pool,
590
- )
591
- return self._data_store_client
592
-
593
- def _get_metadata_store_client(self) -> MetadataStoreClient:
373
+ def _get_metadata_store_client(self) -> MetadataStoreServiceClient:
594
374
  if self._metadata_store_client is None:
595
375
  with self._metadata_store_client_lock:
596
376
  if self._metadata_store_client is None:
597
- self._metadata_store_client = MetadataStoreClient(
377
+ self._metadata_store_client = MetadataStoreServiceClient(
598
378
  discovery_client=self._discovery_client,
599
379
  grpc_channel=self._grpc_channel,
600
380
  grpc_channel_pool=self._grpc_channel_pool,
601
381
  )
602
382
  return self._metadata_store_client
603
-
604
- def _get_moniker_client(self, service_location: str) -> MonikerClient:
605
- parsed_service_location = urlparse(service_location).netloc
606
- if parsed_service_location not in self._moniker_clients_by_service_location:
607
- with self._moniker_clients_lock:
608
- if parsed_service_location not in self._moniker_clients_by_service_location:
609
- self._moniker_clients_by_service_location[parsed_service_location] = (
610
- MonikerClient(
611
- service_location=parsed_service_location,
612
- grpc_channel_pool=self._grpc_channel_pool,
613
- )
614
- )
615
- return self._moniker_clients_by_service_location[parsed_service_location]
616
-
617
- @staticmethod
618
- def _get_publish_measurement_timestamp(
619
- publish_request: PublishMeasurementRequest, client_provided_timestamp: ht.datetime | None
620
- ) -> PrecisionTimestamp:
621
- no_client_timestamp_provided = client_provided_timestamp is None
622
- if no_client_timestamp_provided:
623
- publish_time = hightime_datetime_to_protobuf(ht.datetime.now(std_datetime.timezone.utc))
624
- else:
625
- publish_time = hightime_datetime_to_protobuf(
626
- cast(ht.datetime, client_provided_timestamp)
627
- )
628
-
629
- waveform_t0: PrecisionTimestamp | None = None
630
- value_case = publish_request.WhichOneof("value")
631
- if value_case == "double_analog_waveform":
632
- waveform_t0 = publish_request.double_analog_waveform.t0
633
- elif value_case == "i16_analog_waveform":
634
- waveform_t0 = publish_request.i16_analog_waveform.t0
635
- elif value_case == "double_complex_waveform":
636
- waveform_t0 = publish_request.double_complex_waveform.t0
637
- elif value_case == "i16_complex_waveform":
638
- waveform_t0 = publish_request.i16_complex_waveform.t0
639
- elif value_case == "digital_waveform":
640
- waveform_t0 = publish_request.digital_waveform.t0
641
-
642
- # If an initialized waveform t0 value is present
643
- if waveform_t0 is not None and waveform_t0 != PrecisionTimestamp():
644
- if no_client_timestamp_provided:
645
- # If the client did not provide a timestamp, use the waveform t0 value
646
- publish_time = waveform_t0
647
- elif publish_time != waveform_t0:
648
- raise ValueError(
649
- "The provided timestamp does not match the waveform t0. Please provide a matching timestamp or "
650
- "omit the timestamp to use the waveform t0."
651
- )
652
- return publish_time
@@ -0,0 +1 @@
1
+ """Types for use in accessing the NI Metadata Store."""
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Iterable, MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import MutableMapping
6
6
 
7
- from ni.datastore.grpc_conversion import (
7
+ from ni.datastore.metadata._grpc_conversion import (
8
8
  populate_extension_value_message_map,
9
9
  populate_from_extension_value_message_map,
10
10
  )
File without changes
@@ -1,66 +0,0 @@
1
- """Public API for accessing the NI Data Store Service."""
2
-
3
- from ni.datamonikers.v1.data_moniker_pb2 import Moniker
4
- from ni.datastore._client import Client
5
- from ni.datastore._types._alias import Alias
6
- from ni.datastore._types._extension_schema import ExtensionSchema
7
- from ni.datastore._types._hardware_item import HardwareItem
8
- from ni.datastore._types._operator import Operator
9
- from ni.datastore._types._published_condition import PublishedCondition
10
- from ni.datastore._types._published_measurement import PublishedMeasurement
11
- from ni.datastore._types._software_item import SoftwareItem
12
- from ni.datastore._types._step import Step
13
- from ni.datastore._types._test import Test
14
- from ni.datastore._types._test_adapter import TestAdapter
15
- from ni.datastore._types._test_description import TestDescription
16
- from ni.datastore._types._test_result import TestResult
17
- from ni.datastore._types._test_station import TestStation
18
- from ni.datastore._types._uut import Uut
19
- from ni.datastore._types._uut_instance import UutInstance
20
- from ni.measurements.data.v1.data_store_pb2 import ErrorInformation, Outcome
21
- from ni.measurements.metadata.v1.metadata_store_pb2 import AliasTargetType
22
-
23
- __all__ = [
24
- "Client",
25
- "Alias",
26
- "AliasTargetType",
27
- "ErrorInformation",
28
- "ExtensionSchema",
29
- "HardwareItem",
30
- "Moniker",
31
- "Operator",
32
- "Outcome",
33
- "PublishedCondition",
34
- "PublishedMeasurement",
35
- "SoftwareItem",
36
- "Step",
37
- "Test",
38
- "TestAdapter",
39
- "TestDescription",
40
- "TestResult",
41
- "TestStation",
42
- "Uut",
43
- "UutInstance",
44
- ]
45
-
46
- # Hide that it was not defined in this top-level package
47
- Client.__module__ = __name__
48
- Alias.__module__ = __name__
49
- AliasTargetType.__module__ = __name__
50
- ErrorInformation.__module__ = __name__
51
- ExtensionSchema.__module__ = __name__
52
- HardwareItem.__module__ = __name__
53
- Moniker.__module__ = __name__
54
- Operator.__module__ = __name__
55
- Outcome.__module__ = __name__
56
- PublishedCondition.__module__ = __name__
57
- PublishedMeasurement.__module__ = __name__
58
- SoftwareItem.__module__ = __name__
59
- Step.__module__ = __name__
60
- Test.__module__ = __name__
61
- TestAdapter.__module__ = __name__
62
- TestDescription.__module__ = __name__
63
- TestResult.__module__ = __name__
64
- TestStation.__module__ = __name__
65
- Uut.__module__ = __name__
66
- UutInstance.__module__ = __name__
@@ -1 +0,0 @@
1
- """Types for use in accessing the NI Data Store Service."""