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,181 @@
1
+ from http import HTTPStatus
2
+ from unittest import TestCase
3
+
4
+ import responses
5
+
6
+ from operaton.process_definition.process_definition_client import ProcessDefinitionClient
7
+
8
+
9
+ class ProcessDefinitionClientTest(TestCase):
10
+
11
+ def setUp(self):
12
+ self.process_client = ProcessDefinitionClient()
13
+
14
+ def test_get_process_definitions_url_params_uses_non_none_params(self):
15
+ url_params = self.process_client.get_process_definitions_url_params(
16
+ process_key="PROCESS_KEY",
17
+ version_tag=None,
18
+ tenant_ids=None,
19
+ sort_by="version",
20
+ sort_order="desc"
21
+ )
22
+ self.assertDictEqual({
23
+ "key": 'PROCESS_KEY',
24
+ "sortBy": "version",
25
+ "sortOrder": "desc",
26
+ "firstResult": 0,
27
+ "maxResults": 1,
28
+ }, url_params)
29
+
30
+ def test_get_process_definitions_url_params_uses_all_specified_params(self):
31
+ url_params = self.process_client.get_process_definitions_url_params(
32
+ process_key="PROCESS_KEY",
33
+ version_tag='1.2.3',
34
+ tenant_ids=['tenant1'],
35
+ sort_by="version",
36
+ sort_order="asc"
37
+ )
38
+ self.assertDictEqual({
39
+ "key": "PROCESS_KEY",
40
+ "versionTagLike": "1.2.3%",
41
+ "tenantIdIn": "tenant1",
42
+ "sortBy": "version",
43
+ "sortOrder": "asc",
44
+ "firstResult": 0,
45
+ "maxResults": 1,
46
+ }, url_params)
47
+
48
+ @responses.activate
49
+ def test_start_process_by_version_raises_exception_if_no_process_definitions_found(self):
50
+ get_process_definitions_resp = []
51
+ responses.add(responses.GET, self.process_client.get_process_definitions_url(),
52
+ status=HTTPStatus.OK, json=get_process_definitions_resp)
53
+
54
+ with self.assertRaises(Exception) as context:
55
+ self.process_client.start_process_by_version("ORIGINATION", "3.8.3", {}, "tenant1")
56
+
57
+ self.assertEqual(f"cannot start process because no process definitions found "
58
+ f"for process_key: ORIGINATION, version_tag: 3.8.3 and tenant_id: tenant1",
59
+ str(context.exception))
60
+
61
+ @responses.activate
62
+ def test_start_process_by_version_uses_first_process_definition_id_if_more_than_one_found(self):
63
+ get_process_definitions_resp = [
64
+ {
65
+ "id": "process_definition_id_2",
66
+ "key": "ORIGINATION",
67
+ "category": "http://bpmn.io/schema/bpmn",
68
+ "description": "- Removed unwanted external tasks",
69
+ "name": "Origination",
70
+ "version": 37,
71
+ "resource": "bpmn/origination.bpmn",
72
+ "deploymentId": "05f7527e-737e-11eb-ac5c-0a58a9feac2a",
73
+ "diagram": None,
74
+ "suspended": False,
75
+ "tenantId": "tenant1",
76
+ "versionTag": "3.8.3",
77
+ "historyTimeToLive": None,
78
+ "startableInTasklist": True
79
+ },
80
+ {
81
+ "id": "process_definition_id_1",
82
+ "key": "ORIGINATION",
83
+ "category": "http://bpmn.io/schema/bpmn",
84
+ "description": "- Added new external tasks",
85
+ "name": "Origination",
86
+ "version": 36,
87
+ "resource": "bpmn/origination.bpmn",
88
+ "deploymentId": "035d3c55-5f01-11eb-bcaf-0a58a9feac2a",
89
+ "diagram": None,
90
+ "suspended": False,
91
+ "tenantId": "tenant1",
92
+ "versionTag": "3.8.3",
93
+ "historyTimeToLive": None,
94
+ "startableInTasklist": True
95
+ },
96
+ ]
97
+ responses.add(responses.GET, self.process_client.get_process_definitions_url(),
98
+ status=HTTPStatus.OK, json=get_process_definitions_resp)
99
+
100
+ start_process_resp = {
101
+ "links": [
102
+ {
103
+ "method": "GET",
104
+ "href": "http://localhost:8080/engine-rest/process-instance/e07b461a-80d0-11eb-83ea-0a58a9feac2a",
105
+ "rel": "self"
106
+ }
107
+ ],
108
+ "id": "e07b461a-80d0-11eb-83ea-0a58a9feac2a",
109
+ "definitionId": "process_definition_id_2",
110
+ "businessKey": "businessKey",
111
+ "caseInstanceId": None,
112
+ "ended": False,
113
+ "suspended": False,
114
+ "tenantId": "tenant1",
115
+ "variables": {
116
+ "applicationId": {
117
+ "type": "String",
118
+ "value": "30ea7b40-283b-4526-8979-371f4ffc9ee0",
119
+ "valueInfo": {}
120
+ }
121
+ }
122
+ }
123
+ responses.add(responses.POST, self.process_client.get_start_process_url('process_definition_id_2'),
124
+ status=HTTPStatus.OK, json=start_process_resp)
125
+
126
+ resp_json = self.process_client.start_process_by_version("ORIGINATION", "3.8.3", {}, "tenant1")
127
+
128
+ self.assertDictEqual(start_process_resp, resp_json)
129
+
130
+ @responses.activate
131
+ def test_start_process_by_version_returns_process_details_if_started_successfully(self):
132
+ get_process_definitions_resp = [
133
+ {
134
+ "id": "process_definition_id",
135
+ "key": "ORIGINATION",
136
+ "category": "http://bpmn.io/schema/bpmn",
137
+ "description": "- Removed unwanted external tasks",
138
+ "name": "Origination",
139
+ "version": 37,
140
+ "resource": "bpmn/origination.bpmn",
141
+ "deploymentId": "05f7527e-737e-11eb-ac5c-0a58a9feac2a",
142
+ "diagram": None,
143
+ "suspended": False,
144
+ "tenantId": "tenant1",
145
+ "versionTag": "3.8.3",
146
+ "historyTimeToLive": None,
147
+ "startableInTasklist": True
148
+ }
149
+ ]
150
+ responses.add(responses.GET, self.process_client.get_process_definitions_url(),
151
+ status=HTTPStatus.OK, json=get_process_definitions_resp)
152
+
153
+ start_process_resp = {
154
+ "links": [
155
+ {
156
+ "method": "GET",
157
+ "href": "http://localhost:8080/engine-rest/process-instance/e07b461a-80d0-11eb-83ea-0a58a9feac2a",
158
+ "rel": "self"
159
+ }
160
+ ],
161
+ "id": "e07b461a-80d0-11eb-83ea-0a58a9feac2a",
162
+ "definitionId": "89337817-75c9-11eb-84bb-0a58a9feac2a",
163
+ "businessKey": "businessKey",
164
+ "caseInstanceId": None,
165
+ "ended": False,
166
+ "suspended": False,
167
+ "tenantId": "tenant1",
168
+ "variables": {
169
+ "applicationId": {
170
+ "type": "String",
171
+ "value": "30ea7b40-283b-4526-8979-371f4ffc9ee0",
172
+ "valueInfo": {}
173
+ }
174
+ }
175
+ }
176
+ responses.add(responses.POST, self.process_client.get_start_process_url('process_definition_id'),
177
+ status=HTTPStatus.OK, json=start_process_resp)
178
+
179
+ resp_json = self.process_client.start_process_by_version("ORIGINATION", "3.8.3", {}, "tenant1")
180
+
181
+ self.assertDictEqual(start_process_resp, resp_json)
File without changes
@@ -0,0 +1,28 @@
1
+ import base64
2
+ import copy
3
+ from pydantic import BaseModel
4
+
5
+
6
+ def obfuscate_password(config: dict) -> dict:
7
+ """Obfuscate password value in auth_basic config
8
+
9
+ :param config: config from ExternalTaskWorker or ExternalTaskClient
10
+ :returns: _config with obfuscated password
11
+ """
12
+ _config = copy.deepcopy(config)
13
+ _auth = _config.get('auth_basic')
14
+ if _auth is not None and 'password' in _auth.keys():
15
+ _auth['password'] = '***'
16
+ return _config
17
+
18
+ class AuthBasic(BaseModel):
19
+ username: str
20
+ password: str
21
+ token: str = ""
22
+
23
+ def __init__(self, **data):
24
+ super().__init__(**data)
25
+ token = f"{self.username}:{self.password}"
26
+ bytemsg = base64.b64encode(token.encode('utf-8'))
27
+ tokenb64 = str(bytemsg, "utf-8")
28
+ self.token = f"Basic {tokenb64}"
@@ -0,0 +1,28 @@
1
+ from typing import Any, Dict, Union
2
+
3
+ from pydantic import BaseModel, validator
4
+
5
+
6
+ class AuthBearer(BaseModel):
7
+ access_token: str
8
+
9
+ @validator('access_token', pre=True)
10
+ @classmethod
11
+ def get_token_from_dict(cls, value: Union[str, Dict[str, Any]]) -> str:
12
+ if isinstance(value, str):
13
+ return value
14
+ if not isinstance(value, dict):
15
+ raise ValueError('token should be dict or str')
16
+ if not value.get('access_token'):
17
+ raise KeyError(
18
+ 'you should pass the token inside "access_token" key')
19
+ return value['access_token']
20
+
21
+ @validator('access_token')
22
+ @classmethod
23
+ def concat_bearer(cls, value: str) -> str:
24
+ if not any([
25
+ value.startswith('Bearer'),
26
+ value.startswith('bearer')
27
+ ]):
28
+ return f'Bearer {value}'
@@ -0,0 +1,31 @@
1
+ import logging
2
+
3
+
4
+ def log_with_context(message, context=None, log_level='info', **kwargs):
5
+ context = context if context is not None else {}
6
+ log_function = __get_log_function(log_level)
7
+
8
+ log_context_prefix = __get_log_context_prefix(context)
9
+ if log_context_prefix:
10
+ log_function(f"{log_context_prefix} {message}", **kwargs)
11
+ else:
12
+ log_function(message, **kwargs)
13
+
14
+
15
+ def __get_log_context_prefix(context):
16
+ log_context_prefix = ""
17
+ if context:
18
+ for k, v in context.items():
19
+ if v is not None:
20
+ log_context_prefix += f"[{k}:{v}]"
21
+ return log_context_prefix
22
+
23
+
24
+ def __get_log_function(log_level):
25
+ switcher = {
26
+ 'debug': logging.debug,
27
+ 'info': logging.info,
28
+ 'warning': logging.warning,
29
+ 'error': logging.error
30
+ }
31
+ return switcher.get(log_level, logging.info)
@@ -0,0 +1,35 @@
1
+ def raise_exception_if_not_ok(response):
2
+ # Check if the response has the `ok` attribute
3
+ if hasattr(response, 'ok'):
4
+ if response.ok:
5
+ return
6
+ else:
7
+ # For httpx, treat status_code < 400 as "ok"
8
+ if response.status_code < 400:
9
+ return
10
+
11
+ resp_json = __get_json_or_raise_for_status(response)
12
+
13
+ raise Exception(get_response_error_message(response.status_code, resp_json))
14
+
15
+
16
+ def __get_json_or_raise_for_status(response):
17
+ try:
18
+ return response.json()
19
+ except ValueError as e:
20
+ # if no json available in response then use raise_for_status() to raise exception
21
+ response.raise_for_status()
22
+
23
+
24
+ def get_response_error_message(status_code, resp_json):
25
+ error_msg = f'received {status_code}'
26
+
27
+ err_type = resp_json.get('type', '')
28
+ message = resp_json.get('message', '')
29
+ if err_type:
30
+ error_msg += f" : {err_type}"
31
+
32
+ if message:
33
+ error_msg += f" : {message}"
34
+
35
+ return error_msg
@@ -0,0 +1,30 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.utils.auth_basic import AuthBasic, obfuscate_password
4
+
5
+
6
+ class TestAuthBasic(TestCase):
7
+ def test_auth_basic(self):
8
+ auth_basic = AuthBasic(**{
9
+ "username": "test",
10
+ "password": "test",
11
+ })
12
+ self.assertEqual(auth_basic.token, 'Basic dGVzdDp0ZXN0')
13
+
14
+
15
+ def test_obfuscate_password(self):
16
+ default_config = {
17
+ "auth_basic": {"username": "demo", "password": "demo"},
18
+ "maxTasks": 1,
19
+ "lockDuration": 10000,
20
+ "asyncResponseTimeout": 0,
21
+ "isDebug": True,
22
+ }
23
+ obfuscate_config = {
24
+ "auth_basic": {"username": "demo", "password": "***"},
25
+ "maxTasks": 1,
26
+ "lockDuration": 10000,
27
+ "asyncResponseTimeout": 0,
28
+ "isDebug": True,
29
+ }
30
+ self.assertEqual(obfuscate_password(default_config), obfuscate_config)
@@ -0,0 +1,27 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.utils.auth_bearer import AuthBearer
4
+
5
+
6
+ class TestAuthBasic(TestCase):
7
+ """Can you generate a bearer token using jwt lib.
8
+
9
+ reffer - https://pyjwt.readthedocs.io/en/stable/
10
+ """
11
+ def test_str_token_bearer(self):
12
+ token = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZW1vIn0'
13
+ '.NbMsjy8QQ5nrjGTXqdTrJ6g0dqawRvZAqp4XvNt437M')
14
+ auth_bearer = AuthBearer(access_token=token)
15
+ self.assertEqual(auth_bearer.access_token, f'Bearer {token}')
16
+
17
+ def test_dict_token_bearer(self):
18
+ token = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZW1vIn0'
19
+ '.NbMsjy8QQ5nrjGTXqdTrJ6g0dqawRvZAqp4XvNt437M')
20
+ auth_bearer = AuthBearer(access_token={'access_token': token})
21
+ self.assertEqual(auth_bearer.access_token, f'Bearer {token}')
22
+
23
+ def test_error_dict_token_bearer(self):
24
+ token = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZW1vIn0'
25
+ '.NbMsjy8QQ5nrjGTXqdTrJ6g0dqawRvZAqp4XvNt437M')
26
+ with self.assertRaises(KeyError):
27
+ AuthBearer(access_token={'token': token})
@@ -0,0 +1,43 @@
1
+ from http import HTTPStatus
2
+ from unittest import TestCase
3
+
4
+ import requests
5
+
6
+ from operaton.utils.response_utils import raise_exception_if_not_ok, get_response_error_message
7
+
8
+
9
+ class TestRaiseExceptionIfResponseNotOk(TestCase):
10
+
11
+ def test_does_not_raise_exception_if_response_is_ok(self):
12
+ try:
13
+ raise_exception_if_not_ok(self.__mock_response(HTTPStatus.OK, {}))
14
+ except Exception:
15
+ self.fail("raise_exception_if_not_ok() should not raise Exception when response is ok")
16
+
17
+ def test_raise_exception_if_response_is_not_ok(self):
18
+ data = {'type': "SomeExceptionClass", "message": "a detailed message"}
19
+ with self.assertRaises(Exception) as context:
20
+ raise_exception_if_not_ok(self.__mock_response(HTTPStatus.BAD_REQUEST, data))
21
+
22
+ self.assertEqual("received 400 : SomeExceptionClass : a detailed message", str(context.exception))
23
+
24
+ def __mock_response(self, status_code, data):
25
+ response = requests.Response()
26
+ response.status_code = status_code
27
+ response.json = lambda: data
28
+ return response
29
+
30
+ def test_get_response_error_message_no_error_type_no_message(self):
31
+ data = {}
32
+ error_msg = get_response_error_message(HTTPStatus.BAD_REQUEST, data)
33
+ self.assertEqual("received 400", error_msg)
34
+
35
+ def test_get_response_error_message_only_type_no_msg(self):
36
+ data = {'type': "InvalidRequestType", "message": ""}
37
+ error_msg = get_response_error_message(HTTPStatus.BAD_REQUEST, data)
38
+ self.assertEqual("received 400 : InvalidRequestType", error_msg)
39
+
40
+ def test_get_response_error_message_only_msg_no_type(self):
41
+ data = {"message": "a detailed message"}
42
+ error_msg = get_response_error_message(HTTPStatus.BAD_REQUEST, data)
43
+ self.assertEqual("received 400 : a detailed message", error_msg)
@@ -0,0 +1,21 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.utils.utils import str_to_list, join
4
+
5
+
6
+ class TestUtils(TestCase):
7
+ def test_str_to_list_returns_list_as_is(self):
8
+ self.assertEqual([], str_to_list([]))
9
+ self.assertEqual([1, 2, 3], str_to_list([1, 2, 3]))
10
+ self.assertEqual(["a", "b", "c"], str_to_list(["a", "b", "c"]))
11
+
12
+ def test_str_to_list_returns_list_with_string_passed(self):
13
+ self.assertEqual(["hello"], str_to_list("hello"))
14
+
15
+ def test_join_empty_list(self):
16
+ self.assertEqual("", join(None, ','))
17
+ self.assertEqual("", join([], ','))
18
+
19
+ def test_join_non_empty_list(self):
20
+ self.assertEqual("1", join([1], ','))
21
+ self.assertEqual("1,2,3", join([1, 2, 3], ','))
@@ -0,0 +1,14 @@
1
+ def str_to_list(values):
2
+ if isinstance(values, str):
3
+ return [values]
4
+ return values
5
+
6
+
7
+ def get_exception_detail(exception):
8
+ return f"{type(exception)} : {str(exception)}"
9
+
10
+
11
+ def join(list_of_values, separator):
12
+ if list_of_values:
13
+ return separator.join(str(v) for v in list_of_values)
14
+ return ''
File without changes
@@ -0,0 +1,27 @@
1
+ class Properties:
2
+ """
3
+ Properties are key->value pairs which acts as a kind of constant in Camunda.
4
+ A property can be set via Camunda Modeller's Properties Panel on the Extension tab.
5
+
6
+ Properties appear in a ExternalTask as soon as the config for an ExternalTaskClient will
7
+ have a configuration includeExtensionProperties: True (which is the default)
8
+
9
+ Properties will store strings only.
10
+ """
11
+ def __init__(self, properties={}):
12
+ self.properties = properties
13
+
14
+ def get_property(self, property_name) -> str:
15
+ """
16
+ access a single property
17
+ """
18
+ return self.properties.get(property_name, None)
19
+
20
+ def to_dict(self) -> dict:
21
+ """
22
+ Converts the properties to a simple dictionary
23
+ """
24
+ result = {}
25
+ for k, v in self.properties.items():
26
+ result[k] = v
27
+ return result
@@ -0,0 +1,20 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.variables.properties import Properties
4
+
5
+
6
+ class PropertiesTest(TestCase):
7
+
8
+ def test_get_variable_returns_none_when_variable_absent(self):
9
+ properties = Properties({})
10
+ self.assertIsNone(properties.get_property("var1"))
11
+
12
+ def test_get_variable_returns_value_when_variable_present(self):
13
+ properties = Properties({"var1": "one"})
14
+ self.assertEqual("one", properties.get_property("var1"))
15
+
16
+ def test_to_dict_returns_variables_as_dict(self):
17
+ properties = Properties({"var1": "Sample1",
18
+ "var2": "Sample2",
19
+ "var3": "Sample3"})
20
+ self.assertDictEqual({"var1": "Sample1", "var2": "Sample2", "var3": "Sample3"}, properties.to_dict())
@@ -0,0 +1,60 @@
1
+ from unittest import TestCase
2
+
3
+ from operaton.variables.variables import Variables
4
+
5
+
6
+ class VariablesTest(TestCase):
7
+
8
+ def test_get_variable_returns_none_when_variable_absent(self):
9
+ variables = Variables({})
10
+ self.assertIsNone(variables.get_variable("var1"))
11
+
12
+ def test_get_variable_returns_value_when_variable_present(self):
13
+ variables = Variables({"var1": {"value": 1}})
14
+ self.assertEqual(1, variables.get_variable("var1"))
15
+
16
+ def test_get_variable_returns_with_meta(self):
17
+ var1_raw = {"value": 1}
18
+ variables = Variables({"var1": var1_raw})
19
+ self.assertEqual(var1_raw, variables.get_variable("var1", True))
20
+
21
+ def test_get_variable_returns_without_meta(self):
22
+ var1_raw = {"value": 1}
23
+ variables = Variables({"var1": var1_raw})
24
+ self.assertEqual(1, variables.get_variable("var1", False))
25
+
26
+ def test_format_returns_empty_dict_when_none_is_passed(self):
27
+ variables = None
28
+ self.assertDictEqual({}, Variables.format(variables))
29
+
30
+ def test_format_returns_empty_dict_when_variables_absent(self):
31
+ variables = {}
32
+ self.assertDictEqual({}, Variables.format(variables))
33
+
34
+ def test_format_returns_dict_with_value_when_nested_dict(self):
35
+ var1_raw = {"var2": 1, "var3": "test"}
36
+ variables = {"var1": var1_raw}
37
+ self.assertDictEqual({"var1": {"value": var1_raw}}, Variables.format(variables))
38
+
39
+ def test_format_returns_formatted_variables_when_variables_present(self):
40
+ variables = {"var1": 1, "var2": True, "var3": "string"}
41
+ formatted_vars = Variables.format(variables)
42
+ self.assertDictEqual({"var1": {"value": 1},
43
+ "var2": {"value": True},
44
+ "var3": {"value": "string"}}, formatted_vars)
45
+
46
+ def test_format_returns_formatted_variables_keeps_already_formatted(self):
47
+ variables = {"var1": 1, "var2": True, "var3": "string", "var4": {"value": 1}}
48
+ formatted_vars = Variables.format(variables)
49
+ self.assertDictEqual({"var1": {"value": 1},
50
+ "var2": {"value": True},
51
+ "var3": {"value": "string"},
52
+ "var4": {"value": 1}}, formatted_vars)
53
+
54
+ def test_to_dict_returns_variables_as_dict(self):
55
+ variables = Variables({"var1": {"value": 1},
56
+ "var2": {"value": True},
57
+ "var3": {"value": "string"}})
58
+ self.assertDictEqual({"var1": 1, "var2": True, "var3": "string"}, variables.to_dict())
59
+
60
+
@@ -0,0 +1,45 @@
1
+
2
+ import json
3
+
4
+ class Variables:
5
+ def __init__(self, variables={}):
6
+ self.variables = variables
7
+
8
+ def get_variable(self, variable_name, with_meta=False):
9
+ variable = self.variables.get(variable_name, None)
10
+ if not variable:
11
+ return None
12
+ if with_meta:
13
+ return variable
14
+ return variable["value"]
15
+
16
+ @classmethod
17
+ def format(cls, variables):
18
+ """
19
+ Gives the correct format to variables.
20
+ :param variables: dict - Dictionary of variable names to values.
21
+ :return: Dictionary of well formed variables
22
+ {"var1": 1, "var2": True}
23
+ ->
24
+ {"var1": {"value": 1}, "var2": {"value": True}}
25
+ """
26
+ formatted_vars = {}
27
+ if variables:
28
+ formatted_vars = {
29
+ k: v if (isinstance(v, dict) and "value" in v.keys()) else {"value": v}
30
+ for k, v in variables.items()
31
+ }
32
+ return formatted_vars
33
+
34
+ def to_dict(self):
35
+ """
36
+ Converts the variables to a simple dictionary
37
+ :return: dict
38
+ {"var1": {"value": 1}, "var2": {"value": True}}
39
+ ->
40
+ {"var1": 1, "var2": True}
41
+ """
42
+ result = {}
43
+ for k, v in self.variables.items():
44
+ result[k] = v["value"]
45
+ return result