def test_parse_string_in_quotes_input(self): tests = [('x="true"', [{ 'x': "true" }]), ('y="True"', [{ 'y': "True" }]), ('c="TRUE"', [{ 'c': "TRUE" }]), ('d="123"', [{ 'd': '123' }]), ('e="abcde"', [{ 'e': 'abcde' }]), ('f=""', [{ 'f': '' }]), ("x='false'", [{ 'x': 'false' }]), ("y='False'", [{ 'y': 'False' }]), ("c='FALSE'", [{ 'c': 'FALSE' }]), ("d='123'", [{ 'd': '123' }]), ("e='abcde'", [{ 'e': 'abcde' }]), ("f=''", [{ 'f': '' }])] for s, d in tests: self.assertListEqual(args_util.parse_inline_params(s), d)
def test_parse_multiple(self): tests = [('x="abc" y="def"', [{ 'x': 'abc' }, { 'y': 'def' }]), ('y="def" x="abc"', [{ 'y': 'def' }, { 'x': 'abc' }]), ('x="abc", y="def"', [{ 'x': 'abc' }, { 'y': 'def' }]), ('y="def", x="abc"', [{ 'y': 'def' }, { 'x': 'abc' }]), ('x="abc"; y="def"', [{ 'x': 'abc' }, { 'y': 'def' }]), ('y="def"; x="abc"', [{ 'y': 'def' }, { 'x': 'abc' }])] for s, d in tests: self.assertListEqual(args_util.parse_inline_params(s), d)
def test_parse_expression(self): tests = [('x=<% $.abc %>', { 'x': '<% $.abc %>' }), ('x={{ _.abc }}', { 'x': '{{ _.abc }}' })] for s, d in tests: self.assertDictEqual(args_util.parse_inline_params(s)[0], d)
def __init__(self, *args, **kwargs): super(TaskSpec, self).__init__(*args, **kwargs) action_spec = getattr(self, 'action', str()) input_spec = args_util.parse_inline_params(action_spec, preserve_order=False) if input_spec: self.action = action_spec[:action_spec.index(' ')] self.input = input_spec
def __init__(self, *args, **kwargs): super(TaskTransitionSpec, self).__init__(*args, **kwargs) publish_spec = getattr(self, 'publish', None) if publish_spec and isinstance(publish_spec, six.string_types): self.publish = args_util.parse_inline_params(publish_spec or str()) do_spec = getattr(self, 'do', None) if not do_spec: self.do = 'continue'
def test_parse_bool_input(self): tests = [('x=true', [{ 'x': True }]), ('y=True', [{ 'y': True }]), ('c=TRUE', [{ 'c': True }]), ('x=false', [{ 'x': False }]), ('y=False', [{ 'y': False }]), ('c=FALSE', [{ 'c': False }])] for s, d in tests: self.assertListEqual(args_util.parse_inline_params(s), d)
def test_parse_combination(self): s = 'i=123 j="abc" k=true x=<% $.abc %> y={{ _.abc }} z=\'{\"a\": 1}\'' d = [{ 'i': 123 }, { 'j': 'abc' }, { 'k': True }, { 'x': '<% $.abc %>' }, { 'y': '{{ _.abc }}' }, { 'z': { 'a': 1 } }] self.assertListEqual(args_util.parse_inline_params(s), d)
def test_parse_basic(self): tests = [('x=null', { 'x': None }), ('x=123', { 'x': 123 }), ('x=-123', { 'x': -123 }), ('x=0.123', { 'x': 0.123 }), ('x=-0.123', { 'x': -0.123 }), ('x=true', { 'x': True }), ('x=false', { 'x': False }), ('x="abc"', { 'x': 'abc' }), ("x='abc'", { 'x': 'abc' })] for s, d in tests: self.assertDictEqual(args_util.parse_inline_params(s)[0], d)
def test_parse_dictionary(self): tests = [('x=\'{\"a\": 1}\'', {'x': {'a': 1}})] for s, d in tests: self.assertDictEqual(args_util.parse_inline_params(s)[0], d)
def test_parse_other_types(self): self.assertListEqual(args_util.parse_inline_params(123), []) self.assertListEqual(args_util.parse_inline_params(True), []) self.assertListEqual(args_util.parse_inline_params([1, 2, 3]), []) self.assertListEqual(args_util.parse_inline_params({'a': 123}), [])
def test_parse_null_type(self): self.assertListEqual(args_util.parse_inline_params(None), [])
def test_parse_empty_string(self): self.assertListEqual(args_util.parse_inline_params(str()), [])
def inspect_context(self, parent=None): if parent and not parent.get("spec_path", None): raise ValueError("Parent context is missing spec path.") if parent and not parent.get("schema_path", None): raise ValueError("Parent context is missing schema path.") def decorate_ctx_var(variable, spec_path, schema_path): return { "type": variable[0], "expression": variable[1], "name": variable[2], "spec_path": spec_path, "schema_path": schema_path, } def decorate_ctx_var_error(var, msg): error = expr_util.format_error( var["type"], var["expression"], msg, var["spec_path"], var["schema_path"] ) return error def get_ctx_inputs(prop_name, prop_value): ctx_inputs = [] # By default, context inputs support only dictionary, # list of single item dictionaries, or string spec_types. if isinstance(prop_value, dict): ctx_inputs = list(prop_value.keys()) elif isinstance(prop_value, list): for prop_value_item in prop_value: if isinstance(prop_value_item, six.string_types): ctx_inputs.append(prop_value_item) elif isinstance(prop_value_item, dict) and len(prop_value_item) == 1: ctx_inputs.extend(list(prop_value_item.keys())) elif isinstance(prop_value, six.string_types): ctx_inputs.append(prop_value) return ctx_inputs def inspect_ctx(prop_name, prop_value, spec_path, schema_path, rolling_ctx, errors): ctx_vars = [] for var in expr_base.extract_vars(prop_value): ctx_vars.append(decorate_ctx_var(var, spec_path, schema_path)) for ctx_var in ctx_vars: if ctx_var["name"].startswith("__"): err_msg = ( 'Variable "%s" that is prefixed with double underscores is considered ' "a private variable and cannot be referenced." % ctx_var["name"] ) errors.append(decorate_ctx_var_error(ctx_var, err_msg)) if ctx_var["name"] not in rolling_ctx: err_msg = 'Variable "%s" is referenced before assignment.' % ctx_var["name"] errors.append(decorate_ctx_var_error(ctx_var, err_msg)) if prop_name in self._context_inputs: updated_ctx = get_ctx_inputs(prop_name, prop_value) rolling_ctx = list(set(rolling_ctx + updated_ctx)) return rolling_ctx, errors errors = [] parent_ctx = parent.get("ctx", []) if parent else [] rolling_ctx = list(set(parent_ctx)) for prop_name in self._context_evaluation_sequence: prop_value = getattr(self, prop_name) if not prop_value: continue spec_path = self.get_spec_path(prop_name, parent=parent) schema_path = self.get_schema_path(prop_name, parent=parent) # Pass the inspection downstream if value is a spec. if isinstance(prop_value, Spec): item_parent = { "ctx": rolling_ctx, "spec_path": spec_path, "schema_path": schema_path, } result = prop_value.inspect_context(parent=item_parent) errors.extend(result[0]) rolling_ctx = list(set(rolling_ctx + result[1])) continue # Parse inline parameters from value if value is a string. if isinstance(prop_value, six.string_types): inline_params = args_util.parse_inline_params(prop_value) if inline_params: prop_value = inline_params # Preserve evaluation order if value is a list. if isinstance(prop_value, list): for i in range(0, len(prop_value)): rolling_ctx, errors = inspect_ctx( prop_name, prop_value[i], spec_path + "[" + str(i) + "]", schema_path, rolling_ctx, errors, ) continue # Otherwise evaluate the value as is. rolling_ctx, errors = inspect_ctx( prop_name, prop_value, spec_path, schema_path, rolling_ctx, errors ) return (sorted(errors, key=lambda x: x["spec_path"]), rolling_ctx)
def inspect_context(self, parent=None): if parent and not parent.get('spec_path', None): raise ValueError('Parent context is missing spec path.') if parent and not parent.get('schema_path', None): raise ValueError('Parent context is missing schema path.') def decorate_ctx_var(variable, spec_path, schema_path): return { 'type': variable[0], 'expression': variable[1], 'name': variable[2], 'spec_path': spec_path, 'schema_path': schema_path } def decorate_ctx_var_error(var): error = expr_utils.format_error( var['type'], var['expression'], 'Variable "%s" is referenced before assignment.' % var['name'], var['spec_path'], var['schema_path']) return error def get_ctx_inputs(prop_name, prop_value): ctx_inputs = [] # By default, context inputs support only dictionary, # list of single item dictionaries, or string types. if isinstance(prop_value, dict): ctx_inputs = list(prop_value.keys()) elif isinstance(prop_value, list): for prop_value_item in prop_value: if isinstance(prop_value_item, six.string_types): ctx_inputs.append(prop_value_item) elif isinstance(prop_value_item, dict) and len(prop_value_item) == 1: ctx_inputs.extend(list(prop_value_item.keys())) elif isinstance(prop_value, six.string_types): ctx_inputs.append(prop_value) return ctx_inputs def inspect_ctx(prop_name, prop_value, spec_path, schema_path, rolling_ctx, errors): ctx_vars = [] for var in expr.extract_vars(prop_value): ctx_vars.append(decorate_ctx_var(var, spec_path, schema_path)) for ctx_var in ctx_vars: if ctx_var['name'] not in rolling_ctx: errors.append(decorate_ctx_var_error(ctx_var)) if prop_name in self._context_inputs: updated_ctx = get_ctx_inputs(prop_name, prop_value) rolling_ctx = list(set(rolling_ctx + updated_ctx)) return rolling_ctx, errors errors = [] parent_ctx = parent.get('ctx', []) if parent else [] rolling_ctx = list(set(parent_ctx)) for prop_name in self._context_evaluation_sequence: prop_value = getattr(self, prop_name) if not prop_value: continue spec_path = self.get_spec_path(prop_name, parent=parent) schema_path = self.get_schema_path(prop_name, parent=parent) # Pass the inspection downstream if value is a spec. if isinstance(prop_value, Spec): item_parent = { 'ctx': rolling_ctx, 'spec_path': spec_path, 'schema_path': schema_path } result = prop_value.inspect_context(parent=item_parent) errors.extend(result[0]) rolling_ctx = list(set(rolling_ctx + result[1])) continue # Parse inline parameters from value if value is a string. if isinstance(prop_value, six.string_types): inline_params = args_utils.parse_inline_params(prop_value) if inline_params: prop_value = inline_params # Preserve evaluation order if value is a list. if isinstance(prop_value, list): for i in range(0, len(prop_value)): rolling_ctx, errors = inspect_ctx( prop_name, prop_value[i], spec_path + '[' + str(i) + ']', schema_path, rolling_ctx, errors) continue # Otherwise evaluate the value as is. rolling_ctx, errors = inspect_ctx(prop_name, prop_value, spec_path, schema_path, rolling_ctx, errors) return (sorted(errors, key=lambda x: x['spec_path']), rolling_ctx)