Exemplo n.º 1
0
    def test_execute(self):
        condition1 = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='(.*)')])
            ])
        condition2 = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='(.*)')]),
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='a')])
            ])

        inputs = [('name1', None, ActionResult('aaaa', 'Success'), True),
                  ('name2', condition1, ActionResult('anyString',
                                                     'Success'), True),
                  ('name3', condition2, ActionResult('anyString',
                                                     'Success'), True),
                  ('name4', condition2, ActionResult('bbbb',
                                                     'Success'), False),
                  ('name4', condition2, ActionResult('aaaa', 'Custom'), False)]

        for name, condition, input_str, expect_name in inputs:
            branch = Branch(source_id=1, destination_id=2, condition=condition)
            if expect_name:
                expected_name = branch.destination_id
                self.assertEqual(branch.execute(input_str, {}), expected_name)
            else:
                self.assertIsNone(branch.execute(input_str, {}))
Exemplo n.º 2
0
 def test_execute_no_args(self):
     action = Action(app_name='HelloWorld',
                     action_name='helloWorld',
                     name='helloWorld')
     instance = AppInstance.create(app_name='HelloWorld',
                                   device_name='device1')
     self.assertEqual(action.execute(instance.instance, {}),
                      ActionResult({'message': 'HELLO WORLD'}, 'Success'))
     self.assertEqual(action._output,
                      ActionResult({'message': 'HELLO WORLD'}, 'Success'))
Exemplo n.º 3
0
 def __handle_execution_error(self, e):
     formatted_error = format_exception_message(e)
     if isinstance(e, InvalidArgument):
         event = WalkoffEvent.ActionArgumentsInvalid
         return_type = 'InvalidArguments'
     else:
         event = WalkoffEvent.ActionExecutionError
         return_type = 'UnhandledException'
     self._output = ActionResult('error: {0}'.format(formatted_error), return_type)
     WalkoffEvent.CommonWorkflowSignal.send(self, event=event, data=self._output.as_json())
Exemplo n.º 4
0
def format_result(result):
    """Converts a result to an ActionResult object

    Args:
        result (str|tuple): The result of the action

    Returns:
        (ActionResult): An ActionResult object with the result included in the object
    """
    if not isinstance(result, tuple):
        return ActionResult(result, None)
    else:
        return ActionResult(*result)
Exemplo n.º 5
0
    def test_branch_with_priority(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=10)
        action2 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=5)
        action3 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=1)

        condition = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='aaa')])
            ])

        branch_one = Branch(source_id=action.id,
                            destination_id=5,
                            condition=condition,
                            priority=5)
        branch_two = Branch(source_id=action.id,
                            destination_id=1,
                            condition=condition,
                            priority=1)

        action_result = ActionResult(result='aaa', status='Success')
        workflow = Workflow('test',
                            1,
                            actions=[action, action2, action3],
                            branches=[branch_one, branch_two])
        wf_ctx = WorkflowExecutionContext(workflow, {}, None, None)
        wf_ctx.accumulator[action.id] = action_result.result
        wf_ctx.last_status = action_result.status
        wf_ctx.executing_action = action

        self.assertEqual(
            Executor.get_branch(wf_ctx, LocalActionExecutionStrategy()), 1)
Exemplo n.º 6
0
    def test_get_branch(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=10)
        action2 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=2)
        condition = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='aaa')])
            ])
        branch = Branch(source_id=action.id,
                        destination_id=2,
                        condition=condition)
        action._output = ActionResult(result='aaa', status='Success')
        workflow = Workflow("helloWorld",
                            action.id,
                            actions=[action, action2],
                            branches=[branch])

        result = {'triggered': False}

        def validate_sent_data(sender, **kwargs):
            if isinstance(sender, Branch):
                self.assertIn('event', kwargs)
                self.assertEqual(kwargs['event'], WalkoffEvent.BranchTaken)
                result['triggered'] = True

        WalkoffEvent.CommonWorkflowSignal.connect(validate_sent_data)

        self.assertEqual(workflow.get_branch(action, {}), 2)
        self.assertTrue(result['triggered'])
