hatchet-sdk 1.3.2__py3-none-any.whl → 1.4.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.

@@ -42,7 +42,7 @@ class GetActionListenerRequest(BaseModel):
42
42
  worker_name: str
43
43
  services: list[str]
44
44
  actions: list[str]
45
- slots: int = 100
45
+ slots: int
46
46
  raw_labels: dict[str, str | int] = Field(default_factory=dict)
47
47
 
48
48
  labels: dict[str, WorkerLabels] = Field(default_factory=dict)
@@ -37,6 +37,7 @@ class Context:
37
37
  durable_event_listener: DurableEventListener | None,
38
38
  worker: WorkerContext,
39
39
  runs_client: RunsClient,
40
+ lifespan_context: Any | None,
40
41
  ):
41
42
  self.worker = worker
42
43
 
@@ -59,6 +60,8 @@ class Context:
59
60
 
60
61
  self.input = self.data.input
61
62
 
63
+ self._lifespan_context = lifespan_context
64
+
62
65
  def was_skipped(self, task: "Task[TWorkflowInput, R]") -> bool:
63
66
  return self.data.parents.get(task.name, {}).get("skipped", False)
64
67
 
@@ -116,6 +119,10 @@ class Context:
116
119
  def workflow_input(self) -> JSONSerializableMapping:
117
120
  return self.input
118
121
 
122
+ @property
123
+ def lifespan(self) -> Any:
124
+ return self._lifespan_context
125
+
119
126
  @property
120
127
  def workflow_run_id(self) -> str:
121
128
  return self.action.workflow_run_id
hatchet_sdk/hatchet.py CHANGED
@@ -33,7 +33,7 @@ from hatchet_sdk.runnables.types import (
33
33
  )
34
34
  from hatchet_sdk.runnables.workflow import BaseWorkflow, Workflow
35
35
  from hatchet_sdk.utils.timedelta_to_expression import Duration
36
- from hatchet_sdk.worker.worker import Worker
36
+ from hatchet_sdk.worker.worker import LifespanFn, Worker
37
37
 
38
38
 
39
39
  class Hatchet:
@@ -134,8 +134,10 @@ class Hatchet:
134
134
  self,
135
135
  name: str,
136
136
  slots: int = 100,
137
+ durable_slots: int = 1_000,
137
138
  labels: dict[str, str | int] = {},
138
139
  workflows: list[BaseWorkflow[Any]] = [],
140
+ lifespan: LifespanFn | None = None,
139
141
  ) -> Worker:
