Skip to content

Commit b8e09eb

Browse files
Mike ProsserMike Prosser
authored andcommitted
add set_values() for batch updates
1 parent dcaf336 commit b8e09eb

File tree

11 files changed

+343
-7
lines changed

11 files changed

+343
-7
lines changed

examples/simple_graph/simple_graph.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@
2525
time_points = np.linspace(0, num_points, num_points)
2626
sine_values = amplitude * np.sin(frequency * time_points)
2727

28-
panel.set_value("time_points", time_points.tolist())
29-
panel.set_value("sine_values", sine_values.tolist())
30-
panel.set_value("amplitude", amplitude)
31-
panel.set_value("frequency", frequency)
28+
panel.set_values(
29+
{
30+
"time_points": time_points.tolist(),
31+
"sine_values": sine_values.tolist(),
32+
"amplitude": amplitude,
33+
"frequency": frequency,
34+
}
35+
)
36+
37+
# panel.set_value("time_points", time_points.tolist())
38+
# panel.set_value("sine_values", sine_values.tolist())
39+
# panel.set_value("amplitude", amplitude)
40+
# panel.set_value("frequency", frequency)
3241

3342
# Slowly vary the frequency for a more dynamic visualization
3443
frequency = 1.0 + 0.5 * math.sin(time.time() / 5.0)

protos/ni/pythonpanel/v1/python_panel_service.proto

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ service PythonPanelService {
4141
// Status Codes for errors:
4242
// - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
4343
rpc SetValue(SetValueRequest) returns (SetValueResponse);
44+
45+
// Set values for multiple controls on the panel
46+
// Status Codes for errors:
47+
// - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
48+
rpc SetValues(SetValuesRequest) returns (SetValuesResponse);
4449
}
4550

4651
message StartPanelRequest {
@@ -114,4 +119,26 @@ message SetValueRequest {
114119
}
115120

116121
message SetValueResponse {
122+
}
123+
124+
message SetValuesRequest {
125+
// Unique ID of the panel
126+
string panel_id = 1;
127+
128+
// Value IDs and Values
129+
repeated ValueInformation values = 2;
130+
131+
// Notify other clients of these new values
132+
bool notify = 3;
133+
}
134+
135+
message ValueInformation {
136+
// Unique ID of the value
137+
string value_id = 1;
138+
139+
// The value
140+
google.protobuf.Any value = 2;
141+
}
142+
143+
message SetValuesResponse {
117144
}

src/ni/pythonpanel/v1/python_panel_service_pb2.py

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ni/pythonpanel/v1/python_panel_service_pb2.pyi

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,62 @@ class SetValueResponse(google.protobuf.message.Message):
212212
) -> None: ...
213213

214214
global___SetValueResponse = SetValueResponse
215+
216+
@typing.final
217+
class SetValuesRequest(google.protobuf.message.Message):
218+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
219+
220+
PANEL_ID_FIELD_NUMBER: builtins.int
221+
VALUES_FIELD_NUMBER: builtins.int
222+
NOTIFY_FIELD_NUMBER: builtins.int
223+
panel_id: builtins.str
224+
"""Unique ID of the panel"""
225+
notify: builtins.bool
226+
"""Notify other clients of these new values"""
227+
@property
228+
def values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ValueInformation]:
229+
"""Value IDs and Values"""
230+
231+
def __init__(
232+
self,
233+
*,
234+
panel_id: builtins.str = ...,
235+
values: collections.abc.Iterable[global___ValueInformation] | None = ...,
236+
notify: builtins.bool = ...,
237+
) -> None: ...
238+
def ClearField(self, field_name: typing.Literal["notify", b"notify", "panel_id", b"panel_id", "values", b"values"]) -> None: ...
239+
240+
global___SetValuesRequest = SetValuesRequest
241+
242+
@typing.final
243+
class ValueInformation(google.protobuf.message.Message):
244+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
245+
246+
VALUE_ID_FIELD_NUMBER: builtins.int
247+
VALUE_FIELD_NUMBER: builtins.int
248+
value_id: builtins.str
249+
"""Unique ID of the value"""
250+
@property
251+
def value(self) -> google.protobuf.any_pb2.Any:
252+
"""The value"""
253+
254+
def __init__(
255+
self,
256+
*,
257+
value_id: builtins.str = ...,
258+
value: google.protobuf.any_pb2.Any | None = ...,
259+
) -> None: ...
260+
def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ...
261+
def ClearField(self, field_name: typing.Literal["value", b"value", "value_id", b"value_id"]) -> None: ...
262+
263+
global___ValueInformation = ValueInformation
264+
265+
@typing.final
266+
class SetValuesResponse(google.protobuf.message.Message):
267+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
268+
269+
def __init__(
270+
self,
271+
) -> None: ...
272+
273+
global___SetValuesResponse = SetValuesResponse