Exemplo n.º 7
0
    def test_branch_with_priority(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=10)
        action2 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=5)
        action3 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=1)

        condition = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='aaa')])
            ])

        branch_one = Branch(source_id=action.id,
                            destination_id=5,
                            condition=condition,
                            priority=5)
        branch_two = Branch(source_id=action.id,
                            destination_id=1,
                            condition=condition,
                            priority=1)

        action._output = ActionResult(result='aaa', status='Success')
        workflow = Workflow('test',
                            1,
                            actions=[action, action2, action3],
                            branches=[branch_one, branch_two])

        self.assertEqual(workflow.get_branch(action, {}), 1)
Exemplo n.º 8
0
 def __handle_execution_error(self, e):
     formatted_error = format_exception_message(e)
     if isinstance(e, InvalidArgument):
         event = WalkoffEvent.ActionArgumentsInvalid
         return_type = 'InvalidArguments'
     else:
         event = WalkoffEvent.ActionExecutionError
         return_type = 'UnhandledException'
     logger.warning('Exception in {0}: \n{1}'.format(
         self.name, traceback.format_exc()))
     logger.error('Error calling action {0}. Error: {1}'.format(
         self.name, formatted_error))
     self._output = ActionResult('error: {0}'.format(formatted_error),
                                 return_type)
     WalkoffEvent.CommonWorkflowSignal.send(self,
                                            event=event,
                                            data=self._output.as_json())
Exemplo n.º 9
0
    def test_execute(self):
        class MockAction(object):
            id = 13

        condition1 = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='(.*)')])
            ])
        condition2 = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='(.*)')]),
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='a')])
            ])

        inputs = [('name1', None, ActionResult('aaaa', 'Success'), True),
                  ('name2', condition1, ActionResult('anyString',
                                                     'Success'), True),
                  ('name3', condition2, ActionResult('anyString',
                                                     'Success'), True),
                  ('name4', condition2, ActionResult('bbbb',
                                                     'Success'), False),
                  ('name4', condition2, ActionResult('aaaa', 'Custom'), False)]

        for name, condition, previous_result, expect_name in inputs:
            branch = Branch(source_id=1, destination_id=2, condition=condition)
            acc = {MockAction.id: previous_result.result}
            status = previous_result.status
            action = MockAction()
            if expect_name:
                expected_name = branch.destination_id
                self.assertEqual(
                    branch.execute(LocalActionExecutionStrategy(), status,
                                   action, acc), expected_name)
            else:
                self.assertIsNone(
                    branch.execute(LocalActionExecutionStrategy(), status,
                                   action, acc))
Exemplo n.º 10
0
    def execute(self, accumulator, instance=None, arguments=None, resume=False):
        """Executes an Action by calling the associated app function.

        Args:
            accumulator (dict): Dict containing the results of the previous actions
            instance (App, optional): The instance of an App object to be used to execute the associated function.
                This field is required if the Action is a bounded action. Otherwise, it defaults to None.
            arguments (list[Argument], optional): List of Arguments to be used if the Action is the starting step of
                the Workflow. Defaults to None.
            resume (bool, optional): Optional boolean to resume a previously paused workflow. Defaults to False.

        Returns:
            (ActionResult): The result of the executed function.
        """
        logger.info('Executing action {} (id={})'.format(self.name, str(self.name)))
        self._execution_id = str(uuid.uuid4())

        if self.device_id:
            self._resolved_device_id = self.device_id.get_value(accumulator)
            logger.debug('Device resolved to {} for action {}'.format(self._resolved_device_id, str(self.id)))

        if arguments:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted,
                                                   data={'start_arguments': arguments})
        else:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted)

        if self.trigger and not resume:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.TriggerActionAwaitingData)
            logger.debug('Trigger Action {} is awaiting data'.format(self.name))
            self._output = None
            return ActionResult("trigger", "trigger")

        arguments = arguments if arguments else self.arguments

        try:
            args = validate_app_action_parameters(self._arguments_api, 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)
            result.set_default_status(self.app_name, self.action_name)
            if result.is_failure(self.app_name, self.action_name):
                WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionError,
                                                       data=result.as_json())
            else:
                WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionSuccess,
                                                       data=result.as_json())
        except Exception as e:
            logger.exception('Error executing action {} (id={})'.format(self.name, str(self.id)))
            self.__handle_execution_error(e)
        else:
            self._output = result
            logger.debug(
                'Action {0}-{1} (id {2}) executed successfully'.format(self.app_name, self.action_name, self.id))
            return result
