コード例 #1
0
    def deploy_multiple_files(self, *args):
        """
        # Due to https://jira.camunda.com/browse/CAM-13105 we cannot use generic camunda client when dealing with
        # multiple files. We have to use plain REST then.
        """

        fields = {
            'deployment-name': f'{os.path.basename(args[0])}',
        }

        for file in args:
            filename = os.path.basename(file)
            fields[f'{filename}'] = (filename, open(file, 'rb'),
                                     'application/octet-stream')

        multipart_data = MultipartEncoder(fields=fields)

        logger.debug(multipart_data.fields)

        response = requests.post(
            f'{self._shared_resources.camunda_url}/deployment/create',
            data=multipart_data,
            headers={'Content-Type': multipart_data.content_type})
        json = response.json()
        try:
            response.raise_for_status()
            logger.debug(json)
        except HTTPError as e:
            logger.error(json)
            raise e

        return json
コード例 #2
0
    def get_process_instance_variable(self, process_instance_id: str,
                                      variable_name: str):
        """
        Returns the variable with the given name from the process instance with
        the given process_instance_id.

        Parameters:
            - ``process_instance_id``: ID of the target process instance
            - ``variable_name``: name of the variable to read

        == Example ==
        | ${variable} | Get Process Instance Variable |
        | ...         | process_instance_id=fcab43bc-b970-11eb-be75-0242ac110002 |
        | ...         | variable_name=foo |

        See also:
        https://docs.camunda.org/manual/7.5/reference/rest/process-instance/variables/get-single-variable/
        """
        with self._shared_resources.api_client as api_client:
            api_instance: ProcessInstanceApi = openapi_client.ProcessInstanceApi(
                api_client)

            try:
                response = api_instance.get_process_instance_variable(
                    id=process_instance_id, var_name=variable_name)
            except ApiException as e:
                logger.error(f'Failed to get variable {variable_name} from '
                             f'process instance {process_instance_id}:\n{e}')
        return response
コード例 #3
0
    def complete(self, result_set: Dict[str, Any] = None, files: Dict = None):
        """
        Completes the task that was fetched before with `fetch workload`.

        *Requires `fetch workload` to run before this one, logs warning instead.*

        Additonal variables can be provided as dictionary in _result_set_ .
        Files can be provided as dictionary of filename and patch.

        Examples:

            | _# fetch and immediately complete_ |
            | | *fetch workload* | _my_topic_ |
            | | *complete task* | |
            | |
            | _# fetch and complete with return values_ |
            | | *fetch workload* | _decide_on_dish_ |
            | ${new_variables} | *Create Dictionary* | _my_dish=salad_ |
            | | *complete task* | _result_set=${new_variables}_ |
            | |
            | _# fetch and complete with return values and files_ |
            | | *fetch workload* | _decide_on_haircut_ |
            | ${return_values} | *Create Dictionary* | _style=short hair_ |
            | ${files} | *Create Dictionary* | _should_look_like=~/favorites/beckham.jpg_ |
            | | *complete task* | _${return_values}_ | _${files}_ |
        """
        if not self.FETCH_RESPONSE:
            logger.warn(
                'No task to complete. Maybe you did not fetch and lock a workitem before?'
            )
        else:
            with self._shared_resources.api_client as api_client:
                api_instance = openapi_client.ExternalTaskApi(api_client)
                variables = CamundaResources.convert_dict_to_openapi_variables(
                    result_set)
                openapi_files = CamundaResources.convert_file_dict_to_openapi_variables(
                    files)
                variables.update(openapi_files)
                complete_task_dto = openapi_client.CompleteExternalTaskDto(
                    worker_id=self.WORKER_ID, variables=variables)
                try:
                    logger.debug(
                        f"Sending to Camunda for completing Task:\n{complete_task_dto}"
                    )
                    api_instance.complete_external_task_resource(
                        self.FETCH_RESPONSE.id,
                        complete_external_task_dto=complete_task_dto)
                    self.drop_fetch_response()
                except ApiException as e:
                    logger.error(
                        f"Exception when calling ExternalTaskApi->complete_external_task_resource: {e}\n"
                    )