src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ def __init__(self, channel):
4040
request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.SerializeToString,
4141
response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.FromString,
4242
)
43+
self.SetValues = channel.unary_unary(
44+
'/ni.pythonpanel.v1.PythonPanelService/SetValues',
45+
request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesRequest.SerializeToString,
46+
response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesResponse.FromString,
47+
)
4348

4449

4550
class PythonPanelServiceServicer(object):
@@ -92,6 +97,15 @@ def SetValue(self, request, context):
9297
context.set_details('Method not implemented!')
9398
raise NotImplementedError('Method not implemented!')
9499

100+
def SetValues(self, request, context):
101+
"""Set values for multiple controls on the panel
102+
Status Codes for errors:
103+
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
104+
"""
105+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
106+
context.set_details('Method not implemented!')
107+
raise NotImplementedError('Method not implemented!')
108+
95109

96110
def add_PythonPanelServiceServicer_to_server(servicer, server):
97111
rpc_method_handlers = {
@@ -120,6 +134,11 @@ def add_PythonPanelServiceServicer_to_server(servicer, server):
120134
request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.FromString,
121135
response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.SerializeToString,
122136
),
137+
'SetValues': grpc.unary_unary_rpc_method_handler(
138+
servicer.SetValues,
139+
request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesRequest.FromString,
140+
response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesResponse.SerializeToString,
141+
),
123142
}
124143
generic_handler = grpc.method_handlers_generic_handler(
125144
'ni.pythonpanel.v1.PythonPanelService', rpc_method_handlers)
@@ -215,3 +234,20 @@ def SetValue(request,
215234
ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.FromString,
216235
options, channel_credentials,
217236
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
237+
238+
@staticmethod
239+
def SetValues(request,
240+
target,
241+
options=(),
242+
channel_credentials=None,
243+
call_credentials=None,
244+
insecure=False,
245+
compression=None,
246+
wait_for_ready=None,
247+
timeout=None,
248+
metadata=None):
249+
return grpc.experimental.unary_unary(request, target, '/ni.pythonpanel.v1.PythonPanelService/SetValues',
250+
ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesRequest.SerializeToString,
251+
ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValuesResponse.FromString,
252+
options, channel_credentials,
253+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ class PythonPanelServiceStub:
6767
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
6868
"""
6969

70+
SetValues: grpc.UnaryUnaryMultiCallable[
71+
ni.pythonpanel.v1.python_panel_service_pb2.SetValuesRequest,
72+
ni.pythonpanel.v1.python_panel_service_pb2.SetValuesResponse,
73+
]
74+
"""Set values for multiple controls on the panel
75+
Status Codes for errors:
76+
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
77+
"""
78+
7079
class PythonPanelServiceAsyncStub:
7180
"""Service interface for interacting with python panels"""
7281

@@ -116,6 +125,15 @@ class PythonPanelServiceAsyncStub:
116125
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
117126
"""
118127

128+
SetValues: grpc.aio.UnaryUnaryMultiCallable[
129+
ni.pythonpanel.v1.python_panel_service_pb2.SetValuesRequest,
130+
ni.pythonpanel.v1.python_panel_service_pb2.SetValuesResponse,
131+
]
132+
"""Set values for multiple controls on the panel
133+
Status Codes for errors:
134+
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
135+
"""
136+
119137
class PythonPanelServiceServicer(metaclass=abc.ABCMeta):
120138
"""Service interface for interacting with python panels"""
121139

@@ -175,4 +193,15 @@ class PythonPanelServiceServicer(metaclass=abc.ABCMeta):
175193
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
176194
"""
177195

196+
@abc.abstractmethod
197+
def SetValues(
198+
self,
199+
request: ni.pythonpanel.v1.python_panel_service_pb2.SetValuesRequest,
200+
context: _ServicerContext,
201+
) -> typing.Union[ni.pythonpanel.v1.python_panel_service_pb2.SetValuesResponse, collections.abc.Awaitable[ni.pythonpanel.v1.python_panel_service_pb2.SetValuesResponse]]:
202+
"""Set values for multiple controls on the panel
203+
Status Codes for errors:
204+
- INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed.
205+
"""
206+
178207
def add_PythonPanelServiceServicer_to_server(servicer: PythonPanelServiceServicer, server: typing.Union[grpc.Server, grpc.aio.Server]) -> None: ...

src/nipanel/_panel_client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
EnumeratePanelsRequest,
1414
GetValueRequest,
1515
SetValueRequest,
16+
SetValuesRequest,
17+
ValueInformation,
1618
)
1719
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub
1820
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
@@ -115,6 +117,24 @@ def set_value(self, panel_id: str, value_id: str, value: object, notify: bool) -
115117
)
116118
self._invoke_with_retry(self._get_stub().SetValue, set_value_request)
117119

120+
def set_values(self, panel_id: str, values: dict[str, object], notify: bool) -> None:
121+
"""Set multiple values for controls in a panel at once.
122+
123+
Args:
124+
panel_id: The ID of the panel.
125+
values: A dictionary mapping value IDs to their corresponding values.
126+
notify: Whether to notify other clients of the new values.
127+
"""
128+
value_informations = []
129+
for value_id, value in values.items():
130+
new_any = to_any(value)
131+
value_informations.append(ValueInformation(value_id=value_id, value=new_any))
132+
133+
set_values_request = SetValuesRequest(
134+
panel_id=panel_id, values=value_informations, notify=notify
135+
)
136+
self._invoke_with_retry(self._get_stub().SetValues, set_values_request)
137+
118138
def get_value(self, panel_id: str, value_id: str) -> object:
119139
"""Get the value for the control with value_id.
120140

src/nipanel/_panel_value_accessor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,11 @@ def set_value(self, value_id: str, value: object) -> None:
8282
self._panel_client.set_value(
8383
self._panel_id, value_id, value, notify=self._notify_on_set_value
8484
)
85+
86+
def set_values(self, values: dict[str, object]) -> None:
87+
"""Set multiple values for controls on the panel.
88+
89+
Args:
90+
values: A dictionary mapping value IDs to their corresponding values.
91+
"""
92+
self._panel_client.set_values(self._panel_id, values, notify=self._notify_on_set_value)

tests/unit/test_panel_client.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,52 @@ def test___set_value___gets_value(fake_panel_channel: grpc.Channel) -> None:
7676
assert client.get_value("panel1", "val1") == "value1"
7777

7878

79+
def test___set_values___enumerate_panels_shows_values(
80+
fake_panel_channel: grpc.Channel,
81+
) -> None:
82+
client = create_panel_client(fake_panel_channel)
83+
84+
values = {"val1": "value1", "val2": 42, "val3": True}
85+
86+
client.set_values("panel1", values, notify=False)
87+
88+
assert client.enumerate_panels() == {"panel1": ("", ["val1", "val2", "val3"])}
89+
90+
91+
def test___set_values___gets_values(fake_panel_channel: grpc.Channel) -> None:
92+
client = create_panel_client(fake_panel_channel)
93+
94+
values = {"val1": "value1", "val2": 42, "val3": True}
95+
96+
client.set_values("panel1", values, notify=False)
97+
98+
assert client.get_value("panel1", "val1") == "value1"
99+
assert client.get_value("panel1", "val2") == 42
100+
assert client.get_value("panel1", "val3") is True
101+
102+
103+
def test___set_values_empty_dict___has_no_effect(fake_panel_channel: grpc.Channel) -> None:
104+
client = create_panel_client(fake_panel_channel)
105+
106+
client.set_values("panel1", {}, notify=False)
107+
108+
assert client.enumerate_panels() == {"panel1": ("", [])}
109+
110+
111+
def test___set_values_then_set_value___both_values_accessible(
112+
fake_panel_channel: grpc.Channel,
113+
) -> None:
114+
client = create_panel_client(fake_panel_channel)
115+
116+
client.set_values("panel1", {"val1": "batch value", "val2": 42}, notify=False)
117+
client.set_value("panel1", "val3", "individual value", notify=False)
118+
119+
assert client.get_value("panel1", "val1") == "batch value"
120+
assert client.get_value("panel1", "val2") == 42
121+
assert client.get_value("panel1", "val3") == "individual value"
122+
assert client.enumerate_panels() == {"panel1": ("", ["val1", "val2", "val3"])}
123+
124+
79125
def create_panel_client(fake_panel_channel: grpc.Channel) -> PanelClient:
80126
return PanelClient(
81127
provided_interface="iface",

0 commit comments

Comments
 (0)