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
    def download_file_from_variable(self, variable_name: str) -> str:
        """
        For performance reasons, files are not retrieved automatically during `fetch workload`. If your task requires
        a file that is attached to a process instance, you need to download the file explicitly.

        Example:
            | ${variables} | *fetch workload* | _first_task_in_demo_ |
            | | *Dictionary Should Contain Key* | _${variables}_ | _my_file_ |
            | ${file} | *Download File From Variable* | ${variables}[my_file] | |
        """
        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:
                    raise ApiException(
                        f"Exception when calling ExternalTaskApi->get_process_instance_variable_binary: {e}\n"
                    )
                return response
 def throw_bpmn_error(self,
                      error_code: str,
                      error_message: str = None,
                      variables: Dict[str, Any] = None,
                      files: Dict = None):
     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(
                 variables)
             openapi_files = CamundaResources.convert_file_dict_to_openapi_variables(
                 files)
             variables.update(openapi_files)
             bpmn_error = openapi_client.ExternalTaskBpmnError(
                 worker_id=self.WORKER_ID,
                 error_message=error_message,
                 error_code=error_code,
                 variables=variables)
             try:
                 logger.debug(f"Sending BPMN error for task:\n{bpmn_error}")
                 api_instance.handle_external_task_bpmn_error(
                     self.FETCH_RESPONSE.id,
                     external_task_bpmn_error=bpmn_error)
                 self.drop_fetch_response()
             except ApiException as e:
                 raise ApiException(
                     f"Exception when calling ExternalTaskApi->handle_external_task_bpmn_error: {e}\n"
                 )
    def complete_task(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.*

        Additional 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:
                    raise ApiException(
                        f"Exception when calling ExternalTaskApi->complete_external_task_resource: {e}\n"
                    )
    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 {}
    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
    def set_camunda_configuration(self, configuration: dict):
        if 'host' not in configuration.keys():
            raise ValueError(
                f"Incomplete configuration. Configuration must include at least the Camunda host url:\t{configuration}"
            )

        # weird things happen when dictionary is not copied and keyword is called repeatedly. Somehow robot or python remember the configuration from the previous call
        camunda_config = configuration.copy()

        host = configuration['host']
        camunda_config['host'] = url_normalize(f'{host}/engine-rest')

        if 'api_key' in configuration.keys():
            api_key = configuration['api_key']
            camunda_config['api_key'] = {'default': api_key}
            if 'api_key_prefix' in configuration.keys():
                api_key_prefix = configuration['api_key_prefix']
                camunda_config['api_key_prefix'] = {'default': api_key_prefix}

        logger.debug(
            f"New configuration for Camunda client:\t{camunda_config}")
        self._shared_resources.client_configuration = Configuration(
            **camunda_config)
    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)