Exemplo n.º 11
0
 def test_execute_no_args(self):
     action = Action('HelloWorld',
                     action_name='helloWorld',
                     name='helloWorld')
     instance = TestAction._make_app_instance()
     acc = {}
     result = action.execute(LocalActionExecutionStrategy(), acc,
                             instance.instance)
     expected = ActionResult({'message': 'HELLO WORLD'}, 'Success')
     self.assertEqual(result, expected.status)
     self.assertEqual(acc[action.id], expected.result)
Exemplo n.º 12
0
    def _do_execute(self, context, accumulator, arguments, instance=None):
        workflow_context = {
            'id': self.workflow_context.id,
            'name': self.workflow_context.name
        }
        execution_context = context.as_json()
        execution_id = execution_context.pop('execution_id')
        app_name = execution_context.pop('app_name')
        arguments = [{
            'name': key,
            'value': value
        } for key, value in arguments.items()]
        request_json = {
            'workflow_context': workflow_context,
            'executable_context': execution_context,
            'arguments': arguments
        }
        url = RemoteActionExecutionStrategy.format_url(
            app_name, self.workflow_context.execution_id, execution_id)
        response = requests.post(url, json=request_json)
        data = response.json()
        if response.status_code == 200:
            if context.is_action():
                result = ActionResult(None, data['status'])
            else:
                result = accumulator[str(context.id)]
            if data['status'] == 'UnhandledException' and not context.is_action(
            ):
                raise ExecutionError(message=result)
            return result
        else:
            message = 'Error executing {} {} (id={}) remotely: {{status: {}, data: {}}}'.format(
                context.type, context.executable_name, context.id,
                response.status_code, data)

            logger.error(message)
            if context.is_action():
                return ActionResult(None, 'UnhandledException')
            else:
                raise ExecutionError(message=message)
Exemplo n.º 13
0
 def test_get_branch_invalid_action(self):
     condition = ConditionalExpression(
         'and',
         conditions=[
             Condition('HelloWorld',
                       action_name='regMatch',
                       arguments=[Argument('regex', value='aaa')])
         ])
     branch = Branch(source_id=1, destination_id=2, condition=condition)
     action = Action('HelloWorld', 'helloWorld', 'helloWorld')
     action._output = ActionResult(result='bbb', status='Success')
     with self.assertRaises(InvalidExecutionElement):
         Workflow('test', 1, actions=[action], branches=[branch])
Exemplo n.º 14
0
    def test_branch_counter(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=1)

        branch = Branch(source_id=action.id, destination_id=action.id)
        self.assertEqual(branch._counter, 0)
        accumulator = {}

        action._output = ActionResult(result='aaa', status='Success')
        workflow = Workflow('test', 1, actions=[action], branches=[branch])
        workflow.get_branch(action, accumulator)

        self.assertEqual(branch._counter, 1)
        self.assertIn(branch.id, accumulator)
        self.assertEqual(accumulator[branch.id], 1)
Exemplo n.º 15
0
    def test_branch_counter(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=1)

        branch = Branch(source_id=action.id, destination_id=action.id)
        self.assertEqual(branch._counter, 0)

        action_result = ActionResult(result='aaa', status='Success')
        workflow = Workflow('test', 1, actions=[action], branches=[branch])
        wf_ctx = WorkflowExecutionContext(workflow, None, None)
        wf_ctx.accumulator[action.id] = action_result.result
        wf_ctx.last_status = action_result.status
        wf_ctx.executing_action = action
        Executor.get_branch(wf_ctx, LocalActionExecutionStrategy())

        self.assertEqual(branch._counter, 1)
        self.assertIn(branch.id, wf_ctx.accumulator)
        self.assertEqual(wf_ctx.accumulator[branch.id], '1')
