PyAres 0.1.4__py3-none-any.whl

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.
@@ -0,0 +1,27 @@
1
+ from ares_datamodel import request_metadata_pb2
2
+ from enum import Enum
3
+
4
+ class AresDataType(Enum):
5
+ UNKNOWN = 0
6
+ NULL = 1
7
+ BOOLEAN = 2
8
+ STRING = 3
9
+ NUMBER = 4
10
+ STRING_ARRAY = 5
11
+ NUMBER_ARRAY = 6
12
+ BYTE_ARRAY = 7
13
+ BOOL_ARRAY = 8
14
+
15
+ class Outcome(Enum):
16
+ UNSPECIFIED_OUTCOME = 0
17
+ SUCCESS = 1
18
+ FAILURE = 2
19
+ WARNING = 3
20
+ CANCELED = 4
21
+
22
+ class RequestMetadata():
23
+ def __init__(self, proto_metadata: request_metadata_pb2.RequestMetadata):
24
+ self.system_name = proto_metadata.system_name
25
+ self.campaign_name = proto_metadata.campaign_name
26
+ self.campaign_id = proto_metadata.campaign_id
27
+ self.experiment_id = proto_metadata.experiment_id
@@ -0,0 +1,9 @@
1
+ from .planner_models import PlanningParameter, PlanRequest, PlanResponse, ParameterHistoryItem
2
+ from .planning_service import AresPlannerService
3
+
4
+ __all__ = [
5
+ "PlanningParameter",
6
+ "PlanRequest",
7
+ "PlanResponse",
8
+ "AresPlannerService"
9
+ ]
@@ -0,0 +1,83 @@
1
+ from typing import Dict, Any, List
2
+ from ..Models import Outcome, AresDataType, RequestMetadata
3
+
4
+ class ParameterHistoryItem:
5
+ """ Represents a single historical parameter item """
6
+ def __init__(self, planned_value: Any, achieved_value: Any):
7
+ """
8
+ Initializes a ParameterHistoryItem
9
+
10
+ Args:
11
+ planned_value: A value that was planned
12
+ achieved_value: Optionally a value that was actually achieved for what was planned
13
+ """
14
+ self.planned_value = planned_value
15
+ self.achieved_value = achieved_value
16
+
17
+ class PlanningParameter:
18
+ """
19
+ Represents a single parameter within a planning request.
20
+
21
+ Designed to provide a more user-friendly abstraction for the user to interact
22
+ with planning parameters through.
23
+ """
24
+ def __init__(self, name: str, minimum_value: float,
25
+ maximum_value: float, param_history: list[ParameterHistoryItem], data_type: AresDataType,
26
+ is_planned: bool, is_result: bool, planner_name: str, initial_value = None):
27
+ """
28
+ Initializes a PlanningParameter.
29
+
30
+ Args:
31
+ name: The name or key associated with the parameter.
32
+ minimum_value: The minimum value the parameter is capable of being assigned.
33
+ maximum_value: The maximum value the parameter is capable of being assigned.
34
+ param_history: A list of historical planned and achieved values associated with the parameter.
35
+ data_type: The data type associated with the parameter.
36
+ is_planned: A bool representing whether this parameter is designed to be planned for.
37
+ is_result: A bool representing whether this parameter is the intended result of the experiment.
38
+ planner_name: The name of the planner ARES requested be used to plan for this parameter.
39
+ initial_value: An optional initial value for the given parameter
40
+ """
41
+ self.name : str = name
42
+ self.minimum_value : float = minimum_value
43
+ self.maximum_value : float= maximum_value
44
+ self.param_history : List = param_history
45
+ self.data_type : AresDataType = data_type
46
+ self.is_planned : bool = is_planned
47
+ self.is_result : bool = is_result
48
+ self.planner_name : str = planner_name
49
+ self.initial_value = initial_value
50
+
51
+ class PlanRequest:
52
+ """
53
+ Represents a PlanRequest message received from ARES.
54
+
55
+ Designed to provide a more user-friendly abstraction for interacting with a plan request message.
56
+ """
57
+ def __init__(self, parameters: list[PlanningParameter], settings: Dict[str, Any], analysis_results: list[float], metadata: RequestMetadata):
58
+ """
59
+ Initializes a PlanRequest.
60
+
61
+ Args:
62
+ parameters: A list of PlanningParameter objects.
63
+ """
64
+ self.parameters = parameters
65
+ self.settings = settings
66
+ self.analysis_results = analysis_results
67
+ self.request_metadata = metadata
68
+
69
+
70
+ class PlanResponse:
71
+ """ Represents a PlanResponse message to be send to ARES. """
72
+ def __init__(self, parameter_names: list[str], parameter_values: list, planning_outcome: Outcome = Outcome.SUCCESS, error_string: str = ""):
73
+ """
74
+ Initializes a PlanResponse.
75
+
76
+ Args:
77
+ parameter_names: A list of names associated with planned parameters.
78
+ parameter_values: A list of values associated with planned parameters.
79
+ """
80
+ self.parameter_names = parameter_names
81
+ self.parameter_values = parameter_values
82
+ self.outcome = planning_outcome
83
+ self.error_string = error_string
@@ -0,0 +1,252 @@
1
+ import grpc
2
+ from concurrent import futures
3
+ from typing import Callable, Awaitable, Union, Dict
4
+
5
+ from ares_datamodel.planning.remote import ares_remote_planner_service_pb2 as planner_service
6
+ from ares_datamodel.planning.remote import ares_remote_planner_service_pb2_grpc as planner_service_grpc
7
+ from ares_datamodel.planning import planner_pb2
8
+ from ares_datamodel.planning import planner_settings_pb2
9
+ from ares_datamodel.planning import planner_service_capabilities_pb2
10
+ from ares_datamodel.planning import plan_pb2
11
+ from ares_datamodel import ares_data_schema_pb2
12
+ from ares_datamodel import ares_data_type_pb2
13
+ from ares_datamodel import ares_outcome_enum_pb2
14
+ from ares_datamodel.connection import connection_state_pb2
15
+ from ares_datamodel.connection import connection_info_pb2
16
+
17
+ # Import Utilities
18
+ from ..Utils import ares_value_utils
19
+ from ..Utils import ares_data_schema_utils
20
+ from ..Utils import ares_data_type_utils
21
+ from ..Utils import ares_struct_utils
22
+ from ..Utils import ares_outcome_utils
23
+
24
+ # Import python models
25
+ from ..Models import ares_data_models
26
+ from .planner_models import *
27
+
28
+ # Type hint for the user's custom planning logic
29
+ PlanLogicFunction = Callable[[PlanRequest], Union[PlanResponse, Awaitable[PlanResponse]]]
30
+
31
+ class AresPlannerServiceWrapper(planner_service_grpc.AresRemotePlannerServiceServicer):
32
+ """
33
+ A wrapper around the gRPC service to expose native Python objects for planning
34
+ """
35
+ def __init__(self, service_name: str, description: str, version: str, timeout: int, custom_plan_logic: PlanLogicFunction):
36
+ self._custom_plan_logic: PlanLogicFunction = custom_plan_logic
37
+ self._service_name: str = service_name
38
+ self._description: str = description
39
+ self._version: str = version
40
+ self._settings: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
41
+ self._planner_options: list[planner_pb2.Planner] = []
42
+ self._supported_types: list[ares_data_type_pb2.AresDataType] = []
43
+ self._timeout: int = timeout
44
+
45
+ def GetPlannerServiceCapabilities(self, request, context) -> planner_service_capabilities_pb2.PlannerServiceCapabilities:
46
+ print("Capabilities Requested!")
47
+ """
48
+ Implements the gRPC Capabilities request method. Responsible for telling ARES what this planner
49
+ service is capable of.
50
+ """
51
+ capabilities = planner_service_capabilities_pb2.PlannerServiceCapabilities(timeout_seconds=self._timeout)
52
+ capabilities.service_name = self._service_name
53
+ capabilities.accepted_types.extend(self._supported_types)
54
+ capabilities.available_planners.extend(self._planner_options)
55
+
56
+ for(key, value) in self._settings.items():
57
+ settings_entry: ares_data_schema_pb2.SchemaEntry = capabilities.settings_schema.fields[key]
58
+ settings_entry.type = value.type
59
+ settings_entry.optional - value.optional
60
+
61
+ if len(value.string_choices.strings) != 0:
62
+ settings_entry.string_choices.strings.extend(value.string_choices.strings)
63
+
64
+ elif len(value.number_choices.numbers) != 0:
65
+ settings_entry.number_choices.numbers.extend(value.number_choices.numbers)
66
+
67
+ print("Capabilites Sent!")
68
+ return capabilities
69
+
70
+ def GetInfo(self, request, context) -> connection_info_pb2.InfoResponse:
71
+ try:
72
+ response = connection_info_pb2.InfoResponse(
73
+ name=self._service_name,
74
+ version=self._version,
75
+ description=self._description
76
+ )
77
+
78
+ return response
79
+
80
+ except Exception as e:
81
+ response = connection_info_pb2.InfoResponse(
82
+ name="ERROR",
83
+ version="ERROR",
84
+ description="Error fetching information"
85
+ )
86
+ print(f"Exception while trying to respond to ARES with information! {e}")
87
+ return response
88
+
89
+ def GetState(self, request, context) -> connection_state_pb2.StateResponse:
90
+ try:
91
+ return connection_state_pb2.StateResponse(state=connection_state_pb2.State.ACTIVE, state_message=f"{self._service_name} is active!")
92
+
93
+ except Exception as e:
94
+ print(f"{e}")
95
+ return connection_state_pb2.StateResponse(state=connection_state_pb2.State.ERROR, state_message=f"Exception while trying to respond to ARES with state! {e}")
96
+
97
+
98
+
99
+ def GetConnectionStatus(self, request, context):
100
+ try:
101
+ return connection_state_pb2.StateResponse(state=connection_state_pb2.State.ACTIVE, state_message=f"{self._service_name} is active!")
102
+
103
+ except Exception as e:
104
+ print(f"Exception while trying to respond to ARES with connection status! {e}")
105
+
106
+
107
+ def Plan(self, request: plan_pb2.PlanningRequest, context) -> plan_pb2.PlanningResponse:
108
+ """
109
+ Implements the gRPC Plan method. This method converts protobuf requests to native Python objects
110
+ before executing the users custom planning logic and converting their response back to protobuf.
111
+ """
112
+ parameters = []
113
+ for proto_param in request.planning_parameters:
114
+ parameters.append(
115
+ PlanningParameter
116
+ (
117
+ name=proto_param.parameter_name,
118
+ maximum_value=proto_param.maximum_value,
119
+ minimum_value=proto_param.minimum_value,
120
+ param_history=[ParameterHistoryItem(ares_value_utils.ares_value_to_py(val.planned_value), ares_value_utils.ares_value_to_py(val.achieved_value)) for val in proto_param.parameter_history],
121
+ data_type=ares_data_type_utils.proto_ares_type_to_python_ares_type(proto_param.data_type),
122
+ is_planned=proto_param.is_planned,
123
+ is_result=proto_param.is_result,
124
+ planner_name=proto_param.planner_name,
125
+ initial_value=ares_value_utils.ares_value_to_py(proto_param.initial_value)
126
+ ))
127
+
128
+ python_request = PlanRequest(parameters=parameters,
129
+ settings=ares_struct_utils.ares_struct_to_dict(request.adapter_settings),
130
+ analysis_results=list(request.analysis_results),
131
+ metadata=RequestMetadata(request.metadata))
132
+
133
+ #Handle call using the user's custom planning logic
134
+ response_proto = plan_pb2.PlanningResponse()
135
+ try:
136
+ python_response = self._custom_plan_logic(python_request)
137
+ if isinstance(python_response, Awaitable):
138
+ python_response = python_response.__await__()
139
+
140
+ except Exception as e:
141
+ #Handle errors from user's logic
142
+ context.set_code(grpc.StatusCode.INTERNAL)
143
+ context.set_details(f"Error in custom planning logic: {e}")
144
+ response_proto.error_string = f"{e}"
145
+ response_proto.planning_outcome = ares_outcome_enum_pb2.FAILURE
146
+ return response_proto
147
+
148
+ if not isinstance(python_response, PlanResponse):
149
+ response_proto.error_string = "The returned response from the user planning method was not a plan response, and thus was invalid."
150
+ response_proto.planning_outcome = ares_outcome_enum_pb2.FAILURE
151
+ return response_proto
152
+
153
+ response_proto.planning_outcome = ares_outcome_utils.python_ares_outcome_to_proto_ares_outcome(python_response.outcome)
154
+ response_proto.error_string = python_response.error_string
155
+
156
+ for i in range(len(python_response.parameter_names)):
157
+ planned_parameter = plan_pb2.PlannedParameter(parameter_value=ares_value_utils.create_ares_value(python_response.parameter_values[i]))
158
+ planned_parameter.parameter_name = python_response.parameter_names[i]
159
+ new_planned_parameter = response_proto.planned_parameters.add()
160
+ new_planned_parameter.CopyFrom(planned_parameter)
161
+
162
+ print("Sending Plan Response.....")
163
+ return response_proto
164
+
165
+ class AresPlannerService:
166
+ """
167
+ Manages the gRPC server for the AresPlannerService
168
+ """
169
+ def __init__(self, custom_plan_logic: PlanLogicFunction, service_name: str, service_description: str, service_version: str, timeout: int = 30, use_localhost: bool = True, port: int = 7082):
170
+ """
171
+ Initializes the AresPlannerService
172
+
173
+ Args:
174
+ custom_plan_logic: A callable function that will be executed when a PlanRequest is received.
175
+ This function should accept a 'PyARES.AresPlanning.PlanRequest' object and return a
176
+ 'PyARES.AresPlanning.PlanResponse' object (or an awaitable that resolves to one).
177
+ service_name: The name descriptor that is associated with your planner service.
178
+ service_description: A brief description describing your implementation of the planner service.
179
+ service_version: The version of your planner service.
180
+ use_localhost: An optional value that allows the user to specify whether to host the service on the local network. Defaults to True.
181
+ port: The port that your planner service will serve on. Defaults to port 7082.
182
+ """
183
+ #Public Values, designed to be accessible to the user
184
+ self.service_name = service_name
185
+ self.service_description = service_description
186
+ self.service_version = service_version
187
+
188
+ #Private values, mostly related to the service
189
+ self._port = port
190
+ self._server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
191
+ self._service_wrapper = AresPlannerServiceWrapper(service_name, service_description, service_version, timeout, custom_plan_logic)
192
+ planner_service_grpc.add_AresRemotePlannerServiceServicer_to_server(self._service_wrapper, self._server)
193
+ if(use_localhost):
194
+ self._server.add_insecure_port(f'localhost:{self._port}')
195
+ else:
196
+ self._server.add_insecure_port(f'[::]:{self._port}')
197
+
198
+ def add_planner_option(self, planner_name: str, planner_description: str, planner_version: str):
199
+ """
200
+ Adds a planner option that is reported to ARES when your services capabilities are requested.
201
+
202
+ Args:
203
+ planner_name (str): The dedicated name of your planner.
204
+ planner_description (str): A brief description of your planner that is displayed in ARES.
205
+ planner_version (str): The version of your planner.
206
+ """
207
+ self._service_wrapper._planner_options.append(planner_pb2.Planner(planner_name=planner_name, description=planner_description, version=planner_version))
208
+
209
+ def add_setting(self, setting_name: str, setting_type: ares_data_models.AresDataType, optional: bool = True, constraints: Union[list[int], list[str], list[float]] = []):
210
+ """
211
+ Adds a planner setting to be reported to ARES when your services capabilities are requested.
212
+
213
+ Args:
214
+ setting_name (str): The name of the setting.
215
+ setting_type (AresDataType): The type of this settings value.
216
+ optional (bool): Whether the setting is optional.
217
+ constraints: An optional list of values to constrain the available setting choices. Can be integers, strings, or floats.
218
+ """
219
+ self._service_wrapper._settings[setting_name] = ares_data_schema_utils.create_settings_schema_entry(setting_type, optional, constraints)
220
+
221
+ def add_supported_type(self, type: ares_data_models.AresDataType):
222
+ """
223
+ Adds the specified type to the list of value types your planenr service accepts.
224
+
225
+ Args:
226
+ type (AresDataType): The type being added to the list of allowed types.
227
+ """
228
+ self._service_wrapper._supported_types.append(ares_data_type_utils.python_ares_type_to_proto_ares_type(type))
229
+
230
+ def set_timeout(self, new_timeout: int):
231
+ """
232
+ Sets the time, in seconds, that ARES will wait to receive a response from this service.
233
+
234
+ Args:
235
+ new_timeout: The time to be assigned as the new timeout value
236
+ """
237
+ self._service_wrapper._timeout = new_timeout
238
+
239
+ def start(self):
240
+ """
241
+ Starts the service on the specified port, and waits for termination.
242
+ """
243
+ print(f"Starting Ares Planner Service on port {self._port}...")
244
+ self._server.start()
245
+ self._server.wait_for_termination()
246
+
247
+ def stop(self):
248
+ """
249
+ Stops the service, terminating the connection.
250
+ """
251
+ print("Stopping Ares Planning Service...")
252
+ self._server.stop(0).wait()
@@ -0,0 +1,27 @@
1
+ from PyAres import AresAnalyzerService, AnalysisRequest, Analysis, AresDataType, Outcome
2
+
3
+ def analyze(request: AnalysisRequest) -> Analysis:
4
+ #Custom Analysis Logic
5
+ temperature = request.inputs.get("Temperature")
6
+
7
+ if not isinstance(temperature, float):
8
+ print("Temperature was not a float")
9
+ temperature = 0.0
10
+
11
+ print(f"Temperature: {temperature}")
12
+
13
+ analysis = Analysis(result=temperature)
14
+ return analysis
15
+
16
+
17
+ if __name__ == "__main__":
18
+ #Basic details about your analyzer
19
+ name = "Python Test Analyzer"
20
+ version = "0.0.1"
21
+ description = "This is a test analyzer to demonstrate working with PyAres to create analyzers!"
22
+ pythonDemoAnalyzer = AresAnalyzerService(analyze, name, version, description)
23
+
24
+ #Add Analysis Parameters
25
+ pythonDemoAnalyzer.add_analysis_parameter("Temperature", AresDataType.NUMBER)
26
+
27
+ pythonDemoAnalyzer.start()
@@ -0,0 +1,48 @@
1
+ from PyAres import AresDeviceService, DeviceCommandDescriptor, DeviceSchemaEntry, AresDataType
2
+ from typing import Dict
3
+ import time
4
+
5
+ class DemoDevice:
6
+ # A simulated device. In reality, these communications would be happening with external hardware over serial, usb, etc.
7
+ def __init__(self):
8
+ self.temperature = 0.0
9
+
10
+ def set_temperature(self, temperature: float):
11
+ self.temperature = temperature
12
+ time.sleep(5)
13
+ return {}
14
+
15
+ def get_temperature(self):
16
+ return { "temperature": self.temperature }
17
+
18
+ def get_device_state(self) -> Dict:
19
+ state_dict = { "temperature": self.temperature }
20
+ return state_dict
21
+
22
+ def enter_safe_mode(self):
23
+ self.temperature = 0
24
+
25
+ device = DemoDevice()
26
+
27
+ if __name__ == "__main__":
28
+ # Basic information about my device
29
+ device_name = "Demo Device"
30
+ description = "A device to demonstrate the PyAres device capabilities"
31
+ version = "1.0.0"
32
+ device_service = AresDeviceService(device.enter_safe_mode, device.get_device_state, device_name, description, version)
33
+
34
+ # Create the "Set Temperature" Command
35
+ parameter_schema = DeviceSchemaEntry(AresDataType.NUMBER, "A numeric temperature value", "Degree's Celsius")
36
+ input_schema = { "temperature": parameter_schema }
37
+ set_temp_descriptor = DeviceCommandDescriptor("Set Temperature", "Set's the temperature of the demo device to the provided value.", input_schema, {})
38
+ device_service.add_new_command(set_temp_descriptor, device.set_temperature)
39
+
40
+ # Create the "Get Temperature" Command
41
+ output_schema = {"temperature": DeviceSchemaEntry(AresDataType.NUMBER, "The current temperature of the device", "Degree's Celsius")}
42
+ get_temp_desc = DeviceCommandDescriptor("Get Temperature", "Get's the current temperature of the demo device.", {}, output_schema)
43
+ device_service.add_new_command(get_temp_desc, device.get_temperature)
44
+
45
+ #Add Settings
46
+ device_service.add_setting("Allow Negative Values", True)
47
+
48
+ device_service.start()
@@ -0,0 +1,85 @@
1
+ from PyAres import *
2
+
3
+ import random
4
+
5
+ def plan(request: PlanRequest) -> PlanResponse:
6
+ print("Planning Requested!")
7
+ new_values = []
8
+ gpdoods = []
9
+ names = []
10
+
11
+ for param in request.parameters:
12
+ if param.planner_name == "GPRDood":
13
+ gpdoods.append(param)
14
+
15
+ if param.planner_name == "Random Planner":
16
+ new_value = random_planner(param)
17
+ new_values.append(new_value)
18
+ names.append(param.name)
19
+
20
+ elif param.planner_name == "Gradual Planner":
21
+ new_value = gradual_planner(param)
22
+ new_values.append(new_value)
23
+ names.append(param.name)
24
+
25
+ else:
26
+ print("Invalid planner name detected... defaulting to random")
27
+ new_value = random_planner(param)
28
+ new_values.append(new_value)
29
+ names.append(param.name)
30
+
31
+ return PlanResponse(parameter_names=names, parameter_values=new_values)
32
+
33
+ def random_planner(param: PlanningParameter) -> float:
34
+ if param.data_type == AresDataType.NUMBER:
35
+ return random.uniform(param.minimum_value, param.maximum_value)
36
+
37
+ else:
38
+ print("Found a non-number....")
39
+ return 0
40
+
41
+ def gradual_planner(param: PlanningParameter) -> float:
42
+ if(param.data_type == AresDataType.NUMBER):
43
+ if len(param.param_history) == 0:
44
+ return param.minimum_value
45
+
46
+ previous_value = param.param_history[-1].planned_value
47
+ previous_value += 5
48
+
49
+ if previous_value > param.maximum_value:
50
+ return param.minimum_value
51
+
52
+ else:
53
+ return previous_value
54
+
55
+ else:
56
+ return 0
57
+
58
+ if __name__ == "__main__":
59
+ #Basic details about your planner
60
+ name = "Python Test Planner"
61
+ version = "1.0.0"
62
+ description = "This is a test planner to demonstrate working with PyAres to create planners!"
63
+ pythonDemoPlanner = AresPlannerService(plan, name, description, version)
64
+
65
+ #Add Supported Types
66
+ pythonDemoPlanner.add_supported_type(AresDataType.NUMBER)
67
+
68
+ #Add Planner Options
69
+ pythonDemoPlanner.add_planner_option("Random Planner", "A planner that returns random values", "1.0.0")
70
+ pythonDemoPlanner.add_planner_option("Gradual Planner", "A planner that gradually increases a value based on the values history", "1.0.0")
71
+
72
+ #Add Planner Settings
73
+ pythonDemoPlanner.add_setting("String Setting", AresDataType.STRING)
74
+ pythonDemoPlanner.add_setting("Number Setting", AresDataType.NUMBER)
75
+ pythonDemoPlanner.add_setting("Boolean Setting", AresDataType.BOOLEAN)
76
+ pythonDemoPlanner.add_setting("String Array Setting", AresDataType.STRING_ARRAY)
77
+ pythonDemoPlanner.add_setting("Constrained Strings", AresDataType.STRING_ARRAY, True, ["One", "Two", "Three"])
78
+ pythonDemoPlanner.add_setting("Number Array Setting", AresDataType.NUMBER_ARRAY)
79
+ pythonDemoPlanner.add_setting("Constrained Numbers", AresDataType.NUMBER_ARRAY, True, [1, 2, 3])
80
+
81
+ #Set Planner Timeout
82
+ pythonDemoPlanner.set_timeout(60)
83
+
84
+ #Start Your Planner Service
85
+ pythonDemoPlanner.start()
@@ -0,0 +1,44 @@
1
+ from typing import Union, Dict
2
+
3
+ #Datamodel Imports
4
+ from ares_datamodel import ares_data_schema_pb2
5
+
6
+ from ..Models import ares_data_models
7
+ # def ares_schema_to_dict(schema: ares_data_schema_pb2.AresDataSchema) -> dict:
8
+ # """Converts an AresDataSchemaSimplified to a dictionary for user logic."""
9
+ # result = {}
10
+ # for key, entry in schema.fields.items():
11
+ # result[key] = {
12
+ # "type": ares_data_schema_pb2.AresDataType.Name(entry.type),
13
+ # "is_array": entry.is_array
14
+ # }
15
+ # return result
16
+
17
+ def create_settings_schema_entry(
18
+ setting_type: ares_data_models.AresDataType,
19
+ optional: bool,
20
+ choices: Union[list[str], list[int], list[float]]) -> ares_data_schema_pb2.SchemaEntry:
21
+ """
22
+ Takes in an AresSetting object and converts it into the protobuf SchemaEntry message.
23
+
24
+ Args:
25
+ new_setting: The AresSetting object that provides all the details around the setting implementation.
26
+
27
+ Returns:
28
+ (SchemaEntry): A new SchemaEntry message.
29
+ """
30
+
31
+ if(isinstance(choices, list)):
32
+ if(len(choices) == 0):
33
+ schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
34
+
35
+ elif(all(isinstance(item, str) for item in choices)):
36
+ schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
37
+ schema_entry.string_choices.strings.extend(choices)
38
+
39
+ elif(all(isinstance(item, (int, float)) for item in choices)):
40
+ schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
41
+ schema_entry.number_choices.numbers.extend(choices)
42
+
43
+ return schema_entry
44
+
@@ -0,0 +1,36 @@
1
+ from typing import Union
2
+ from ares_datamodel import ares_data_type_pb2
3
+ from ..Models import AresDataType
4
+
5
+ def python_ares_type_to_proto_ares_type(py_value: AresDataType) -> ares_data_type_pb2.AresDataType:
6
+ """ A method to convert from the python AresDataType class to the protobuf version """
7
+ return py_value.value
8
+
9
+ def proto_ares_type_to_python_ares_type(proto_value: ares_data_type_pb2.AresDataType) -> AresDataType:
10
+ """ A method to convert from the protobuf AresDataType class to the python version """
11
+ return AresDataType(proto_value)
12
+
13
+ def determine_python_ares_data_type(value: Union[int, float, str, bool, list]):
14
+ """ A method that takes in a value and returns the corresponding `PyAres.Models.AresDataType`"""
15
+ match value:
16
+ case str():
17
+ return AresDataType.STRING
18
+ case bool():
19
+ # Boolean is a subtype of int, meaning we have to check if it's a bool
20
+ # before we check if it's an int, otherwise every bool would return an int instead.
21
+ return AresDataType.BOOLEAN
22
+ case int():
23
+ return AresDataType.NUMBER
24
+ case float():
25
+ return AresDataType.NUMBER
26
+ case list():
27
+ if(all(isinstance(x, str) for x in value)):
28
+ return AresDataType.STRING_ARRAY
29
+ elif(all(isinstance(x, (int, float)) for x in value)):
30
+ return AresDataType.NUMBER_ARRAY
31
+ elif(all(isinstance(x, bool)) for x in value):
32
+ return AresDataType.BOOL_ARRAY
33
+ else:
34
+ return AresDataType.UNKNOWN
35
+ case _:
36
+ return AresDataType.UNKNOWN
@@ -0,0 +1,43 @@
1
+ from typing import Union
2
+
3
+ from ..Device import DeviceCommandDescriptor
4
+ from ..Device import DeviceSchemaEntry
5
+ from ares_datamodel.device import device_command_descriptor_pb2
6
+ from ares_datamodel import ares_data_schema_pb2
7
+
8
+ from . import ares_data_type_utils
9
+
10
+ def python_command_description_to_proto(python_description: DeviceCommandDescriptor) -> device_command_descriptor_pb2.DeviceCommandDescriptor:
11
+ proto_description = device_command_descriptor_pb2.DeviceCommandDescriptor()
12
+
13
+ #First, we need to transform our Python DeviceSchemaEntry classes into the protobuf equivalent. Then add them to our new proto message
14
+ transformed_input_schema = {key: python_device_schema_entry_to_proto(value) for key, value in python_description.input_schema.items()}
15
+ for key, value in transformed_input_schema.items():
16
+ new_entry: ares_data_schema_pb2.SchemaEntry = proto_description.input_schema.fields[key]
17
+ new_entry.CopyFrom(value)
18
+
19
+ transformed_output_schema = {key: python_device_schema_entry_to_proto(value) for key, value in python_description.output_schema.items()}
20
+ for key, value in transformed_output_schema.items():
21
+ new_entry: ares_data_schema_pb2.SchemaEntry = proto_description.output_schema.fields[key]
22
+ new_entry.CopyFrom(value)
23
+
24
+ proto_description.name = python_description.name
25
+ proto_description.description = python_description.description
26
+
27
+ return proto_description
28
+
29
+
30
+ def python_device_schema_entry_to_proto(entry: DeviceSchemaEntry) -> ares_data_schema_pb2.SchemaEntry:
31
+ proto_schema = ares_data_schema_pb2.SchemaEntry()
32
+ proto_schema.type = ares_data_type_utils.python_ares_type_to_proto_ares_type(entry.type)
33
+ proto_schema.optional = entry.optional
34
+ proto_schema.description = entry.description
35
+ proto_schema.unit = entry.unit
36
+
37
+ if all(isinstance(x, (int, float)) for x in entry.contraints):
38
+ proto_schema.number_choices.numbers.extend(entry.contraints)
39
+
40
+ elif all(isinstance(x, str) for x in entry.contraints):
41
+ proto_schema.string_choices.strings.extend(entry.contraints)
42
+
43
+ return proto_schema
@@ -0,0 +1,10 @@
1
+ from ..Models import Outcome
2
+ from ares_datamodel import ares_outcome_enum_pb2
3
+
4
+ def python_ares_outcome_to_proto_ares_outcome(py_value: Outcome) -> ares_outcome_enum_pb2.Outcome:
5
+ """ A method to convert from the python AresDataType class to the protobuf version """
6
+ return py_value.value
7
+
8
+ def proto_ares_outcome_to_python_ares_outcome(proto_value: ares_outcome_enum_pb2.Outcome) -> Outcome:
9
+ """ A method to convert from the protobuf AresDataType class to the python version """
10
+ return Outcome(proto_value)