コード例 #4
0
    def delete_process_instance(self, process_instance_id):
        """
        USE WITH CARE: Deletes a process instance by id. All data in this process instance will be lost.
        """
        with self._shared_resources.api_client as api_client:
            api_instance = openapi_client.ProcessInstanceApi(api_client)

            try:
                api_instance.delete_process_instance(id=process_instance_id)
            except ApiException as e:
                logger.error(
                    f'Failed to delete process instance {process_instance_id}:\n{e}'
                )
                raise e
コード例 #5
0
    def set_task_lock_duration(self, lock_duration: int):
        """
        Sets lock duration used as default when fetching. Camunda locks a process instance for the period. If the
        external task os not completed, Camunda gives the process instance free for another attempt only when lock
        duration has expired.

        Value is in milliseconds (1000 = 1 minute)
        """
        try:
            self.DEFAULT_LOCK_DURATION = int(lock_duration)
        except ValueError:
            logger.error(
                f'Failed to set lock duration. Value does not seem a valid integer:\t{lock_duration}'
            )
コード例 #6
0
    def deliver_message(self, message_name, **kwargs):
        """
        Delivers a message using Camunda REST API: https://docs.camunda.org/manual/latest/reference/rest/message/post-message/

        Example:
            | ${result} | deliver message | msg_payment_received |
            | ${result} | deliver message | msg_payment_received | process_variables = ${variable_dictionary} |
            | ${result} | deliver message | msg_payment_received | business_key = ${correlating_business_key} |
        """
        with self._shared_resources.api_client as api_client:
            correlation_message: CorrelationMessageDto = CorrelationMessageDto(
                **kwargs)
            correlation_message.message_name = message_name
            if not 'result_enabled' in kwargs:
                correlation_message.result_enabled = True
            if 'process_variables' in kwargs:
                correlation_message.process_variables = CamundaResources.dict_to_camunda_json(
                    kwargs['process_variables'])

            serialized_message = api_client.sanitize_for_serialization(
                correlation_message)
            logger.debug(f'Message:\n{serialized_message}')

            headers = self._shared_resources.api_client.default_headers.copy()
            headers['Content-Type'] = 'application/json'

            try:
                response = requests.post(
                    f'{self._shared_resources.camunda_url}/message',
                    json=serialized_message,
                    headers=headers)
            except ApiException as e:
                raise ApiException(f'Failed to deliver message:\n{e}')

        try:
            response.raise_for_status()
        except HTTPError as e:
            logger.error(e)
            raise ApiException(response.text)

        if correlation_message.result_enabled:
            json = response.json()
            logger.debug(json)
            return json
        else:
            return {}
コード例 #7
0
 def unlock(self):
     """
     Unlocks recent task.
     """
     if not self.FETCH_RESPONSE:
         logger.warn(
             'No task to unlock. Maybe you did not fetch and lock a workitem before?'
         )
     else:
         with self._shared_resources.api_client as api_client:
             api_instance = openapi_client.ExternalTaskApi(api_client)
             try:
                 api_instance.unlock(self.FETCH_RESPONSE.id)
                 self.drop_fetch_response()
             except ApiException as e:
                 logger.error(
                     f"Exception when calling ExternalTaskApi->unlock: {e}\n"
                 )
コード例 #8
0
    def get_amount_of_workloads(self, topic: str, **kwargs) -> int:
        """
        Retrieves count of tasks. By default expects a topic name, but all parameters from the original endpoint
        may be provided: https://docs.camunda.org/manual/latest/reference/rest/external-task/get-query-count/
        """
        with self._shared_resources.api_client as api_client:
            api_instance = openapi_client.ExternalTaskApi(api_client)

            try:
                response: CountResultDto = api_instance.get_external_tasks_count(
                    topic_name=topic, **kwargs)
            except ApiException as e:
                logger.error(
                    f'Failed to count workload for topic "{topic}":\n{e}')
                raise e

        logger.info(f'Amount of workloads for "{topic}":\t{response.count}')
        return response.count