Exemplo n.º 16
0
    def test_get_branch(self):
        action = Action('HelloWorld', 'helloWorld', 'helloWorld', id=10)
        action2 = Action('HelloWorld', 'helloWorld', 'helloWorld', id=2)
        condition = ConditionalExpression(
            'and',
            conditions=[
                Condition('HelloWorld',
                          action_name='regMatch',
                          arguments=[Argument('regex', value='aaa')])
            ])
        branch = Branch(source_id=action.id,
                        destination_id=2,
                        condition=condition)
        action_result = ActionResult(result='aaa', status='Success')
        workflow = Workflow("helloWorld",
                            action.id,
                            actions=[action, action2],
                            branches=[branch])

        wf_ctx = WorkflowExecutionContext(workflow, {}, None, None)
        wf_ctx.accumulator[action.id] = action_result.result
        wf_ctx.last_status = action_result.status
        wf_ctx.executing_action = action

        result = {'triggered': False}

        def validate_sent_data(sender, **kwargs):
            if isinstance(sender, Branch):
                self.assertIn('event', kwargs)
                self.assertEqual(kwargs['event'], WalkoffEvent.BranchTaken)
                result['triggered'] = True

        WalkoffEvent.CommonWorkflowSignal.connect(validate_sent_data)

        self.assertEqual(
            Executor.get_branch(wf_ctx, LocalActionExecutionStrategy()), 2)
        self.assertTrue(result['triggered'])
Exemplo n.º 17
0
 def wrapper(*args, **kwargs):
     try:
         return format_result(func(*args, **kwargs))
     except Exception as e:
         logger.exception('Error executing action')
         return ActionResult.from_exception(e, 'UnhandledException')
