qctrl-workflow-client 3.0.0__tar.gz → 3.1.1__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.1
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.1"
4
4
  description = "Q-CTRL Client"
5
5
  license = "https://q-ctrl.com/terms"
6
6
  authors = ["Q-CTRL <support@q-ctrl.com>"]
@@ -79,16 +79,16 @@ 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
- pylint = "^2.14.4"
91
+ pylint = "^3.2.2"
92
92
  pytest = "^7.2.2"
93
93
  pytest-cov = "^4.0.0"
94
94
  pytest-mock = "^3.10.0"
@@ -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.1"
15
15
 
16
16
  from .defaults import (
17
17
  get_authenticated_client_for_product,
@@ -20,6 +20,10 @@ from typing import (
20
20
  )
21
21
  from warnings import warn
22
22
 
23
+ from qctrlcommons.exceptions import QctrlArgumentsValueError
24
+
25
+ from qctrlworkflowclient.router.api import ApiRouter
26
+
23
27
 
24
28
  def core_workflow(
25
29
  get_config: Callable, workflow: str, formatter: Optional[Callable] = None
@@ -61,6 +65,72 @@ def core_workflow(
61
65
  # the function
62
66
  config = get_config()
63
67
  router = config.get_router()
68
+
69
+ if isinstance(router, ApiRouter):
70
+ router.set_async_state(kwargs.pop("is_async", False))
71
+
72
+ data = func(*args, **kwargs)
73
+ result = router(workflow, data)
74
+
75
+ if formatter:
76
+ result = formatter(result)
77
+
78
+ return result
79
+
80
+ return customized_decorator
81
+
82
+ return decorator
83
+
84
+
85
+ def async_core_workflow(
86
+ get_config: Callable, workflow: str, formatter: Optional[Callable] = None
87
+ ):
88
+ """
89
+ Decorator for a function which will execute asynchronously workflow.
90
+ The decorated function should return the data to be used
91
+ during async workflow execution. When being used in a client
92
+ package, it is recommended to use a partial to provide
93
+ a default value for `get_config` e.g.
94
+
95
+ async_fire_opal_workflow = partial(
96
+ async_core_workflow,
97
+ get_fire_opal_config
98
+ )
99
+
100
+ @async_fire_opal_workflow("execute")
101
+ def execute(...):
102
+
103
+ Parameters
104
+ ----------
105
+ get_config : Callable
106
+ Returns a `CoreClientSettings` instance. The configured
107
+ router will be used to execute the workflow.
108
+ workflow : str
109
+ The registered name of the workflow to be executed.
110
+ formatter : Callable, optional
111
+ Optional callable which can be used to format the workflow
112
+ result. The callable should accept exactly one argument
113
+ which is the raw result fo the workflow. If used, the
114
+ decorated function will return the result of this callable.
115
+ """
116
+
117
+ def decorator(func: Callable):
118
+ @wraps(func)
119
+ def customized_decorator(*args, **kwargs):
120
+ # router is instantiated before function is called
121
+ # so any alteration to settings is visible within
122
+ # the function
123
+ config = get_config()
124
+ router = config.get_router()
125
+
126
+ # Set the async state to True
127
+ router.set_async_state(True)
128
+ if "is_async" in kwargs:
129
+ raise QctrlArgumentsValueError(
130
+ description="The asynchronous state of this function cannot be altered.",
131
+ arguments={"is_async": kwargs.get("is_async")},
132
+ )
133
+
64
134
  data = func(*args, **kwargs)
65
135
  result = router(workflow, data)
66
136
 
@@ -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.