PyAres 0.3.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {pyares-0.3.2 → pyares-0.4.0}/PKG-INFO +2 -4
  2. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Analyzing/analysis_service.py +3 -3
  3. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/device_test.py +6 -4
  4. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/rotary_mixer.py +19 -1
  5. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Device/device_models.py +20 -4
  6. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Device/device_service.py +8 -5
  7. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Models/__init__.py +4 -2
  8. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Models/ares_data_models.py +25 -2
  9. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Planning/planning_service.py +2 -10
  10. pyares-0.4.0/PyAres/Utils/ares_data_schema_utils.py +59 -0
  11. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/ares_data_type_utils.py +9 -1
  12. pyares-0.4.0/PyAres/Utils/ares_device_command_utils.py +65 -0
  13. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/ares_value_utils.py +43 -13
  14. {pyares-0.3.2 → pyares-0.4.0}/PyAres/__init__.py +4 -1
  15. {pyares-0.3.2 → pyares-0.4.0}/pyproject.toml +2 -4
  16. {pyares-0.3.2 → pyares-0.4.0}/tests/test_analyzer.py +2 -2
  17. {pyares-0.3.2 → pyares-0.4.0}/tests/test_ares_data_schema_utils.py +1 -3
  18. {pyares-0.3.2 → pyares-0.4.0}/tests/test_ares_device.py +1 -1
  19. pyares-0.3.2/PyAres/Utils/ares_data_schema_utils.py +0 -49
  20. pyares-0.3.2/PyAres/Utils/ares_device_command_utils.py +0 -43
  21. {pyares-0.3.2 → pyares-0.4.0}/.github/dependabot.yml +0 -0
  22. {pyares-0.3.2 → pyares-0.4.0}/.github/workflows/pypi.yaml +0 -0
  23. {pyares-0.3.2 → pyares-0.4.0}/.github/workflows/python-tests.yaml +0 -0
  24. {pyares-0.3.2 → pyares-0.4.0}/.gitignore +0 -0
  25. {pyares-0.3.2 → pyares-0.4.0}/LICENSE +0 -0
  26. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Analyzing/__init__.py +0 -0
  27. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Analyzing/analyzer_models.py +0 -0
  28. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/analyzer_test.py +0 -0
  29. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/analyzer_test_tools_demo.py +0 -0
  30. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/analyzer_wiki.py +0 -0
  31. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/hotplate.py +0 -0
  32. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/planner_test.py +0 -0
  33. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/planner_test_tools_demo.py +0 -0
  34. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Demo/planner_wiki.py +0 -0
  35. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Device/__init__.py +0 -0
  36. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Device/device_warnings.py +0 -0
  37. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Planning/__init__.py +0 -0
  38. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Planning/planner_models.py +0 -0
  39. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/ares_outcome_utils.py +0 -0
  40. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/ares_struct_utils.py +0 -0
  41. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/param_history_info_utils.py +0 -0
  42. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/plan_request_utils.py +0 -0
  43. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/plan_response_utils.py +0 -0
  44. {pyares-0.3.2 → pyares-0.4.0}/PyAres/Utils/planning_param_utils.py +0 -0
  45. {pyares-0.3.2 → pyares-0.4.0}/PyAres/test_tools/__init__.py +0 -0
  46. {pyares-0.3.2 → pyares-0.4.0}/PyAres/test_tools/analyzer_tester.py +0 -0
  47. {pyares-0.3.2 → pyares-0.4.0}/PyAres/test_tools/planner_tester.py +0 -0
  48. {pyares-0.3.2 → pyares-0.4.0}/README.md +0 -0
  49. {pyares-0.3.2 → pyares-0.4.0}/setup.cfg +0 -0
  50. {pyares-0.3.2 → pyares-0.4.0}/setup.py +0 -0
  51. {pyares-0.3.2 → pyares-0.4.0}/tests/__init__.py +0 -0
  52. {pyares-0.3.2 → pyares-0.4.0}/tests/mock_grpc_context.py +0 -0
  53. {pyares-0.3.2 → pyares-0.4.0}/tests/test_ares_data_type_utils.py +0 -0
  54. {pyares-0.3.2 → pyares-0.4.0}/tests/test_ares_struct_utils.py +0 -0
  55. {pyares-0.3.2 → pyares-0.4.0}/tests/test_ares_value_type_conversions.py +0 -0
  56. {pyares-0.3.2 → pyares-0.4.0}/tests/test_planner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAres
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: The official Python library for ARES.
5
5
  Project-URL: Homepage, https://github.com/AFRL-ARES/PyAres
6
6
  Author-email: AFRL <AFRL.RX.AREScontact@us.af.mil>
@@ -9,9 +9,7 @@ License-File: LICENSE
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Requires-Python: >=3.10
12
- Requires-Dist: ares-datamodel>=0.16.0
13
- Requires-Dist: grpcio-tools>=1.75.1
14
- Requires-Dist: grpcio<2.0.0,>=1.71.0
12
+ Requires-Dist: ares-datamodel>=0.20.1
15
13
  Description-Content-Type: text/markdown
