Skip to content

Commit

Permalink
Allow for subsituting YAML value to config variables
Browse files Browse the repository at this point in the history
  • Loading branch information
tehampson committed Nov 24, 2022
1 parent 7f1b4b8 commit 4944ac4
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 21 deletions.
93 changes: 82 additions & 11 deletions src/controller/python/chip/yaml/format_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,49 @@
import binascii


def _substitute_in_config_variables(field_value, config_values: dict):
''' Substitutes values that are config variables.
YAML values can contain a string of a configuration variable name. In these instances we
substitute the configuration variable name with the actual value.
# TODO This should also substitue any saveAs values as well as perform any required
# evaluations.
Args:
'field_value': Value as extracted from YAML.
'config_values': Dictionary of global configuration variables.
Returns:
Value with all global configuration variables substituted with the real value.
'''
if (type(field_value) is dict):
return_values = {}
for key in field_value:
return_values[key] = _substitute_in_config_variables(field_value[key], config_values)

return return_values
elif(type(field_value) is list):
return_values = []
for item in field_value:
return_values.append(_substitute_in_config_variables(item, config_values))
return return_values
elif isinstance(field_value, str) and field_value in config_values:
config_value = config_values[field_value]
if isinstance(config_value, dict) and 'defaultValue' in config_value:
# TODO currently we don't validate that if config_value['type'] is provided
# that the type does in fact match our expectation.
return config_value['defaultValue']
else:
return config_values[field_value]

return field_value


def convert_yaml_octet_string_to_bytes(s: str) -> bytes:
"""Convert YAML octet string body to bytes, handling any c-style hex escapes (e.g. \x5a) and hex: prefix"""
'''Convert YAML octet string body to bytes.
Included handling any c-style hex escapes (e.g. \x5a) and 'hex:' prefix.
'''
# Step 1: handle explicit "hex:" prefix
if s.startswith('hex:'):
return binascii.unhexlify(s[4:])
Expand Down Expand Up @@ -60,14 +101,21 @@ def convert_name_value_pair_to_dict(arg_values):
return ret_value


