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.
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/PKG-INFO +1 -2
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/README.md +0 -1
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/pyproject.toml +1 -1
- ni_datastore-0.1.0.dev2/src/ni/datastore/__init__.py +1 -0
- ni_datastore-0.1.0.dev2/src/ni/datastore/data/__init__.py +30 -0
- ni_datastore-0.1.0.dev2/src/ni/datastore/data/_data_store_client.py +302 -0
- 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
- ni_datastore-0.1.0.dev2/src/ni/datastore/data/_types/__init__.py +1 -0
- {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
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/_step.py +1 -1
- {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
- ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/__init__.py +46 -0
- ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_grpc_conversion.py +39 -0
- 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
- ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_types/__init__.py +1 -0
- {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
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_operator.py +1 -1
- {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
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_test.py +1 -1
- {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
- {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
- {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
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_uut.py +1 -1
- {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
- ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/_types/py.typed +0 -0
- ni_datastore-0.1.0.dev2/src/ni/datastore/metadata/py.typed +0 -0
- ni_datastore-0.1.0.dev2/src/ni/datastore/py.typed +0 -0
- ni_datastore-0.1.0.dev1/src/ni/datastore/__init__.py +0 -66
- ni_datastore-0.1.0.dev1/src/ni/datastore/_types/__init__.py +0 -1
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev2}/LICENSE +0 -0
- {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
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/_types/py.typed +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/py.typed +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/metadata}/_types/_alias.py +0 -0
- {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.
|
|
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)
|
|
@@ -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
|
|
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.
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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.
|
|
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.
|
|
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
|
-
"""
|
|
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.
|
|
15
|
-
from ni.
|
|
16
|
-
from ni.datastore._types.
|
|
17
|
-
from ni.datastore._types.
|
|
18
|
-
from ni.datastore._types.
|
|
19
|
-
from ni.datastore._types.
|
|
20
|
-
from ni.datastore._types.
|
|
21
|
-
from ni.datastore._types.
|
|
22
|
-
from ni.datastore._types.
|
|
23
|
-
from ni.datastore._types.
|
|
24
|
-
from ni.datastore._types.
|
|
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.
|
|
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
|
|
105
|
-
"""
|
|
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
|
-
|
|
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
|
|
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
|
|
141
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
|
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 =
|
|
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 Iterable, MutableMapping
|
|
6
6
|
|
|
7
|
-
from ni.datastore.
|
|
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
|
|
File without changes
|
|
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."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev2/src/ni/datastore/data}/py.typed
RENAMED
|
File without changes
|
|
File without changes
|