コード例 #9
0
    def download_file_from_variable(self, variable_name: str) -> str:
        if not self.FETCH_RESPONSE:
            logger.warn(
                'Could not download file for variable. Maybe you did not fetch and lock a workitem before?'
            )
        else:
            with self._shared_resources.api_client as api_client:
                api_instance = openapi_client.ProcessInstanceApi(api_client)

                try:
                    response = api_instance.get_process_instance_variable_binary(
                        id=self.FETCH_RESPONSE.process_instance_id,
                        var_name=variable_name)
                    logger.debug(response)
                except ApiException as e:
                    logger.error(
                        f"Exception when calling ExternalTaskApi->get_process_instance_variable_binary: {e}\n"
                    )
                return response
コード例 #10
0
    def get_process_definitions(self, **kwargs):
        """
        Returns a list of process definitions that fulfill given parameters.

        See Rest API documentation on ``https://docs.camunda.org/manual`` for available parameters.

        == Example ==
        | ${list} | Get Process Definitions | name=my_process_definition |
        """
        with self._shared_resources.api_client as api_client:
            api_instance: ProcessDefinitionApi = openapi_client.ProcessDefinitionApi(
                api_client)

            try:
                response = api_instance.get_process_definitions(**kwargs)
            except ApiException as e:
                logger.error(f'Failed to get process definitions:\n{e}')
                raise e
        return response
コード例 #11
0
    def get_activity_instance(self, **kwargs):
        """
        Returns an Activity Instance (Tree) for a given process instance.

        == Example ==
        | ${tree} | Get Activity Instance | id=fcab43bc-b970-11eb-be75-0242ac110002 |

        https://docs.camunda.org/manual/7.5/reference/rest/process-instance/get-activity-instances/
        """
        with self._shared_resources.api_client as api_client:
            api_instance: ProcessInstanceApi = openapi_client.ProcessInstanceApi(
                api_client)

            try:
                response = api_instance.get_activity_instance_tree(**kwargs)
            except ApiException as e:
                logger.error(
                    f'failed to get activity tree for process instance {kwargs}:\n{e}'
                )
        return response
コード例 #12
0
    def get_all_process_instances(self, process_definition_key):
        """
        Returns a list of process instances that are active for a certain process definition identified by key.
        """
        with self._shared_resources.api_client as api_client:
            api_instance: ProcessInstanceApi = openapi_client.ProcessInstanceApi(
                api_client)

            try:
                response: List[
                    ProcessInstanceDto] = api_instance.get_process_instances(
                        process_definition_key=process_definition_key,
                        active='true')
            except ApiException as e:
                logger.error(
                    f'Failed to get process instances of process {process_definition_key}:\n{e}'
                )
                raise e

        return [process_instance.to_dict() for process_instance in response]
コード例 #13
0
    def deploy(self, *args):
        """Creates a deployment from all given files and uploads them to camunda.

        Return response from camunda rest api as dictionary.
        Further documentation: https://docs.camunda.org/manual/7.14/reference/rest/deployment/post-deployment/

        By default, this keyword only deploys changed models and filters duplicates. Deployment name is the filename of
        the first file.

        Example:
            | ${response} | *Deploy model from file* | _../bpmn/my_model.bpnm_ | _../forms/my_forms.html_ |
        """
        if not args:
            raise ValueError(
                'Failed deploying model, because no file provided.')

        if len(args) > 1:
            # We have to use plain REST then when uploading more than 1 file.
            return self.deploy_multiple_files(*args)

        filename = os.path.basename(args[0])

        with self._shared_resources.api_client as api_client:
            api_instance = openapi_client.DeploymentApi(api_client)
            data = [*args]
            deployment_name = filename

            try:
                response: DeploymentWithDefinitionsDto = api_instance.create_deployment(
                    deploy_changed_only=True,
                    enable_duplicate_filtering=True,
                    deployment_name=deployment_name,
                    data=data)
                logger.info(f'Response from camunda:\t{response}')
            except ApiException as e:
                logger.error(f'Failed to upload {filename}:\n{e}')
                raise e

        return response.to_dict()
