ni.datastore 0.1.0.dev1__tar.gz → 0.1.0.dev3__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.dev3}/PKG-INFO +5 -6
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev3}/README.md +0 -1
- {ni_datastore-0.1.0.dev1 → ni_datastore-0.1.0.dev3}/pyproject.toml +5 -5
- ni_datastore-0.1.0.dev3/src/ni/datastore/__init__.py +1 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/data/__init__.py +30 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/data/_data_store_client.py +355 -0
- ni_datastore-0.1.0.dev1/src/ni/datastore/grpc_conversion.py → ni_datastore-0.1.0.dev3/src/ni/datastore/data/_grpc_conversion.py +83 -34
- ni_datastore-0.1.0.dev3/src/ni/datastore/data/_types/__init__.py +1 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/_types/_published_measurement.py +38 -18
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/_types/_step.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/_types/_test_result.py +39 -17
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/__init__.py +46 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/_grpc_conversion.py +39 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/_metadata_store_client.py +427 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/_types/__init__.py +1 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_hardware_item.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_operator.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_software_item.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_test.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_test_adapter.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_test_description.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_test_station.py +12 -5
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_uut.py +22 -8
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_uut_instance.py +12 -5
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/_types/py.typed +0 -0
- ni_datastore-0.1.0.dev3/src/ni/datastore/metadata/py.typed +0 -0
- ni_datastore-0.1.0.dev3/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/_client.py +0 -652
- 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.dev3}/LICENSE +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/_types/_published_condition.py +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/_types/py.typed +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/data}/py.typed +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/src/ni/datastore/metadata}/_types/_alias.py +0 -0
- {ni_datastore-0.1.0.dev1/src/ni/datastore → ni_datastore-0.1.0.dev3/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.dev3
|
|
4
4
|
Summary: APIs for publishing and retrieving data from the NI Measurement Data Store
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: datastore
|
|
@@ -24,17 +24,16 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
25
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
26
|
Requires-Dist: hightime (>=0.3.0.dev0)
|
|
27
|
-
Requires-Dist: ni-datamonikers-v1-client (>=0.1.0.
|
|
28
|
-
Requires-Dist: ni-measurements-data-v1-client (>=0.1.0.
|
|
29
|
-
Requires-Dist: ni-measurements-metadata-v1-client (>=0.1.0.
|
|
30
|
-
Requires-Dist: ni-protobuf-types (>=0.1.
|
|
27
|
+
Requires-Dist: ni-datamonikers-v1-client (>=0.1.0.dev1)
|
|
28
|
+
Requires-Dist: ni-measurements-data-v1-client (>=0.1.0.dev1)
|
|
29
|
+
Requires-Dist: ni-measurements-metadata-v1-client (>=0.1.0.dev1)
|
|
30
|
+
Requires-Dist: ni-protobuf-types (>=1.0.1.dev0)
|
|
31
31
|
Requires-Dist: protobuf (>=4.21)
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ni.datastore"
|
|
3
|
-
version = "0.1.0-
|
|
3
|
+
version = "0.1.0-dev3"
|
|
4
4
|
license = "MIT"
|
|
5
5
|
description = "APIs for publishing and retrieving data from the NI Measurement Data Store"
|
|
6
6
|
authors = [{name = "NI", email = "opensource@ni.com"}]
|
|
@@ -37,10 +37,10 @@ requires-poetry = '>=2.1,<3.0'
|
|
|
37
37
|
[tool.poetry.dependencies]
|
|
38
38
|
python = "^3.9"
|
|
39
39
|
protobuf = {version=">=4.21"}
|
|
40
|
-
ni-datamonikers-v1-client = { version = ">=0.1.0.
|
|
41
|
-
ni-measurements-data-v1-client = { version = ">=0.1.0.
|
|
42
|
-
ni-measurements-metadata-v1-client = { version = ">=0.1.0.
|
|
43
|
-
ni-protobuf-types = { version = ">=0.1.
|
|
40
|
+
ni-datamonikers-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
|
|
41
|
+
ni-measurements-data-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
|
|
42
|
+
ni-measurements-metadata-v1-client = { version = ">=0.1.0.dev1", allow-prereleases = true }
|
|
43
|
+
ni-protobuf-types = { version = ">=1.0.1.dev0", allow-prereleases = true }
|
|
44
44
|
hightime = { version = ">=0.3.0.dev0", allow-prereleases = true }
|
|
45
45
|
|
|
46
46
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -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,355 @@
|
|
|
1
|
+
"""Data store client for publishing and reading data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Iterable, Sequence
|
|
8
|
+
from threading import Lock
|
|
9
|
+
from types import TracebackType
|
|
10
|
+
from typing import TYPE_CHECKING, Type, TypeVar, overload
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
import hightime as ht
|
|
14
|
+
from grpc import Channel
|
|
15
|
+
from ni.datamonikers.v1.client import MonikerClient
|
|
16
|
+
from ni.datamonikers.v1.data_moniker_pb2 import Moniker
|
|
17
|
+
from ni.datastore.data._grpc_conversion import (
|
|
18
|
+
get_publish_measurement_timestamp,
|
|
19
|
+
populate_publish_condition_batch_request_values,
|
|
20
|
+
populate_publish_condition_request_value,
|
|
21
|
+
populate_publish_measurement_batch_request_values,
|
|
22
|
+
populate_publish_measurement_request_value,
|
|
23
|
+
unpack_and_convert_from_protobuf_any,
|
|
24
|
+
)
|
|
25
|
+
from ni.datastore.data._types._published_condition import PublishedCondition
|
|
26
|
+
from ni.datastore.data._types._published_measurement import PublishedMeasurement
|
|
27
|
+
from ni.datastore.data._types._step import Step
|
|
28
|
+
from ni.datastore.data._types._test_result import TestResult
|
|
29
|
+
from ni.measurementlink.discovery.v1.client import DiscoveryClient
|
|
30
|
+
from ni.measurements.data.v1.client import DataStoreClient as DataStoreServiceClient
|
|
31
|
+
from ni.measurements.data.v1.data_store_pb2 import (
|
|
32
|
+
ErrorInformation,
|
|
33
|
+
Outcome,
|
|
34
|
+
)
|
|
35
|
+
from ni.measurements.data.v1.data_store_service_pb2 import (
|
|
36
|
+
CreateStepRequest,
|
|
37
|
+
CreateTestResultRequest,
|
|
38
|
+
GetStepRequest,
|
|
39
|
+
GetTestResultRequest,
|
|
40
|
+
PublishConditionBatchRequest,
|
|
41
|
+
PublishConditionRequest,
|
|
42
|
+
PublishMeasurementBatchRequest,
|
|
43
|
+
PublishMeasurementRequest,
|
|
44
|
+
QueryConditionsRequest,
|
|
45
|
+
QueryMeasurementsRequest,
|
|
46
|
+
QueryStepsRequest,
|
|
47
|
+
)
|
|
48
|
+
from ni.protobuf.types.precision_timestamp_conversion import (
|
|
49
|
+
hightime_datetime_to_protobuf,
|
|
50
|
+
)
|
|
51
|
+
from ni_grpc_extensions.channelpool import GrpcChannelPool
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
if sys.version_info >= (3, 11):
|
|
55
|
+
from typing import Self
|
|
56
|
+
else:
|
|
57
|
+
from typing_extensions import Self
|
|
58
|
+
|
|
59
|
+
TRead = TypeVar("TRead")
|
|
60
|
+
|
|
61
|
+
_logger = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DataStoreClient:
|
|
65
|
+
"""Data store client for publishing and reading data."""
|
|
66
|
+
|
|
67
|
+
__slots__ = (
|
|
68
|
+
"_closed",
|
|
69
|
+
"_discovery_client",
|
|
70
|
+
"_grpc_channel",
|
|
71
|
+
"_grpc_channel_pool",
|
|
72
|
+
"_data_store_client",
|
|
73
|
+
"_data_store_client_lock",
|
|
74
|
+
"_moniker_clients_by_service_location",
|
|
75
|
+
"_moniker_clients_lock",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
_DATA_STORE_CLIENT_CLOSED_ERROR = "This DataStoreClient has been closed. Create a new DataStoreClient for further interaction with the data store."
|
|
79
|
+
|
|
80
|
+
_closed: bool
|
|
81
|
+
_discovery_client: DiscoveryClient | None
|
|
82
|
+
_grpc_channel: Channel | None
|
|
83
|
+
_grpc_channel_pool: GrpcChannelPool | None
|
|
84
|
+
_data_store_client: DataStoreServiceClient | None
|
|
85
|
+
_moniker_clients_by_service_location: dict[str, MonikerClient]
|
|
86
|
+
_data_store_client_lock: Lock
|
|
87
|
+
_moniker_clients_lock: Lock
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
discovery_client: DiscoveryClient | None = None,
|
|
92
|
+
grpc_channel: Channel | None = None,
|
|
93
|
+
grpc_channel_pool: GrpcChannelPool | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Initialize the DataStoreClient.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
discovery_client: An optional discovery client (recommended).
|
|
99
|
+
|
|
100
|
+
grpc_channel: An optional data store gRPC channel. Providing this channel will bypass
|
|
101
|
+
discovery service resolution of the data store. (Note: Reading data from a moniker
|
|
102
|
+
will still always use a channel corresponding to the service location specified by
|
|
103
|
+
that moniker.)
|
|
104
|
+
|
|
105
|
+
grpc_channel_pool: An optional gRPC channel pool (recommended).
|
|
106
|
+
"""
|
|
107
|
+
self._discovery_client = discovery_client
|
|
108
|
+
self._grpc_channel = grpc_channel
|
|
109
|
+
self._grpc_channel_pool = grpc_channel_pool
|
|
110
|
+
|
|
111
|
+
self._data_store_client = None
|
|
112
|
+
self._moniker_clients_by_service_location = {}
|
|
113
|
+
|
|
114
|
+
self._data_store_client_lock = Lock()
|
|
115
|
+
self._moniker_clients_lock = Lock()
|
|
116
|
+
|
|
117
|
+
self._closed = False
|
|
118
|
+
|
|
119
|
+
def __enter__(self) -> Self:
|
|
120
|
+
"""Enter the runtime context of the data store client."""
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def __exit__(
|
|
124
|
+
self,
|
|
125
|
+
exc_type: type[BaseException] | None,
|
|
126
|
+
exc_val: BaseException | None,
|
|
127
|
+
traceback: TracebackType | None,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Exit the runtime context of the data store client."""
|
|
130
|
+
self.close()
|
|
131
|
+
|
|
132
|
+
def close(self) -> None:
|
|
133
|
+
"""Close the data store client and clean up resources that it owns."""
|
|
134
|
+
self._closed = True
|
|
135
|
+
|
|
136
|
+
with self._data_store_client_lock:
|
|
137
|
+
if self._data_store_client is not None:
|
|
138
|
+
self._data_store_client.close()
|
|
139
|
+
self._data_store_client = None
|
|
140
|
+
|
|
141
|
+
with self._moniker_clients_lock:
|
|
142
|
+
for _, moniker_client in self._moniker_clients_by_service_location.items():
|
|
143
|
+
moniker_client.close()
|
|
144
|
+
self._moniker_clients_by_service_location.clear()
|
|
145
|
+
|
|
146
|
+
def publish_condition(
|
|
147
|
+
self,
|
|
148
|
+
condition_name: str,
|
|
149
|
+
type: str,
|
|
150
|
+
value: object,
|
|
151
|
+
step_id: str,
|
|
152
|
+
) -> PublishedCondition:
|
|
153
|
+
"""Publish a condition value to the data store."""
|
|
154
|
+
publish_request = PublishConditionRequest(
|
|
155
|
+
condition_name=condition_name,
|
|
156
|
+
type=type,
|
|
157
|
+
step_id=step_id,
|
|
158
|
+
)
|
|
159
|
+
populate_publish_condition_request_value(publish_request, value)
|
|
160
|
+
publish_response = self._get_data_store_client().publish_condition(publish_request)
|
|
161
|
+
return PublishedCondition.from_protobuf(publish_response.published_condition)
|
|
162
|
+
|
|
163
|
+
def publish_condition_batch(
|
|
164
|
+
self, condition_name: str, type: str, values: object, step_id: str
|
|
165
|
+
) -> PublishedCondition:
|
|
166
|
+
"""Publish a batch of N values for a condition to the data store."""
|
|
167
|
+
publish_request = PublishConditionBatchRequest(
|
|
168
|
+
condition_name=condition_name,
|
|
169
|
+
type=type,
|
|
170
|
+
step_id=step_id,
|
|
171
|
+
)
|
|
172
|
+
populate_publish_condition_batch_request_values(publish_request, values)
|
|
173
|
+
publish_response = self._get_data_store_client().publish_condition_batch(publish_request)
|
|
174
|
+
return PublishedCondition.from_protobuf(publish_response.published_condition)
|
|
175
|
+
|
|
176
|
+
def publish_measurement(
|
|
177
|
+
self,
|
|
178
|
+
measurement_name: str,
|
|
179
|
+
value: object, # More strongly typed Union[bool, AnalogWaveform] can be used if needed
|
|
180
|
+
step_id: str,
|
|
181
|
+
timestamp: ht.datetime | None = None,
|
|
182
|
+
outcome: Outcome.ValueType = Outcome.OUTCOME_UNSPECIFIED,
|
|
183
|
+
error_information: ErrorInformation | None = None,
|
|
184
|
+
hardware_item_ids: Iterable[str] = tuple(),
|
|
185
|
+
test_adapter_ids: Iterable[str] = tuple(),
|
|
186
|
+
software_item_ids: Iterable[str] = tuple(),
|
|
187
|
+
notes: str = "",
|
|
188
|
+
) -> PublishedMeasurement:
|
|
189
|
+
"""Publish a measurement value to the data store."""
|
|
190
|
+
publish_request = PublishMeasurementRequest(
|
|
191
|
+
measurement_name=measurement_name,
|
|
192
|
+
step_id=step_id,
|
|
193
|
+
outcome=outcome,
|
|
194
|
+
error_information=error_information,
|
|
195
|
+
hardware_item_ids=hardware_item_ids,
|
|
196
|
+
test_adapter_ids=test_adapter_ids,
|
|
197
|
+
software_item_ids=software_item_ids,
|
|
198
|
+
notes=notes,
|
|
199
|
+
)
|
|
200
|
+
populate_publish_measurement_request_value(publish_request, value)
|
|
201
|
+
publish_request.timestamp.CopyFrom(
|
|
202
|
+
get_publish_measurement_timestamp(publish_request, timestamp)
|
|
203
|
+
)
|
|
204
|
+
publish_response = self._get_data_store_client().publish_measurement(publish_request)
|
|
205
|
+
return PublishedMeasurement.from_protobuf(publish_response.published_measurement)
|
|
206
|
+
|
|
207
|
+
def publish_measurement_batch(
|
|
208
|
+
self,
|
|
209
|
+
measurement_name: str,
|
|
210
|
+
values: object,
|
|
211
|
+
step_id: str,
|
|
212
|
+
timestamps: Iterable[ht.datetime] = tuple(),
|
|
213
|
+
outcomes: Iterable[Outcome.ValueType] = tuple(),
|
|
214
|
+
error_information: Iterable[ErrorInformation] = tuple(),
|
|
215
|
+
hardware_item_ids: Iterable[str] = tuple(),
|
|
216
|
+
test_adapter_ids: Iterable[str] = tuple(),
|
|
217
|
+
software_item_ids: Iterable[str] = tuple(),
|
|
218
|
+
) -> Sequence[PublishedMeasurement]:
|
|
219
|
+
"""Publish a batch of N values of a measurement to the data store."""
|
|
220
|
+
publish_request = PublishMeasurementBatchRequest(
|
|
221
|
+
measurement_name=measurement_name,
|
|
222
|
+
step_id=step_id,
|
|
223
|
+
timestamp=[hightime_datetime_to_protobuf(ts) for ts in timestamps],
|
|
224
|
+
outcome=outcomes,
|
|
225
|
+
error_information=error_information,
|
|
226
|
+
hardware_item_ids=hardware_item_ids,
|
|
227
|
+
test_adapter_ids=test_adapter_ids,
|
|
228
|
+
software_item_ids=software_item_ids,
|
|
229
|
+
)
|
|
230
|
+
populate_publish_measurement_batch_request_values(publish_request, values)
|
|
231
|
+
publish_response = self._get_data_store_client().publish_measurement_batch(publish_request)
|
|
232
|
+
return [
|
|
233
|
+
PublishedMeasurement.from_protobuf(pm) for pm in publish_response.published_measurements
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
@overload
|
|
237
|
+
def read_data(
|
|
238
|
+
self,
|
|
239
|
+
moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
|
|
240
|
+
expected_type: Type[TRead],
|
|
241
|
+
) -> TRead: ...
|
|
242
|
+
|
|
243
|
+
@overload
|
|
244
|
+
def read_data(
|
|
245
|
+
self,
|
|
246
|
+
moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
|
|
247
|
+
) -> object: ...
|
|
248
|
+
|
|
249
|
+
def read_data(
|
|
250
|
+
self,
|
|
251
|
+
moniker_source: Moniker | PublishedMeasurement | PublishedCondition,
|
|
252
|
+
expected_type: Type[TRead] | None = None,
|
|
253
|
+
) -> TRead | object:
|
|
254
|
+
"""Read data published to the data store."""
|
|
255
|
+
if isinstance(moniker_source, Moniker):
|
|
256
|
+
moniker = moniker_source
|
|
257
|
+
elif isinstance(moniker_source, PublishedMeasurement):
|
|
258
|
+
if moniker_source.moniker is None:
|
|
259
|
+
raise ValueError("PublishedMeasurement must have a Moniker to read data")
|
|
260
|
+
moniker = moniker_source.moniker
|
|
261
|
+
elif isinstance(moniker_source, PublishedCondition):
|
|
262
|
+
if moniker_source.moniker is None:
|
|
263
|
+
raise ValueError("PublishedCondition must have a Moniker to read data")
|
|
264
|
+
moniker = moniker_source.moniker
|
|
265
|
+
|
|
266
|
+
moniker_client = self._get_moniker_client(moniker.service_location)
|
|
267
|
+
read_result = moniker_client.read_from_moniker(moniker)
|
|
268
|
+
converted_data = unpack_and_convert_from_protobuf_any(read_result.value)
|
|
269
|
+
if expected_type is not None and not isinstance(converted_data, expected_type):
|
|
270
|
+
raise TypeError(f"Expected type {expected_type}, got {type(converted_data)}")
|
|
271
|
+
return converted_data
|
|
272
|
+
|
|
273
|
+
def create_step(self, step: Step) -> str:
|
|
274
|
+
"""Create a step in the data store."""
|
|
275
|
+
create_request = CreateStepRequest(step=step.to_protobuf())
|
|
276
|
+
create_response = self._get_data_store_client().create_step(create_request)
|
|
277
|
+
return create_response.step_id
|
|
278
|
+
|
|
279
|
+
def get_step(self, step_id: str) -> Step:
|
|
280
|
+
"""Get a step from the data store."""
|
|
281
|
+
get_request = GetStepRequest(step_id=step_id)
|
|
282
|
+
get_response = self._get_data_store_client().get_step(get_request)
|
|
283
|
+
return Step.from_protobuf(get_response.step)
|
|
284
|
+
|
|
285
|
+
def create_test_result(self, test_result: TestResult) -> str:
|
|
286
|
+
"""Create a test result in the data store."""
|
|
287
|
+
create_request = CreateTestResultRequest(test_result=test_result.to_protobuf())
|
|
288
|
+
create_response = self._get_data_store_client().create_test_result(create_request)
|
|
289
|
+
return create_response.test_result_id
|
|
290
|
+
|
|
291
|
+
def get_test_result(self, test_result_id: str) -> TestResult:
|
|
292
|
+
"""Get a test result from the data store."""
|
|
293
|
+
get_request = GetTestResultRequest(test_result_id=test_result_id)
|
|
294
|
+
get_response = self._get_data_store_client().get_test_result(get_request)
|
|
295
|
+
return TestResult.from_protobuf(get_response.test_result)
|
|
296
|
+
|
|
297
|
+
def query_conditions(self, odata_query: str = "") -> Sequence[PublishedCondition]:
|
|
298
|
+
"""Query conditions from the data store."""
|
|
299
|
+
query_request = QueryConditionsRequest(odata_query=odata_query)
|
|
300
|
+
query_response = self._get_data_store_client().query_conditions(query_request)
|
|
301
|
+
return [
|
|
302
|
+
PublishedCondition.from_protobuf(published_condition)
|
|
303
|
+
for published_condition in query_response.published_conditions
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
def query_measurements(self, odata_query: str = "") -> Sequence[PublishedMeasurement]:
|
|
307
|
+
"""Query measurements from the data store."""
|
|
308
|
+
query_request = QueryMeasurementsRequest(odata_query=odata_query)
|
|
309
|
+
query_response = self._get_data_store_client().query_measurements(query_request)
|
|
310
|
+
return [
|
|
311
|
+
PublishedMeasurement.from_protobuf(published_measurement)
|
|
312
|
+
for published_measurement in query_response.published_measurements
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
def query_steps(self, odata_query: str = "") -> Sequence[Step]:
|
|
316
|
+
"""Query steps from the data store."""
|
|
317
|
+
query_request = QueryStepsRequest(odata_query=odata_query)
|
|
318
|
+
query_response = self._get_data_store_client().query_steps(query_request)
|
|
319
|
+
return [Step.from_protobuf(step) for step in query_response.steps]
|
|
320
|
+
|
|
321
|
+
def _get_data_store_client(self) -> DataStoreServiceClient:
|
|
322
|
+
if self._closed:
|
|
323
|
+
raise RuntimeError(self._DATA_STORE_CLIENT_CLOSED_ERROR)
|
|
324
|
+
|
|
325
|
+
if self._data_store_client is None:
|
|
326
|
+
with self._data_store_client_lock:
|
|
327
|
+
if self._data_store_client is None:
|
|
328
|
+
self._data_store_client = self._instantiate_data_store_client()
|
|
329
|
+
return self._data_store_client
|
|
330
|
+
|
|
331
|
+
def _instantiate_data_store_client(self) -> DataStoreServiceClient:
|
|
332
|
+
return DataStoreServiceClient(
|
|
333
|
+
discovery_client=self._discovery_client,
|
|
334
|
+
grpc_channel=self._grpc_channel,
|
|
335
|
+
grpc_channel_pool=self._grpc_channel_pool,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _get_moniker_client(self, service_location: str) -> MonikerClient:
|
|
339
|
+
if self._closed:
|
|
340
|
+
raise RuntimeError(self._DATA_STORE_CLIENT_CLOSED_ERROR)
|
|
341
|
+
|
|
342
|
+
parsed_service_location = urlparse(service_location).netloc
|
|
343
|
+
if parsed_service_location not in self._moniker_clients_by_service_location:
|
|
344
|
+
with self._moniker_clients_lock:
|
|
345
|
+
if parsed_service_location not in self._moniker_clients_by_service_location:
|
|
346
|
+
self._moniker_clients_by_service_location[parsed_service_location] = (
|
|
347
|
+
self._instantiate_moniker_client(parsed_service_location)
|
|
348
|
+
)
|
|
349
|
+
return self._moniker_clients_by_service_location[parsed_service_location]
|
|
350
|
+
|
|
351
|
+
def _instantiate_moniker_client(self, parsed_service_location: str) -> MonikerClient:
|
|
352
|
+
return MonikerClient(
|
|
353
|
+
service_location=parsed_service_location,
|
|
354
|
+
grpc_channel_pool=self._grpc_channel_pool,
|
|
355
|
+
)
|
|
@@ -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
|
|
@@ -40,11 +44,16 @@ from ni.protobuf.types.waveform_pb2 import (
|
|
|
40
44
|
I16AnalogWaveform,
|
|
41
45
|
I16ComplexWaveform,
|
|
42
46
|
)
|
|
47
|
+
from ni.protobuf.types.xydata_conversion import (
|
|
48
|
+
float64_xydata_from_protobuf,
|
|
49
|
+
float64_xydata_to_protobuf,
|
|
50
|
+
)
|
|
43
51
|
from ni.protobuf.types.xydata_pb2 import DoubleXYData
|
|
44
52
|
from nitypes.complex import ComplexInt32DType
|
|
45
53
|
from nitypes.scalar import Scalar
|
|
46
54
|
from nitypes.vector import Vector
|
|
47
55
|
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
|
|
56
|
+
from nitypes.xy_data import XYData
|
|
48
57
|
|
|
49
58
|
_logger = logging.getLogger(__name__)
|
|
50
59
|
|
|
@@ -73,9 +82,19 @@ def populate_publish_condition_batch_request_values(
|
|
|
73
82
|
publish_request: PublishConditionBatchRequest, values: object
|
|
74
83
|
) -> None:
|
|
75
84
|
"""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
85
|
if isinstance(values, Vector):
|
|
78
86
|
publish_request.scalar_values.CopyFrom(vector_to_protobuf(values))
|
|
87
|
+
elif isinstance(values, Iterable):
|
|
88
|
+
if not values:
|
|
89
|
+
raise ValueError("Cannot publish an empty Iterable.")
|
|
90
|
+
try:
|
|
91
|
+
vector = Vector(values)
|
|
92
|
+
except (TypeError, ValueError):
|
|
93
|
+
raise TypeError(
|
|
94
|
+
f"Unsupported iterable: {values}. Subtype must be bool, float, int, or string."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
publish_request.scalar_values.CopyFrom(vector_to_protobuf(vector))
|
|
79
98
|
else:
|
|
80
99
|
raise TypeError(
|
|
81
100
|
f"Unsupported condition values type: {type(values)}. Please consult the documentation."
|
|
@@ -123,20 +142,45 @@ def populate_publish_measurement_request_value(
|
|
|
123
142
|
raise TypeError(f"Unsupported Spectrum dtype: {value.dtype}")
|
|
124
143
|
elif isinstance(value, DigitalWaveform):
|
|
125
144
|
publish_request.digital_waveform.CopyFrom(digital_waveform_to_protobuf(value))
|
|
145
|
+
elif isinstance(value, XYData):
|
|
146
|
+
if value.dtype == np.float64:
|
|
147
|
+
publish_request.x_y_data.CopyFrom(float64_xydata_to_protobuf(value))
|
|
148
|
+
else:
|
|
149
|
+
raise TypeError(f"Unsupported XYData dtype: {value.dtype}")
|
|
150
|
+
elif isinstance(value, Iterable):
|
|
151
|
+
if not value:
|
|
152
|
+
raise ValueError("Cannot publish an empty Iterable.")
|
|
153
|
+
try:
|
|
154
|
+
vector = Vector(value)
|
|
155
|
+
except (TypeError, ValueError):
|
|
156
|
+
raise TypeError(
|
|
157
|
+
f"Unsupported iterable: {value}. Subtype must be bool, float, int, or string."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
publish_request.vector.CopyFrom(vector_to_protobuf(vector))
|
|
126
161
|
else:
|
|
127
162
|
raise TypeError(
|
|
128
163
|
f"Unsupported measurement value type: {type(value)}. Please consult the documentation."
|
|
129
164
|
)
|
|
130
|
-
# TODO: Implement conversion from proper XYData type
|
|
131
165
|
|
|
132
166
|
|
|
133
167
|
def populate_publish_measurement_batch_request_values(
|
|
134
168
|
publish_request: PublishMeasurementBatchRequest, values: object
|
|
135
169
|
) -> None:
|
|
136
170
|
"""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
171
|
if isinstance(values, Vector):
|
|
139
172
|
publish_request.scalar_values.CopyFrom(vector_to_protobuf(values))
|
|
173
|
+
elif isinstance(values, Iterable):
|
|
174
|
+
if not values:
|
|
175
|
+
raise ValueError("Cannot publish an empty Iterable.")
|
|
176
|
+
try:
|
|
177
|
+
vector = Vector(values)
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
raise TypeError(
|
|
180
|
+
f"Unsupported iterable: {values}. Subtype must be bool, float, int, or string."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
publish_request.scalar_values.CopyFrom(vector_to_protobuf(vector))
|
|
140
184
|
else:
|
|
141
185
|
raise TypeError(
|
|
142
186
|
f"Unsupported measurement values type: {type(values)}. Please consult the documentation."
|
|
@@ -173,10 +217,7 @@ def unpack_and_convert_from_protobuf_any(read_value: Any) -> object:
|
|
|
173
217
|
elif value_type == DoubleXYData.DESCRIPTOR.full_name:
|
|
174
218
|
xydata = DoubleXYData()
|
|
175
219
|
read_value.Unpack(xydata)
|
|
176
|
-
|
|
177
|
-
"DoubleXYData conversion is not yet implemented. Returning the raw protobuf object."
|
|
178
|
-
)
|
|
179
|
-
return xydata
|
|
220
|
+
return float64_xydata_from_protobuf(xydata)
|
|
180
221
|
elif value_type == VectorProto.DESCRIPTOR.full_name:
|
|
181
222
|
vector = VectorProto()
|
|
182
223
|
read_value.Unpack(vector)
|
|
@@ -185,29 +226,37 @@ def unpack_and_convert_from_protobuf_any(read_value: Any) -> object:
|
|
|
185
226
|
raise TypeError(f"Unsupported data type Name: {value_type}")
|
|
186
227
|
|
|
187
228
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
destination[key].string_value = value
|
|
229
|
+
def get_publish_measurement_timestamp(
|
|
230
|
+
publish_request: PublishMeasurementRequest, client_provided_timestamp: ht.datetime | None
|
|
231
|
+
) -> PrecisionTimestamp:
|
|
232
|
+
"""Determine the correct timestamp to use for publishing a measurement."""
|
|
233
|
+
no_client_timestamp_provided = client_provided_timestamp is None
|
|
234
|
+
if no_client_timestamp_provided:
|
|
235
|
+
publish_time = hightime_datetime_to_protobuf(ht.datetime.now(std_datetime.timezone.utc))
|
|
236
|
+
else:
|
|
237
|
+
publish_time = hightime_datetime_to_protobuf(cast(ht.datetime, client_provided_timestamp))
|
|
198
238
|
|
|
239
|
+
waveform_t0: PrecisionTimestamp | None = None
|
|
240
|
+
value_case = publish_request.WhichOneof("value")
|
|
241
|
+
if value_case == "double_analog_waveform":
|
|
242
|
+
waveform_t0 = publish_request.double_analog_waveform.t0
|
|
243
|
+
elif value_case == "i16_analog_waveform":
|
|
244
|
+
waveform_t0 = publish_request.i16_analog_waveform.t0
|
|
245
|
+
elif value_case == "double_complex_waveform":
|
|
246
|
+
waveform_t0 = publish_request.double_complex_waveform.t0
|
|
247
|
+
elif value_case == "i16_complex_waveform":
|
|
248
|
+
waveform_t0 = publish_request.i16_complex_waveform.t0
|
|
249
|
+
elif value_case == "digital_waveform":
|
|
250
|
+
waveform_t0 = publish_request.digital_waveform.t0
|
|
199
251
|
|
|
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}")
|
|
252
|
+
# If an initialized waveform t0 value is present
|
|
253
|
+
if waveform_t0 is not None and waveform_t0 != PrecisionTimestamp():
|
|
254
|
+
if no_client_timestamp_provided:
|
|
255
|
+
# If the client did not provide a timestamp, use the waveform t0 value
|
|
256
|
+
publish_time = waveform_t0
|
|
257
|
+
elif publish_time != waveform_t0:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
"The provided timestamp does not match the waveform t0. Please provide a matching timestamp or "
|
|
260
|
+
"omit the timestamp to use the waveform t0."
|
|
261
|
+
)
|
|
262
|
+
return publish_time
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Types for use in accessing the NI Data Store."""
|