Beispiel #1
0
def convert_action_device(action):
    device_name = action.pop('device')
    action_run, _ = get_app_action_api(action['app_name'], action['action_name'])
    if is_app_action_bound(action['app_name'], action_run):
        device_id = convert_device_to_device_id(action['app_name'], device_name, action['action_name'])
        if device_id is not None:
            action['device_id'] = device_id
Beispiel #2
0
 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])
Beispiel #3
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
Beispiel #4
0
def format_app_action_api(api, app_name, action_type):
    ret = deepcopy(api)
    if 'returns' in api:
        ret['returns'] = format_returns(ret['returns'], 'event' in api)
    if action_type in ('conditions', 'transforms') or not is_app_action_bound(app_name, api['run']):
        ret['global'] = True
    if 'parameters' in api:
        ret['parameters'] = [extract_schema(param_api) for param_api in ret['parameters']]
    else:
        ret['parameters'] = []
    return ret
Beispiel #5
0
def format_app_action_api(api, app_name, action_type):
    ret = deepcopy(api)
    if 'returns' in api:
        ret['returns'] = format_returns(ret['returns'], 'event' in api)
    if action_type in ('conditions', 'transforms') or not is_app_action_bound(app_name, api['run']):
        ret['global'] = True
    if 'parameters' in api:
        ret['parameters'] = [extract_schema(param_api) for param_api in ret['parameters']]
    else:
        ret['parameters'] = []
    return ret
Beispiel #6
0
    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)
            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} (uid {2}) executed successfully'.format(
                    self.app_name, self.action_name, self.uid))
            return result
Beispiel #7
0
 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
Beispiel #8
0
 def validate(self):
     """Validates the object"""
     errors = []
     try:
         self._run, self._arguments_api = get_app_action_api(self.app_name, self.action_name)
         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
Beispiel #9
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
Beispiel #10
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'