Exemplo n.º 18
0
class Action(ExecutionElement, Execution_Base):
    __tablename__ = 'action'
    workflow_id = Column(UUIDType(binary=False), ForeignKey('workflow.id'))
    app_name = Column(String(80), nullable=False)
    action_name = Column(String(80), nullable=False)
    name = Column(String(80), nullable=False)
    device_id = relationship('Argument', uselist=False, cascade='all, delete-orphan',
                             foreign_keys=[Argument.action_device_id])
    arguments = relationship('Argument', cascade='all, delete, delete-orphan', foreign_keys=[Argument.action_id])
    trigger = relationship('ConditionalExpression', cascade='all, delete-orphan', uselist=False)
    position = relationship('Position', uselist=False, cascade='all, delete-orphan')
    children = ('arguments', 'trigger')

    def __init__(self, app_name, action_name, name, device_id=None, id=None, arguments=None, trigger=None,
                 position=None):
        """Initializes a new Action object. A Workflow has one or more 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): The name of the Action object.
            device_id (Argument, optional): The device_id for the Action. This device_id is specified in the Argument
                object. If the device_id should be static, then device_id.value should be set to the static device_id.
                If the device_id should be fetched from a previous Action, then the reference and optional selection
                fields of the Argument object should be filled. Defaults to None.
            id (str|UUID, optional): Optional UUID to pass into the Action. Must be UUID object or valid UUID string.
                Defaults to None.
            arguments (list[Argument], optional): A list of Argument objects that are parameters to the action.
                Defaults to None.
            trigger (ConditionalExpression, optional): A ConditionalExpression which causes an Action to wait until the
                data is sent fulfilling the condition. Defaults to None.
            position (Position, optional): Position object for the Action. Defaults to None.
        """
        ExecutionElement.__init__(self, id)

        self.trigger = trigger

        self.name = name
        self.device_id = device_id
        self.app_name = app_name
        self.action_name = action_name

        self.arguments = []
        if arguments:
            self.arguments = arguments

        self.position = position

        self._run = None
        self._arguments_api = None
        self._output = None
        self._execution_id = 'default'
        self._action_executable = None
        self._resolved_device_id = -1
        self.validate()

    @orm.reconstructor
    def init_on_load(self):
        """Loads all necessary fields upon Action being loaded from database"""
        if not self.errors:
            errors = []
            try:
                self._run, self._arguments_api = get_app_action_api(self.app_name, self.action_name)
                self._action_executable = get_app_action(self.app_name, self._run)
            except UnknownApp:
                errors.append('Unknown app {}'.format(self.app_name))
            except UnknownAppAction:
                errors.append('Unknown app action {}'.format(self.action_name))
            self.errors = errors
        self._output = None
        self._execution_id = 'default'
        self._resolved_device_id = -1

    def validate(self):
        """Validates the object"""
        errors = []
        try:
            self._run, self._arguments_api = get_app_action_api(self.app_name, self.action_name)
            self._action_executable = get_app_action(self.app_name, self._run)
            if is_app_action_bound(self.app_name, self._run) and not self.device_id:
                message = 'App action is bound but no device ID was provided.'.format(self.name)
                errors.append(message)
            validate_app_action_parameters(self._arguments_api, self.arguments, self.app_name, self.action_name)
        except UnknownApp:
            errors.append('Unknown app {}'.format(self.app_name))
        except UnknownAppAction:
            errors.append('Unknown app action {}'.format(self.action_name))
        except InvalidArgument as e:
            errors.extend(e.errors)
        self.errors = errors

    def get_output(self):
        """Gets the output of an Action (the result)

        Returns:
            (ActionResult): The result of the Action
        """
        return self._output

    def get_execution_id(self):
        """Gets the execution ID of the Action

        Returns:
            (UUID): The execution ID
        """
        return self._execution_id

    def execute(self, accumulator, instance=None, arguments=None, resume=False):
        """Executes an Action by calling the associated app function.

        Args:
            accumulator (dict): Dict containing the results of the previous actions
            instance (App, optional): The instance of an App object to be used to execute the associated function.
                This field is required if the Action is a bounded action. Otherwise, it defaults to None.
            arguments (list[Argument], optional): List of Arguments to be used if the Action is the starting step of
                the Workflow. Defaults to None.
            resume (bool, optional): Optional boolean to resume a previously paused workflow. Defaults to False.

        Returns:
            (ActionResult): The result of the executed function.
        """
        logger.info('Executing action {} (id={})'.format(self.name, str(self.name)))
        self._execution_id = str(uuid.uuid4())

        if self.device_id:
            self._resolved_device_id = self.device_id.get_value(accumulator)
            logger.debug('Device resolved to {} for action {}'.format(self._resolved_device_id, str(self.id)))

        if arguments:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted,
                                                   data={'start_arguments': arguments})
        else:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted)

        if self.trigger and not resume:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.TriggerActionAwaitingData)
            logger.debug('Trigger Action {} is awaiting data'.format(self.name))
            self._output = None
            return ActionResult("trigger", "trigger")

        arguments = arguments if arguments else self.arguments

        try:
            args = validate_app_action_parameters(self._arguments_api, 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)
            result.set_default_status(self.app_name, self.action_name)
            if result.is_failure(self.app_name, self.action_name):
                WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionError,
                                                       data=result.as_json())
            else:
                WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionSuccess,
                                                       data=result.as_json())
        except Exception as e:
            logger.exception('Error executing action {} (id={})'.format(self.name, str(self.id)))
            self.__handle_execution_error(e)
        else:
            self._output = result
            logger.debug(
                'Action {0}-{1} (id {2}) executed successfully'.format(self.app_name, self.action_name, self.id))
            return result

    def __handle_execution_error(self, e):
        formatted_error = format_exception_message(e)
        if isinstance(e, InvalidArgument):
            event = WalkoffEvent.ActionArgumentsInvalid
            return_type = 'InvalidArguments'
        else:
            event = WalkoffEvent.ActionExecutionError
            return_type = 'UnhandledException'
        self._output = ActionResult('error: {0}'.format(formatted_error), return_type)
        WalkoffEvent.CommonWorkflowSignal.send(self, event=event, data=self._output.as_json())

    def execute_trigger(self, data_in, accumulator):
        """Executes the trigger for an Action, which will continue execution if the trigger returns True

        Args:
            data_in (dict): The data to send to the trigger to test against
            accumulator (dict): Dict containing the results of the previous actions

        Returns:
            (bool): True if the trigger returned True, False otherwise
        """
        if self.trigger.execute(data_in=data_in, accumulator=accumulator):
            logger.debug('Trigger is valid for input {0}'.format(data_in))
            return True
        else:
            logger.debug('Trigger is not valid for input {0}'.format(data_in))
            return False

    def get_resolved_device_id(self):
        return self._resolved_device_id
