operaton-external-task-client-python3 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. examples/__init__.py +0 -0
  2. examples/bpmn_error_example.py +75 -0
  3. examples/correlate_message.py +11 -0
  4. examples/event_subprocess_example.py +50 -0
  5. examples/examples_auth_basic/__init__.py +0 -0
  6. examples/examples_auth_basic/fetch_and_execute.py +31 -0
  7. examples/examples_auth_basic/get_process_instance.py +12 -0
  8. examples/examples_auth_basic/start_process.py +15 -0
  9. examples/examples_auth_basic/task_handler_example.py +44 -0
  10. examples/fetch_and_execute.py +30 -0
  11. examples/get_process_instance.py +12 -0
  12. examples/retry_task_example.py +58 -0
  13. examples/start_process.py +14 -0
  14. examples/task_handler_example.py +44 -0
  15. examples/tasks_example.py +36 -0
  16. operaton/__init__.py +0 -0
  17. operaton/client/__init__.py +0 -0
  18. operaton/client/async_external_task_client.py +171 -0
  19. operaton/client/engine_client.py +180 -0
  20. operaton/client/external_task_client.py +166 -0
  21. operaton/client/tests/__init__.py +0 -0
  22. operaton/client/tests/test_async_external_task_client.py +128 -0
  23. operaton/client/tests/test_async_external_task_client_auth.py +42 -0
  24. operaton/client/tests/test_async_external_task_client_bearer.py +43 -0
  25. operaton/client/tests/test_engine_client.py +228 -0
  26. operaton/client/tests/test_engine_client_auth.py +231 -0
  27. operaton/client/tests/test_engine_client_bearer.py +237 -0
  28. operaton/client/tests/test_external_task_client.py +17 -0
  29. operaton/client/tests/test_external_task_client_auth.py +19 -0
  30. operaton/client/tests/test_external_task_client_bearer.py +24 -0
  31. operaton/external_task/__init__.py +0 -0
  32. operaton/external_task/async_external_task_executor.py +91 -0
  33. operaton/external_task/async_external_task_worker.py +181 -0
  34. operaton/external_task/external_task.py +173 -0
  35. operaton/external_task/external_task_executor.py +88 -0
  36. operaton/external_task/external_task_worker.py +92 -0
  37. operaton/external_task/tests/__init__.py +0 -0
  38. operaton/external_task/tests/test_async_external_task_executor.py +139 -0
  39. operaton/external_task/tests/test_async_external_task_worker.py +129 -0
  40. operaton/external_task/tests/test_external_task.py +106 -0
  41. operaton/external_task/tests/test_external_task_executor.py +200 -0
  42. operaton/external_task/tests/test_external_task_worker.py +147 -0
  43. operaton/process_definition/__init__.py +0 -0
  44. operaton/process_definition/process_definition_client.py +123 -0
  45. operaton/process_definition/tests/__init__.py +0 -0
  46. operaton/process_definition/tests/test_process_definition_client.py +181 -0
  47. operaton/utils/__init__.py +0 -0
  48. operaton/utils/auth_basic.py +28 -0
  49. operaton/utils/auth_bearer.py +28 -0
  50. operaton/utils/log_utils.py +31 -0
  51. operaton/utils/response_utils.py +35 -0
  52. operaton/utils/tests/test_auth_basic.py +30 -0
  53. operaton/utils/tests/test_auth_bearer.py +27 -0
  54. operaton/utils/tests/test_response_utils.py +43 -0
  55. operaton/utils/tests/test_utils.py +21 -0
  56. operaton/utils/utils.py +14 -0
  57. operaton/variables/__init__.py +0 -0
  58. operaton/variables/properties.py +27 -0
  59. operaton/variables/tests/test_properties.py +20 -0
  60. operaton/variables/tests/test_variables.py +60 -0
  61. operaton/variables/variables.py +45 -0
  62. operaton_external_task_client_python3-1.0.0.dist-info/METADATA +258 -0
  63. operaton_external_task_client_python3-1.0.0.dist-info/RECORD +66 -0
  64. operaton_external_task_client_python3-1.0.0.dist-info/WHEEL +5 -0
  65. operaton_external_task_client_python3-1.0.0.dist-info/licenses/LICENSE +201 -0
  66. operaton_external_task_client_python3-1.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,106 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.external_task.external_task import ExternalTask