コード例 #14
0
    def evaluate_decision(self, key: str, variables: dict) -> list:
        """
        Evaluates a given decision and returns the result.
        The input values of the decision have to be supplied with `variables`.

        == Example ==
        | ${variables} | Create Dictionary | my_input=42 |
        | ${response} | Evaluate Decision | my_decision_table | ${variables} |
        """
        with self._shared_resources.api_client as api_client:
            api_instance = openapi_client.DecisionDefinitionApi(api_client)
            dto = CamundaResources.convert_dict_to_openapi_variables(variables)
            try:
                response = api_instance.evaluate_decision_by_key(
                    key=key,
                    evaluate_decision_dto=openapi_client.EvaluateDecisionDto(
                        dto))
                return [
                    CamundaResources.convert_openapi_variables_to_dict(r)
                    for r in response
                ]
            except ApiException as e:
                logger.error(f'Failed to evaluate decision {key}:\n{e}')
コード例 #15
0
    def get_deployments(self, deployment_id: str = None, **kwargs):
        """
        Retrieves all deployments that match given criteria. All parameters are available from https://docs.camunda.org/manual/latest/reference/rest/deployment/get-query/

        Example:
            | ${list_of_deployments} | get deployments | ${my_deployments_id} |
            | ${list_of_deployments} | get deployments | id=${my_deployments_id} |
            | ${list_of_deployments} | get deployments | after=2013-01-23T14:42:45.000+0200 |
        """
        if deployment_id:
            kwargs['id'] = deployment_id

        with self._shared_resources.api_client as api_client:
            api_instance = openapi_client.DeploymentApi(api_client)

            try:
                response: List[DeploymentDto] = api_instance.get_deployments(
                    **kwargs)
                logger.info(f'Response from camunda:\t{response}')
            except ApiException as e:
                logger.error(f'Failed get deployments:\n{e}')
                raise e

        return [r.to_dict() for r in response]
コード例 #16
0
    def start_process(self,
                      process_key: str,
                      variables: Dict = None,
                      files: Dict = None,
                      before_activity_id: str = None,
                      after_activity_id: str = None,
                      **kwargs) -> Dict:
        """
        Starts a new process instance from a process definition with given key.

        variables: _optional_ dictionary like: {'variable name' : 'value'}

        files: _optional_ dictionary like: {'variable name' : path}. will be attached to variables in Camunda

        before_activity_id: _optional_ id of activity at which the process starts before. *CANNOT BE USED TOGETHER WITH _after_activity_id_*

        after_activity_id: _optional_ id of activity at which the process starts after. *CANNOT BE USED TOGETHER WITH _before_activity_id_*

        Returns response from Camunda as dictionary

        == Examples ==
        | `start process`      | apply for job promotion    | _variables_= { 'employee' : 'John Doe', 'permission_for_application_granted' : True}   | _files_ = { 'cv' : 'documents/my_life.md'}  | _after_activity_id_ = 'Activity_ask_boss_for_persmission'   |
        | `start process` | apply for promotion | business_key=John again |
        """
        if not process_key:
            raise ValueError(
                'Error starting process. No process key provided.')

        if before_activity_id and after_activity_id:
            raise AssertionError(
                '2 activity ids provided. Cannot start before and after an activity.'
            )

        with self._shared_resources.api_client as api_client:
            api_instance: ProcessDefinitionApi = openapi_client.ProcessDefinitionApi(
                api_client)
            openapi_variables = CamundaResources.convert_dict_to_openapi_variables(
                variables)
            openapi_files = CamundaResources.convert_file_dict_to_openapi_variables(
                files)
            openapi_variables.update(openapi_files)

            if before_activity_id or after_activity_id:
                instruction: ProcessInstanceModificationInstructionDto = ProcessInstanceModificationInstructionDto(
                    type='startBeforeActivity'
                    if before_activity_id else 'startAfterActivity',
                    activity_id=before_activity_id
                    if before_activity_id else after_activity_id,
                )
                kwargs.update(start_instructions=[instruction])

            start_process_instance_dto: StartProcessInstanceDto = StartProcessInstanceDto(
                variables=openapi_variables, **kwargs)

            try:
                response: ProcessInstanceWithVariablesDto = api_instance.start_process_instance_by_key(
                    key=process_key,
                    start_process_instance_dto=start_process_instance_dto)
            except ApiException as e:
                logger.error(f'Failed to start process {process_key}:\n{e}')
                raise e
        logger.info(f'Response:\n{response}')

        return response.to_dict()