Exemplo n.º 19
0
class Action(ExecutionElement, Device_Base):
    __tablename__ = 'action'
    workflow_id = Column(UUIDType(binary=False), ForeignKey('workflow.id'))
    app_name = Column(String(80), nullable=False)
    action_name = Column(String(80), nullable=False)
    name = Column(String(80), nullable=False)
    device_id = Column(Integer)
    arguments = relationship('Argument', cascade='all, delete, delete-orphan')
    trigger = relationship('ConditionalExpression',
                           cascade='all, delete-orphan',
                           uselist=False)
    position = relationship('Position',
                            uselist=False,
                            cascade='all, delete-orphan')

    def __init__(self,
                 app_name,
                 action_name,
                 name,
                 device_id=None,
                 id=None,
                 arguments=None,
                 trigger=None,
                 position=None):
        """Initializes a new Action object. A Workflow has one or more 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): The name of the Action object.
            device_id (int, optional): The id of the device associated with the app associated with the Action. Defaults
                to None.
            id (str|UUID, optional): Optional UUID to pass into the Action. Must be UUID object or valid UUID string.
                Defaults to None.
            arguments (list[Argument], optional): A list of Argument objects that are parameters to the action.
                Defaults to None.
            trigger (ConditionalExpression, optional): A ConditionalExpression which causes an Action to wait until the
                data is sent fulfilling the condition. Defaults to None.
            position (Position, optional): Position object for the Action. Defaults to None.
        """
        ExecutionElement.__init__(self, id)

        self.trigger = trigger

        self.name = name
        self.device_id = device_id
        self.app_name = app_name
        self.action_name = action_name

        self.arguments = []
        if arguments:
            self.arguments = arguments

        self.position = position

        self._run = None
        self._arguments_api = None
        self._output = None
        self._execution_id = 'default'

        self.validate()
        self._action_executable = get_app_action(self.app_name, self._run)

    @orm.reconstructor
    def init_on_load(self):
        """Loads all necessary fields upon Action being loaded from database"""
        self._run, self._arguments_api = get_app_action_api(
            self.app_name, self.action_name)
        self._output = None
        self._action_executable = get_app_action(self.app_name, self._run)
        self._execution_id = 'default'

    def validate(self):
        errors = {}
        try:
            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:
                message = 'App action is bound but no device ID was provided.'.format(
                    self.name)
                errors['executable'] = message
            validate_app_action_parameters(self._arguments_api, self.arguments,
                                           self.app_name, self.action_name)
        except UnknownApp:
            errors['executable'] = 'Unknown app {}'.format(self.app_name)
        except UnknownAppAction:
            errors['executable'] = 'Unknown app action {}'.format(
                self.action_name)
        except InvalidArgument as e:
            errors['arguments'] = e.errors
        if errors:
            raise InvalidExecutionElement(self.id,
                                          self.action_name,
                                          'Invalid action {}'.format(
                                              self.id or self.action_name),
                                          errors=[errors])

    def get_output(self):
        """Gets the output of an Action (the result)
        Returns:
            The result of the Action
        """
        return self._output

    def get_execution_id(self):
        """Gets the execution ID of the Action
        Returns:
            The execution ID
        """
        return self._execution_id

    def set_arguments(self, new_arguments):
        """Updates the arguments for an Action object.
        Args:
            new_arguments ([Argument]): The new Arguments for the Action object.
        """
        validate_app_action_parameters(self._arguments_api, new_arguments,
                                       self.app_name, self.action_name)
        self.arguments = new_arguments

    def execute(self, instance, accumulator, arguments=None, resume=False):
        """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
            arguments (list[Argument]): Optional list of Arguments to be used if the Action is the starting step of
                the Workflow. Defaults to None.
            resume (bool, optional): Optional boolean to resume a previously paused workflow. Defaults to False.
        Returns:
            The result of the executed function.
        """
        self._execution_id = str(uuid.uuid4())

        WalkoffEvent.CommonWorkflowSignal.send(
            self, event=WalkoffEvent.ActionStarted)
        if self.trigger and not resume:
            WalkoffEvent.CommonWorkflowSignal.send(
                self, event=WalkoffEvent.TriggerActionAwaitingData)
            logger.debug('Trigger Action {} is awaiting data'.format(
                self.name))
            self._output = None
            return ActionResult("trigger", "trigger")

        arguments = arguments if arguments else self.arguments

        try:
            args = validate_app_action_parameters(self._arguments_api,
                                                  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)
            result.set_default_status(self.app_name, self.action_name)
            if result.is_failure(self.app_name, self.action_name):
                WalkoffEvent.CommonWorkflowSignal.send(
                    self,
                    event=WalkoffEvent.ActionExecutionError,
                    data=result.as_json())
            else:
                WalkoffEvent.CommonWorkflowSignal.send(
                    self,
                    event=WalkoffEvent.ActionExecutionSuccess,
                    data=result.as_json())
        except Exception as e:
            self.__handle_execution_error(e)
        else:
            self._output = result
            logger.debug(
                'Action {0}-{1} (id {2}) executed successfully'.format(
                    self.app_name, self.action_name, self.id))
            return result

    def __handle_execution_error(self, e):
        formatted_error = format_exception_message(e)
        if isinstance(e, InvalidArgument):
            event = WalkoffEvent.ActionArgumentsInvalid
            return_type = 'InvalidArguments'
        else:
            event = WalkoffEvent.ActionExecutionError
            return_type = 'UnhandledException'
        logger.warning('Exception in {0}: \n{1}'.format(
            self.name, traceback.format_exc()))
        logger.error('Error calling action {0}. Error: {1}'.format(
            self.name, formatted_error))
        self._output = ActionResult('error: {0}'.format(formatted_error),
                                    return_type)
        WalkoffEvent.CommonWorkflowSignal.send(self,
                                               event=event,
                                               data=self._output.as_json())

    def execute_trigger(self, data_in, accumulator):
        """Executes the trigger for an Action, which will continue execution if the trigger returns True
        Args:
            data_in (dict): The data to send to the trigger to test against
            accumulator (dict): Dict containing the results of the previous actions
        Returns:
            True if the trigger returned True, False otherwise
        """
        if self.trigger.execute(data_in=data_in, accumulator=accumulator):
            logger.debug('Trigger is valid for input {0}'.format(data_in))
            return True
        else:
            logger.debug('Trigger is not valid for input {0}'.format(data_in))
            return False