4
+
5
+
6
+ class ExternalTaskTest(TestCase):
7
+
8
+ def test_external_task_creation_from_context(self):
9
+ context = {
10
+ "id": "123", "workerId": "321", "topicName": "my_topic", "tenantId": "tenant1",
11
+ "processInstanceId": "processInstanceId1",
12
+ "variables": {
13
+ "applicationId": {
14
+ "type": "String",
15
+ "value": "appId987",
16
+ "valueInfo": {}
17
+ }
18
+ }
19
+ }
20
+ task = ExternalTask(context=context)
21
+
22
+ self.assertEqual("123", task.get_task_id())
23
+ self.assertEqual("321", task.get_worker_id())
24
+ self.assertEqual("my_topic", task.get_topic_name())
25
+ self.assertEqual("tenant1", task.get_tenant_id())
26
+ self.assertEqual("processInstanceId1", task.get_process_instance_id())
27
+ self.assertDictEqual({"applicationId": "appId987"}, task.get_variables())
28
+ self.assertEqual("empty_task_result", str(task.get_task_result()))
29
+
30
+ def test_complete_returns_success_task_result(self):
31
+ task = ExternalTask(context={})
32
+ task_result = task.complete({})
33
+
34
+ self.assertEqual(task, task_result.get_task())
35
+ self.assertEqual(task_result, task.get_task_result())
36
+
37
+ self.assertTrue(task_result.is_success())
38
+ self.assertFalse(task_result.is_failure())
39
+ self.assertFalse(task_result.is_bpmn_error())
40
+
41
+ def test_failure_returns_failure_task_result(self):
42
+ task = ExternalTask(context={})
43
+ task_result = task.failure(error_message="unknown error", error_details="error details here",
44
+ max_retries=3, retry_timeout=1000)
45
+
46
+ self.assertEqual(task, task_result.get_task())
47
+ self.assertEqual(task_result, task.get_task_result())
48
+
49
+ self.assertFalse(task_result.is_success())
50
+ self.assertTrue(task_result.is_failure())
51
+ self.assertFalse(task_result.is_bpmn_error())
52
+
53
+ self.assertEqual("unknown error", task_result.error_message)
54
+ self.assertEqual("error details here", task_result.error_details)
55
+ self.assertEqual(3, task_result.retries)
56
+ self.assertEqual(1000, task_result.retry_timeout)
57
+
58
+ def test_bpmn_error_returns_bpmn_error_task_result(self):
59
+ task = ExternalTask(context={})
60
+ task_result = task.bpmn_error(error_code="bpmn_error_code_1", error_message="bpmn error")
61
+
62
+ self.assertEqual(task, task_result.get_task())
63
+ self.assertEqual(task_result, task.get_task_result())
64
+
65
+ self.assertFalse(task_result.is_success())
66
+ self.assertFalse(task_result.is_failure())
67
+ self.assertTrue(task_result.is_bpmn_error())
68
+
69
+ self.assertEqual("bpmn_error_code_1", task_result.bpmn_error_code)
70
+
71
+ def test_task_with_retries_returns_failure_task_result_with_decremented_retries(self):
72
+ retries = 3
73
+ task = ExternalTask(context={"retries": retries})
74
+ task_result = task.failure(error_message="unknown error", error_details="error details here",
75
+ max_retries=10, retry_timeout=1000)
76
+
77
+ self.assertEqual(retries - 1, task_result.retries)
78
+
79
+ def test_get_variable_returns_none_for_missing_variable(self):
80
+ task = ExternalTask(context={})
81
+ variable = task.get_variable("var_name")
82
+ self.assertIsNone(variable)
83
+
84
+ def test_get_variable_returns_value_for_variable_present(self):
85
+ task = ExternalTask(context={"variables": {"var_name": {"value": 1}}})
86
+ variable = task.get_variable("var_name")
87
+ self.assertEqual(1, variable)
88
+
89
+ def test_get_variable_returns_with_meta(self):
90
+ task = ExternalTask(context={"variables": {"var_name": {"value": 1}}})
91
+ variable = task.get_variable("var_name", True)
92
+ self.assertEqual({"value": 1}, variable)
93
+
94
+ def test_get_variable_returns_without_meta(self):
95
+ task = ExternalTask(context={"variables": {"var_name": {"value": 1}}})
96
+ variable = task.get_variable("var_name", False)
97
+ self.assertEqual(1, variable)
98
+
99
+ def test_get_property_returns_value_for_property_present(self):
100
+ task = ExternalTask(context={"extensionProperties": {"var1":"one","var2":"two"}})
101
+ prop = task.get_extension_property("var1")
102
+ self.assertEqual("one", prop)
103
+
104
+ def test_str(self):
105
+ task = ExternalTask(context={"variables": {"var_name": {"value": 1}}})
106
+ self.assertEqual("{'variables': {'var_name': {'value': 1}}}", str(task))
@@ -0,0 +1,200 @@
1
+ import base64
2
+ import collections
3
+ from http import HTTPStatus
4
+ from unittest import TestCase
5
+
6
+ import responses
7
+
8
+ from operaton.client.external_task_client import ExternalTaskClient
9
+ from operaton.external_task.external_task import TaskResult, ExternalTask
10
+ from operaton.external_task.external_task_executor import ExternalTaskExecutor
11
+
12
+
13
+ class ExternalTaskExecutorTest(TestCase):
14
+
15
+ @staticmethod
16
+ def task_success_action(task):
17
+ output_vars = {"var1": 1, "var2": "value", "var3": True}
18
+ return TaskResult.success(task, output_vars)
19
+
20
+ @responses.activate
21
+ def test_task_complete(self):
22
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
23
+ output_vars = {"var1": 1, "var2": "value", "var3": True}
24
+ expected_task_result = TaskResult.success(task, output_vars)
25
+
26
+ external_task_client = ExternalTaskClient(worker_id=1)
27
+ responses.add(
28
+ responses.POST,
29
+ external_task_client.get_task_complete_url(task.get_task_id()),
30
+ status=HTTPStatus.NO_CONTENT
31
+ )
32
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=external_task_client)
33
+
34
+ actual_task_result = executor.execute_task(task, self.task_success_action)
35
+ self.assertEqual(str(expected_task_result), str(actual_task_result))
36
+
37
+ @staticmethod
38
+ def task_failure_action(task):
39
+ return TaskResult.failure(task, error_message="unknown task failure", error_details="unknown error",
40
+ retries=3, retry_timeout=30000)
41
+
42
+ @responses.activate
43
+ def test_task_failure(self):
44
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
45
+ expected_task_result = TaskResult.failure(
46
+ task,
47
+ error_message="unknown task failure",
48
+ error_details="unknown error",
49
+ retries=3,
50
+ retry_timeout=30000
51
+ )
52
+
53
+ external_task_client = ExternalTaskClient(worker_id=1)
54
+ responses.add(
55
+ responses.POST,
56
+ external_task_client.get_task_failure_url(task.get_task_id()),
57
+ status=HTTPStatus.NO_CONTENT
58
+ )
59
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=external_task_client)
60
+
61
+ actual_task_result = executor.execute_task(task, self.task_failure_action)
62
+ self.assertEqual(str(expected_task_result), str(actual_task_result))
63
+
64
+ @staticmethod
65
+ def task_bpmn_error_action(task):
66
+ return TaskResult.bpmn_error(task, error_code="bpmn_err_code_1", error_message="bpmn error")
67
+
68
+ @responses.activate
69
+ def test_task_bpmn_error(self):
70
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
71
+ expected_task_result = TaskResult.bpmn_error(task, error_code="bpmn_err_code_1", error_message="bpmn error")
72
+
73
+ external_task_client = ExternalTaskClient(worker_id=1)
74
+ responses.add(
75
+ responses.POST,
76
+ external_task_client.get_task_bpmn_error_url(task.get_task_id()),
77
+ status=HTTPStatus.NO_CONTENT
78
+ )
79
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=external_task_client)
80
+
81
+ actual_task_result = executor.execute_task(task, self.task_bpmn_error_action)
82
+ self.assertEqual(str(expected_task_result), str(actual_task_result))
83
+
84
+ @staticmethod
85
+ def task_result_not_complete_failure_bpmn_error(task):
86
+ return TaskResult.empty_task_result(task)
87
+
88
+ def test_task_result_not_complete_failure_bpmn_error_raises_exception(self):
89
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
90
+ external_task_client = ExternalTaskClient(worker_id=1)
91
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=external_task_client)
92
+
93
+ with self.assertRaises(Exception) as exception_ctx:
94
+ executor.execute_task(task, self.task_result_not_complete_failure_bpmn_error)
95
+
96
+ self.assertEqual(
97
+ "task result for task_id=1 must be either complete/failure/BPMNError",
98
+ str(exception_ctx.exception)
99
+ )
100
+
101
+ @responses.activate
102
+ def test_execute_task_raises_exception_raised_when_updating_status_in_engine(self):
103
+ client = ExternalTaskClient(worker_id=1)
104
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
105
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=client)
106
+
107
+ TaskResultStatusInput = collections.namedtuple(
108
+ 'TaskResultStatusInput',
109
+ ['task_status', 'task_action', 'task_status_url', 'error_message']
110
+ )
111
+
112
+ task_result_tests = [
113
+ TaskResultStatusInput(
114
+ "complete", self.task_success_action,
115
+ client.get_task_complete_url(task.get_task_id()),
116
+ "cannot update task status to complete"
117
+ ),
118
+ TaskResultStatusInput(
119
+ "failure", self.task_failure_action,
120
+ client.get_task_failure_url(task.get_task_id()),
121
+ "cannot update task status to failure"
122
+ ),
123
+ TaskResultStatusInput(
124
+ "bpmn_error", self.task_bpmn_error_action,
125
+ client.get_task_bpmn_error_url(task.get_task_id()),
126
+ "cannot update task status to BPMN err"
127
+ )
128
+ ]
129
+
130
+ for task_result_test in task_result_tests:
131
+ with self.subTest(task_result_test.task_status):
132
+ responses.add(
133
+ responses.POST,
134
+ task_result_test.task_status_url,
135
+ body=Exception(task_result_test.error_message)
136
+ )
137
+
138
+ with self.assertRaises(Exception) as exception_ctx:
139
+ executor.execute_task(task, task_result_test.task_action)
140
+
141
+ self.assertEqual(task_result_test.error_message, str(exception_ctx.exception))
142
+
143
+ @responses.activate
144
+ def test_execute_task_raises_exception_if_engine_returns_http_status_other_than_no_content_204(self):
145
+ client = ExternalTaskClient(worker_id=1)
146
+ task = ExternalTask({"id": "1", "topicName": "my_topic"})
147
+ executor = ExternalTaskExecutor(worker_id=1, external_task_client=client)
148
+
149
+ TaskResultStatusInput = collections.namedtuple('TaskResultStatusInput',
150
+ ['task_status', 'task_action', 'task_status_url',
151
+ 'http_status_code', 'expected_error_message'])
152
+
153
+ task_result_tests = [
154
+ TaskResultStatusInput(
155
+ "complete", self.task_success_action,
156
+ client.get_task_complete_url(task.get_task_id()), HTTPStatus.OK,
157
+ 'Not able to mark complete for task_id=1 for topic=my_topic, worker_id=1'
158
+ ),
159
+ TaskResultStatusInput(
160
+ "failure", self.task_failure_action,
161
+ client.get_task_failure_url(task.get_task_id()), HTTPStatus.CREATED,
162
+ 'Not able to mark failure for task_id=1 for topic=my_topic, worker_id=1'
163
+ ),
164
+ TaskResultStatusInput(
165
+ "bpmn_error", self.task_bpmn_error_action,
166
+ client.get_task_bpmn_error_url(task.get_task_id()), HTTPStatus.ACCEPTED,
167
+ 'Not able to mark BPMN Error for task_id=1 for topic=my_topic, worker_id=1'
168
+ )
169
+ ]
170
+
171
+ for task_result_test in task_result_tests:
172
+ with self.subTest(task_result_test.task_status):
173
+ responses.add(
174
+ responses.POST, task_result_test.task_status_url,
175
+ status=task_result_test.http_status_code
176
+ )
177
+
178
+ with self.assertRaises(Exception) as exception_ctx:
179
+ executor.execute_task(task, task_result_test.task_action)
180
+
181
+ self.assertEqual(task_result_test.expected_error_message, str(exception_ctx.exception))
182
+
183
+ def test_strip_long_variables(self):
184
+ variables = {
185
+ "var0": "string",
186
+ "var1": {"value": "string"},
187
+ "var2": {"value": 1},
188
+ "var3": {"value": "{\"key\": \"value\"}", "type": "Json"},
189
+ "var4": {"value": base64.encodebytes(b"string"), "type": "Bytes"},
190
+ "var5": {"value": "some file content", "type": "File"},
191
+ }
192
+ cleaned = ExternalTaskExecutor("worker-1", None)._strip_long_variables(variables)
193
+ self.assertEqual({
194
+ "var0": "string",
195
+ "var1": {"value": "string"},
196
+ "var2": {"value": 1},
197
+ "var3": {"value": "{\"key\": \"value\"}", "type": "Json"},
198
+ "var4": {"value": "...", "type": "Bytes"},
199
+ "var5": {"value": "...", "type": "File"},
200
+ }, cleaned)
@@ -0,0 +1,147 @@
1
+ from http import HTTPStatus
2
+ from unittest import mock, TestCase
3
+ from unittest.mock import patch
4
+
5
+ import responses
6
+
7
+ from operaton.client.external_task_client import ExternalTaskClient
8
+ from operaton.external_task.external_task import TaskResult, ExternalTask
9
+ from operaton.external_task.external_task_worker import ExternalTaskWorker
10
+
11
+
12
+ class ExternalTaskWorkerTest(TestCase):
13
+
14
+ @responses.activate
15
+ @patch('operaton.client.external_task_client.ExternalTaskClient.complete')
16
+ def test_fetch_and_execute_calls_task_action_for_each_task_fetched(self, _):
17
+ external_task_client = ExternalTaskClient(worker_id=0)
18
+ resp_payload = [{
19
+ "activityId": "anActivityId",
20
+ "activityInstanceId": "anActivityInstanceId",
21
+ "errorMessage": "anErrorMessage",
22
+ "errorDetails": "anErrorDetails",
23
+ "executionId": "anExecutionId",
24
+ "id": "anExternalTaskId",
25
+ "lockExpirationTime": "2015-10-06T16:34:42",
26
+ "processDefinitionId": "aProcessDefinitionId",
27
+ "processDefinitionKey": "aProcessDefinitionKey",
28
+ "processInstanceId": "aProcessInstanceId",
29
+ "tenantId": None,
30
+ "retries": 3,
31
+ "workerId": "aWorkerId",
32
+ "priority": 4,
33
+ "topicName": "createOrder",
34
+ "variables": {
35
+ "orderId": {
36
+ "type": "String",
37
+ "value": "1234",
38
+ "valueInfo": {}
39
+ }
40
+ }
41
+ },
42
+ {
43
+ "activityId": "anActivityId",
44
+ "activityInstanceId": "anActivityInstanceId",
45
+ "errorMessage": "anErrorMessage",
46
+ "errorDetails": "anotherErrorDetails",
47
+ "executionId": "anExecutionId",
48
+ "id": "anExternalTaskId",
49
+ "lockExpirationTime": "2015-10-06T16:34:42",
50
+ "processDefinitionId": "aProcessDefinitionId",
51
+ "processDefinitionKey": "aProcessDefinitionKey",
52
+ "processInstanceId": "aProcessInstanceId",
53
+ "tenantId": None,
54
+ "retries": 3,
55
+ "workerId": "aWorkerId",
56
+ "priority": 0,
57
+ "topicName": "createOrder",
58
+ "variables": {
59
+ "orderId": {
60
+ "type": "String",
61
+ "value": "3456",
62
+ "valueInfo": {}
63
+ }
64
+ }
65
+ }]
66
+ responses.add(responses.POST, external_task_client.get_fetch_and_lock_url(),
67
+ status=HTTPStatus.OK, json=resp_payload)
68
+
69
+ worker = ExternalTaskWorker(worker_id=0)
70
+ mock_action = mock.Mock()
71
+ task = ExternalTask({"id": "anExternalTaskId", "workerId": "aWorkerId", "topicName": "createOrder"})
72
+ mock_action.return_value = TaskResult.success(task=task, global_variables={})
73
+
74
+ worker.fetch_and_execute("my_topic", mock_action)
75
+ self.assertEqual(2, mock_action.call_count)
76
+
77
+ @responses.activate
78
+ def test_fetch_and_execute_raises_exception_if_task_action_raises_exception(self):
79
+ external_task_client = ExternalTaskClient(worker_id=0)
80
+ resp_payload = [{
81
+ "activityId": "anActivityId",
82
+ "activityInstanceId": "anActivityInstanceId",
83
+ "errorMessage": "anErrorMessage",
84
+ "errorDetails": "anErrorDetails",
85
+ "executionId": "anExecutionId",
86
+ "id": "anExternalTaskId",
87
+ "lockExpirationTime": "2015-10-06T16:34:42",
88
+ "processDefinitionId": "aProcessDefinitionId",
89
+ "processDefinitionKey": "aProcessDefinitionKey",
90
+ "processInstanceId": "aProcessInstanceId",
91
+ "tenantId": None,
92
+ "retries": 3,
93
+ "workerId": "aWorkerId",
94
+ "priority": 4,
95
+ "topicName": "createOrder",
96
+ "variables": {
97
+ "orderId": {
98
+ "type": "String",
99
+ "value": "1234",
100
+ "valueInfo": {}
101
+ }
102
+ }
103
+ }]
104
+ responses.add(responses.POST, external_task_client.get_fetch_and_lock_url(),
105
+ status=HTTPStatus.OK, json=resp_payload)
106
+
107
+ worker = ExternalTaskWorker(worker_id=0)
108
+ mock_action = mock.Mock()
109
+ mock_action.side_effect = Exception("error executing task action")
110
+
111
+ with self.assertRaises(Exception) as exception_ctx:
112
+ worker.fetch_and_execute("my_topic", mock_action)
113
+
114
+ self.assertEqual("error executing task action", str(exception_ctx.exception))
115
+
116
+ @responses.activate
117
+ def test_fetch_and_execute_raises_exception_if_no_tasks_found(self):
118
+ external_task_client = ExternalTaskClient(worker_id=0)
119
+ resp_payload = []
120
+ responses.add(responses.POST, external_task_client.get_fetch_and_lock_url(),
121
+ status=HTTPStatus.OK, json=resp_payload)
122
+
123
+ worker = ExternalTaskWorker(worker_id=0)
124
+ mock_action = mock.Mock()
125
+ process_variables = {"var1": "value1", "var2": "value2"}
126
+ with self.assertRaises(Exception) as context:
127
+ worker.fetch_and_execute("my_topic", mock_action, process_variables)
128
+
129
+ self.assertEqual(f"no External Task found for Topics: my_topic, Process variables: {process_variables}",
130
+ str(context.exception))
131
+
132
+ @responses.activate
133
+ @patch('time.sleep', return_value=None)
134
+ def test_fetch_and_execute_safe_raises_exception_sleep_is_called(self, mock_time_sleep):
135
+ external_task_client = ExternalTaskClient(worker_id=0)
136
+ responses.add(responses.POST, external_task_client.get_fetch_and_lock_url(),
137
+ status=HTTPStatus.INTERNAL_SERVER_ERROR)
138
+
139
+ sleep_seconds = 100
140
+ worker = ExternalTaskWorker(worker_id=0, config={"sleepSeconds": sleep_seconds})
141
+ mock_action = mock.Mock()
142
+
143
+ worker._fetch_and_execute_safe("my_topic", mock_action)
144
+
145
+ self.assertEqual(0, mock_action.call_count)
146
+ self.assertEqual(1, mock_time_sleep.call_count)
147
+ mock_time_sleep.assert_called_with(sleep_seconds)
File without changes
@@ -0,0 +1,123 @@
1
+ import logging
2
+
3
+ import requests
4
+
5
+ from operaton.client.engine_client import EngineClient, ENGINE_LOCAL_BASE_URL
6
+ from operaton.utils.response_utils import raise_exception_if_not_ok
7
+ from operaton.utils.utils import join
8
+ from operaton.variables.variables import Variables
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class ProcessDefinitionClient(EngineClient):
14
+ def __init__(self, engine_base_url=ENGINE_LOCAL_BASE_URL, config=None):
15
+ super().__init__(engine_base_url, config=config)
16
+
17
+ def get_process_definitions(
18
+ self,
19
+ process_key,
20
+ version_tag,
21
+ tenant_ids,
22
+ sort_by="version",
23
+ sort_order="desc",
24
+ offset=0,
25
+ limit=1,
26
+ ):
27
+ url = self.get_process_definitions_url()
28
+ url_params = self.get_process_definitions_url_params(
29
+ process_key, version_tag, tenant_ids, sort_by, sort_order, offset, limit
30
+ )
31
+ response = requests.get(url, headers=self._get_headers(), params=url_params)
32
+ raise_exception_if_not_ok(response)
33
+ return response.json()
34
+
35
+ def get_process_definitions_url(self):
36
+ return f"{self.engine_base_url}/process-definition"
37
+
38
+ def get_process_definitions_url_params(
39
+ self,
40
+ process_key,
41
+ version_tag=None,
42
+ tenant_ids=None,
43
+ sort_by="version",
44
+ sort_order="desc",
45
+ offset=0,
46
+ limit=1,
47
+ ):
48
+ """
49
+ offset starts with zero
50
+ sort_order can be "asc" or "desc
51
+ """
52
+ url_params = {
53
+ "key": process_key,
54
+ "versionTagLike": f"{version_tag}%" if version_tag else None,
55
+ "tenantIdIn": join(tenant_ids, ","),
56
+ "sortBy": sort_by,
57
+ "sortOrder": sort_order,
58
+ "firstResult": offset,
59
+ "maxResults": limit,
60
+ }
61
+
62
+ url_params = {k: v for k, v in url_params.items() if v is not None and v != ""}
63
+
64
+ return url_params
65
+
66
+ def start_process_by_version(
67
+ self, process_key, version_tag, variables, tenant_id=None, business_key=None
68
+ ):
69
+ """
70
+ Start a process instance with the process_key and specified version tag and variables passed.
71
+ If multiple versions with same version tag found, it triggers the latest one
72
+ :param process_key: Mandatory
73
+ :param version_tag:
74
+ :param variables: Mandatory - can be empty dict
75
+ :param tenant_id: Optional
76
+ :param business_key: Optional
77
+ :return: response json
78
+ """
79
+ tenant_ids = [tenant_id] if tenant_id else []
80
+ process_definitions = self.get_process_definitions(
81
+ process_key,
82
+ version_tag,
83
+ tenant_ids,
84
+ sort_by="version",
85
+ sort_order="desc",
86
+ offset=0,
87
+ limit=1,
88
+ )
89
+
90
+ if len(process_definitions) == 0:
91
+ raise Exception(
92
+ f"cannot start process because no process definitions found "
93
+ f"for process_key: {process_key}, version_tag: {version_tag} and tenant_id: {tenant_id}"
94
+ )
95
+
96
+ process_definition_id = process_definitions[0]["id"]
97
+ version = process_definitions[0]["version"]
98
+ if len(process_definitions) > 1:
99
+ logger.info(
100
+ f"multiple process definitions found for process_key: {process_key}, "
101
+ f"version_tag: {version_tag} and tenant_id: {tenant_id}, "
102
+ f"using latest process_definition_id: {process_definition_id} with version: {version}"
103
+ )
104
+ else:
105
+ logger.info(
106
+ f"exactly one process definition found for process_key: {process_key}, "
107
+ f"version_tag: {version_tag} and tenant_id: {tenant_id}, "
108
+ f"using process_definition_id: {process_definition_id} with version: {version}"
109
+ )
110
+
111
+ url = self.get_start_process_url(process_definition_id)
112
+ body = {"variables": Variables.format(variables)}
113
+ if business_key:
114
+ body["businessKey"] = business_key
115
+
116
+ response = requests.post(url, headers=self._get_headers(), json=body)
117
+ raise_exception_if_not_ok(response)
118
+ return response.json()
119
+
120
+ def get_start_process_url(self, process_definition_id):
121
+ return (
122
+ f"{self.engine_base_url}/process-definition/{process_definition_id}/start"
123
+ )
File without changes