140
142
  """
141
143
  Create a Hatchet worker on which to run workflows.
@@ -165,11 +167,13 @@ class Hatchet:
165
167
  return Worker(
166
168
  name=name,
167
169
  slots=slots,
170
+ durable_slots=durable_slots,
168
171
  labels=labels,
169
172
  config=self._client.config,
170
173
  debug=self._client.debug,
171
174
  owned_loop=loop is None,
172
175
  workflows=workflows,
176
+ lifespan=lifespan,
173
177
  )
174
178
 
175
179
  @overload
hatchet_sdk/waits.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from datetime import datetime
2
3
  from enum import Enum
3
4
  from typing import TYPE_CHECKING
4
5
  from uuid import uuid4
@@ -58,10 +59,13 @@ class Condition(ABC):
58
59
 
59
60
 
60
61
  class SleepCondition(Condition):
61
- def __init__(self, duration: Duration) -> None:
62
+ def __init__(
63
+ self, duration: Duration, readable_data_key: str | None = None
64
+ ) -> None:
62
65
  super().__init__(
63
66
  BaseCondition(
64
- readable_data_key=f"sleep:{timedelta_to_expr(duration)}",
67
+ readable_data_key=readable_data_key
68
+ or f"sleep:{timedelta_to_expr(duration)}",
65
69
  )
66
70
  )
67
71
 
@@ -75,10 +79,15 @@ class SleepCondition(Condition):
75
79
 
76
80
 
77
81
  class UserEventCondition(Condition):
78
- def __init__(self, event_key: str, expression: str | None = None) -> None:
82
+ def __init__(
83
+ self,
84
+ event_key: str,
85
+ expression: str | None = None,
86
+ readable_data_key: str | None = None,
87
+ ) -> None:
79
88
  super().__init__(
80
89
  BaseCondition(
81
- readable_data_key=event_key,
90
+ readable_data_key=readable_data_key or event_key,
82
91
  expression=expression,
83
92
  )
84
93
  )
@@ -95,10 +104,22 @@ class UserEventCondition(Condition):
95
104
 
96
105
  class ParentCondition(Condition):
97
106
  def __init__(
98
- self, parent: "Task[TWorkflowInput, R]", expression: str | None = None
107
+ self,
108
+ parent: "Task[TWorkflowInput, R]",
109
+ expression: str | None = None,
110
+ readable_data_key: str | None = None,
99
111
  ) -> None:
100
112
  super().__init__(
101
- BaseCondition(readable_data_key=parent.name, expression=expression)
113
+ BaseCondition(
114
+ readable_data_key=readable_data_key
115
+ or (
116
+ parent.name
117
+ + (f":{expression}" if expression else "")
118
+ + ":"
119
+ + datetime.now().isoformat()
120
+ ),
121
+ expression=expression,
122
+ )
102
123
  )
103
124
 
104
125
  self.parent = parent
@@ -23,7 +23,7 @@ class WorkerActionRunLoopManager:
23
23
  self,
24
24
  name: str,
25
25
  action_registry: dict[str, Task[Any, Any]],
26
- slots: int | None,
26
+ slots: int,
27
27
  config: ClientConfig,
28
28
  action_queue: "Queue[Action | STOP_LOOP_TYPE]",
29
29
  event_queue: "Queue[ActionEvent]",
@@ -31,6 +31,7 @@ class WorkerActionRunLoopManager:
31
31
  handle_kill: bool = True,
32
32
  debug: bool = False,
33
33
  labels: dict[str, str | int] = {},
34
+ lifespan_context: Any | None = None,
34
35
  ) -> None:
35
36
  self.name = name
36
37
  self.action_registry = action_registry
@@ -42,6 +43,7 @@ class WorkerActionRunLoopManager:
42
43
  self.handle_kill = handle_kill
43
44
  self.debug = debug
44
45
  self.labels = labels
46
+ self.lifespan_context = lifespan_context
45
47
 
46
48
  if self.debug:
47
49
  logger.setLevel(logging.DEBUG)
@@ -86,6 +88,7 @@ class WorkerActionRunLoopManager:
86
88
  self.handle_kill,
87
89
  self.action_registry,
88
90
  self.labels,
91
+ self.lifespan_context,
89
92
  )
90
93
 
91
94
  logger.debug(f"'{self.name}' waiting for {list(self.action_registry.keys())}")
@@ -59,10 +59,11 @@ class Runner:
59
59
  self,
60
60
  event_queue: "Queue[ActionEvent]",
61
61
  config: ClientConfig,
62
- slots: int | None = None,
62
+ slots: int,
63
63
  handle_kill: bool = True,
64
64
  action_registry: dict[str, Task[TWorkflowInput, R]] = {},
65
65
  labels: dict[str, str | int] = {},
66
+ lifespan_context: Any | None = None,
66
67
  ):
67
68
  # We store the config so we can dynamically create clients for the dispatcher client.
68
69
  self.config = config
@@ -102,6 +103,8 @@ class Runner:
102
103
  labels=labels, client=Client(config=config).dispatcher
103
104
  )
104
105
 
106
+ self.lifespan_context = lifespan_context
107
+
105
108
  def create_workflow_run_url(self, action: Action) -> str:
106
109
  return f"{self.config.server_url}/workflow-runs/{action.workflow_run_id}?tenant={action.tenant_id}"
107
110
 
@@ -303,6 +306,7 @@ class Runner:
303
306
  durable_event_listener=self.durable_event_listener,
304
307
  worker=self.worker_context,
305
308
  runs_client=self.runs_client,
309
+ lifespan_context=self.lifespan_context,
306
310
  )
307
311
 
308
312
  ## IMPORTANT: Keep this method's signature in sync with the wrapper in the OTel instrumentor
@@ -5,12 +5,13 @@ import os
5
5
  import re
6
6
  import signal
7
7
  import sys
8
+ from contextlib import AsyncExitStack, asynccontextmanager
8
9
  from dataclasses import dataclass, field
9
10
  from enum import Enum
10
11
  from multiprocessing import Queue
11
12
  from multiprocessing.process import BaseProcess
12
13
  from types import FrameType
13
- from typing import Any, TypeVar
14
+ from typing import Any, AsyncGenerator, Callable, TypeVar
14
15
  from warnings import warn
15
16
 
16
17
  from aiohttp import web
@@ -63,21 +64,41 @@ class HealthCheckResponse(BaseModel):
63
64
  python_version: str
64
65
 
65
66
 
67
+ LifespanGenerator = AsyncGenerator[Any, Any]
68
+ LifespanFn = Callable[[], LifespanGenerator]
69
+
70
+
71
+ @asynccontextmanager
72
+ async def _create_async_context_manager(
73
+ gen: LifespanGenerator,
74
+ ) -> AsyncGenerator[None, None]:
75
+ try:
76
+ yield
77
+ finally:
78
+ try:
79
+ await anext(gen)
80
+ except StopAsyncIteration:
81
+ pass
82
+
83
+
66
84
  class Worker:
67
85
  def __init__(
68
86
  self,
69
87
  name: str,
70
88
  config: ClientConfig,
71
- slots: int | None = None,
89
+ slots: int,
90
+ durable_slots: int,
72
91
  labels: dict[str, str | int] = {},
73
92
  debug: bool = False,
74
93
  owned_loop: bool = True,
75
94
  handle_kill: bool = True,
76
95
  workflows: list[BaseWorkflow[Any]] = [],
96
+ lifespan: LifespanFn | None = None,
77
97
  ) -> None:
78
98
  self.config = config
79
99
  self.name = self.config.namespace + name
80
100
  self.slots = slots
101
+ self.durable_slots = durable_slots
81
102
  self.debug = debug
82
103
  self.labels = labels
83
104
  self.handle_kill = handle_kill
@@ -119,6 +140,9 @@ class Worker:
119
140
  self.has_any_durable = False
120
141
  self.has_any_non_durable = False
121
142
 
143
+ self.lifespan = lifespan
144
+ self.lifespan_stack: AsyncExitStack | None = None
145
+
122
146
  self.register_workflows(workflows)
123
147
 
124
148
  def register_workflow_from_opts(self, opts: CreateWorkflowVersionRequest) -> None:
@@ -130,6 +154,11 @@ class Worker:
130
154
  sys.exit(1)
131
155
 
132
156
  def register_workflow(self, workflow: BaseWorkflow[Any]) -> None:
157
+ if not workflow.tasks:
158
+ raise ValueError(
159
+ "workflow must have at least one task registered before registering"
160
+ )
161
+
133
162
  opts = workflow.to_proto()
134
163
  name = workflow.name
135
164
 
@@ -213,6 +242,11 @@ class Worker:
213
242
  logger.info(f"healthcheck server running on port {port}")
214
243
 
215
244
  def start(self, options: WorkerStartOptions = WorkerStartOptions()) -> None:
245
+ if not (self.action_registry or self.durable_action_registry):
246
+ raise ValueError(
247
+ "no actions registered, register workflows before starting worker"
248
+ )
249
+
216
250
  if options.loop is not None:
217
251
  warn(
218
252
  "Passing a custom event loop is deprecated and will be removed in the future. This option no longer has any effect",
@@ -252,15 +286,23 @@ class Worker:
252
286
  if self.config.healthcheck.enabled:
253
287
  await self._start_health_server()
254
288
 
289
+ lifespan_context = None
290
+ if self.lifespan:
291
+ lifespan_context = await self._setup_lifespan()
292
+
255
293
  if self.has_any_non_durable:
256
294
  self.action_listener_process = self._start_action_listener(is_durable=False)
257
- self.action_runner = self._run_action_runner(is_durable=False)
295
+ self.action_runner = self._run_action_runner(
296
+ is_durable=False, lifespan_context=lifespan_context
297
+ )
258
298
 
259
299
  if self.has_any_durable:
260
300
  self.durable_action_listener_process = self._start_action_listener(
261
301
  is_durable=True
262
302
  )
263
- self.durable_action_runner = self._run_action_runner(is_durable=True)
303
+ self.durable_action_runner = self._run_action_runner(
304
+ is_durable=True, lifespan_context=lifespan_context
305
+ )
264
306
 
265
307
  if self.loop:
266
308
  self.action_listener_health_check = self.loop.create_task(
@@ -269,13 +311,15 @@ class Worker:
269
311
 
270
312
  await self.action_listener_health_check
271
313
 
272
- def _run_action_runner(self, is_durable: bool) -> WorkerActionRunLoopManager:
314
+ def _run_action_runner(
315
+ self, is_durable: bool, lifespan_context: Any | None
316
+ ) -> WorkerActionRunLoopManager:
273
317
  # Retrieve the shared queue
274
318
  if self.loop:
275
319
  return WorkerActionRunLoopManager(
276
320
  self.name + ("_durable" if is_durable else ""),
277
321
  self.durable_action_registry if is_durable else self.action_registry,
278
- 1_000 if is_durable else self.slots,
322
+ self.durable_slots if is_durable else self.slots,
279
323
  self.config,
280
324
  self.durable_action_queue if is_durable else self.action_queue,
281
325
  self.durable_event_queue if is_durable else self.event_queue,
@@ -283,10 +327,31 @@ class Worker:
283
327
  self.handle_kill,
284
328
  self.client.debug,
285
329
  self.labels,
330
+ lifespan_context,
286
331
  )
287
332
 
288
333
  raise RuntimeError("event loop not set, cannot start action runner")
289
334
 
335
+ async def _setup_lifespan(self) -> Any:
336
+ if self.lifespan is None:
337
+ return None
338
+
339
+ self.lifespan_stack = AsyncExitStack()
340
+
341
+ lifespan_gen = self.lifespan()
342
+ try:
343
+ context = await anext(lifespan_gen)
344
+ await self.lifespan_stack.enter_async_context(
345
+ _create_async_context_manager(lifespan_gen)
346
+ )
347
+ return context
348
+ except StopAsyncIteration:
349
+ return None
350
+
351
+ async def _cleanup_lifespan(self) -> None:
352
+ if self.lifespan_stack is not None:
353
+ await self.lifespan_stack.aclose()
354
+
290
355
  def _start_action_listener(
291
356
  self, is_durable: bool
292
357
  ) -> multiprocessing.context.SpawnProcess:
@@ -300,7 +365,7 @@ class Worker:
300
365
  if is_durable
301
366
  else list(self.action_registry.keys())
302
367
  ),
303
- 1_000 if is_durable else self.slots,
368
+ self.durable_slots if is_durable else self.slots,
304
369
  self.config,
305
370
  self.durable_action_queue if is_durable else self.action_queue,
306
371
  self.durable_event_queue if is_durable else self.event_queue,
@@ -394,6 +459,8 @@ class Worker:
394
459
  ):
395
460
  self.durable_action_listener_process.kill()
396
461
 
462
+ await self._cleanup_lifespan()
463
+
397
464
  await self._close()
398
465
  if self.loop and self.owned_loop:
399
466
  self.loop.stop()
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hatchet-sdk
3
- Version: 1.3.2
3
+ Version: 1.4.1
4
4
  Summary:
5
+ License: MIT
5
6
  Author: Alexander Belanger
6
7
  Author-email: alexander@hatchet.run
7
8
  Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
8
10
  Classifier: Programming Language :: Python :: 3
9
11
  Classifier: Programming Language :: Python :: 3.10
10
12
  Classifier: Programming Language :: Python :: 3.11
@@ -1,7 +1,7 @@
1
1
  hatchet_sdk/__init__.py,sha256=LUj6VyGVSHCYTQTaoyiVhjyJLOfv6gMCmb-s4hRyISM,10031
2
2
  hatchet_sdk/client.py,sha256=tbOeMuaJmgpyYSQg8QUz_J4AdqRNvV9E0aEZpgsiZTE,2207
3
3
  hatchet_sdk/clients/admin.py,sha256=qY9-nB8o-jCpQQF65nooemH8HOioXjntp9ethpGke9o,17266
4
- hatchet_sdk/clients/dispatcher/action_listener.py,sha256=sAboL2Dr59MAPBR3KcJ7sSBfOwnTR3rdsRTJ0bDRuuc,16279
4
+ hatchet_sdk/clients/dispatcher/action_listener.py,sha256=yJhHqnp_RAhUfOJUpBCLIpqMyXaLX-CSUsm0TgPxGEs,16273
5
5
  hatchet_sdk/clients/dispatcher/dispatcher.py,sha256=IL-hDXG8Lzas9FieVuNr47E_3Gvpc-aL4Xu_l385Vp8,8140
6
6
  hatchet_sdk/clients/event_ts.py,sha256=OjYc_y1K3fNNZ7QLRJYBDnfgsyUVERkxofYldO4QVVs,1672
7
7
  hatchet_sdk/clients/events.py,sha256=sKqzGwkrW_AKWW0Q_rxfwo9jXV5EUpLx7fTQvCNHaVo,5443
@@ -221,7 +221,7 @@ hatchet_sdk/clients/v1/api_client.py,sha256=mJQUZ3cOxlFJiwWKK5F8jBxcpNZ7A2292Huc
221
221
  hatchet_sdk/config.py,sha256=jJA76BOvVdfOQHy6TKclAvr2qyblcM-Pz5J-hVAdpQ4,3588
222
222
  hatchet_sdk/connection.py,sha256=B5gT5NL9BBB5-l9U_cN6pMlraQk880rEYMnqaK_dgL0,2590
223
223
  hatchet_sdk/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
- hatchet_sdk/context/context.py,sha256=povQ0iO-QOi_lzntSWUiZPGbtpNTq-1m80b3zjIX74A,9163
224
+ hatchet_sdk/context/context.py,sha256=DWaHE3AuXFvRKPxYaKW5-CUY8R9lz-lF4t3sgucwZPs,9336
225
225
  hatchet_sdk/context/worker_context.py,sha256=OVcEWvdT_Kpd0nlg61VAPUgIPSFzSLs0aSrXWj-1GX4,974
226
226
  hatchet_sdk/contracts/dispatcher_pb2.py,sha256=SN4CIKeQwYkrbfRGhdhZo2uBn4nGzjUWIC1vvXdDeOQ,14503
227
227
  hatchet_sdk/contracts/dispatcher_pb2.pyi,sha256=ZSGio5eYxkw-QuQx2C5ASTNcKzeMQn5JTnWaRiThafM,18455
@@ -250,7 +250,7 @@ hatchet_sdk/features/runs.py,sha256=2069-4YUaouYloFUxa3-WmtkWhCjEIGMeGrN_nlnYCA,
250
250
  hatchet_sdk/features/scheduled.py,sha256=1_GMP7jSC9UCFFWLVES5HtTGQInQd9cJeqJDhq0Hnmg,8813
251
251
  hatchet_sdk/features/workers.py,sha256=fG8zoRXMm-lNNdNfeovKKpmJ0RzjZd6CIq_dVsu-ih0,1515
252
252
  hatchet_sdk/features/workflows.py,sha256=x1Z_HZM_ZQZbWZhOfzJaEiPfRNSn6YX3IaTmFZ5D7MY,2069
253
- hatchet_sdk/hatchet.py,sha256=7wDYLGBlPdQ0IvnJUdAe8Ms2B9IZWpedRED4AflZCW0,22982
253
+ hatchet_sdk/hatchet.py,sha256=oHE27SRgYddR1KiJyQwGAa0eV_uEH73w4_jbXxFIUqU,23146
254
254
  hatchet_sdk/labels.py,sha256=nATgxWE3lFxRTnfISEpoIRLGbMfAZsHF4lZTuG4Mfic,182
255
255
  hatchet_sdk/logger.py,sha256=5uOr52T4mImSQm1QvWT8HvZFK5WfPNh3Y1cBQZRFgUQ,333
256
256
  hatchet_sdk/metadata.py,sha256=XkRbhnghJJGCdVvF-uzyGBcNaTqpeQ3uiQvNNP1wyBc,107
@@ -498,15 +498,15 @@ hatchet_sdk/v0/worker/runner/utils/error_with_traceback.py,sha256=Iih_s8JNqrinXE
498
498
  hatchet_sdk/v0/worker/worker.py,sha256=0yU0z-0si7NzG0U9et9J0tiwfVBSHl4QSiOW-WNmTQM,13027
499
499
  hatchet_sdk/v0/workflow.py,sha256=d4o425efk7J3JgLIge34MW_A3pzwnwSRtwEOgIqM2pc,9387
500
500
  hatchet_sdk/v0/workflow_run.py,sha256=jsEZprXshrSV7i_TtL5uoCL03D18zQ3NeJCq7mp97Dg,1752
501
- hatchet_sdk/waits.py,sha256=dTrelK8Lk0W5anq3TlpJwAK5z8TJ0FW7XgoSg8bf8fA,3351
501
+ hatchet_sdk/waits.py,sha256=L2xZUcmrQX-pTVXWv1W8suMoYU_eA0uowpollauQmOM,3893
502
502
  hatchet_sdk/worker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
503
503
  hatchet_sdk/worker/action_listener_process.py,sha256=KxS7-wBpfKnsq0LNSvk-MG442Lh60iQMy3VpD1FW3mU,11703
504
- hatchet_sdk/worker/runner/run_loop_manager.py,sha256=QrhhAMnQmyThDMgohZwFw-0AVPHvhT4seK2FVCzvC7c,3690
505
- hatchet_sdk/worker/runner/runner.py,sha256=t8U4QK2GYNjgxGHFmD9RW12e3vungu_bvBSBBdIb_cs,17219
504
+ hatchet_sdk/worker/runner/run_loop_manager.py,sha256=RNWKDCjR57nJ0LCoLUMi0_3pnmpqyo80mz_RaxHYGIc,3812
505
+ hatchet_sdk/worker/runner/runner.py,sha256=J-gTpoYF9WphB1zZhpPJryCv10VjU-MiCdJgMYH-nds,17352
506
506
  hatchet_sdk/worker/runner/utils/capture_logs.py,sha256=nHRPSiDBqzhObM7i2X7t03OupVFnE7kQBdR2Ckgg-2w,2709
507
- hatchet_sdk/worker/worker.py,sha256=5DfmNNjGuYtn-n9Qce1B2XCLKjnIXzNwF8VLW-wtI5Y,14161
507
+ hatchet_sdk/worker/worker.py,sha256=m11u3QPyAqXJpF6Y3lTUEVqTKdIigGKTXTymNPqq6hw,16149
508
508
  hatchet_sdk/workflow_run.py,sha256=ZwH0HLFGFVXz6jbiqSv4w0Om2XuR52Tzzw6LH4y65jQ,2765
509
- hatchet_sdk-1.3.2.dist-info/METADATA,sha256=tWsWj0cUquv41Ih8O3mIM3Pwk6jl3mp0rIBWExejSI0,3571
510
- hatchet_sdk-1.3.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
511
- hatchet_sdk-1.3.2.dist-info/entry_points.txt,sha256=5mTp_AsCWK5raiVxP_MU9eBCgkRGl4OsN6chpHcvm7o,1235
512
- hatchet_sdk-1.3.2.dist-info/RECORD,,
509
+ hatchet_sdk-1.4.1.dist-info/METADATA,sha256=vACzxEl9nCc_VuqANxq7gsEgrDHrEdOT_2Xsa6YIha8,3635
510
+ hatchet_sdk-1.4.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
511
+ hatchet_sdk-1.4.1.dist-info/entry_points.txt,sha256=uYc-ivF7fUATjrWnwHP32ojgMCmogIs6yFOU8lEA4mk,1276
512
+ hatchet_sdk-1.4.1.dist-info/RECORD,,
@@ -14,6 +14,7 @@ events=examples.events.worker:main
14
14
  existing_loop=examples.worker_existing_loop.worker:main
15
15
  fanout=examples.fanout.worker:main
16
16
  fanout_sync=examples.fanout_sync.worker:main
17
+ lifespans=examples.lifespans.worker:main
17
18
  logger=examples.logger.worker:main
18
19
  manual_trigger=examples.manual_trigger.worker:main
19
20
  on_failure=examples.on_failure.worker:main