コード例 #17
0
    def fetch_workload(self,
                       topic: str,
                       async_response_timeout=None,
                       use_priority=None,
                       **kwargs) -> Dict:
        """
        Locks and fetches workloads from camunda on a given topic. Returns a list of variable dictionary.
        Each dictionary representing 1 workload from a process instance.

        If a process instance was fetched, the process instance is cached and can be retrieved by keyword
        `Get recent process instance`

        The only mandatory parameter for this keyword is *topic* which is the name of the topic to fetch workload from.
        More parameters can be added from the Camunda documentation: https://docs.camunda.org/manual/7.14/reference/rest/external-task/fetch/

        If not provided, this keyword will use a lock_duration of 60000 ms (10 minutes) and set {{deserialize_value=True}}

        Examples:
            | ${input_variables} | *Create Dictionary* | _name=Robot_ |
            | | *start process* | _my_demo_ | _${input_variables}_ |
            | ${variables} | *fetch and lock workloads* | _first_task_in_demo_ |
            | | *Dictionary Should Contain Key* | _${variables}_ | _name_ |
            | | *Should Be Equal As String* | _Robot_ | _${variables}[name]_ |

        Example deserializing only some variables:
            | ${input_variables} | *Create Dictionary* | _name=Robot_ | _profession=Framework_ |
            | | *start process* | _my_demo_ | _${input_variables}_ |
            | ${variables_of_interest} | *Create List* | _profession_ |
            | ${variables} | *Fetch Workload* | _first_task_in_demo_ | _variables=${variables_of_interest}_ |
            | | *Dictionary Should Not Contain Key* | _${variables}_ | _name_ |
            | | *Dictionary Should Contain Key* | _${variables}_ | _profession_ |
            | | *Should Be Equal As String* | _Framework_ | _${variables}[profession]_ |
        """
        api_response = []
        with self._shared_resources.api_client as api_client:
            # Create an instance of the API class
            api_instance = openapi_client.ExternalTaskApi(api_client)
            if 'lock_duration' not in kwargs:
                kwargs['lock_duration'] = 60000
            if 'deserialize_values' not in kwargs:
                kwargs['deserialize_values'] = True
            topic_dto = FetchExternalTaskTopicDto(topic_name=topic, **kwargs)
            fetch_external_tasks_dto = FetchExternalTasksDto(
                worker_id=self.WORKER_ID,
                max_tasks=1,
                async_response_timeout=async_response_timeout,
                use_priority=use_priority,
                topics=[topic_dto])

            try:
                api_response = api_instance.fetch_and_lock(
                    fetch_external_tasks_dto=fetch_external_tasks_dto)
                logger.info(api_response)
            except ApiException as e:
                logger.error(
                    "Exception when calling ExternalTaskApi->fetch_and_lock: %s\n"
                    % e)

        work_items: List[LockedExternalTaskDto] = api_response
        if work_items:
            logger.debug(
                f'Received {len(work_items)} work_items from camunda engine for topic:\t{topic}'
            )
        else:
            logger.debug(
                f'Received no work items from camunda engine for topic:\t{topic}'
            )

        if not work_items:
            return {}

        self.FETCH_RESPONSE = work_items[0]

        variables: Dict[str, VariableValueDto] = self.FETCH_RESPONSE.variables
        return CamundaResources.convert_openapi_variables_to_dict(variables)