qctrl-workflow-client 3.0.0__tar.gz → 3.1.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qctrl-workflow-client
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Q-CTRL Client
5
5
  Home-page: https://q-ctrl.com
6
6
  License: https://q-ctrl.com/terms
@@ -33,7 +33,7 @@ Requires-Dist: importlib-metadata (>=6.8.0,<7.0.0)
33
33
  Requires-Dist: packaging (>=23.1.0,<24.0.0)
34
34
  Requires-Dist: qctrl-client (>=9.1.0,<10.0.0)
35
35
  Requires-Dist: qctrl-commons (>=22.0.0,<23.0.0)
36
- Requires-Dist: requests (>=2.31.0,<3.0.0)
36
+ Requires-Dist: requests (>=2.32.0,<3.0.0)
37
37
  Requires-Dist: tenacity (>=8.1.0,<9.0.0)
38
38
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
39
39
  Project-URL: Documentation, https://docs.q-ctrl.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qctrl-workflow-client"
3
- version = "3.0.0"
3
+ version = "3.1.0"
4
4
  description = "Q-CTRL Client"
5
5
  license = "https://q-ctrl.com/terms"
6
6
  authors = ["Q-CTRL <support@q-ctrl.com>"]
@@ -79,13 +79,13 @@ tomli = "^2.0.1"
79
79
  importlib-metadata = "^6.8.0"
80
80
  gql = "^3.4.0"
81
81
  tenacity = "^8.1.0"
82
- requests = "^2.31.0"
82
+ requests = "^2.32.0"
83
83
  packaging = "^23.1.0"
84
84
  qctrl-commons = { version = "^22.0.0", source = "PyPI" }
85
85
  qctrl-client = { version = "^9.1.0", source = "PyPI" }
86
86
 
87
87
  [tool.poetry.group.dev.dependencies]
88
- black = "^23.7.0"
88
+ black = "^24.3.0"
89
89
  isort = "^5.12.0"
90
90
  pre-commit = "^3.3.3"
91
91
  pylint = "^2.14.4"
@@ -11,7 +11,7 @@
11
11
  # distributed under the License is distributed on an "AS IS" BASIS. See the
12
12
  # License for the specific language.
13
13
 
14
- __version__ = "3.0.0"
14
+ __version__ = "3.1.0"
15
15
 
16
16
  from .defaults import (
17
17
  get_authenticated_client_for_product,
@@ -20,6 +20,8 @@ from typing import (
20
20
  )
21
21
  from warnings import warn
22
22
 
23
+ from qctrlcommons.exceptions import QctrlArgumentsValueError
24
+
23
25
 
24
26
  def core_workflow(
25
27
  get_config: Callable, workflow: str, formatter: Optional[Callable] = None
@@ -61,6 +63,71 @@ def core_workflow(
61
63
  # the function
62
64
  config = get_config()
63
65
  router = config.get_router()
66
+
67
+ router.set_async_state(kwargs.pop("is_async", False))
68
+
69
+ data = func(*args, **kwargs)
70
+ result = router(workflow, data)
71
+
72
+ if formatter:
73
+ result = formatter(result)
74
+
75
+ return result
76
+
77
+ return customized_decorator
78
+
79
+ return decorator
80
+
81
+
82
+ def async_core_workflow(
83
+ get_config: Callable, workflow: str, formatter: Optional[Callable] = None
84
+ ):
85
+ """
86
+ Decorator for a function which will execute asynchronously workflow.
87
+ The decorated function should return the data to be used
88
+ during async workflow execution. When being used in a client
89
+ package, it is recommended to use a partial to provide
90
+ a default value for `get_config` e.g.
91
+
92
+ async_fire_opal_workflow = partial(
93
+ async_core_workflow,
94
+ get_fire_opal_config
95
+ )
96
+
97
+ @async_fire_opal_workflow("execute")
98
+ def execute(...):
99
+
100
+ Parameters
101
+ ----------
102
+ get_config : Callable
103
+ Returns a `CoreClientSettings` instance. The configured
104
+ router will be used to execute the workflow.
105
+ workflow : str
106
+ The registered name of the workflow to be executed.
107
+ formatter : Callable, optional
108
+ Optional callable which can be used to format the workflow
109
+ result. The callable should accept exactly one argument
110
+ which is the raw result fo the workflow. If used, the
111
+ decorated function will return the result of this callable.
112
+ """
113
+
114
+ def decorator(func: Callable):
115
+ @wraps(func)
116
+ def customized_decorator(*args, **kwargs):
117
+ # router is instantiated before function is called
118
+ # so any alteration to settings is visible within
119
+ # the function
120
+ config = get_config()
121
+ router = config.get_router()
122
+
123
+ # Set the async state to True
124
+ router.set_async_state(True)
125
+ if "is_async" in kwargs:
126
+ raise QctrlArgumentsValueError(
127
+ description="The asynchronous state of this function cannot be altered.",
128
+ arguments={"is_async": kwargs.get("is_async")},
129
+ )
130
+
64
131
  data = func(*args, **kwargs)
65
132
  result = router(workflow, data)
66
133
 
@@ -199,6 +199,13 @@ class ApiRouter(BaseRouter):
199
199
  self._settings = settings
200
200
  self._validate()
201
201
  self._parallel_task_collector = None
202
+ self._async = False
203
+
204
+ def set_async_state(self, is_async: bool):
205
+ """
206
+ Toggle asynchronous state of workflow execution.
207
+ """
208
+ self._async = is_async
202
209
 
203
210
  def _validate(self):
204
211
  """
@@ -384,6 +391,9 @@ class ApiRouter(BaseRouter):
384
391
  errors=action_data["errors"],
385
392
  )
386
393
 
394
+ if self._async:
395
+ return {"async_result": action}
396
+
387
397
  if self._parallel_task_collector is not None:
388
398
  async_result = {"async_result": action}
389
399
  self._parallel_task_collector.add(async_result)
@@ -428,16 +438,11 @@ class ApiRouter(BaseRouter):
428
438
  message = warning_data["message"]
429
439
  warn(Warning(message))
430
440
 
431
- @retry(
432
- wait=_POLL_WAIT_CHAIN,
433
- retry=retry_if_result(lambda action: not action.is_finished()),
434
- )
435
- def _poll_for_completion(self, action: Action) -> Action:
441
+ def update_action_status(self, action: Action) -> Action:
436
442
  """
437
- Poll the API waiting for the action to be finished.
438
- When finished, an updated `Action` object is returned.
443
+ Update the action status. When finished, an updated
444
+ `Action` object is returned.
439
445
  """
440
-
441
446
  _query = gql.gql(
442
447
  """
443
448
  query($modelId: String!) {
@@ -467,6 +472,17 @@ class ApiRouter(BaseRouter):
467
472
 
468
473
  return action
469
474
 
475
+ @retry(
476
+ wait=_POLL_WAIT_CHAIN,
477
+ retry=retry_if_result(lambda action: not action.is_finished()),
478
+ )
479
+ def _poll_for_completion(self, action: Action) -> Action:
480
+ """
481
+ Poll the API waiting for the action to be finished.
482
+ When finished, an updated `Action` object is returned.
483
+ """
484
+ return self.update_action_status(action)
485
+
470
486
  def get_result(self, action: Action) -> DecodedResult:
471
487
  """
472
488
  Return the result of the action.