hatchet-sdk 1.0.0__py3-none-any.whl → 1.0.1__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.
Potentially problematic release.
This version of hatchet-sdk might be problematic. Click here for more details.
- hatchet_sdk/__init__.py +32 -16
- hatchet_sdk/client.py +25 -63
- hatchet_sdk/clients/admin.py +203 -142
- hatchet_sdk/clients/dispatcher/action_listener.py +42 -42
- hatchet_sdk/clients/dispatcher/dispatcher.py +18 -16
- hatchet_sdk/clients/durable_event_listener.py +327 -0
- hatchet_sdk/clients/rest/__init__.py +12 -1
- hatchet_sdk/clients/rest/api/log_api.py +258 -0
- hatchet_sdk/clients/rest/api/task_api.py +32 -6
- hatchet_sdk/clients/rest/api/workflow_runs_api.py +626 -0
- hatchet_sdk/clients/rest/models/__init__.py +12 -1
- hatchet_sdk/clients/rest/models/v1_log_line.py +94 -0
- hatchet_sdk/clients/rest/models/v1_log_line_level.py +39 -0
- hatchet_sdk/clients/rest/models/v1_log_line_list.py +110 -0
- hatchet_sdk/clients/rest/models/v1_task_summary.py +80 -64
- hatchet_sdk/clients/rest/models/v1_trigger_workflow_run_request.py +95 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run_display_name.py +98 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run_display_name_list.py +114 -0
- hatchet_sdk/clients/rest/models/workflow_run_shape_item_for_workflow_run_details.py +9 -4
- hatchet_sdk/clients/rest/models/workflow_runs_metrics.py +5 -1
- hatchet_sdk/clients/run_event_listener.py +0 -1
- hatchet_sdk/clients/v1/api_client.py +81 -0
- hatchet_sdk/context/context.py +86 -159
- hatchet_sdk/contracts/dispatcher_pb2_grpc.py +1 -1
- hatchet_sdk/contracts/events_pb2.py +2 -2
- hatchet_sdk/contracts/events_pb2_grpc.py +1 -1
- hatchet_sdk/contracts/v1/dispatcher_pb2.py +36 -0
- hatchet_sdk/contracts/v1/dispatcher_pb2.pyi +38 -0
- hatchet_sdk/contracts/v1/dispatcher_pb2_grpc.py +145 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2.py +39 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2.pyi +72 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2_grpc.py +29 -0
- hatchet_sdk/contracts/v1/workflows_pb2.py +67 -0
- hatchet_sdk/contracts/v1/workflows_pb2.pyi +228 -0
- hatchet_sdk/contracts/v1/workflows_pb2_grpc.py +234 -0
- hatchet_sdk/contracts/workflows_pb2_grpc.py +1 -1
- hatchet_sdk/features/cron.py +91 -121
- hatchet_sdk/features/logs.py +16 -0
- hatchet_sdk/features/metrics.py +75 -0
- hatchet_sdk/features/rate_limits.py +45 -0
- hatchet_sdk/features/runs.py +221 -0
- hatchet_sdk/features/scheduled.py +114 -131
- hatchet_sdk/features/workers.py +41 -0
- hatchet_sdk/features/workflows.py +55 -0
- hatchet_sdk/hatchet.py +463 -165
- hatchet_sdk/opentelemetry/instrumentor.py +8 -13
- hatchet_sdk/rate_limit.py +33 -39
- hatchet_sdk/runnables/contextvars.py +12 -0
- hatchet_sdk/runnables/standalone.py +192 -0
- hatchet_sdk/runnables/task.py +144 -0
- hatchet_sdk/runnables/types.py +138 -0
- hatchet_sdk/runnables/workflow.py +771 -0
- hatchet_sdk/utils/aio_utils.py +0 -79
- hatchet_sdk/utils/proto_enums.py +0 -7
- hatchet_sdk/utils/timedelta_to_expression.py +23 -0
- hatchet_sdk/utils/typing.py +2 -2
- hatchet_sdk/v0/clients/rest_client.py +9 -0
- hatchet_sdk/v0/worker/action_listener_process.py +18 -2
- hatchet_sdk/waits.py +120 -0
- hatchet_sdk/worker/action_listener_process.py +64 -30
- hatchet_sdk/worker/runner/run_loop_manager.py +35 -26
- hatchet_sdk/worker/runner/runner.py +72 -55
- hatchet_sdk/worker/runner/utils/capture_logs.py +3 -11
- hatchet_sdk/worker/worker.py +155 -118
- hatchet_sdk/workflow_run.py +4 -5
- {hatchet_sdk-1.0.0.dist-info → hatchet_sdk-1.0.1.dist-info}/METADATA +1 -2
- {hatchet_sdk-1.0.0.dist-info → hatchet_sdk-1.0.1.dist-info}/RECORD +69 -43
- {hatchet_sdk-1.0.0.dist-info → hatchet_sdk-1.0.1.dist-info}/entry_points.txt +2 -0
- hatchet_sdk/clients/rest_client.py +0 -636
- hatchet_sdk/semver.py +0 -30
- hatchet_sdk/worker/runner/utils/error_with_traceback.py +0 -6
- hatchet_sdk/workflow.py +0 -527
- {hatchet_sdk-1.0.0.dist-info → hatchet_sdk-1.0.1.dist-info}/WHEEL +0 -0
|
@@ -8,18 +8,19 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from multiprocessing import Queue
|
|
10
10
|
from threading import Thread, current_thread
|
|
11
|
-
from typing import Any, Callable, Dict,
|
|
11
|
+
from typing import Any, Callable, Dict, Literal, cast, overload
|
|
12
12
|
|
|
13
13
|
from pydantic import BaseModel
|
|
14
14
|
|
|
15
|
-
from hatchet_sdk.client import
|
|
15
|
+
from hatchet_sdk.client import Client
|
|
16
16
|
from hatchet_sdk.clients.admin import AdminClient
|
|
17
17
|
from hatchet_sdk.clients.dispatcher.action_listener import Action, ActionType
|
|
18
18
|
from hatchet_sdk.clients.dispatcher.dispatcher import DispatcherClient
|
|
19
|
+
from hatchet_sdk.clients.durable_event_listener import DurableEventListener
|
|
19
20
|
from hatchet_sdk.clients.run_event_listener import RunEventListenerClient
|
|
20
21
|
from hatchet_sdk.clients.workflow_listener import PooledWorkflowRunListener
|
|
21
22
|
from hatchet_sdk.config import ClientConfig
|
|
22
|
-
from hatchet_sdk.context.context import Context
|
|
23
|
+
from hatchet_sdk.context.context import Context, DurableContext
|
|
23
24
|
from hatchet_sdk.context.worker_context import WorkerContext
|
|
24
25
|
from hatchet_sdk.contracts.dispatcher_pb2 import (
|
|
25
26
|
GROUP_KEY_EVENT_TYPE_COMPLETED,
|
|
@@ -30,12 +31,18 @@ from hatchet_sdk.contracts.dispatcher_pb2 import (
|
|
|
30
31
|
STEP_EVENT_TYPE_STARTED,
|
|
31
32
|
)
|
|
32
33
|
from hatchet_sdk.logger import logger
|
|
34
|
+
from hatchet_sdk.runnables.contextvars import (
|
|
35
|
+
ctx_step_run_id,
|
|
36
|
+
ctx_worker_id,
|
|
37
|
+
ctx_workflow_run_id,
|
|
38
|
+
spawn_index_lock,
|
|
39
|
+
workflow_spawn_indices,
|
|
40
|
+
)
|
|
41
|
+
from hatchet_sdk.runnables.task import Task
|
|
42
|
+
from hatchet_sdk.runnables.types import R, TWorkflowInput
|
|
33
43
|
from hatchet_sdk.utils.typing import WorkflowValidator
|
|
34
44
|
from hatchet_sdk.worker.action_listener_process import ActionEvent
|
|
35
|
-
from hatchet_sdk.worker.runner.utils.capture_logs import copy_context_vars
|
|
36
|
-
from hatchet_sdk.workflow import Step
|
|
37
|
-
|
|
38
|
-
T = TypeVar("T")
|
|
45
|
+
from hatchet_sdk.worker.runner.utils.capture_logs import copy_context_vars
|
|
39
46
|
|
|
40
47
|
|
|
41
48
|
class WorkerStatus(Enum):
|
|
@@ -48,29 +55,27 @@ class WorkerStatus(Enum):
|
|
|
48
55
|
class Runner:
|
|
49
56
|
def __init__(
|
|
50
57
|
self,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
event_queue: "Queue[ActionEvent]",
|
|
59
|
+
config: ClientConfig,
|
|
60
|
+
slots: int | None = None,
|
|
54
61
|
handle_kill: bool = True,
|
|
55
|
-
action_registry: dict[str,
|
|
62
|
+
action_registry: dict[str, Task[TWorkflowInput, R]] = {},
|
|
56
63
|
validator_registry: dict[str, WorkflowValidator] = {},
|
|
57
|
-
config: ClientConfig = ClientConfig(),
|
|
58
64
|
labels: dict[str, str | int] = {},
|
|
59
65
|
):
|
|
60
66
|
# We store the config so we can dynamically create clients for the dispatcher client.
|
|
61
67
|
self.config = config
|
|
62
|
-
self.client =
|
|
63
|
-
self.
|
|
64
|
-
self.max_runs = max_runs
|
|
68
|
+
self.client = Client(config)
|
|
69
|
+
self.slots = slots
|
|
65
70
|
self.tasks: dict[str, asyncio.Task[Any]] = {} # Store run ids and futures
|
|
66
71
|
self.contexts: dict[str, Context] = {} # Store run ids and contexts
|
|
67
|
-
self.action_registry
|
|
72
|
+
self.action_registry = action_registry
|
|
68
73
|
self.validator_registry = validator_registry
|
|
69
74
|
|
|
70
75
|
self.event_queue = event_queue
|
|
71
76
|
|
|
72
77
|
# The thread pool is used for synchronous functions which need to run concurrently
|
|
73
|
-
self.thread_pool = ThreadPoolExecutor(max_workers=
|
|
78
|
+
self.thread_pool = ThreadPoolExecutor(max_workers=slots)
|
|
74
79
|
self.threads: Dict[str, Thread] = {} # Store run ids and threads
|
|
75
80
|
|
|
76
81
|
self.killing = False
|
|
@@ -82,9 +87,10 @@ class Runner:
|
|
|
82
87
|
self.admin_client = AdminClient(self.config)
|
|
83
88
|
self.workflow_run_event_listener = RunEventListenerClient(self.config)
|
|
84
89
|
self.client.workflow_listener = PooledWorkflowRunListener(self.config)
|
|
90
|
+
self.durable_event_listener = DurableEventListener(self.config)
|
|
85
91
|
|
|
86
92
|
self.worker_context = WorkerContext(
|
|
87
|
-
labels=labels, client=
|
|
93
|
+
labels=labels, client=Client(config=config).dispatcher
|
|
88
94
|
)
|
|
89
95
|
|
|
90
96
|
def create_workflow_run_url(self, action: Action) -> str:
|
|
@@ -130,7 +136,7 @@ class Runner:
|
|
|
130
136
|
ActionEvent(
|
|
131
137
|
action=action,
|
|
132
138
|
type=STEP_EVENT_TYPE_FAILED,
|
|
133
|
-
payload=str(
|
|
139
|
+
payload=str(pretty_format_exception(f"{e}", e)),
|
|
134
140
|
)
|
|
135
141
|
)
|
|
136
142
|
|
|
@@ -172,7 +178,7 @@ class Runner:
|
|
|
172
178
|
ActionEvent(
|
|
173
179
|
action=action,
|
|
174
180
|
type=GROUP_KEY_EVENT_TYPE_FAILED,
|
|
175
|
-
payload=str(
|
|
181
|
+
payload=str(pretty_format_exception(f"{e}", e)),
|
|
176
182
|
)
|
|
177
183
|
)
|
|
178
184
|
|
|
@@ -195,7 +201,9 @@ class Runner:
|
|
|
195
201
|
|
|
196
202
|
return inner_callback
|
|
197
203
|
|
|
198
|
-
def thread_action_func(
|
|
204
|
+
def thread_action_func(
|
|
205
|
+
self, ctx: Context, task: Task[TWorkflowInput, R], action: Action
|
|
206
|
+
) -> R:
|
|
199
207
|
if action.step_run_id is not None and action.step_run_id != "":
|
|
200
208
|
self.threads[action.step_run_id] = current_thread()
|
|
201
209
|
elif (
|
|
@@ -204,22 +212,23 @@ class Runner:
|
|
|
204
212
|
):
|
|
205
213
|
self.threads[action.get_group_key_run_id] = current_thread()
|
|
206
214
|
|
|
207
|
-
return
|
|
215
|
+
return task.call(ctx)
|
|
208
216
|
|
|
209
217
|
# We wrap all actions in an async func
|
|
210
218
|
async def async_wrapped_action_func(
|
|
211
219
|
self,
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
ctx: Context,
|
|
221
|
+
task: Task[TWorkflowInput, R],
|
|
214
222
|
action: Action,
|
|
215
223
|
run_id: str,
|
|
216
|
-
) ->
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
) -> R:
|
|
225
|
+
ctx_step_run_id.set(action.step_run_id)
|
|
226
|
+
ctx_workflow_run_id.set(action.workflow_run_id)
|
|
227
|
+
ctx_worker_id.set(action.worker_id)
|
|
219
228
|
|
|
220
229
|
try:
|
|
221
|
-
if
|
|
222
|
-
return await
|
|
230
|
+
if task.is_async_function:
|
|
231
|
+
return await task.aio_call(ctx)
|
|
223
232
|
else:
|
|
224
233
|
pfunc = functools.partial(
|
|
225
234
|
# we must copy the context vars to the new thread, as only asyncio natively supports
|
|
@@ -227,8 +236,8 @@ class Runner:
|
|
|
227
236
|
copy_context_vars,
|
|
228
237
|
contextvars.copy_context().items(),
|
|
229
238
|
self.thread_action_func,
|
|
230
|
-
|
|
231
|
-
|
|
239
|
+
ctx,
|
|
240
|
+
task,
|
|
232
241
|
action,
|
|
233
242
|
)
|
|
234
243
|
|
|
@@ -236,7 +245,7 @@ class Runner:
|
|
|
236
245
|
return await loop.run_in_executor(self.thread_pool, pfunc)
|
|
237
246
|
except Exception as e:
|
|
238
247
|
logger.error(
|
|
239
|
-
|
|
248
|
+
pretty_format_exception(
|
|
240
249
|
f"exception raised in action ({action.action_id}, retry={action.retry_count}):\n{e}",
|
|
241
250
|
e,
|
|
242
251
|
)
|
|
@@ -255,17 +264,28 @@ class Runner:
|
|
|
255
264
|
if run_id in self.contexts:
|
|
256
265
|
del self.contexts[run_id]
|
|
257
266
|
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
@overload
|
|
268
|
+
def create_context(
|
|
269
|
+
self, action: Action, is_durable: Literal[True] = True
|
|
270
|
+
) -> DurableContext: ...
|
|
271
|
+
|
|
272
|
+
@overload
|
|
273
|
+
def create_context(
|
|
274
|
+
self, action: Action, is_durable: Literal[False] = False
|
|
275
|
+
) -> Context: ...
|
|
276
|
+
|
|
277
|
+
def create_context(
|
|
278
|
+
self, action: Action, is_durable: bool = True
|
|
279
|
+
) -> Context | DurableContext:
|
|
280
|
+
constructor = DurableContext if is_durable else Context
|
|
281
|
+
|
|
282
|
+
return constructor(
|
|
260
283
|
action,
|
|
261
284
|
self.dispatcher_client,
|
|
262
285
|
self.admin_client,
|
|
263
286
|
self.client.event,
|
|
264
|
-
self.
|
|
265
|
-
self.client.workflow_listener,
|
|
266
|
-
self.workflow_run_event_listener,
|
|
287
|
+
self.durable_event_listener,
|
|
267
288
|
self.worker_context,
|
|
268
|
-
self.client.config.namespace,
|
|
269
289
|
validator_registry=self.validator_registry,
|
|
270
290
|
)
|
|
271
291
|
|
|
@@ -276,11 +296,12 @@ class Runner:
|
|
|
276
296
|
# Find the corresponding action function from the registry
|
|
277
297
|
action_func = self.action_registry.get(action_name)
|
|
278
298
|
|
|
279
|
-
context = self.create_context(action)
|
|
280
|
-
|
|
281
|
-
self.contexts[action.step_run_id] = context
|
|
282
|
-
|
|
283
299
|
if action_func:
|
|
300
|
+
context = self.create_context(
|
|
301
|
+
action, True if action_func.is_durable else False
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
self.contexts[action.step_run_id] = context
|
|
284
305
|
self.event_queue.put(
|
|
285
306
|
ActionEvent(action=action, type=STEP_EVENT_TYPE_STARTED, payload="")
|
|
286
307
|
)
|
|
@@ -301,20 +322,16 @@ class Runner:
|
|
|
301
322
|
# do nothing, this should be caught in the callback
|
|
302
323
|
pass
|
|
303
324
|
|
|
325
|
+
## Once the step run completes, we need to remove the workflow spawn index
|
|
326
|
+
## so we don't leak memory
|
|
327
|
+
if action.workflow_run_id in workflow_spawn_indices:
|
|
328
|
+
async with spawn_index_lock:
|
|
329
|
+
workflow_spawn_indices.pop(action.workflow_run_id)
|
|
330
|
+
|
|
304
331
|
## IMPORTANT: Keep this method's signature in sync with the wrapper in the OTel instrumentor
|
|
305
332
|
async def handle_start_group_key_run(self, action: Action) -> Exception | None:
|
|
306
333
|
action_name = action.action_id
|
|
307
|
-
context =
|
|
308
|
-
action,
|
|
309
|
-
self.dispatcher_client,
|
|
310
|
-
self.admin_client,
|
|
311
|
-
self.client.event,
|
|
312
|
-
self.client.rest,
|
|
313
|
-
self.client.workflow_listener,
|
|
314
|
-
self.workflow_run_event_listener,
|
|
315
|
-
self.worker_context,
|
|
316
|
-
self.client.config.namespace,
|
|
317
|
-
)
|
|
334
|
+
context = self.create_context(action)
|
|
318
335
|
|
|
319
336
|
self.contexts[action.get_group_key_run_id] = context
|
|
320
337
|
|
|
@@ -412,7 +429,7 @@ class Runner:
|
|
|
412
429
|
|
|
413
430
|
if output is not None:
|
|
414
431
|
try:
|
|
415
|
-
return json.dumps(output)
|
|
432
|
+
return json.dumps(output, default=str)
|
|
416
433
|
except Exception as e:
|
|
417
434
|
logger.error(f"Could not serialize output: {e}")
|
|
418
435
|
return str(output)
|
|
@@ -427,6 +444,6 @@ class Runner:
|
|
|
427
444
|
running = len(self.tasks.keys())
|
|
428
445
|
|
|
429
446
|
|
|
430
|
-
def
|
|
447
|
+
def pretty_format_exception(message: str, e: Exception) -> str:
|
|
431
448
|
trace = "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
432
449
|
return f"{message}\n{trace}"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import contextvars
|
|
2
1
|
import functools
|
|
3
2
|
import logging
|
|
4
3
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -8,14 +7,7 @@ from typing import Any, Awaitable, Callable, ItemsView, ParamSpec, TypeVar
|
|
|
8
7
|
|
|
9
8
|
from hatchet_sdk.clients.events import EventClient
|
|
10
9
|
from hatchet_sdk.logger import logger
|
|
11
|
-
|
|
12
|
-
wr: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
13
|
-
"workflow_run_id", default=None
|
|
14
|
-
)
|
|
15
|
-
sr: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
16
|
-
"step_run_id", default=None
|
|
17
|
-
)
|
|
18
|
-
|
|
10
|
+
from hatchet_sdk.runnables.contextvars import ctx_step_run_id, ctx_workflow_run_id
|
|
19
11
|
|
|
20
12
|
T = TypeVar("T")
|
|
21
13
|
P = ParamSpec("P")
|
|
@@ -37,8 +29,8 @@ class InjectingFilter(logging.Filter):
|
|
|
37
29
|
# otherwise we would use emit within the CustomLogHandler
|
|
38
30
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
39
31
|
## TODO: Change how we do this to not assign to the log record
|
|
40
|
-
record.workflow_run_id =
|
|
41
|
-
record.step_run_id =
|
|
32
|
+
record.workflow_run_id = ctx_workflow_run_id.get()
|
|
33
|
+
record.step_run_id = ctx_step_run_id.get()
|
|
42
34
|
return True
|
|
43
35
|
|
|
44
36
|
|