def clean(self): if self.type == "static" and not isinstance(self.value, (list, dict)): raise ModelValidationError( "Error saving choices '%s' - type was 'static' " "but the value was not a list or dictionary" % self.value) elif self.type == "url" and not isinstance(self.value, six.string_types): raise ModelValidationError( "Error saving choices '%s' - type was 'url' but " "the value was not a string" % self.value) elif self.type == "command" and not isinstance( self.value, (six.string_types, dict)): raise ModelValidationError( "Error saving choices '%s' - type was 'command' " "but the value was not a string or dict" % self.value) if self.type == "command" and isinstance(self.value, dict): value_keys = self.value.keys() for required_key in ("command", "system", "version"): if required_key not in value_keys: raise ModelValidationError( "Error saving choices '%s' - specifying " "value as a dictionary requires a '%s' " "item" % (self.value, required_key)) try: if self.details == {}: if isinstance(self.value, six.string_types): self.details = parse(self.value) elif isinstance(self.value, dict): self.details = parse(self.value["command"]) except (LarkError, ParseError): raise ModelValidationError( "Error saving choices '%s' - unable to parse" % self.value)
def clean(self): if self.type == "static" and not isinstance(self.value, (list, dict)): raise ModelValidationError( f"Can not save choices '{self}': type is 'static' but the value is " "not a list or dictionary") elif self.type == "url" and not isinstance(self.value, six.string_types): raise ModelValidationError( f"Can not save choices '{self}': type is 'url' but the value is " "not a string") elif self.type == "command" and not isinstance( self.value, (six.string_types, dict)): raise ModelValidationError( f"Can not save choices '{self}': type is 'command' but the value is " "not a string or dict") if self.type == "command" and isinstance(self.value, dict): value_keys = self.value.keys() for required_key in ("command", "system", "version"): if required_key not in value_keys: raise ModelValidationError( f"Can not save choices '{self}': specifying value as a " f"dictionary requires a '{required_key}' item") try: if self.details == {}: if isinstance(self.value, six.string_types): self.details = parse(self.value) elif isinstance(self.value, dict): self.details = parse(self.value["command"]) except (LarkError, ParseError): raise ModelValidationError( f"Can not save choices '{self}': Unable to parse")
def test_parse_func_error(self, input_string): with pytest.raises(ParseError): parse(input_string, parse_as="func")
def test_parse_func(self, input_string, expected): assert expected == parse(input_string, parse_as="func")
def test_parse_no_hint(self, input_string, expected): assert expected == parse(input_string)
def test_parse_empty(self): with pytest.raises(ParseError): parse("")
def test_parse_reference_error(self, input_string): with pytest.raises(ParseError): parse(input_string, parse_as="reference")
def test_parse_reference(self): assert "index" == parse("${index}", parse_as="reference")
def test_parse_url_error(self, input_string): with pytest.raises(ParseError): parse(input_string, parse_as="url")
def test_parse_url(self, input_string, expected): assert expected == parse(input_string, parse_as="url")
def _validate_value_in_choices(self, request, value, command_parameter): """Validate that the value(s) are valid according to the choice constraints""" if (value is not None and not command_parameter.optional and command_parameter.choices and command_parameter.choices.strict): choices = command_parameter.choices def map_param_values(kv_pair_list): param_map = {} for param_name, param_ref in kv_pair_list: if param_ref == "instance_name": param_map[param_name] = request.instance_name else: param_map[param_name] = request.parameters[param_ref] return param_map if choices.type == "static": if isinstance(choices.value, list): raw_allowed = choices.value elif isinstance(choices.value, dict): key = choices.details.get("key_reference") if key is None: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Choices" " with a dictionary value must specify a key_reference" % command_parameter.key) if key == "instance_name": key_reference_value = request.instance_name else: # Mongoengine stores None keys as 'null', use instead of None key_reference_value = request.parameters.get( key) or "null" raw_allowed = choices.value.get(key_reference_value) if raw_allowed is None: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Choices" " dictionary doesn't contain an entry with key '%s'" % (command_parameter.key, key_reference_value)) else: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Choices value" " must be a list or dictionary " % command_parameter.key) elif choices.type == "url": parsed_value = parse(choices.value, parse_as="url") query_params = map_param_values(parsed_value["args"]) raw_allowed = json.loads( self._session.get(parsed_value["address"], params=query_params).text) elif choices.type == "command": if isinstance(choices.value, six.string_types): parsed_value = parse(choices.value, parse_as="func") choices_request = Request( system=request.system, system_version=request.system_version, instance_name=request.instance_name, namespace=request.namespace, command=parsed_value["name"], parameters=map_param_values(parsed_value["args"]), ) elif isinstance(choices.value, dict): parsed_value = parse(choices.value["command"], parse_as="func") choices_request = Request( system=choices.value.get("system"), system_version=choices.value.get("version"), namespace=choices.value.get("namespace", config.get("garden.name")), instance_name=choices.value.get( "instance_name", "default"), command=parsed_value["name"], parameters=map_param_values(parsed_value["args"]), ) else: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Choices value" " must be a string or dictionary " % command_parameter.key) try: response = process_wait(choices_request, self._command_timeout) except TimeoutError: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Choices " "request took too long to complete" % command_parameter.key) raw_allowed = json.loads(response.output) if isinstance(raw_allowed, list): if len(raw_allowed) < 1: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Result " "of choices query was empty list" % command_parameter.key) else: raise ModelValidationError( "Unable to validate choices for parameter '%s' - Result of " " choices query must be a list" % command_parameter.key) else: raise ModelValidationError( "Unable to validate choices for parameter '%s' - No valid type " "specified (valid types are %s)" % (command_parameter.key, Choices.TYPES)) # At this point raw_allowed is a list, but that list can potentially contain # {"value": "", "text": ""} dicts. Need to collapse those to strings allowed_values = [] for allowed in raw_allowed: if isinstance(allowed, dict): allowed_values.append(allowed["value"]) else: allowed_values.append(allowed) if command_parameter.multi: for single_value in value: if single_value not in allowed_values: raise ModelValidationError( "Value '%s' is not a valid choice for parameter with key " "'%s'. Valid choices are: %s" % (single_value, command_parameter.key, allowed_values)) else: if value not in allowed_values: raise ModelValidationError( "Value '%s' is not a valid choice for parameter with key '%s'. " "Valid choices are: %s" % (value, command_parameter.key, allowed_values))
def _format_choices(choices): def determine_display(display_value): if isinstance(display_value, six.string_types): return 'typeahead' return 'select' if len(display_value) <= 50 else 'typeahead' def determine_type(type_value): if isinstance(type_value, (list, dict)): return 'static' elif type_value.startswith('http'): return 'url' else: return 'command' if not choices: return None if not isinstance(choices, (list, six.string_types, dict)): raise PluginParamError( "Invalid 'choices' provided. Must be a list, dictionary or string." ) elif isinstance(choices, dict): if not choices.get('value'): raise PluginParamError( "No 'value' provided for choices. You must at least " "provide valid values.") value = choices.get('value') display = choices.get('display', determine_display(value)) choice_type = choices.get('type') strict = choices.get('strict', True) if choice_type is None: choice_type = determine_type(value) elif choice_type not in Choices.TYPES: raise PluginParamError( "Invalid choices type '%s' - Valid type options are %s" % (choice_type, Choices.TYPES)) else: if (choice_type == 'command' and not isinstance(value, (six.string_types, dict))) \ or (choice_type == 'url' and not isinstance(value, six.string_types)) \ or (choice_type == 'static' and not isinstance(value, (list, dict))): allowed_types = { 'command': "('string', 'dictionary')", 'url': "('string')", 'static': "('list', 'dictionary)" } raise PluginParamError( "Invalid choices value type '%s' - Valid value types for " "choice type '%s' are %s" % (type(value), choice_type, allowed_types[choice_type])) if display not in Choices.DISPLAYS: raise PluginParamError( "Invalid choices display '%s' - Valid display options are %s" % (display, Choices.DISPLAYS)) else: value = choices display = determine_display(value) choice_type = determine_type(value) strict = True # Now parse out type-specific aspects unparsed_value = '' try: if choice_type == 'command': if isinstance(value, six.string_types): unparsed_value = value else: unparsed_value = value['command'] details = parse(unparsed_value, parse_as='func') elif choice_type == 'url': unparsed_value = value details = parse(unparsed_value, parse_as='url') else: if isinstance(value, dict): unparsed_value = choices.get('key_reference') if unparsed_value is None: raise PluginParamError( 'Specifying a static choices dictionary requires a ' '"key_reference" field with a reference to another ' 'parameter ("key_reference": "${param_key}")') details = { 'key_reference': parse(unparsed_value, parse_as='reference') } else: details = {} except ParseError: raise PluginParamError( "Invalid choices definition - Unable to parse '%s'" % unparsed_value) return Choices(type=choice_type, display=display, value=value, strict=strict, details=details)
def test_parse_reference(self): assert 'index' == parse('${index}', parse_as='reference')
def _format_choices(choices): def determine_display(display_value): if isinstance(display_value, six.string_types): return "typeahead" return "select" if len(display_value) <= 50 else "typeahead" def determine_type(type_value): if isinstance(type_value, (list, dict)): return "static" elif type_value.startswith("http"): return "url" else: return "command" if not choices: return None if not isinstance(choices, (list, six.string_types, dict)): raise PluginParamError( "Invalid 'choices' provided. Must be a list, dictionary or string." ) elif isinstance(choices, dict): if not choices.get("value"): raise PluginParamError( "No 'value' provided for choices. You must at least " "provide valid values.") value = choices.get("value") display = choices.get("display", determine_display(value)) choice_type = choices.get("type") strict = choices.get("strict", True) if choice_type is None: choice_type = determine_type(value) elif choice_type not in Choices.TYPES: raise PluginParamError( "Invalid choices type '%s' - Valid type options are %s" % (choice_type, Choices.TYPES)) else: if ((choice_type == "command" and not isinstance(value, (six.string_types, dict))) or (choice_type == "url" and not isinstance(value, six.string_types)) or (choice_type == "static" and not isinstance(value, (list, dict)))): allowed_types = { "command": "('string', 'dictionary')", "url": "('string')", "static": "('list', 'dictionary)", } raise PluginParamError( "Invalid choices value type '%s' - Valid value types for " "choice type '%s' are %s" % (type(value), choice_type, allowed_types[choice_type])) if display not in Choices.DISPLAYS: raise PluginParamError( "Invalid choices display '%s' - Valid display options are %s" % (display, Choices.DISPLAYS)) else: value = choices display = determine_display(value) choice_type = determine_type(value) strict = True # Now parse out type-specific aspects unparsed_value = "" try: if choice_type == "command": if isinstance(value, six.string_types): unparsed_value = value else: unparsed_value = value["command"] details = parse(unparsed_value, parse_as="func") elif choice_type == "url": unparsed_value = value details = parse(unparsed_value, parse_as="url") else: if isinstance(value, dict): unparsed_value = choices.get("key_reference") if unparsed_value is None: raise PluginParamError( "Specifying a static choices dictionary requires a " '"key_reference" field with a reference to another ' 'parameter ("key_reference": "${param_key}")') details = { "key_reference": parse(unparsed_value, parse_as="reference") } else: details = {} except ParseError: raise PluginParamError( "Invalid choices definition - Unable to parse '%s'" % unparsed_value) return Choices(type=choice_type, display=display, value=value, strict=strict, details=details)