def set_arguments(self, new_arguments): """Updates the arguments for an Action object. Args: new_arguments ([Argument]): The new Arguments for the Action object. """ new_arguments = {arg.name: arg for arg in new_arguments} validate_app_action_parameters(self._arguments_api, new_arguments, self.app_name, self.action_name) self.arguments = new_arguments
def _from_xml(self, step_xml, parent_name='', ancestry=None): self.raw_xml = step_xml name = step_xml.get('id') ExecutionElement.__init__(self, name=name, parent_name=parent_name, ancestry=ancestry) self.action = step_xml.find('action').text self.app = step_xml.find('app').text self.run, self.input_api = get_app_action_api(self.app, self.action) is_templated_xml = step_xml.find('templated') self.templated = is_templated_xml is not None and bool( is_templated_xml.text) get_app_action(self.app, self.run) input_xml = step_xml.find('inputs') if input_xml is not None: inputs = inputs_xml_to_dict(input_xml) or {} if not self.templated: self.input = validate_app_action_parameters( self.input_api, inputs, self.app, self.action) else: self.input = inputs else: self.input = validate_app_action_parameters( self.input_api, {}, self.app, self.action) device_field = step_xml.find('device') self.device = device_field.text if device_field is not None else '' risk_field = step_xml.find('risk') self.risk = risk_field.text if risk_field is not None else 0 self.conditionals = [ nextstep.NextStep(xml=next_step_element, parent_name=self.name, ancestry=self.ancestry) for next_step_element in step_xml.findall('next') ] self.widgets = [ _Widget(widget.get('app'), widget.text) for widget in step_xml.findall('widgets/*') ] position = step_xml.find('position') if position is None: self.position = {} else: x_position = position.find('x') y_position = position.find('y') if x_position is not None and y_position is not None: self.position = {'x': x_position.text, 'y': y_position.text} else: self.position = {}
def execute(self, instance, accumulator): """Executes a Step by calling the associated app function. Args: instance (App): The instance of an App object to be used to execute the associated function. accumulator (dict): Dict containing the results of the previous steps Returns: The result of the executed function. """ callbacks.StepInputValidated.send(self) try: args = dereference_step_routing(self.input, accumulator, 'In step {0}'.format(self.name)) args = validate_app_action_parameters(self.input_api, args, self.app, self.action) action = get_app_action(self.app, self.run) result = action(instance, **args) callbacks.FunctionExecutionSuccess.send(self, data=json.dumps({"result": result})) except InvalidInput as e: logger.error('Error calling step {0}. Error: {1}'.format(self.name, str(e))) callbacks.StepInputInvalid.send(self) self.output = 'error: {0}'.format(str(e)) raise except Exception as e: logger.error('Error calling step {0}. Error: {1}'.format(self.name, str(e))) self.output = 'error: {0}'.format(str(e)) raise else: self.output = result for widget in self.widgets: get_widget_signal(widget.app, widget.widget).send(self, data=json.dumps({"result": result})) logger.debug('Step {0} executed successfully'.format(self.ancestry)) return result
def __init__(self, xml=None, name='', action='', app='', device='', inputs=None, next_steps=None, errors=None, parent_name='', position=None, ancestry=None, widgets=None, risk=0): """Initializes a new Step object. A Workflow has many steps that it executes. Args: xml (ElementTree, optional): The XML element tree object. Defaults to None. name (str, optional): The name of the Step object. Defaults to an empty string. action (str, optional): The name of the action associated with a Step. Defaults to an empty string. app (str, optional): The name of the app associated with the Step. Defaults to an empty string. device (str, optional): The name of the device associated with the app associated with the Step. Defaults to an empty string. inputs (dict, optional): A dictionary of Argument objects that are input to the step execution. Defaults to None. next_steps (list[NextStep], optional): A list of NextStep objects for the Step object. Defaults to None. errors (list[NextStep], optional): A list of NextStep error objects for the Step object. Defaults to None. parent_name (str, optional): The name of the parent for ancestry purposes. Defaults to an empty string. position (dict, optional): A dictionary with the x and y coordinates of the Step object. This is used for UI display purposes. Defaults to None. ancestry (list[str], optional): The ancestry for the Step object. Defaults to None. widgets (list[tuple(str, str)], optional): A list of widget tuples, which holds the app and the corresponding widget. Defaults to None. risk (int, optional): The risk associated with the Step. Defaults to 0. """ ExecutionElement.__init__(self, name=name, parent_name=parent_name, ancestry=ancestry) if xml is not None: self._from_xml(xml, parent_name=parent_name, ancestry=ancestry) else: if action == '' or app == '': raise InvalidElementConstructed('Either both action and app or xml must be ' 'specified in step constructor') self.action = action self.app = app self.run, self.input_api = get_app_action_api(self.app, self.action) get_app_action(self.app, self.run) inputs = inputs if inputs is not None else {} self.input = validate_app_action_parameters(self.input_api, inputs, self.app, self.action) self.device = device self.risk = risk self.conditionals = next_steps if next_steps is not None else [] self.errors = errors if errors is not None else [] self.position = position if position is not None else {} self.widgets = [_Widget(widget_app, widget_name) for (widget_app, widget_name) in widgets] if widgets is not None else [] self.raw_xml = self.to_xml() self.templated = False self.output = None self.next_up = None
def set_input(self, new_input): """Updates the input for a Step object. Args: new_input (dict): The new inputs for the Step object. """ self.inputs = validate_app_action_parameters(self._input_api, new_input, self.app, self.action)
def _update_json(self, updated_json): self.action_name = updated_json['action_name'] self.app_name = updated_json['app_name'] self.device_id = updated_json[ 'device_id'] if 'device_id' in updated_json else None arguments = {} if 'arguments' in updated_json: for argument_json in updated_json['arguments']: argument = Argument(**argument_json) arguments[argument.name] = argument if arguments is not None: if not self.templated: validate_app_action_parameters(self._arguments_api, arguments, self.app_name, self.action_name) else: validate_app_action_parameters(self._arguments_api, [], self.app_name, self.action_name) self.arguments = arguments
def _update_xml(self, step_xml): self.action = step_xml.find('action').text self.app = step_xml.find('app').text device_field = step_xml.find('device') self.device = device_field.text if device_field is not None else '' risk_field = step_xml.find('risk') self.risk = risk_field.text if risk_field is not None else 0 input_xml = step_xml.find('inputs') if input_xml is not None: inputs = inputs_xml_to_dict(input_xml) or {} if not self.templated: self.input = validate_app_action_parameters(self.input_api, inputs, self.app, self.action) else: self.input = inputs else: self.input = validate_app_action_parameters(self.input_api, {}, self.app, self.action) self.conditionals = [nextstep.NextStep(xml=next_step_element, parent_name=self.name, ancestry=self.ancestry) for next_step_element in step_xml.findall('next')] self.errors = [nextstep.NextStep(xml=error_step_element, parent_name=self.name, ancestry=self.ancestry) for error_step_element in step_xml.findall('error')]
def _update_json(self, updated_json): self.action = updated_json['action'] self.app = updated_json['app'] self.device = updated_json['device'] if 'device' in updated_json else '' self.risk = updated_json['risk'] if 'risk' in updated_json else 0 inputs = {arg['name']: arg['value'] for arg in updated_json['inputs'] } if 'inputs' in updated_json else {} if inputs is not None: if not self.templated: self.inputs = validate_app_action_parameters( self._input_api, inputs, self.app, self.action) else: self.inputs = inputs else: self.inputs = validate_app_action_parameters( self._input_api, {}, self.app, self.action) self.next_steps = [ NextStep.create(cond_json) for cond_json in updated_json['next_steps'] ]
def __init__(self, app, action, name='', device='', inputs=None, triggers=None, next_steps=None, position=None, widgets=None, risk=0, uid=None, templated=False, raw_representation=None): """Initializes a new Step object. A Workflow has many steps that it executes. Args: app (str): The name of the app associated with the Step action (str): The name of the action associated with a Step name (str, optional): The name of the Step object. Defaults to an empty string. device (str, optional): The name of the device associated with the app associated with the Step. Defaults to an empty string. inputs (dict, optional): A dictionary of Argument objects that are input to the step execution. Defaults to None. triggers (list[Flag], optional): A list of Flag objects for the Step. If a Step should wait for data before continuing, then include these Trigger objects in the Step init. Defaults to None. next_steps (list[NextStep], optional): A list of NextStep objects for the Step object. Defaults to None. position (dict, optional): A dictionary with the x and y coordinates of the Step object. This is used for UI display purposes. Defaults to None. widgets (list[tuple(str, str)], optional): A list of widget tuples, which holds the app and the corresponding widget. Defaults to None. risk (int, optional): The risk associated with the Step. Defaults to 0. uid (str, optional): A universally unique identifier for this object. Created from uuid.uuid4().hex in Python templated (bool, optional): Whether or not the Step is templated. Used for Jinja templating. raw_representation (dict, optional): JSON representation of this object. Used for Jinja templating. """ ExecutionElement.__init__(self, uid) self.triggers = triggers if triggers is not None else [] self._incoming_data = AsyncResult() self.name = name self.app = app self.action = action self._run, self._input_api = get_app_action_api(self.app, self.action) get_app_action(self.app, self._run) if isinstance(inputs, list): inputs = {arg['name']: arg['value'] for arg in inputs} elif isinstance(inputs, dict): inputs = inputs else: inputs = {} self.templated = templated if not self.templated: self.inputs = validate_app_action_parameters( self._input_api, inputs, self.app, self.action) else: self.inputs = inputs self.device = device if (device is not None and device != 'None') else '' self.risk = risk self.next_steps = next_steps if next_steps is not None else [] self.position = position if position is not None else {} self.widgets = [ widget if isinstance(widget, Widget) else Widget(**widget) for widget in widgets ] if widgets is not None else [] self._output = None self._next_up = None self._raw_representation = raw_representation if raw_representation is not None else {} self._execution_uid = 'default'
def execute(self, instance, accumulator): """Executes a Step by calling the associated app function. Args: instance (App): The instance of an App object to be used to execute the associated function. accumulator (dict): Dict containing the results of the previous steps Returns: The result of the executed function. """ self._execution_uid = uuid.uuid4().hex data_sent.send(self, callback_name="Step Started", object_type="Step") if self.triggers: data_sent.send(self, callback_name="Trigger Step Awaiting Data", object_type="Step") logger.debug('Trigger Step {} is awaiting data'.format(self.name)) while True: try: data = self._incoming_data.get(timeout=1) self._incoming_data = AsyncResult() except gevent.Timeout: gevent.sleep(0.1) continue data_in = data['data_in'] inputs = data['inputs'] if 'inputs' in data else {} if all( flag.execute(data_in=data_in, accumulator=accumulator) for flag in self.triggers): data_sent.send(self, callback_name="Trigger Step Taken", object_type="Step") logger.debug( 'Trigger is valid for input {0}'.format(data_in)) accumulator[self.name] = data_in if inputs: self.inputs.update(inputs) break else: logger.debug( 'Trigger is not valid for input {0}'.format(data_in)) data_sent.send(self, callback_name="Trigger Step Not Taken", object_type="Step") gevent.sleep(0.1) try: args = dereference_step_routing(self.inputs, accumulator, 'In step {0}'.format(self.name)) args = validate_app_action_parameters(self._input_api, args, self.app, self.action) action = get_app_action(self.app, self._run) if is_app_action_bound(self.app, self._run): result = action(instance, **args) else: result = action(**args) data_sent.send(self, callback_name="Function Execution Success", object_type="Step", data=json.dumps({"result": result.as_json()})) except InvalidInput as e: formatted_error = format_exception_message(e) logger.error('Error calling step {0}. Error: {1}'.format( self.name, formatted_error)) data_sent.send(self, callback_name="Step Input Invalid", object_type="Step") self._output = ActionResult('error: {0}'.format(formatted_error), 'InvalidInput') raise except Exception as e: formatted_error = format_exception_message(e) logger.error('Error calling step {0}. Error: {1}'.format( self.name, formatted_error)) self._output = ActionResult('error: {0}'.format(formatted_error), 'UnhandledException') raise else: self._output = result for widget in self.widgets: get_widget_signal(widget.app, widget.name).send( self, data=json.dumps({"result": result.as_json()})) logger.debug('Step {0}-{1} (uid {2}) executed successfully'.format( self.app, self.action, self.uid)) return result
def set_input(self, new_input): self.input = validate_app_action_parameters(self.input_api, new_input, self.app, self.action)
def __init__(self, app_name, action_name, name='', device_id=None, arguments=None, triggers=None, position=None, uid=None, templated=False, raw_representation=None): """Initializes a new Action object. A Workflow has many actions that it executes. Args: app_name (str): The name of the app associated with the Action action_name (str): The name of the action associated with a Action name (str, optional): The name of the Action object. Defaults to an empty string. device_id (int, optional): The id of the device associated with the app associated with the Action. Defaults to None. arguments ([Argument], optional): A list of Argument objects that are parameters to the action. Defaults to None. triggers (list[Flag], optional): A list of Flag objects for the Action. If a Action should wait for data before continuing, then include these Trigger objects in the Action init. Defaults to None. position (dict, optional): A dictionary with the x and y coordinates of the Action object. This is used for UI display purposes. Defaults to None. uid (str, optional): A universally unique identifier for this object. Created from uuid.uuid4().hex in Python templated (bool, optional): Whether or not the Action is templated. Used for Jinja templating. raw_representation (dict, optional): JSON representation of this object. Used for Jinja templating. """ ExecutionElement.__init__(self, uid) self.triggers = triggers if triggers is not None else [] self._incoming_data = None self._event = threading.Event() self.name = name self.device_id = device_id self.app_name = app_name self.action_name = action_name self._run, self._arguments_api = get_app_action_api( self.app_name, self.action_name) if is_app_action_bound(self.app_name, self._run) and not self.device_id: raise InvalidArgument( "Cannot initialize Action {}. App action is bound but no device ID was provided." .format(self.name)) self._action_executable = get_app_action(self.app_name, self._run) arguments = {argument.name: argument for argument in arguments } if arguments is not None else {} self.templated = templated if not self.templated: validate_app_action_parameters(self._arguments_api, arguments, self.app_name, self.action_name) self.arguments = arguments self.position = position if position is not None else {} self._output = None self._raw_representation = raw_representation if raw_representation is not None else {} self._execution_uid = 'default'
def execute(self, instance, accumulator): """Executes an Action by calling the associated app function. Args: instance (App): The instance of an App object to be used to execute the associated function. accumulator (dict): Dict containing the results of the previous actions Returns: The result of the executed function. """ self._execution_uid = str(uuid.uuid4()) WalkoffEvent.CommonWorkflowSignal.send( self, event=WalkoffEvent.ActionStarted) if self.triggers: WalkoffEvent.CommonWorkflowSignal.send( self, event=WalkoffEvent.TriggerActionAwaitingData) logger.debug('Trigger Action {} is awaiting data'.format( self.name)) self._wait_for_trigger(accumulator) try: args = validate_app_action_parameters(self._arguments_api, self.arguments, self.app_name, self.action_name, accumulator=accumulator) if is_app_action_bound(self.app_name, self._run): result = self._action_executable(instance, **args) else: result = self._action_executable(**args) WalkoffEvent.CommonWorkflowSignal.send( self, event=WalkoffEvent.ActionExecutionSuccess, data=result.as_json()) except InvalidArgument as e: formatted_error = format_exception_message(e) logger.error('Error calling action {0}. Error: {1}'.format( self.name, formatted_error)) # TODO: Should this event return the error? WalkoffEvent.CommonWorkflowSignal.send( self, event=WalkoffEvent.ActionArgumentsInvalid) self._output = ActionResult('error: {0}'.format(formatted_error), 'InvalidArguments') except Exception as e: formatted_error = format_exception_message(e) logger.exception('Error calling action {0}. Error: {1}'.format( self.name, formatted_error)) self._output = ActionResult('error: {0}'.format(formatted_error), 'UnhandledException') WalkoffEvent.CommonWorkflowSignal.send( self, event=WalkoffEvent.ActionExecutionError, data=self._output.as_json()) else: self._output = result logger.debug( 'Action {0}-{1} (uid {2}) executed successfully'.format( self.app_name, self.action_name, self.uid)) return result