Exemplo n.º 20
0
 def wrapper(*args, **kwargs):
     try:
         return format_result(func(*args, **kwargs))
     except Exception as e:
         logger.exception('Error executing action')
         return ActionResult.from_exception(e, 'UnhandledException')
Exemplo n.º 21
0
    def execute(self, action_execution_strategy, accumulator, instance=None, arguments=None, resume=False):
        """Executes an Action by calling the associated app function.

        Args:
            action_execution_strategy: The strategy used to execute the action (e.g. LocalActionExecutionStrategy)
            accumulator (dict): Dict containing the results of the previous actions
            instance (App, optional): The instance of an App object to be used to execute the associated function.
                This field is required if the Action is a bounded action. Otherwise, it defaults to None.
            arguments (list[Argument], optional): List of Arguments to be used if the Action is the starting step of
                the Workflow. Defaults to None.
            resume (bool, optional): Optional boolean to resume a previously paused workflow. Defaults to False.

        Returns:
            (ActionResult): The result of the executed function.
        """
        logger.info('Executing action {} (id={})'.format(self.name, str(self.name)))
        self._execution_id = str(uuid.uuid4())

        if self.device_id:
            self._resolved_device_id = self.device_id.get_value(accumulator)
            logger.debug('Device resolved to {} for action {}'.format(self._resolved_device_id, str(self.id)))

        if arguments:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted,
                                                   data={'start_arguments': arguments})
        else:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionStarted)

        if self.trigger and not resume:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.TriggerActionAwaitingData)
            logger.debug('Trigger Action {} is awaiting data'.format(self.name))
            return ActionResult("trigger", "trigger")

        arguments = arguments if arguments else self.arguments

        try:
            args = validate_app_action_parameters(self._arguments_api, arguments, self.app_name, self.action_name,
                                                  accumulator=accumulator)
        except InvalidArgument as e:
            result = ActionResult.from_exception(e, 'InvalidArguments')
            accumulator[self.id] = result.result
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionArgumentsInvalid,
                                                   data=result.as_json())
            return result.status

        if is_app_action_bound(self.app_name, self._run):
            result = action_execution_strategy.execute(self, accumulator, args, instance=instance)
        else:
            result = action_execution_strategy.execute(self, accumulator, args)

        if result.status == 'UnhandledException':
            logger.error('Error executing action {} (id={})'.format(self.name, str(self.id)))
        else:
            logger.debug(
                'Action {0}-{1} (id {2}) executed successfully'.format(self.app_name, self.action_name, self.id))

        result.set_default_status(self.app_name, self.action_name)
        if result.is_failure(self.app_name, self.action_name):
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionError,
                                                   data=result.as_json())
        else:
            WalkoffEvent.CommonWorkflowSignal.send(self, event=WalkoffEvent.ActionExecutionSuccess,
                                                   data=result.as_json())
        return result.status