Beispiel #1
0
 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
Beispiel #2
0
    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 = {}
Beispiel #3
0
def validate_actions(actions, dereferencer, app_name):
    from apps import get_all_actions_for_app, get_app_action
    defined_actions = get_all_actions_for_app(app_name)
    seen = set()
    for action_name, action in actions.items():
        if action['run'] not in defined_actions:
            raise InvalidApi('Action {0} has "run" property {1} '
                             'which is not defined in App {2}'.format(
                                 action_name, action['run'], app_name))
        action = dereferencer(action)
        action_params = dereferencer(action.get('parameters', []))
        event = action.get('event', '')
        if action_params:
            validate_action_params(action_params,
                                   dereferencer,
                                   app_name,
                                   action_name,
                                   get_app_action(app_name, action['run']),
                                   event=event)
        validate_app_action_return_codes(action.get('returns', []), app_name,
                                         action_name)
        seen.add(action['run'])
    if seen != set(defined_actions.keys()):
        logger.warning(
            'App {0} has defined the following actions which do not have a corresponding API: '
            '{1}'.format(app_name, (set(defined_actions.keys()) - seen)))
Beispiel #4
0
 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 test_validate_action_params_too_few_params_in_api(self):
     self.basicapi['actions']['Add Three'] = {'run': 'main.Main.addThree',
                                              'parameters': [{'name': 'num1',
                                                              'type': 'number'},
                                                             {'name': 'num2',
                                                              'type': 'number'}]}
     self.__generate_resolver_dereferencer(self.basicapi)
     with self.assertRaises(InvalidApi):
         validate_action_params(self.basicapi['actions']['Add Three']['parameters'],
                                self.dereferencer,
                                'HelloWorldBounded',
                                'Add Three',
                                get_app_action('HelloWorldBounded', 'main.Main.addThree'))
 def test_validate_action_params_no_duplicate_params_matches_signature(self):
     self.basicapi['actions']['Add Three'] = {'run': 'main.Main.addThree',
                                              'parameters': [{'name': 'num1',
                                                              'type': 'number'},
                                                             {'name': 'num2',
                                                              'type': 'number'},
                                                             {'name': 'num3',
                                                              'type': 'number'}]}
     self.__generate_resolver_dereferencer(self.basicapi)
     validate_action_params(self.basicapi['actions']['Add Three']['parameters'],
                            self.dereferencer,
                            'HelloWorldBounded',
                            'Add Three',
                            get_app_action('HelloWorldBounded', 'main.Main.addThree'))
 def test_validate_action_params_duplicate_param_name(self):
     self.basicapi['actions']['Add Three'] = {'run': 'main.Main.addThree',
                                              'parameters': [{'name': 'num1',
                                                              'type': 'number'},
                                                             {'name': 'num1',
                                                              'type': 'string'},
                                                             {'name': 'num2',
                                                              'type': 'number'}]}
     self.__generate_resolver_dereferencer(self.basicapi, expected_success=False)
     with self.assertRaises(InvalidApi):
         validate_action_params(self.basicapi['actions']['Add Three']['parameters'],
                                self.dereferencer,
                                'HelloWorldBounded',
                                'Add Three',
                                get_app_action('HelloWorldBounded', 'main.Main.addThree'))
Beispiel #8
0
 def test_validate_action_params_event(self):
     self.basicapi['actions']['Sample Event'] = {
         'run': 'main.Main.sample_event',
         'event': 'Event1',
         'parameters': [
             {
                 'name': 'arg1',
                 'type': 'number'
             },
         ]
     }
     self.__generate_resolver_dereferencer(self.basicapi)
     validate_action_params(
         self.basicapi['actions']['Sample Event']['parameters'],
         self.dereferencer,
         'HelloWorld',
         'Sample Event',
         get_app_action('HelloWorld', 'main.Main.sample_event'),
         event='event1')
Beispiel #9
0
    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'
Beispiel #10
0
    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
Beispiel #11
0
    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'