cadence-python-client 0.1.0__tar.gz → 0.2.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.
- {cadence_python_client-0.1.0/cadence_python_client.egg-info → cadence_python_client-0.2.0}/PKG-INFO +5 -5
- cadence_python_client-0.2.0/cadence/_internal/activity/__init__.py +10 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/activity/_activity_executor.py +5 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/activity/_context.py +11 -9
- cadence_python_client-0.2.0/cadence/_internal/activity/_definition.py +178 -0
- cadence_python_client-0.2.0/cadence/_internal/fn_signature.py +90 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/rpc/yarpc.py +3 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/context.py +0 -4
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/deterministic_event_loop.py +37 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/activity_state_machine.py +14 -9
- cadence_python_client-0.2.0/cadence/_internal/workflow/statemachine/cancellation.py +36 -0
- cadence_python_client-0.2.0/cadence/_internal/workflow/statemachine/completion_state_machine.py +25 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/decision_manager.py +72 -13
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/decision_state_machine.py +31 -13
- cadence_python_client-0.2.0/cadence/_internal/workflow/statemachine/nondeterminism.py +308 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/timer_state_machine.py +13 -8
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/workflow_engine.py +96 -100
- cadence_python_client-0.2.0/cadence/_internal/workflow/workflow_instance.py +53 -0
- cadence_python_client-0.2.0/cadence/activity.py +342 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/common_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/common_pb2.pyi +4 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/common_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/decision_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/decision_pb2.pyi +7 -5
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/decision_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/domain_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/domain_pb2.pyi +6 -4
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/domain_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/error_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/error_pb2.pyi +2 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/error_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/history_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/history_pb2.pyi +10 -8
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/history_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/query_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/query_pb2.pyi +2 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/query_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_domain_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_domain_pb2.pyi +5 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_domain_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_meta_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_meta_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_visibility_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_visibility_pb2.pyi +2 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_visibility_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_worker_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_worker_pb2.pyi +5 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_worker_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_workflow_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_workflow_pb2.pyi +4 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_workflow_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/tasklist_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/tasklist_pb2.pyi +5 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/tasklist_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/visibility_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/visibility_pb2.pyi +4 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/visibility_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/workflow_pb2.py +3 -3
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/workflow_pb2.pyi +9 -7
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/workflow_pb2_grpc.py +1 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/client.py +55 -1
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/data_converter.py +9 -5
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/__init__.py +0 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_registry.py +67 -81
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/workflow.py +26 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0/cadence_python_client.egg-info}/PKG-INFO +5 -5
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence_python_client.egg-info/SOURCES.txt +6 -2
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence_python_client.egg-info/requires.txt +4 -4
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/pyproject.toml +6 -6
- cadence_python_client-0.1.0/cadence/_internal/activity/__init__.py +0 -5
- cadence_python_client-0.1.0/cadence/_internal/workflow/decisions_helper.py +0 -312
- cadence_python_client-0.1.0/cadence/_internal/workflow/workflow_intance.py +0 -44
- cadence_python_client-0.1.0/cadence/activity.py +0 -255
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/LICENSE +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/NOTICE +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/README.md +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/rpc/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/rpc/error.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/rpc/retry.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/decision_events_iterator.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/history_event_iterator.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/statemachine/event_dispatcher.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/api/v1/service_meta_pb2.pyi +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/error.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/metrics/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/metrics/constants.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/metrics/metrics.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/metrics/prometheus.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/sample/__init__.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/sample/client_example.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/sample/grpc_usage_example.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/sample/simple_usage_example.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/signal.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_activity.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_base_task_handler.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_decision.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_decision_task_handler.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_poller.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_types.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/worker/_worker.py +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence_python_client.egg-info/dependency_links.txt +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence_python_client.egg-info/top_level.txt +0 -0
- {cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/setup.cfg +0 -0
{cadence_python_client-0.1.0/cadence_python_client.egg-info → cadence_python_client-0.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadence-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python framework for authoring Cadence workflows and activities
|
|
5
5
|
Author: Cadence
|
|
6
6
|
License: Apache-2.0
|
|
@@ -23,14 +23,14 @@ Requires-Python: <3.14,>=3.11
|
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
License-File: NOTICE
|
|
26
|
-
Requires-Dist: grpcio
|
|
27
|
-
Requires-Dist: grpcio-status>=1.
|
|
26
|
+
Requires-Dist: grpcio>=1.73.1
|
|
27
|
+
Requires-Dist: grpcio-status>=1.73.1
|
|
28
28
|
Requires-Dist: msgspec>=0.19.0
|
|
29
|
-
Requires-Dist: protobuf
|
|
29
|
+
Requires-Dist: protobuf<7.0.0,>=6.31.0
|
|
30
30
|
Requires-Dist: typing-extensions>=4.0.0
|
|
31
31
|
Requires-Dist: prometheus-client>=0.21.0
|
|
32
32
|
Provides-Extra: dev
|
|
33
|
-
Requires-Dist: grpcio-tools
|
|
33
|
+
Requires-Dist: grpcio-tools>=1.73.1; extra == "dev"
|
|
34
34
|
Requires-Dist: pytest>=8.4.1; extra == "dev"
|
|
35
35
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from concurrent.futures import ThreadPoolExecutor
|
|
2
2
|
from logging import getLogger
|
|
3
3
|
from traceback import format_exception
|
|
4
|
-
from typing import Any, Callable
|
|
4
|
+
from typing import Any, Callable, cast
|
|
5
5
|
from google.protobuf.duration import to_timedelta
|
|
6
6
|
from google.protobuf.timestamp import to_datetime
|
|
7
7
|
|
|
8
8
|
from cadence._internal.activity._context import _Context, _SyncContext
|
|
9
|
-
from cadence.activity import
|
|
9
|
+
from cadence._internal.activity._definition import BaseDefinition, ExecutionStrategy
|
|
10
|
+
from cadence.activity import ActivityInfo, ActivityDefinition
|
|
10
11
|
from cadence.api.v1.common_pb2 import Failure
|
|
11
12
|
from cadence.api.v1.service_worker_pb2 import (
|
|
12
13
|
PollForActivityTaskResponse,
|
|
@@ -42,12 +43,13 @@ class ActivityExecutor:
|
|
|
42
43
|
result = await context.execute(task.input)
|
|
43
44
|
await self._report_success(task, result)
|
|
44
45
|
except Exception as e:
|
|
46
|
+
_logger.exception("Activity failed")
|
|
45
47
|
await self._report_failure(task, e)
|
|
46
48
|
|
|
47
49
|
def _create_context(self, task: PollForActivityTaskResponse) -> _Context:
|
|
48
50
|
activity_type = task.activity_type.name
|
|
49
51
|
try:
|
|
50
|
-
activity_def = self._registry(activity_type)
|
|
52
|
+
activity_def = cast(BaseDefinition, self._registry(activity_type))
|
|
51
53
|
except KeyError:
|
|
52
54
|
raise KeyError(f"Activity type not found: {activity_type}") from None
|
|
53
55
|
|
{cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/activity/_context.py
RENAMED
|
@@ -3,7 +3,8 @@ from concurrent.futures.thread import ThreadPoolExecutor
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
from cadence import Client
|
|
6
|
-
from cadence.activity import
|
|
6
|
+
from cadence._internal.activity._definition import BaseDefinition
|
|
7
|
+
from cadence.activity import ActivityInfo, ActivityContext
|
|
7
8
|
from cadence.api.v1.common_pb2 import Payload
|
|
8
9
|
|
|
9
10
|
|
|
@@ -12,20 +13,21 @@ class _Context(ActivityContext):
|
|
|
12
13
|
self,
|
|
13
14
|
client: Client,
|
|
14
15
|
info: ActivityInfo,
|
|
15
|
-
|
|
16
|
+
activity_def: BaseDefinition[[Any], Any],
|
|
16
17
|
):
|
|
17
18
|
self._client = client
|
|
18
19
|
self._info = info
|
|
19
|
-
self.
|
|
20
|
+
self._activity_def = activity_def
|
|
20
21
|
|
|
21
22
|
async def execute(self, payload: Payload) -> Any:
|
|
22
23
|
params = self._to_params(payload)
|
|
23
24
|
with self._activate():
|
|
24
|
-
return await self.
|
|
25
|
+
return await self._activity_def.impl_fn(*params)
|
|
25
26
|
|
|
26
27
|
def _to_params(self, payload: Payload) -> list[Any]:
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
return self._activity_def.signature.params_from_payload(
|
|
29
|
+
self._client.data_converter, payload
|
|
30
|
+
)
|
|
29
31
|
|
|
30
32
|
def client(self) -> Client:
|
|
31
33
|
return self._client
|
|
@@ -39,10 +41,10 @@ class _SyncContext(_Context):
|
|
|
39
41
|
self,
|
|
40
42
|
client: Client,
|
|
41
43
|
info: ActivityInfo,
|
|
42
|
-
|
|
44
|
+
activity_def: BaseDefinition[[Any], Any],
|
|
43
45
|
executor: ThreadPoolExecutor,
|
|
44
46
|
):
|
|
45
|
-
super().__init__(client, info,
|
|
47
|
+
super().__init__(client, info, activity_def)
|
|
46
48
|
self._executor = executor
|
|
47
49
|
|
|
48
50
|
async def execute(self, payload: Payload) -> Any:
|
|
@@ -52,7 +54,7 @@ class _SyncContext(_Context):
|
|
|
52
54
|
|
|
53
55
|
def _run(self, args: list[Any]) -> Any:
|
|
54
56
|
with self._activate():
|
|
55
|
-
return self.
|
|
57
|
+
return self._activity_def.impl_fn(*args)
|
|
56
58
|
|
|
57
59
|
def client(self) -> Client:
|
|
58
60
|
raise RuntimeError("client is only supported in async activities")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from functools import update_wrapper, partial
|
|
5
|
+
from typing import (
|
|
6
|
+
Generic,
|
|
7
|
+
Callable,
|
|
8
|
+
Unpack,
|
|
9
|
+
Self,
|
|
10
|
+
ParamSpec,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Awaitable,
|
|
13
|
+
cast,
|
|
14
|
+
Concatenate,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from cadence._internal.fn_signature import FnSignature
|
|
18
|
+
from cadence.workflow import ActivityOptions, WorkflowContext, execute_activity
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
R = TypeVar("R")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExecutionStrategy(Enum):
|
|
26
|
+
ASYNC = "async"
|
|
27
|
+
THREAD_POOL = "thread_pool"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseDefinition(ABC, Generic[P, R]):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
wrapped: Callable,
|
|
35
|
+
strategy: ExecutionStrategy,
|
|
36
|
+
signature: FnSignature,
|
|
37
|
+
):
|
|
38
|
+
self._name = name
|
|
39
|
+
self._wrapped = wrapped
|
|
40
|
+
self._strategy = strategy
|
|
41
|
+
self._signature = signature
|
|
42
|
+
self._execution_options = ActivityOptions()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def strategy(self) -> ExecutionStrategy:
|
|
46
|
+
return self._strategy
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def signature(self) -> FnSignature:
|
|
50
|
+
return self._signature
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def impl_fn(self) -> Callable:
|
|
54
|
+
return self._wrapped
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def name(self) -> str:
|
|
58
|
+
return self._name
|
|
59
|
+
|
|
60
|
+
@abc.abstractmethod
|
|
61
|
+
def clone(self) -> Self: ...
|
|
62
|
+
|
|
63
|
+
def rebind(self, fn: Callable) -> Self:
|
|
64
|
+
res = self.clone()
|
|
65
|
+
res._wrapped = fn
|
|
66
|
+
return res
|
|
67
|
+
|
|
68
|
+
def with_options(self, **kwargs: Unpack[ActivityOptions]) -> Self:
|
|
69
|
+
res = self.clone()
|
|
70
|
+
new_opts = self._execution_options.copy()
|
|
71
|
+
new_opts.update(kwargs)
|
|
72
|
+
res._execution_options = new_opts
|
|
73
|
+
return res
|
|
74
|
+
|
|
75
|
+
async def execute(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
76
|
+
result_type = cast(type[R], self._signature.return_type)
|
|
77
|
+
return await execute_activity(
|
|
78
|
+
self._name,
|
|
79
|
+
result_type,
|
|
80
|
+
*self._signature.params_from_call(args, kwargs),
|
|
81
|
+
**self._execution_options,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SyncImpl(BaseDefinition[P, R]):
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
wrapped: Callable[P, R],
|
|
89
|
+
name: str,
|
|
90
|
+
signature: FnSignature,
|
|
91
|
+
):
|
|
92
|
+
super().__init__(name, wrapped, ExecutionStrategy.THREAD_POOL, signature)
|
|
93
|
+
update_wrapper(self, wrapped)
|
|
94
|
+
|
|
95
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
96
|
+
if WorkflowContext.is_set():
|
|
97
|
+
raise RuntimeError(
|
|
98
|
+
"Attempting to invoke sync function in workflow. Use execute"
|
|
99
|
+
)
|
|
100
|
+
return self._wrapped(*args, **kwargs) # type: ignore
|
|
101
|
+
|
|
102
|
+
def clone(self) -> "SyncImpl[P, R]":
|
|
103
|
+
return SyncImpl[P, R](self._wrapped, self._name, self._signature)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SyncMethodImpl(BaseDefinition[P, R], Generic[T, P, R]):
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
wrapped: Callable[Concatenate[T, P], R],
|
|
110
|
+
name: str,
|
|
111
|
+
signature: FnSignature,
|
|
112
|
+
):
|
|
113
|
+
super().__init__(name, wrapped, ExecutionStrategy.THREAD_POOL, signature)
|
|
114
|
+
update_wrapper(self, wrapped)
|
|
115
|
+
|
|
116
|
+
def __get__(self, instance, owner):
|
|
117
|
+
if instance is None:
|
|
118
|
+
return self
|
|
119
|
+
# If we bound the method to an instance, then drop the self parameter. It's a normal function again
|
|
120
|
+
return SyncImpl[P, R](
|
|
121
|
+
partial(self._wrapped, instance), self.name, self._signature
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def __call__(self, original_self: T, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
125
|
+
if WorkflowContext.is_set():
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"Attempting to invoke sync function in workflow. Use execute"
|
|
128
|
+
)
|
|
129
|
+
return self._wrapped(original_self, *args, **kwargs) # type: ignore
|
|
130
|
+
|
|
131
|
+
def clone(self) -> "SyncMethodImpl[T, P, R]":
|
|
132
|
+
return SyncMethodImpl[T, P, R](self._wrapped, self._name, self._signature)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class AsyncImpl(BaseDefinition[P, R]):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
wrapped: Callable[P, Awaitable[R]],
|
|
139
|
+
name: str,
|
|
140
|
+
signature: FnSignature,
|
|
141
|
+
):
|
|
142
|
+
super().__init__(name, wrapped, ExecutionStrategy.ASYNC, signature)
|
|
143
|
+
update_wrapper(self, wrapped)
|
|
144
|
+
|
|
145
|
+
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
146
|
+
if WorkflowContext.is_set():
|
|
147
|
+
return await self.execute(*args, **kwargs) # type: ignore
|
|
148
|
+
return await self._wrapped(*args, **kwargs) # type: ignore
|
|
149
|
+
|
|
150
|
+
def clone(self) -> "AsyncImpl[P, R]":
|
|
151
|
+
return AsyncImpl[P, R](self._wrapped, self._name, self._signature)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class AsyncMethodImpl(BaseDefinition[P, R], Generic[T, P, R]):
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
wrapped: Callable[Concatenate[T, P], Awaitable[R]],
|
|
158
|
+
name: str,
|
|
159
|
+
signature: FnSignature,
|
|
160
|
+
):
|
|
161
|
+
super().__init__(name, wrapped, ExecutionStrategy.ASYNC, signature)
|
|
162
|
+
update_wrapper(self, wrapped)
|
|
163
|
+
|
|
164
|
+
def __get__(self, instance, owner):
|
|
165
|
+
if instance is None:
|
|
166
|
+
return self
|
|
167
|
+
# If we bound the method to an instance, then drop the self parameter. It's a normal function again
|
|
168
|
+
return AsyncImpl[P, R](
|
|
169
|
+
partial(self._wrapped, instance), self.name, self._signature
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
async def __call__(self, original_self: T, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
173
|
+
if WorkflowContext.is_set():
|
|
174
|
+
return await self.execute(*args, **kwargs) # type: ignore
|
|
175
|
+
return await self._wrapped(original_self, *args, **kwargs) # type: ignore
|
|
176
|
+
|
|
177
|
+
def clone(self) -> "AsyncMethodImpl[T, P, R]":
|
|
178
|
+
return AsyncMethodImpl[T, P, R](self._wrapped, self._name, self._signature)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from inspect import signature, Parameter
|
|
3
|
+
from typing import (
|
|
4
|
+
Type,
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Sequence,
|
|
8
|
+
get_type_hints,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from cadence.api.v1.common_pb2 import Payload
|
|
12
|
+
from cadence.data_converter import DataConverter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FnParameter:
|
|
17
|
+
name: str
|
|
18
|
+
type_hint: Type | None
|
|
19
|
+
has_default: bool = False
|
|
20
|
+
default_value: Any = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class FnSignature:
|
|
25
|
+
params: list[FnParameter]
|
|
26
|
+
return_type: Type
|
|
27
|
+
|
|
28
|
+
def params_from_call(
|
|
29
|
+
self, args: Sequence[Any], kwargs: dict[str, Any]
|
|
30
|
+
) -> list[Any]:
|
|
31
|
+
result: list[Any] = []
|
|
32
|
+
if len(args) > len(self.params):
|
|
33
|
+
raise ValueError(f"Too many positional arguments: {args}")
|
|
34
|
+
|
|
35
|
+
for value, param_spec in zip(args, self.params):
|
|
36
|
+
result.append(value)
|
|
37
|
+
|
|
38
|
+
i = len(result)
|
|
39
|
+
while i < len(self.params):
|
|
40
|
+
param = self.params[i]
|
|
41
|
+
if param.name not in kwargs and not param.has_default:
|
|
42
|
+
raise ValueError(f"Missing parameter: {param.name}")
|
|
43
|
+
|
|
44
|
+
value = kwargs.pop(param.name, param.default_value)
|
|
45
|
+
result.append(value)
|
|
46
|
+
i = i + 1
|
|
47
|
+
|
|
48
|
+
if len(kwargs) > 0:
|
|
49
|
+
raise ValueError(f"Unexpected keyword arguments: {kwargs}")
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
def params_from_payload(
|
|
54
|
+
self, data_converter: DataConverter, payload: Payload
|
|
55
|
+
) -> list[Any]:
|
|
56
|
+
type_hints = [param.type_hint for param in self.params]
|
|
57
|
+
return data_converter.from_data(payload, type_hints)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def of(fn: Callable) -> "FnSignature":
|
|
61
|
+
sig = signature(fn)
|
|
62
|
+
args = sig.parameters
|
|
63
|
+
hints = get_type_hints(fn)
|
|
64
|
+
params = []
|
|
65
|
+
for name, param in args.items():
|
|
66
|
+
# "unbound functions" aren't a thing in the Python spec. We don't have a way to determine whether the function
|
|
67
|
+
# is part of a class or is standalone.
|
|
68
|
+
# Filter out the self parameter and hope they followed the convention.
|
|
69
|
+
if param.name == "self":
|
|
70
|
+
continue
|
|
71
|
+
default = None
|
|
72
|
+
has_default = False
|
|
73
|
+
if param.default != Parameter.empty:
|
|
74
|
+
default = param.default
|
|
75
|
+
has_default = param.default is not None
|
|
76
|
+
if param.kind in (
|
|
77
|
+
Parameter.POSITIONAL_ONLY,
|
|
78
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
79
|
+
):
|
|
80
|
+
type_hint = hints.get(name, None)
|
|
81
|
+
params.append(FnParameter(name, type_hint, has_default, default))
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Parameters must be positional. {name} is {param.kind}, and not valid"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Treat unspecified return type as Any
|
|
88
|
+
return_type = hints.get("return", Any)
|
|
89
|
+
|
|
90
|
+
return FnSignature(params, return_type)
|
|
@@ -8,6 +8,8 @@ SERVICE_KEY = "rpc-service"
|
|
|
8
8
|
CALLER_KEY = "rpc-caller"
|
|
9
9
|
ENCODING_KEY = "rpc-encoding"
|
|
10
10
|
ENCODING_PROTO = "proto"
|
|
11
|
+
CALLER_TYPE_KEY = "cadence-caller-type"
|
|
12
|
+
CALLER_TYPE_VALUE = "sdk"
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class YarpcMetadataInterceptor(UnaryUnaryClientInterceptor):
|
|
@@ -16,6 +18,7 @@ class YarpcMetadataInterceptor(UnaryUnaryClientInterceptor):
|
|
|
16
18
|
(SERVICE_KEY, service),
|
|
17
19
|
(CALLER_KEY, caller),
|
|
18
20
|
(ENCODING_KEY, ENCODING_PROTO),
|
|
21
|
+
(CALLER_TYPE_KEY, CALLER_TYPE_VALUE),
|
|
19
22
|
)
|
|
20
23
|
|
|
21
24
|
async def intercept_unary_unary(
|
{cadence_python_client-0.1.0 → cadence_python_client-0.2.0}/cadence/_internal/workflow/context.py
RENAMED
|
@@ -4,7 +4,6 @@ from math import ceil
|
|
|
4
4
|
from typing import Iterator, Optional, Any, Unpack, Type, cast
|
|
5
5
|
|
|
6
6
|
from cadence._internal.workflow.statemachine.decision_manager import DecisionManager
|
|
7
|
-
from cadence._internal.workflow.decisions_helper import DecisionsHelper
|
|
8
7
|
from cadence.api.v1.common_pb2 import ActivityType
|
|
9
8
|
from cadence.api.v1.decision_pb2 import ScheduleActivityTaskDecisionAttributes
|
|
10
9
|
from cadence.api.v1.tasklist_pb2 import TaskList, TaskListKind
|
|
@@ -21,7 +20,6 @@ class Context(WorkflowContext):
|
|
|
21
20
|
self._info = info
|
|
22
21
|
self._replay_mode = True
|
|
23
22
|
self._replay_current_time_milliseconds: Optional[int] = None
|
|
24
|
-
self._decision_helper = DecisionsHelper()
|
|
25
23
|
self._decision_manager = decision_manager
|
|
26
24
|
|
|
27
25
|
def info(self) -> WorkflowInfo:
|
|
@@ -70,9 +68,7 @@ class Context(WorkflowContext):
|
|
|
70
68
|
)
|
|
71
69
|
|
|
72
70
|
activity_input = self.data_converter().to_data(list(args))
|
|
73
|
-
activity_id = self._decision_helper.generate_activity_id(activity)
|
|
74
71
|
schedule_attributes = ScheduleActivityTaskDecisionAttributes(
|
|
75
|
-
activity_id=activity_id,
|
|
76
72
|
activity_type=ActivityType(name=activity),
|
|
77
73
|
domain=self.info().workflow_domain,
|
|
78
74
|
task_list=TaskList(kind=TaskListKind.TASK_LIST_KIND_NORMAL, name=task_list),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import traceback
|
|
1
2
|
from asyncio import AbstractEventLoop, Handle, TimerHandle, futures, tasks, Future, Task
|
|
2
3
|
from contextvars import Context
|
|
3
4
|
import logging
|
|
@@ -9,6 +10,12 @@ from typing_extensions import Unpack, TypeVarTuple
|
|
|
9
10
|
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
13
|
+
|
|
14
|
+
class FatalDecisionError(Exception):
|
|
15
|
+
def __init__(self, *args) -> None:
|
|
16
|
+
super().__init__(*args)
|
|
17
|
+
|
|
18
|
+
|
|
12
19
|
_Ts = TypeVarTuple("_Ts")
|
|
13
20
|
_T = TypeVar("_T")
|
|
14
21
|
|
|
@@ -455,9 +462,36 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
455
462
|
)
|
|
456
463
|
|
|
457
464
|
def call_exception_handler(self, context: dict[str, Any]) -> None:
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
)
|
|
465
|
+
# This is called if a task has an unhandled exception. Short term, it's helpful to log these for debugging.
|
|
466
|
+
# Long term, we need some combination of failing decision tasks or workflows based on these errors.
|
|
467
|
+
message = context.get("message")
|
|
468
|
+
if not message:
|
|
469
|
+
message = "Unhandled exception in event loop"
|
|
470
|
+
|
|
471
|
+
exception = context.get("exception")
|
|
472
|
+
if isinstance(exception, BaseException):
|
|
473
|
+
exc_info = exception
|
|
474
|
+
else:
|
|
475
|
+
exc_info = None
|
|
476
|
+
|
|
477
|
+
log_lines = [message]
|
|
478
|
+
for key in sorted(context):
|
|
479
|
+
if key in {"message", "exception"}:
|
|
480
|
+
continue
|
|
481
|
+
value = context[key]
|
|
482
|
+
if key == "source_traceback":
|
|
483
|
+
tb = "".join(traceback.format_list(value))
|
|
484
|
+
value = "Object created at (most recent call last):\n"
|
|
485
|
+
value += tb.rstrip()
|
|
486
|
+
elif key == "handle_traceback":
|
|
487
|
+
tb = "".join(traceback.format_list(value))
|
|
488
|
+
value = "Handle created at (most recent call last):\n"
|
|
489
|
+
value += tb.rstrip()
|
|
490
|
+
else:
|
|
491
|
+
value = repr(value)
|
|
492
|
+
log_lines.append(f"{key}: {value}")
|
|
493
|
+
|
|
494
|
+
logger.error("\n".join(log_lines), exc_info=exc_info)
|
|
461
495
|
|
|
462
496
|
# Task factory
|
|
463
497
|
def set_task_factory( # type: ignore[override]
|
|
@@ -6,6 +6,9 @@ from cadence._internal.workflow.statemachine.decision_state_machine import (
|
|
|
6
6
|
BaseDecisionStateMachine,
|
|
7
7
|
)
|
|
8
8
|
from cadence._internal.workflow.statemachine.event_dispatcher import EventDispatcher
|
|
9
|
+
from cadence._internal.workflow.statemachine.nondeterminism import (
|
|
10
|
+
record_immediate_cancel,
|
|
11
|
+
)
|
|
9
12
|
from cadence.api.v1 import decision, history
|
|
10
13
|
from cadence.api.v1.common_pb2 import Payload
|
|
11
14
|
from cadence.error import ActivityFailure
|
|
@@ -30,12 +33,14 @@ class ActivityStateMachine(BaseDecisionStateMachine):
|
|
|
30
33
|
return DecisionId(DecisionType.ACTIVITY, self.request.activity_id)
|
|
31
34
|
|
|
32
35
|
def get_decision(self) -> decision.Decision | None:
|
|
33
|
-
if self.state is DecisionState.
|
|
36
|
+
if self.state is DecisionState.REQUESTED:
|
|
34
37
|
return decision.Decision(
|
|
35
38
|
schedule_activity_task_decision_attributes=self.request
|
|
36
39
|
)
|
|
40
|
+
if self.state is DecisionState.CANCELED_AFTER_REQUESTED:
|
|
41
|
+
return record_immediate_cancel(self.request)
|
|
37
42
|
|
|
38
|
-
if self.state is DecisionState.
|
|
43
|
+
if self.state is DecisionState.CANCELED_AFTER_RECORDED:
|
|
39
44
|
return decision.Decision(
|
|
40
45
|
request_cancel_activity_task_decision_attributes=decision.RequestCancelActivityTaskDecisionAttributes(
|
|
41
46
|
activity_id=self.request.activity_id,
|
|
@@ -45,20 +50,20 @@ class ActivityStateMachine(BaseDecisionStateMachine):
|
|
|
45
50
|
return None
|
|
46
51
|
|
|
47
52
|
def request_cancel(self) -> bool:
|
|
48
|
-
if self.state is DecisionState.
|
|
49
|
-
self._transition(DecisionState.
|
|
53
|
+
if self.state is DecisionState.REQUESTED:
|
|
54
|
+
self._transition(DecisionState.CANCELED_AFTER_REQUESTED)
|
|
50
55
|
self.completed.force_cancel()
|
|
51
56
|
return True
|
|
52
57
|
|
|
53
|
-
if self.state is DecisionState.
|
|
54
|
-
self._transition(DecisionState.
|
|
58
|
+
if self.state is DecisionState.RECORDED:
|
|
59
|
+
self._transition(DecisionState.CANCELED_AFTER_RECORDED)
|
|
55
60
|
return True
|
|
56
61
|
|
|
57
62
|
return False
|
|
58
63
|
|
|
59
64
|
@activity_events.event(id_attr="activity_id", event_id_is_alias=True)
|
|
60
65
|
def handle_scheduled(self, _: history.ActivityTaskScheduledEventAttributes) -> None:
|
|
61
|
-
self._transition(DecisionState.
|
|
66
|
+
self._transition(DecisionState.RECORDED)
|
|
62
67
|
|
|
63
68
|
@activity_events.event()
|
|
64
69
|
def handle_started(self, _: history.ActivityTaskStartedEventAttributes) -> None:
|
|
@@ -97,10 +102,10 @@ class ActivityStateMachine(BaseDecisionStateMachine):
|
|
|
97
102
|
def handle_cancel_requested(
|
|
98
103
|
self, _: history.ActivityTaskCancelRequestedEventAttributes
|
|
99
104
|
) -> None:
|
|
100
|
-
self._transition(DecisionState.
|
|
105
|
+
self._transition(DecisionState.CANCELLATION_RECORDED)
|
|
101
106
|
|
|
102
107
|
@activity_events.event("activity_id")
|
|
103
108
|
def handle_cancel_failed(
|
|
104
109
|
self, _: history.RequestCancelActivityTaskFailedEventAttributes
|
|
105
110
|
) -> None:
|
|
106
|
-
self._transition(DecisionState.
|
|
111
|
+
self._transition(DecisionState.RECORDED)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Dict, Any, Tuple
|
|
2
|
+
|
|
3
|
+
from cadence._internal.workflow.statemachine.decision_state_machine import (
|
|
4
|
+
DecisionId,
|
|
5
|
+
DecisionType,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from cadence.api.v1 import decision, history
|
|
9
|
+
from cadence.api.v1.common_pb2 import Payload
|
|
10
|
+
from msgspec import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
MARKER_PREFIX = "Cancel_"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_immediate_cancel(marker: history.MarkerRecordedEventAttributes) -> bool:
|
|
17
|
+
return marker.marker_name.startswith(MARKER_PREFIX)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_marker(
|
|
21
|
+
decision_id: DecisionId, props: Dict[str, Any]
|
|
22
|
+
) -> decision.RecordMarkerDecisionAttributes:
|
|
23
|
+
data = props | {"type": decision_id.decision_type.name}
|
|
24
|
+
return decision.RecordMarkerDecisionAttributes(
|
|
25
|
+
marker_name=MARKER_PREFIX + decision_id.id,
|
|
26
|
+
details=Payload(data=json.encode(data)),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def from_marker(
|
|
31
|
+
marker: history.MarkerRecordedEventAttributes,
|
|
32
|
+
) -> Tuple[DecisionId, Dict[str, Any]]:
|
|
33
|
+
decision_id = marker.marker_name.replace(MARKER_PREFIX, "")
|
|
34
|
+
props = json.decode(marker.details.data)
|
|
35
|
+
decision_type = DecisionType[props.pop("type")]
|
|
36
|
+
return DecisionId(decision_type, decision_id), props
|
cadence_python_client-0.2.0/cadence/_internal/workflow/statemachine/completion_state_machine.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from cadence._internal.workflow.statemachine.decision_state_machine import (
|
|
2
|
+
DecisionStateMachine,
|
|
3
|
+
DecisionId,
|
|
4
|
+
DecisionType,
|
|
5
|
+
)
|
|
6
|
+
from cadence.api.v1 import decision
|
|
7
|
+
|
|
8
|
+
COMPLETE = "complete"
|
|
9
|
+
|
|
10
|
+
COMPLETION_ID = DecisionId(DecisionType.WORKFLOW_COMPLETE, COMPLETE)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CompletionStateMachine(DecisionStateMachine):
|
|
14
|
+
def __init__(self, outcome: decision.Decision) -> None:
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.outcome = outcome
|
|
17
|
+
|
|
18
|
+
def get_id(self) -> DecisionId:
|
|
19
|
+
return COMPLETION_ID
|
|
20
|
+
|
|
21
|
+
def get_decision(self) -> decision.Decision | None:
|
|
22
|
+
return self.outcome
|
|
23
|
+
|
|
24
|
+
def request_cancel(self) -> bool:
|
|
25
|
+
return False
|