def convert_yaml_type(field_value, field_type, use_from_dict=False):
''' Converts yaml value to expected type.
def convert_yaml_type(field_value, field_type, inline_cast_dict_to_struct):
''' Converts yaml value to provided pythonic type.
The YAML representation when converted to a Python dictionary does not
quite line up in terms of type (see each of the specific if branches
below for the rationale for the necessary fix-ups). This function does
a fix-up given a field value (as present in the YAML) and its matching
cluster object type and returns it.
The YAML representation when converted to a dictionary does not line up to
the python type data model for the various command/attribute/event object
types. This function converts 'field_value' to the appropriate provided
'field_type'.
Args:
'field_value': Value as extracted from yaml
'field_type': Pythonic command/attribute/event object type that we
are converting value to.
'inline_cast_dict_to_struct': If true, for any dictionary 'field_value'
types provided we will do an inline convertion to the corresponding
struct in `field_type` by doing field_type.FromDict(...).
'''
origin = typing.get_origin(field_type)

Expand Down Expand Up @@ -110,8 +158,8 @@ def convert_yaml_type(field_value, field_type, use_from_dict=False):
f'Did not find field "{item}" in {str(field_type)}') from None

return_field_value[field_descriptor.Label] = convert_yaml_type(
field_value[item], field_descriptor.Type, use_from_dict)
if use_from_dict:
field_value[item], field_descriptor.Type, inline_cast_dict_to_struct)
if inline_cast_dict_to_struct:
return field_type.FromDict(return_field_value)
return return_field_value
elif(type(field_value) is float):
Expand All @@ -122,7 +170,8 @@ def convert_yaml_type(field_value, field_type, use_from_dict=False):

# The field type passed in is the type of the list element and not list[T].
for idx, item in enumerate(field_value):
field_value[idx] = convert_yaml_type(item, list_element_type, use_from_dict)
field_value[idx] = convert_yaml_type(item, list_element_type,
inline_cast_dict_to_struct)
return field_value
# YAML conversion treats all numbers as ints. Convert to a uint type if the schema
# type indicates so.
Expand All @@ -139,3 +188,25 @@ def convert_yaml_type(field_value, field_type, use_from_dict=False):
# By default, just return the field_value casted to field_type.
else:
return field_type(field_value)


def parse_and_convert_yaml_value(field_value, field_type, config_values: dict,
inline_cast_dict_to_struct: bool = False):
''' Parse and converts YAML type
Parsing the YAML value means performing required substitutions and evaluations. Parsing is
than followed by converting from the YAML type done using yaml.safe_load() to the type used in
the various command/attribute/event object data model types.
Args:
'field_value': Value as extracted from yaml to be parsed
'field_type': Pythonic command/attribute/event object type that we
are converting value to.
'config_values': Dictionary of global configuration variables.
'inline_cast_dict_to_struct': If true, for any dictionary 'field_value'
types provided we will do an inline convertion to the corresponding
struct in `field_type` by doing field_type.FromDict(...).
'''
field_value_with_config_variables = _substitute_in_config_variables(field_value, config_values)
return convert_yaml_type(field_value_with_config_variables, field_type,
inline_cast_dict_to_struct)
20 changes: 10 additions & 10 deletions src/controller/python/chip/yaml/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def __init__(self, value, field_type, context: _ExecutionContext):
if isinstance(value, str) and self._variable_storage.is_key_saved(value):
self._indirect_value_key = value
else:
self._value = Converter.convert_yaml_type(
value, field_type)
self._value = Converter.parse_and_convert_yaml_value(
value, field_type, context.config_values)

def get_value(self):
'''Gets the current value of the constraint.
Expand Down Expand Up @@ -173,8 +173,8 @@ def __init__(self, value, response_type, context: _ExecutionContext):
if isinstance(value, str) and self._variable_storage.is_key_saved(value):
self._load_expected_response_in_verify = value
else:
self._expected_response = Converter.convert_yaml_type(
value, response_type, use_from_dict=True)
self._expected_response = Converter.parse_and_convert_yaml_value(
value, response_type, context.config_values, inline_cast_dict_to_struct=True)

def verify(self, response):
if (self._expected_response_type is None):
Expand Down Expand Up @@ -249,8 +249,8 @@ def __init__(self, item: dict, cluster: str, context: _ExecutionContext):
request_data_as_dict = Converter.convert_name_value_pair_to_dict(args)

try:
request_data = Converter.convert_yaml_type(
request_data_as_dict, type(command_object))
request_data = Converter.parse_and_convert_yaml_value(
request_data_as_dict, type(command_object), context.config_values)
except ValueError:
raise ParsingError('Could not covert yaml type')

Expand All @@ -270,8 +270,8 @@ def __init__(self, item: dict, cluster: str, context: _ExecutionContext):
expected_response_args = self._expected_raw_response['values']
expected_response_data_as_dict = Converter.convert_name_value_pair_to_dict(
expected_response_args)
expected_response_data = Converter.convert_yaml_type(
expected_response_data_as_dict, expected_command)
expected_response_data = Converter.parse_and_convert_yaml_value(
expected_response_data_as_dict, expected_command, context.config_values)
self._expected_response_object = expected_command.FromDict(expected_response_data)

def run_action(self, dev_ctrl: ChipDeviceCtrl, endpoint: int, node_id: int):
Expand Down Expand Up @@ -430,8 +430,8 @@ def __init__(self, item: dict, cluster: str, context: _ExecutionContext):
if (item.get('arguments')):
args = item['arguments']['value']
try:
request_data = Converter.convert_yaml_type(
args, attribute.attribute_type.Type)
request_data = Converter.parse_and_convert_yaml_value(
args, attribute.attribute_type.Type, context.config_values)
except ValueError:
raise ParsingError('Could not covert yaml type')

Expand Down

0 comments on commit 4944ac4

Please sign in to comment.