16
14
 
17
15
  # PyAres
@@ -36,8 +36,8 @@ class AresAnalyzerServiceWrapper(analyzer_service_grpc.AresRemoteAnalyzerService
36
36
  self._info = info
37
37
  self._timeout = timeout
38
38
  self._custom_analysis_logic = custom_analysis_logic
39
- self._settings: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
40
- self._analysis_parameters: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
39
+ self._settings: Dict[str, ares_data_schema_pb2.AresValueSchema] = {}
40
+ self._analysis_parameters: Dict[str, ares_data_schema_pb2.AresValueSchema] = {}
41
41
 
42
42
  def GetInfo(self, request, context) -> connection_info_pb2.InfoResponse:
43
43
  print("Info Requested!")
@@ -138,7 +138,7 @@ class AresAnalyzerServiceWrapper(analyzer_service_grpc.AresRemoteAnalyzerService
138
138
 
139
139
  def ValidateInputs(self, request: analyzer_service.ParameterValidationRequest, context):
140
140
  response = analyzer_service.ParameterValidationResult(success=True)
141
- provided_params: Mapping[str, ares_data_schema_pb2.SchemaEntry] = request.input_schema.fields
141
+ provided_params: Mapping[str, ares_data_schema_pb2.AresValueSchema] = request.input_schema.fields
142
142
 
143
143
  for stored_key, stored_schema in self._analysis_parameters.items():
144
144
  if stored_key in provided_params:
@@ -1,5 +1,6 @@
1
- from PyAres import AresDeviceService, DeviceCommandDescriptor, DeviceSchemaEntry, AresDataType
1
+ from PyAres import *
2
2
  from typing import Dict
3
+ import pint
3
4
  import time
4
5
 
5
6
  class DemoDevice:
@@ -25,20 +26,21 @@ class DemoDevice:
25
26
  device = DemoDevice()
26
27
 
27
28
  if __name__ == "__main__":
28
- # Basic information about my device
29
+ ureg = pint.UnitRegistry() # Basic information about my device
29
30
  device_name = "Demo Device"
30
31
  description = "A device to demonstrate the PyAres device capabilities"
31
32
  version = "1.0.0"
32
33
  device_service = AresDeviceService(device.enter_safe_mode, device.get_device_state, device_name, description, version)
33
34
 
34
35
  # Create the "Set Temperature" Command
35
- parameter_schema = DeviceSchemaEntry(AresDataType.NUMBER, "A numeric temperature value", "Degree's Celsius")
36
+ quantity_schema = QuantitySchema(ureg.degree_Celsius, 40.0, 125.0)
37
+ parameter_schema = DeviceSchemaEntry(AresDataType.QUANTITY, "A numeric temperature value", quantity_schema=quantity_schema)
36
38
  input_schema = { "temperature": parameter_schema }
37
39
  set_temp_descriptor = DeviceCommandDescriptor("Set Temperature", "Set's the temperature of the demo device to the provided value.", input_schema, {})
38
40
  device_service.add_new_command(set_temp_descriptor, device.set_temperature)
39
41
 
40
42
  # Create the "Get Temperature" Command
41
- output_schema = {"temperature": DeviceSchemaEntry(AresDataType.NUMBER, "The current temperature of the device", "Degree's Celsius")}
43
+ output_schema = {"temperature": DeviceSchemaEntry(AresDataType.QUANTITY, "The current temperature of the device", quantity_schema=quantity_schema)}
42
44
  get_temp_desc = DeviceCommandDescriptor("Get Temperature", "Get's the current temperature of the demo device.", {}, output_schema)
43
45
  device_service.add_new_command(get_temp_desc, device.get_temperature)
44
46
 
@@ -1,14 +1,26 @@
1
1
  from PyAres import AresDeviceService, AresDataType, DeviceSchemaEntry, DeviceCommandDescriptor
2
2
 
3
+ class RotaryMixer:
4
+ def __init__(self, speed):
5
+ self.speed = speed
6
+
7
+
8
+ mixer = RotaryMixer(1200)
9
+
3
10
  # 1. Define your hardware logic
4
11
  def set_speed(rpm: float):
5
12
  print(f"Setting motor speed to {rpm}")
13
+ mixer.speed = rpm
6
14
  # Hardware communication goes here...
7
15
  return {} # Return empty dict if no data needs to be sent back
8
16
 
17
+ def get_speed():
18
+ print("Hey I got speed")
19
+ return { "rpm": mixer.speed }
20
+
9
21
  def get_status():
10
22
  # Return a dictionary matching your state schema
11
- return { "rpm": 1200 }
23
+ return { "rpm": mixer.speed }
12
24
 
13
25
  def safe_mode():
14
26
  print("Stopping motor immediately!")
@@ -30,8 +42,14 @@ input_schema = {
30
42
  }
31
43
  cmd_descriptor = DeviceCommandDescriptor("Set Speed", "Sets mixer speed", input_schema, {})
32
44
 
45
+ output_schema = {
46
+ "rpm": DeviceSchemaEntry(AresDataType.NUMBER, "Speed in RPM", "RPM")
47
+ }
48
+ get_speed_descriptor = DeviceCommandDescriptor("Get Speed", "Gets mixer speed", {}, output_schema)
49
+
33
50
  # 4. Register the command
34
51
  service.add_new_command(cmd_descriptor, set_speed)
52
+ service.add_new_command(get_speed_descriptor, get_speed)
35
53
 
36
54
  # 5. Start
37
55
  service.start()
@@ -1,10 +1,16 @@
1
- from typing import Dict, Union
1
+ from typing import Dict, Union, Optional
2
2
  from ..Models import ares_data_models
3
3
 
4
4
  class DeviceSchemaEntry:
5
5
  """ A class that describes an input or output parameter for a device command """
6
6
 
7
- def __init__(self, type: ares_data_models.AresDataType, description: str = "", unit: str = "", optional: bool = False, constraints: Union[list[int], list[float], list[str]] = []):
7
+ def __init__(self, type: ares_data_models.AresDataType, description: str = "", unit: str = "", optional: bool = False,
8
+ constraints: Union[list[int], list[float], list[str]] = [],
9
+ quantity_schema: Optional[ares_data_models.QuantitySchema] = None,
10
+ struct_schema: Optional[Dict[str, 'DeviceSchemaEntry']] = None,
11
+ list_element_schema: Optional['DeviceSchemaEntry'] = None,
12
+ min_number_value: Optional[float] = None,
13
+ max_number_value: Optional[float] = None):
8
14
  """
9
15
  Initializes a new DeviceSchemaEntry
10
16
 
@@ -13,14 +19,24 @@ class DeviceSchemaEntry:
13
19
  description (str): A description of the given schema entry
14
20
  unit (str): The unit associated with this schema entry
15
21
  optional (bool): A boolean value that determines whether or not this schema entry's inclusion is optional
16
- contraints (Union[list[int], list[float], list[str]]): An optional list of contraints to limit the number of choices available for this schema entry
22
+ constraints (Union[list[int], list[float], list[str]]): An optional list of constraints to limit the number of choices available for this schema entry
23
+ quantity_schema (ares_data_models.QuantitySchema): Optional metadata for quantity types
24
+ struct_schema (Dict[str, DeviceSchemaEntry]): Optional schema for struct types
25
+ list_element_schema (DeviceSchemaEntry): Optional schema for list elements
26
+ min_number_value (float): Optional minimum value for numeric types
27
+ max_number_value (float): Optional maximum value for numeric types
17
28
  """
18
29
 
19
30
  self.type = type
20
31
  self.optional = optional
21
32
  self.description = description
22
33
  self.unit = unit
23
- self.contraints = constraints
34
+ self.constraints = constraints
35
+ self.quantity_schema = quantity_schema
36
+ self.struct_schema = struct_schema
37
+ self.list_element_schema = list_element_schema
38
+ self.min_number_value = min_number_value
39
+ self.max_number_value = max_number_value
24
40
 
25
41
  class DeviceCommandDescriptor:
26
42
  """ A class that contains all the necessary information to describe a device command """
@@ -37,9 +37,9 @@ class AresDeviceServiceWrapper(device_service_grpc.AresRemoteDeviceServiceServic
37
37
  self.version = version
38
38
  self._enter_safe_mode = enter_safe_mode
39
39
  self._update_device_state = update_device_state
40
- self._setting_schema: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
41
- self._current_settings: Dict[str, ares_struct_pb2.AresValue] = {}
42
- self._state_schema: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
40
+ self._setting_schema: Dict[str, ares_data_schema_pb2.AresValueSchema] = {}
41
+ self._current_settings: Dict[str, ares_struct_pb2.AresStruct] = {}
42
+ self._state_schema: Dict[str, ares_data_schema_pb2.AresValueSchema] = {}
43
43
  self._commands: list[DeviceCommandDescriptor] = []
44
44
  self._command_methods: Dict[str, Callable] = {}
45
45
 
@@ -85,8 +85,11 @@ class AresDeviceServiceWrapper(device_service_grpc.AresRemoteDeviceServiceServic
85
85
  provided_param_dict = ares_struct_utils.ares_struct_to_dict(request.arguments)
86
86
  result : Dict[str, Any] = method(**provided_param_dict)
87
87
 
88
- for key, value in result.items():
89
- ares_struct_utils.add_value_to_struct(response.result, key, ares_value_utils.create_ares_value(value))
88
+ if isinstance(result, dict):
89
+ for key, value in result.items():
90
+ ares_struct_utils.add_value_to_struct(response.result.struct_value, key, ares_value_utils.create_ares_value(value))
91
+ else:
92
+ response.result.CopyFrom(ares_value_utils.create_ares_value(result))
90
93
 
91
94
  response.success = True
92
95
  return response
@@ -1,8 +1,10 @@
1
- from .ares_data_models import AresDataType, Outcome, RequestMetadata, AresSchemaEntry
1
+ from .ares_data_models import AresDataType, Outcome, RequestMetadata, AresSchemaEntry, Quantity, QuantitySchema
2
2
 
3
3
  __all__ = [
4
4
  "AresDataType",
5
5
  "Outcome",
6
6
  "RequestMetadata",
7
- "AresSchemaEntry"
7
+ "AresSchemaEntry",
8
+ "Quantity",
9
+ "QuantitySchema"
8
10
  ]
@@ -2,6 +2,7 @@ from ares_datamodel import request_metadata_pb2
2
2
  from enum import Enum
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Union, List, Optional, Dict
5
+ import pint
5
6
 
6
7
  class AresDataType(Enum):
7
8
  UNKNOWN = 0
@@ -14,6 +15,10 @@ class AresDataType(Enum):
14
15
  LIST = 7
15
16
  STRUCT = 8
16
17
  BYTE_ARRAY = 9
18
+ ANY = 10
19
+ UNIT = 11
20
+ FUNCTION = 12
21
+ QUANTITY = 13
17
22
 
18
23
  class Outcome(Enum):
19
24
  UNSPECIFIED_OUTCOME = 0
@@ -27,7 +32,9 @@ class RequestMetadata():
27
32
  self.system_name = proto_metadata.system_name
28
33
  self.campaign_name = proto_metadata.campaign_name
29
34
  self.campaign_id = proto_metadata.campaign_id
30
- self.experiment_id = proto_metadata.experiment_id
35
+ self.experiment_id = proto_metadata.experiment_id
36
+ dt = proto_metadata.experiment_start_time.ToDatetime()
37
+ self.experiment_start_time = dt.strftime("%Y-%m-%d %H:%M:%S")
31
38
 
32
39
  @classmethod
33
40
  def from_default_values(cls):
@@ -35,6 +42,18 @@ class RequestMetadata():
35
42
  default = request_metadata_pb2.RequestMetadata(system_name="TEST SYSTEM", campaign_name="TEST CAMPAIGN", campaign_id="TEST ID", experiment_id="TEST EXPERIMENT ID")
36
43
  return cls(default)
37
44
 
45
+ @dataclass
46
+ class Quantity:
47
+ scalar: float
48
+ type: int # QuantityType enum value
49
+ unit: str
50
+
51
+ @dataclass
52
+ class QuantitySchema:
53
+ bounds_unit: pint.Unit = None
54
+ min_scalar_value: Optional[float] = None
55
+ max_scalar_value: Optional[float] = None
56
+
38
57
  @dataclass
39
58
  class AresSchemaEntry:
40
59
  type: AresDataType
@@ -42,4 +61,8 @@ class AresSchemaEntry:
42
61
  description: str = ""
43
62
  unit: str = ""
44
63
  choices: Union[List[str], List[int], List[float]] = field(default_factory=list)
45
- struct_schema: Optional[Dict[str, 'AresSchemaEntry']] = None
64
+ quantity_schema: Optional[QuantitySchema] = None
65
+ struct_schema: Optional[Dict[str, 'AresSchemaEntry']] = None
66
+ list_element_schema: Optional['AresSchemaEntry'] = None
67
+ min_number_value: Optional[float] = None
68
+ max_number_value: Optional[float] = None
@@ -37,7 +37,7 @@ class AresPlannerServiceWrapper(planner_service_grpc.AresRemotePlannerServiceSer
37
37
  self._service_name: str = service_name
38
38
  self._description: str = description
39
39
  self._version: str = version
40
- self._settings: Dict[str, ares_data_schema_pb2.SchemaEntry] = {}
40
+ self._settings: Dict[str, ares_data_schema_pb2.AresValueSchema] = {}
41
41
  self._planner_options: list[planner_pb2.Planner] = []
42
42
  self._supported_types: list[ares_data_type_pb2.AresDataType] = []
43
43
  self._timeout: int = timeout
@@ -54,15 +54,7 @@ class AresPlannerServiceWrapper(planner_service_grpc.AresRemotePlannerServiceSer
54
54
  capabilities.available_planners.extend(self._planner_options)
55
55
 
56
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)
57
+ capabilities.settings_schema.fields[key].CopyFrom(value)
66
58
 
67
59
  print("Capabilites Sent!")
68
60
  return capabilities
@@ -0,0 +1,59 @@
1
+ from typing import Union, Dict, Optional, List
2
+ from ares_datamodel import ares_data_schema_pb2
3
+ from ..Models import ares_data_models
4
+ from ..Models.ares_data_models import AresSchemaEntry
5
+
6
+ def convert_ares_schema_entry_to_proto(entry: AresSchemaEntry) -> ares_data_schema_pb2.AresValueSchema:
7
+ proto_entry = create_settings_schema_entry(entry.type, entry.optional, entry.choices, entry.struct_schema)
8
+ proto_entry.description = entry.description
9
+
10
+ if entry.quantity_schema:
11
+ proto_entry.quantity_schema.quantity_type = entry.quantity_schema.quantity_type
12
+ proto_entry.quantity_schema.bounds_unit = entry.quantity_schema.bounds_unit
13
+ if entry.quantity_schema.min_scalar_value is not None:
14
+ proto_entry.quantity_schema.min_scalar_value = entry.quantity_schema.min_scalar_value
15
+ if entry.quantity_schema.max_scalar_value is not None:
16
+ proto_entry.quantity_schema.max_scalar_value = entry.quantity_schema.max_scalar_value
17
+
18
+ if entry.list_element_schema:
19
+ proto_entry.list_element_schema.CopyFrom(convert_ares_schema_entry_to_proto(entry.list_element_schema))
20
+
21
+ if entry.min_number_value is not None:
22
+ proto_entry.min_number_value = entry.min_number_value
23
+ if entry.max_number_value is not None:
24
+ proto_entry.max_number_value = entry.max_number_value
25
+
26
+ return proto_entry
27
+
28
+ def create_settings_schema_entry(
29
+ setting_type: ares_data_models.AresDataType,
30
+ optional: bool,
31
+ choices: Union[list[str], list[int], list[float]],
32
+ struct_schema: Optional[Dict[str, AresSchemaEntry]] = None) -> ares_data_schema_pb2.AresValueSchema:
33
+ """
34
+ Creates a protobuf AresValueSchema message from the provided setting details.
35
+
36
+ Args:
37
+ setting_type (AresDataType): The data type of the setting.
38
+ optional (bool): Whether the setting is optional.
39
+ choices (Union[list[str], list[int], list[float]]): A list of valid choices for the setting.
40
+ struct_schema (Optional[Dict[str, AresSchemaEntry]]): Nested schema definition for STRUCT types.
41
+
42
+ Returns:
43
+ (AresValueSchema): A new AresValueSchema message.
44
+ """
45
+ schema_entry = ares_data_schema_pb2.AresValueSchema()
46
+ schema_entry.type = setting_type.value
47
+ schema_entry.optional = optional
48
+
49
+ if isinstance(choices, list) and len(choices) > 0:
50
+ if all(isinstance(item, str) for item in choices):
51
+ schema_entry.string_choices.strings.extend(choices)
52
+ elif all(isinstance(item, (int, float)) for item in choices):
53
+ schema_entry.number_choices.numbers.extend(choices)
54
+
55
+ if struct_schema is not None:
56
+ for key, value in struct_schema.items():
57
+ schema_entry.struct_schema.fields[key].CopyFrom(convert_ares_schema_entry_to_proto(value))
58
+
59
+ return schema_entry
@@ -11,7 +11,7 @@ def proto_ares_type_to_python_ares_type(proto_value: ares_data_type_pb2.AresData
11
11
  """ A method to convert from the protobuf AresDataType class to the python version """
12
12
  return AresDataType(proto_value)
13
13
 
14
- def determine_python_ares_data_type(value: Union[int, float, str, bool, list]):
14
+ def determine_python_ares_data_type(value: Union[int, float, str, bool, list, dict, bytes]):
15
15
  """ A method that takes in a value and returns the corresponding `PyAres.Models.AresDataType`"""
16
16
  match value:
17
17
  case str():
@@ -22,6 +22,10 @@ def determine_python_ares_data_type(value: Union[int, float, str, bool, list]):
22
22
  return AresDataType.NUMBER
23
23
  case float():
24
24
  return AresDataType.NUMBER
25
+ case bytes():
26
+ return AresDataType.BYTE_ARRAY
27
+ case dict():
28
+ return AresDataType.STRUCT
25
29
  case list():
26
30
  if(all(isinstance(x, bool) for x in value)):
27
31
  return AresDataType.LIST
@@ -31,5 +35,9 @@ def determine_python_ares_data_type(value: Union[int, float, str, bool, list]):
31
35
  return AresDataType.NUMBER_ARRAY
32
36
  else:
33
37
  return AresDataType.LIST
38
+ case None:
39
+ return AresDataType.NULL
34
40
  case _:
41
+ # If it has specific attributes, it might be a Quantity or other complex type
42
+ # This part might need more refinement as we define how Quantity looks in Python
35
43
  return AresDataType.UNKNOWN
@@ -0,0 +1,65 @@
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, ares_data_type_pb2
7
+ from ares_datamodel import to_quantity_schema
8
+
9
+ from . import ares_data_type_utils
10
+
11
+ def python_command_description_to_proto(python_description: DeviceCommandDescriptor) -> device_command_descriptor_pb2.DeviceCommandDescriptor:
12
+ proto_description = device_command_descriptor_pb2.DeviceCommandDescriptor()
13
+
14
+ # Update input_schema (AresStructSchema)
15
+ for key, value in python_description.input_schema.items():
16
+ proto_description.input_schema.fields[key].CopyFrom(python_device_schema_entry_to_proto(value))
17
+
18
+ # Update output_schema (AresValueSchema)
19
+ # If there is only one output and the key is empty or "output", we can use it directly.
20
+ # Otherwise, if there are multiple outputs, we should probably wrap them in a struct.
21
+ # However, the proto says output_schema is a single AresValueSchema.
22
+ # For now, let's assume we use the first one if there's only one, or wrap in a struct if there are multiple.
23
+ if len(python_description.output_schema) == 1:
24
+ first_val = list(python_description.output_schema.values())[0]
25
+ proto_description.output_schema.CopyFrom(python_device_schema_entry_to_proto(first_val))
26
+ elif len(python_description.output_schema) > 1:
27
+ proto_description.output_schema.type = ares_data_type_pb2.AresDataType.STRUCT
28
+ for key, value in python_description.output_schema.items():
29
+ proto_description.output_schema.struct_schema.fields[key].CopyFrom(python_device_schema_entry_to_proto(value))
30
+
31
+ proto_description.name = python_description.name
32
+ proto_description.description = python_description.description
33
+
34
+ return proto_description
35
+
36
+
37
+ def python_device_schema_entry_to_proto(entry: DeviceSchemaEntry) -> ares_data_schema_pb2.AresValueSchema:
38
+ proto_schema = ares_data_schema_pb2.AresValueSchema()
39
+ proto_schema.type = ares_data_type_utils.python_ares_type_to_proto_ares_type(entry.type)
40
+ proto_schema.optional = entry.optional
41
+ proto_schema.description = entry.description
42
+
43
+ if entry.constraints:
44
+ if all(isinstance(x, (int, float)) for x in entry.constraints):
45
+ proto_schema.number_choices.numbers.extend(entry.constraints)
46
+ elif all(isinstance(x, str) for x in entry.constraints):
47
+ proto_schema.string_choices.strings.extend(entry.constraints)
48
+
49
+ # If a quantity schema is provided, utilize it. Use datamodel helpers to create the protobuf representation
50
+ if entry.quantity_schema:
51
+ proto_schema.quantity_schema.CopyFrom(to_quantity_schema(entry.quantity_schema.bounds_unit, entry.quantity_schema.min_scalar_value, entry.quantity_schema.max_scalar_value))
52
+
53
+ if entry.struct_schema:
54
+ for key, sub_entry in entry.struct_schema.items():
55
+ proto_schema.struct_schema.fields[key].CopyFrom(python_device_schema_entry_to_proto(sub_entry))
56
+
57
+ if entry.list_element_schema:
58
+ proto_schema.list_element_schema.CopyFrom(python_device_schema_entry_to_proto(entry.list_element_schema))
59
+
60
+ if entry.min_number_value is not None:
61
+ proto_schema.min_number_value = entry.min_number_value
62
+ if entry.max_number_value is not None:
63
+ proto_schema.max_number_value = entry.max_number_value
64
+
65
+ return proto_schema
@@ -3,23 +3,33 @@ from ares_datamodel import ares_data_type_pb2
3
3
  from typing import Union, Any, Dict
4
4
 
5
5
  from . import ares_data_type_utils
6
- from ..Models import AresDataType
6
+ from ..Models import AresDataType, Quantity
7
7
 
8
8
  def ares_value_to_py(ares_value: ares_struct_pb2.AresValue):
9
9
  """Converts an AresValue protobuf message to a Python native type."""
10
- type_map = {
11
- "null_value": None,
12
- "number_value": ares_value.number_value,
13
- "string_value": ares_value.string_value,
14
- "bool_value": ares_value.bool_value,
15
- "string_array_value": ares_value.string_array_value.strings,
16
- "number_array_value": ares_value.number_array_value.numbers,
17
- "bytes_value": ares_value.bytes_value
18
- }
19
-
20
10
  field = ares_value.WhichOneof("kind")
21
- if field in type_map:
22
- return type_map[field]
11
+ if field == "null_value":
12
+ return None
13
+ elif field == "number_value":
14
+ return ares_value.number_value
15
+ elif field == "string_value":
16
+ return ares_value.string_value
17
+ elif field == "bool_value":
18
+ return ares_value.bool_value
19
+ elif field == "string_array_value":
20
+ return list(ares_value.string_array_value.strings)
21
+ elif field == "number_array_value":
22
+ return list(ares_value.number_array_value.numbers)
23
+ elif field == "bytes_value":
24
+ return ares_value.bytes_value
25
+ elif field == "list_value":
26
+ return [ares_value_to_py(v) for v in ares_value.list_value.values]
27
+ elif field == "struct_value":
28
+ return {k: ares_value_to_py(v) for k, v in ares_value.struct_value.fields.items()}
29
+ elif field == "quantity_value":
30
+ return Quantity(scalar=ares_value.quantity_value.scalar,
31
+ type=ares_value.quantity_value.type,
32
+ unit=ares_value.quantity_value.unit)
23
33
  return None
24
34
 
25
35
  def py_to_ares_value(py_value, ares_value: ares_struct_pb2.AresValue):
@@ -32,6 +42,13 @@ def py_to_ares_value(py_value, ares_value: ares_struct_pb2.AresValue):
32
42
  ares_value.number_value = py_value
33
43
  elif isinstance(py_value, bytes):
34
44
  ares_value.bytes_value = py_value
45
+ elif isinstance(py_value, Quantity):
46
+ ares_value.quantity_value.scalar = py_value.scalar
47
+ ares_value.quantity_value.type = py_value.type
48
+ ares_value.quantity_value.unit = py_value.unit
49
+ elif isinstance(py_value, dict):
50
+ for k, v in py_value.items():
51
+ py_to_ares_value(v, ares_value.struct_value.fields[k])
35
52
  elif isinstance(py_value, list):
36
53
  if(all(isinstance(x, str) for x in py_value)):
37
54
  ares_value.string_array_value.strings.extend(py_value)
@@ -47,6 +64,14 @@ def py_to_ares_value(py_value, ares_value: ares_struct_pb2.AresValue):
47
64
  else:
48
65
  raise TypeError(f"Unsupported type for AresValue: {type(py_value)}")
49
66
 
67
+ def create_quantity(value: Quantity) -> ares_struct_pb2.AresValue:
68
+ """ Creates a new AresValue, initialized to the provided Quantity. """
69
+ ares_val = ares_struct_pb2.AresValue()
70
+ ares_val.quantity_value.scalar = value.scalar
71
+ ares_val.quantity_value.type = value.type
72
+ ares_val.quantity_value.unit = value.unit
73
+ return ares_val
74
+
50
75
  def create_number(value: Union[int, float]) -> ares_struct_pb2.AresValue:
51
76
  """
52
77
  Creates a new AresValue, initialized to the provided number value.
@@ -203,6 +228,8 @@ def create_default(python_datatype: AresDataType) -> ares_struct_pb2.AresValue:
203
228
 
204
229
  elif(dataType == ares_data_type_pb2.AresDataType.STRUCT):
205
230
  return create_struct({})
231
+ elif(dataType == ares_data_type_pb2.AresDataType.QUANTITY):
232
+ return create_quantity(Quantity(0, 0, ""))
206
233
  else:
207
234
  return create_null()
208
235
 
@@ -229,6 +256,9 @@ def create_ares_value(value: Any) -> ares_struct_pb2.AresValue:
229
256
  elif(isinstance(value, bytes)):
230
257
  return create_bytes(value)
231
258
 
259
+ elif(isinstance(value, Quantity)):
260
+ return create_quantity(value)
261
+
232
262
  elif(isinstance(value, list)):
233
263
  if all(isinstance(x, bool) for x in value):
234
264
  return create_array(value)
@@ -11,4 +11,7 @@ from .Device import AresDeviceService
11
11
  from .Device import DeviceCommandDescriptor
12
12
  from .Device import DeviceSchemaEntry
13
13
  from .Models import AresDataType
14
- from .Models import Outcome
14
+ from .Models import Outcome
15
+ from .Models import AresSchemaEntry
16
+ from .Models import Quantity
17
+ from .Models import QuantitySchema
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling", "googleapis-common-protos>=1.70.0"]
2
+ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -16,9 +16,7 @@ classifiers = [
16
16
  ]
17
17
 
18
18
  dependencies = [
19
- "grpcio-tools >= 1.75.1",
20
- "grpcio >= 1.71.0, < 2.0.0",
21
- "ares-datamodel >= 0.16.0",
19
+ "ares-datamodel >= 0.20.1",
22
20
  ]
23
21
 
24
22
  [project.urls]
@@ -107,7 +107,7 @@ class TestAresAnalyzerService(unittest.TestCase):
107
107
  # Case A: Success (Matches Schema)
108
108
  req = analyzer_service.ParameterValidationRequest()
109
109
  entry = req.input_schema.fields["Voltage"]
110
- new_schema_entry = ares_data_schema_pb2.SchemaEntry(type=ares_data_type_pb2.AresDataType.NUMBER, optional=False, description="Voltage value", unit="Volts")
110
+ new_schema_entry = ares_data_schema_pb2.AresValueSchema(type=ares_data_type_pb2.AresDataType.NUMBER, optional=False, description="Voltage value")
111
111
  entry.CopyFrom(new_schema_entry)
112
112
  resp = self.service._service_wrapper.ValidateInputs(req, None)
113
113
  self.assertTrue(resp.success, "Validation should pass for correct schema")
@@ -115,7 +115,7 @@ class TestAresAnalyzerService(unittest.TestCase):
115
115
  # Case B: Failure (Wrong Type)
116
116
  req_bad_type = analyzer_service.ParameterValidationRequest()
117
117
  bad_entry = req_bad_type.input_schema.fields["Voltage"]
118
- bad_schema_entry = ares_data_schema_pb2.SchemaEntry(type=ares_data_type_pb2.AresDataType.STRING, optional=False, description="Voltage value", unit="Volts")
118
+ bad_schema_entry = ares_data_schema_pb2.AresValueSchema(type=ares_data_type_pb2.AresDataType.STRING, optional=False, description="Voltage value")
119
119
  bad_entry.CopyFrom(bad_schema_entry)
120
120
  resp_bad = self.service._service_wrapper.ValidateInputs(req_bad_type, None)
121
121
  self.assertFalse(resp_bad.success, "Validation should fail for type mismatch")
@@ -48,15 +48,13 @@ class TestAresDataSchemaUtils(unittest.TestCase):
48
48
  type=AresDataType.NUMBER,
49
49
  optional=True,
50
50
  description="Test Desc",
51
- unit="m/s",
52
51
  choices=[1.0, 2.0]
53
52
  )
54
53
  proto = ares_data_schema_utils.convert_ares_schema_entry_to_proto(py_entry)
55
54
  self.assertEqual(proto.type, ares_data_type_pb2.AresDataType.NUMBER)
56
55
  self.assertTrue(proto.optional)
57
56
  self.assertEqual(proto.description, "Test Desc")
58
- self.assertEqual(proto.unit, "m/s")
59
- self.assertEqual(proto.number_choices.numbers, [1.0, 2.0])
57
+ self.assertEqual(list(proto.number_choices.numbers), [1.0, 2.0])
60
58
 
61
59
  def test_nested_struct_schema(self):
62
60
  nested_field = AresSchemaEntry(type=AresDataType.STRING, description="Inner")
@@ -111,7 +111,7 @@ class TestAresDeviceService(unittest.TestCase):
111
111
  response = self.service._service_wrapper.ExecuteCommand(req, None)
112
112
 
113
113
  self.assertTrue(response.success, response.error)
114
- result_dict = ares_struct_utils.ares_struct_to_dict(response.result)
114
+ result_dict = ares_struct_utils.ares_struct_to_dict(response.result.struct_value)
115
115
  self.assertEqual(result_dict["axis"], "X")
116
116
  self.assertEqual(result_dict["final_speed"], 50.0)
117
117
 
@@ -1,49 +0,0 @@
1
- from typing import Union, Dict, Optional, List
2
- from ares_datamodel import ares_data_schema_pb2
3
- from ..Models import ares_data_models
4
- from ..Models.ares_data_models import AresSchemaEntry
5
-
6
- def convert_ares_schema_entry_to_proto(entry: AresSchemaEntry) -> ares_data_schema_pb2.SchemaEntry:
7
- proto_entry = create_settings_schema_entry(entry.type, entry.optional, entry.choices, entry.struct_schema)
8
- proto_entry.description = entry.description
9
- proto_entry.unit = entry.unit
10
- return proto_entry
11
-
12
- def create_settings_schema_entry(
13
- setting_type: ares_data_models.AresDataType,
14
- optional: bool,
15
- choices: Union[list[str], list[int], list[float]],
16
- struct_schema: Optional[Dict[str, AresSchemaEntry]] = None) -> ares_data_schema_pb2.SchemaEntry:
17
- """
18
- Creates a protobuf SchemaEntry message from the provided setting details.
19
-
20
- Args:
21
- setting_type (AresDataType): The data type of the setting.
22
- optional (bool): Whether the setting is optional.
23
- choices (Union[list[str], list[int], list[float]]): A list of valid choices for the setting.
24
- struct_schema (Optional[Dict[str, AresSchemaEntry]]): Nested schema definition for STRUCT types.
25
-
26
- Returns:
27
- (SchemaEntry): A new SchemaEntry message.
28
- """
29
-
30
- if(isinstance(choices, list)):
31
- if(len(choices) == 0):
32
- schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
33
-
34
- elif(all(isinstance(item, str) for item in choices)):
35
- schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
36
- schema_entry.string_choices.strings.extend(choices)
37
-
38
- elif(all(isinstance(item, (int, float)) for item in choices)):
39
- schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
40
- schema_entry.number_choices.numbers.extend(choices)
41
-
42
- else:
43
- schema_entry = ares_data_schema_pb2.SchemaEntry(type=setting_type.value, optional=optional)
44
-
45
- if struct_schema is not None:
46
- for key, value in struct_schema.items():
47
- schema_entry.struct_schema.fields[key].CopyFrom(convert_ares_schema_entry_to_proto(value))
48
-
49
- return schema_entry
@@ -1,43 +0,0 @@
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes