edda-framework 0.14.0__py3-none-any.whl → 0.15.0__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.
edda/locking.py CHANGED
@@ -192,7 +192,6 @@ async def _refresh_lock_periodically(
192
192
 
193
193
  async def cleanup_stale_locks_periodically(
194
194
  storage: StorageProtocol,
195
- worker_id: str,
196
195
  interval: int = 60,
197
196
  ) -> None:
198
197
  """
@@ -204,49 +203,37 @@ async def cleanup_stale_locks_periodically(
204
203
  Note: This function only cleans up locks without resuming workflows.
205
204
  For automatic workflow resumption, use auto_resume_stale_workflows_periodically().
206
205
 
207
- Uses system-level locking to ensure only one pod executes cleanup at a time.
206
+ Important: This function should only be run by a single worker (e.g., via leader
207
+ election). It does not perform its own distributed coordination.
208
208
 
209
209
  Example:
210
210
  >>> asyncio.create_task(
211
- ... cleanup_stale_locks_periodically(storage, worker_id, interval=60)
211
+ ... cleanup_stale_locks_periodically(storage, interval=60)
212
212
  ... )
213
213
 
214
214
  Args:
215
215
  storage: Storage backend
216
- worker_id: Unique identifier for this worker (for global lock coordination)
217
216
  interval: Cleanup interval in seconds (default: 60)
218
217
  """
219
218
  with suppress(asyncio.CancelledError):
220
219
  while True:
221
- # Add jitter to prevent thundering herd in multi-pod deployments
220
+ # Add jitter to prevent thundering herd
222
221
  jitter = random.uniform(0, interval * 0.3)
223
222
  await asyncio.sleep(interval + jitter)
224
223
 
225
- # Try to acquire global lock for this task
226
- lock_acquired = await storage.try_acquire_system_lock(
227
- lock_name="cleanup_stale_locks",
228
- worker_id=worker_id,
229
- timeout_seconds=interval,
230
- )
231
-
232
- if not lock_acquired:
233
- # Another pod is handling this task
234
- continue
235
-
236
224
  try:
237
225
  # Clean up stale locks
238
226
  workflows = await storage.cleanup_stale_locks()
239
227
 
240
228
  if len(workflows) > 0:
241
229
  logger.info("Cleaned up %d stale locks", len(workflows))
242
- finally:
243
- await storage.release_system_lock("cleanup_stale_locks", worker_id)
230
+ except Exception as e:
231
+ logger.error("Failed to cleanup stale locks: %s", e, exc_info=True)
244
232
 
245
233
 
246
234
  async def auto_resume_stale_workflows_periodically(
247
235
  storage: StorageProtocol,
248
236
  replay_engine: Any,
249
- worker_id: str,
250
237
  interval: int = 60,
251
238
  ) -> None:
252
239
  """
@@ -255,39 +242,27 @@ async def auto_resume_stale_workflows_periodically(
255
242
  This combines lock cleanup with automatic workflow resumption, ensuring
256
243
  that workflows interrupted by worker crashes are automatically recovered.
257
244
 
258
- Uses system-level locking to ensure only one pod executes this task at a time,
259
- preventing duplicate workflow execution (CRITICAL for safety).
245
+ Important: This function should only be run by a single worker (e.g., via leader
246
+ election). It does not perform its own distributed coordination.
260
247
 
261
248
  Example:
262
249
  >>> asyncio.create_task(
263
250
  ... auto_resume_stale_workflows_periodically(
264
- ... storage, replay_engine, worker_id, interval=60
251
+ ... storage, replay_engine, interval=60
265
252
  ... )
266
253
  ... )
267
254
 
268
255
  Args:
269
256
  storage: Storage backend
270
257
  replay_engine: ReplayEngine instance for resuming workflows
271
- worker_id: Unique identifier for this worker (for global lock coordination)
272
258
  interval: Cleanup interval in seconds (default: 60)
273
259
  """
274
260
  with suppress(asyncio.CancelledError):
275
261
  while True:
276
- # Add jitter to prevent thundering herd in multi-pod deployments
262
+ # Add jitter to prevent thundering herd
277
263
  jitter = random.uniform(0, interval * 0.3)
278
264
  await asyncio.sleep(interval + jitter)
279
265
 
280
- # Try to acquire global lock for this task
281
- lock_acquired = await storage.try_acquire_system_lock(
282
- lock_name="auto_resume_stale_workflows",
283
- worker_id=worker_id,
284
- timeout_seconds=interval,
285
- )
286
-
287
- if not lock_acquired:
288
- # Another pod is handling this task
289
- continue
290
-
291
266
  try:
292
267
  # Clean up stale locks and get workflows to resume
293
268
  workflows_to_resume = await storage.cleanup_stale_locks()
@@ -369,8 +344,8 @@ async def auto_resume_stale_workflows_periodically(
369
344
  e,
370
345
  exc_info=True,
371
346
  )
372
- finally:
373
- await storage.release_system_lock("auto_resume_stale_workflows", worker_id)
347
+ except Exception as e:
348
+ logger.error("Failed to cleanup stale locks: %s", e, exc_info=True)
374
349
 
375
350
 
376
351
  class LockNotAcquiredError(Exception):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: Lightweight Durable Execution Framework
5
5
  Project-URL: Homepage, https://github.com/i2y/edda
6
6
  Project-URL: Documentation, https://github.com/i2y/edda#readme
@@ -42,6 +42,10 @@ Requires-Dist: starlette>=0.40.0; extra == 'dev'
42
42
  Requires-Dist: testcontainers[mysql]>=4.0.0; extra == 'dev'
43
43
  Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
44
44
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
45
+ Provides-Extra: graph
46
+ Requires-Dist: pydantic-graph>=0.1.0; extra == 'graph'
47
+ Provides-Extra: llamaindex
48
+ Requires-Dist: llama-index-core>=0.12.0; extra == 'llamaindex'
45
49
  Provides-Extra: mcp
46
50
  Requires-Dist: mcp>=1.22.0; extra == 'mcp'
47
51
  Provides-Extra: mirascope
@@ -97,6 +101,8 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
97
101
  - ⚡ **Instant Notifications**: PostgreSQL LISTEN/NOTIFY for near-instant event delivery (optional)
98
102
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
99
103
  - 🧠 **Mirascope Integration**: Durable LLM calls
104
+ - 🦙 **LlamaIndex Integration**: Make LlamaIndex Workflows durable with crash recovery
105
+ - 📊 **pydantic-graph Integration**: Durable graph-based workflows (experimental)
100
106
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
101
107
 
102
108
  ## Use Cases
@@ -233,6 +239,12 @@ uv add edda-framework --extra viewer
233
239
  # With PostgreSQL instant notifications (LISTEN/NOTIFY)
234
240
  uv add edda-framework --extra postgres-notify
235
241
 
242
+ # With LlamaIndex Workflow integration
243
+ uv add edda-framework --extra llamaindex
244
+
245
+ # With pydantic-graph integration (experimental)
246
+ uv add edda-framework --extra graph
247
+
236
248
  # All extras (PostgreSQL, MySQL, Viewer UI)
237
249
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
238
250
  ```
@@ -1,18 +1,27 @@
1
1
  edda/__init__.py,sha256=hGC6WR2R36M8LWC97F-0Rw4Ln0QUUT_1xC-7acOy_Fk,2237
2
2
  edda/activity.py,sha256=nRm9eBrr0lFe4ZRQ2whyZ6mo5xd171ITIVhqytUhOpw,21025
3
- edda/app.py,sha256=ITTc7x5S4ykCP3KPZXKxuNczXkPtbn04ZQaxcem46Hw,68406
3
+ edda/app.py,sha256=gtBtNsWpib8BHwzC02MlP7LxUHNXqcZVwtFcUhTWGkk,67801
4
4
  edda/channels.py,sha256=6JFZkeOs0xDumexr0_bLI_Mb4S245hLJM_Sqp3xPCCA,37676
5
5
  edda/compensation.py,sha256=iKLlnTxiF1YSatmYQW84EkPB1yMKUEZBtgjuGnghLtY,11824
6
6
  edda/context.py,sha256=Qqm_nUC5NNnOfHAb7taqKqZVIc0GoRWUrjZ4L9_-q70,22128
7
7
  edda/exceptions.py,sha256=-ntBLGpVQgPFG5N1o8m_7weejAYkNrUdxTkOP38vsHk,1766
8
8
  edda/hooks.py,sha256=HUZ6FTM__DZjwuomDfTDEroQ3mugEPuJHcGm7CTQNvg,8193
9
- edda/locking.py,sha256=NAFJmw-JaSVsXn4Y4czJyv_s9bWG8cdrzDBWIEag5X8,13661
9
+ edda/locking.py,sha256=ZMdzGO4u3h8m3kDysmkDpAkJNvQQADbILPsmo52EQis,12716
10
10
  edda/pydantic_utils.py,sha256=dGVPNrrttDeq1k233PopCtjORYjZitsgASPfPnO6R10,9056
11
11
  edda/replay.py,sha256=IQGByw9mlTpRulyUgsHJSPsZUULmM2YqFcm2WeB4jtw,43227
12
12
  edda/retry.py,sha256=t4_E1skrhotA1XWHTLbKi-DOgCMasOUnhI9OT-O_eCE,6843
13
13
  edda/workflow.py,sha256=hfBZM0JrtK0IkvZSrva0VmYVyvKCdiJ5FWFmIVENfrM,8807
14
14
  edda/wsgi.py,sha256=1pGE5fhHpcsYnDR8S3NEFKWUs5P0JK4roTAzX9BsIj0,2391
15
15
  edda/integrations/__init__.py,sha256=F_CaTvlDEbldfOpPKq_U9ve1E573tS6XzqXnOtyHcXI,33
16
+ edda/integrations/graph/__init__.py,sha256=MwGgkTDsOH1eaLYzWFxBR2DHumiSAz-UUsqXSNU8aWw,1652
17
+ edda/integrations/graph/context.py,sha256=ZQaesBVDAmF01P1liX2BtxaprQDRUgLKL0e94P8Yrdc,2529
18
+ edda/integrations/graph/exceptions.py,sha256=FwDNUafYHzWlPeObLLTjyOVj8M94HgZ2a3DLR6VcOj4,286
19
+ edda/integrations/graph/graph.py,sha256=RY3BbCUO_rsqQ2wP7ghfstT59stC_KNyJ7LmvljJSFk,12957
20
+ edda/integrations/graph/nodes.py,sha256=JHoJYCEAGBKTYJ-458pMYvVxEu9hunAzzEuNVcEBbvM,4746
21
+ edda/integrations/llamaindex/__init__.py,sha256=YR1ikaf7AanaQztGZfHDZtoMqz3ieTGjBeIQ3sAkzP8,1604
22
+ edda/integrations/llamaindex/events.py,sha256=brs0UVu3kM5N9mWKo91FNBBh3FL9xI2WEzgHmp-j4a8,4751
23
+ edda/integrations/llamaindex/exceptions.py,sha256=41BefzhS1qd2L1U-JHBNIkTQS_Vz480OATqOtJNmDGU,376
24
+ edda/integrations/llamaindex/workflow.py,sha256=MXvPe8d7FLivCLoDlzQA-TgQi82nulUmW0omasq4QEk,10813
16
25
  edda/integrations/mcp/__init__.py,sha256=YK-8m0DIdP-RSqewlIX7xnWU7TD3NioCiW2_aZSgnn8,1232
17
26
  edda/integrations/mcp/decorators.py,sha256=31SmbDwmHEGvUNa3aaatW91hBkpnS5iN9uy47dID3J4,10037
18
27
  edda/integrations/mcp/server.py,sha256=Q5r4AbMn-9gBcy2CZocbgW7O0fn7Qb4e9CBJa1FEmzU,14507
@@ -47,8 +56,8 @@ edda/visualizer/mermaid_generator.py,sha256=XWa2egoOTNDfJEjPcwoxwQmblUqXf7YInWFj
47
56
  edda/migrations/mysql/20251217000000_initial_schema.sql,sha256=LpINasESRhadOeqABwDk4JZ0OZ4_zQw_opnhIR4Xe9U,12367
48
57
  edda/migrations/postgresql/20251217000000_initial_schema.sql,sha256=hCaGMWeptpzpnsjfNKVsMYuwPRe__fK9E0VZpClAumQ,11732
49
58
  edda/migrations/sqlite/20251217000000_initial_schema.sql,sha256=Wq9gCnQ0K9SOt0PY_8f1MG4va8rLVWIIcf2lnRzSK5g,11906
50
- edda_framework-0.14.0.dist-info/METADATA,sha256=FbHEPrrr0THCfzFccY0_NJyCPuGArsxK6AKVrmTUKjQ,37567
51
- edda_framework-0.14.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
52
- edda_framework-0.14.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
53
- edda_framework-0.14.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
54
- edda_framework-0.14.0.dist-info/RECORD,,
59
+ edda_framework-0.15.0.dist-info/METADATA,sha256=d7VXuP5MWTx0WvL4KzwmZVl0AUtTJzTxaLWL7Q0Ou8Q,38074
60
+ edda_framework-0.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
61
+ edda_framework-0.15.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
62
+ edda_framework-0.15.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
63
+ edda_framework-0.15.0.dist-info/RECORD,,