edda-framework 0.11.0__py3-none-any.whl → 0.13.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/app.py +203 -35
- edda/channels.py +57 -12
- edda/context.py +24 -0
- edda/migrations/mysql/20251217000000_initial_schema.sql +284 -0
- edda/migrations/postgresql/20251217000000_initial_schema.sql +284 -0
- edda/migrations/sqlite/20251217000000_initial_schema.sql +284 -0
- edda/outbox/relayer.py +34 -7
- edda/replay.py +41 -23
- edda/storage/migrations.py +435 -0
- edda/storage/models.py +2 -0
- edda/storage/pg_notify.py +5 -8
- edda/storage/sqlalchemy_storage.py +97 -61
- edda/viewer_ui/app.py +11 -4
- {edda_framework-0.11.0.dist-info → edda_framework-0.13.0.dist-info}/METADATA +43 -3
- {edda_framework-0.11.0.dist-info → edda_framework-0.13.0.dist-info}/RECORD +18 -14
- {edda_framework-0.11.0.dist-info → edda_framework-0.13.0.dist-info}/WHEEL +0 -0
- {edda_framework-0.11.0.dist-info → edda_framework-0.13.0.dist-info}/entry_points.txt +0 -0
- {edda_framework-0.11.0.dist-info → edda_framework-0.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -88,6 +88,7 @@ class WorkflowInstance(Base):
|
|
|
88
88
|
workflow_name: Mapped[str] = mapped_column(String(255))
|
|
89
89
|
source_hash: Mapped[str] = mapped_column(String(64))
|
|
90
90
|
owner_service: Mapped[str] = mapped_column(String(255))
|
|
91
|
+
framework: Mapped[str] = mapped_column(String(50), server_default=text("'python'"))
|
|
91
92
|
status: Mapped[str] = mapped_column(String(50), server_default=text("'running'"))
|
|
92
93
|
current_activity_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
93
94
|
continued_from: Mapped[str | None] = mapped_column(
|
|
@@ -121,6 +122,7 @@ class WorkflowInstance(Base):
|
|
|
121
122
|
Index("idx_instances_status", "status"),
|
|
122
123
|
Index("idx_instances_workflow", "workflow_name"),
|
|
123
124
|
Index("idx_instances_owner", "owner_service"),
|
|
125
|
+
Index("idx_instances_framework", "framework"),
|
|
124
126
|
Index("idx_instances_locked", "locked_by", "locked_at"),
|
|
125
127
|
Index("idx_instances_lock_expires", "lock_expires_at"),
|
|
126
128
|
Index("idx_instances_updated", "updated_at"),
|
|
@@ -516,6 +518,7 @@ class SQLAlchemyStorage:
|
|
|
516
518
|
self,
|
|
517
519
|
engine: AsyncEngine,
|
|
518
520
|
notify_listener: Any | None = None,
|
|
521
|
+
migrations_dir: str | None = None,
|
|
519
522
|
):
|
|
520
523
|
"""
|
|
521
524
|
Initialize SQLAlchemy storage.
|
|
@@ -525,9 +528,12 @@ class SQLAlchemyStorage:
|
|
|
525
528
|
notify_listener: Optional notify listener for PostgreSQL LISTEN/NOTIFY.
|
|
526
529
|
If provided and PostgreSQL is used, NOTIFY messages
|
|
527
530
|
will be sent after key operations.
|
|
531
|
+
migrations_dir: Optional path to migrations directory. If None,
|
|
532
|
+
auto-detects from package or schema/ submodule.
|
|
528
533
|
"""
|
|
529
534
|
self.engine = engine
|
|
530
535
|
self._notify_listener = notify_listener
|
|
536
|
+
self._migrations_dir = migrations_dir
|
|
531
537
|
|
|
532
538
|
@property
|
|
533
539
|
def _is_postgresql(self) -> bool:
|
|
@@ -551,20 +557,21 @@ class SQLAlchemyStorage:
|
|
|
551
557
|
self._notify_listener = listener
|
|
552
558
|
|
|
553
559
|
async def initialize(self) -> None:
|
|
554
|
-
"""Initialize database connection and
|
|
560
|
+
"""Initialize database connection and apply migrations.
|
|
555
561
|
|
|
556
|
-
This method
|
|
557
|
-
|
|
558
|
-
|
|
562
|
+
This method automatically applies dbmate migration files to create
|
|
563
|
+
tables and update schema. It tracks applied migrations in the
|
|
564
|
+
schema_migrations table (compatible with dbmate CLI).
|
|
559
565
|
"""
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
566
|
+
from pathlib import Path
|
|
567
|
+
|
|
568
|
+
from .migrations import apply_dbmate_migrations
|
|
563
569
|
|
|
564
|
-
#
|
|
565
|
-
|
|
570
|
+
# Apply dbmate migrations
|
|
571
|
+
migrations_dir = Path(self._migrations_dir) if self._migrations_dir else None
|
|
572
|
+
await apply_dbmate_migrations(self.engine, migrations_dir)
|
|
566
573
|
|
|
567
|
-
# Auto-migrate CHECK constraints
|
|
574
|
+
# Auto-migrate CHECK constraints (for existing tables)
|
|
568
575
|
await self._auto_migrate_check_constraints()
|
|
569
576
|
|
|
570
577
|
# Initialize schema version
|
|
@@ -1227,6 +1234,7 @@ class SQLAlchemyStorage:
|
|
|
1227
1234
|
workflow_name=workflow_name,
|
|
1228
1235
|
source_hash=source_hash,
|
|
1229
1236
|
owner_service=owner_service,
|
|
1237
|
+
framework="python",
|
|
1230
1238
|
input_data=json.dumps(input_data),
|
|
1231
1239
|
lock_timeout_seconds=lock_timeout_seconds,
|
|
1232
1240
|
continued_from=continued_from,
|
|
@@ -1332,6 +1340,7 @@ class SQLAlchemyStorage:
|
|
|
1332
1340
|
WorkflowInstance.source_hash == WorkflowDefinition.source_hash,
|
|
1333
1341
|
),
|
|
1334
1342
|
)
|
|
1343
|
+
.where(WorkflowInstance.framework == "python")
|
|
1335
1344
|
.order_by(
|
|
1336
1345
|
WorkflowInstance.started_at.desc(),
|
|
1337
1346
|
WorkflowInstance.instance_id.desc(),
|
|
@@ -1700,6 +1709,7 @@ class SQLAlchemyStorage:
|
|
|
1700
1709
|
WorkflowInstance.lock_expires_at.isnot(None),
|
|
1701
1710
|
self._make_datetime_comparable(WorkflowInstance.lock_expires_at)
|
|
1702
1711
|
< self._get_current_time_expr(),
|
|
1712
|
+
WorkflowInstance.framework == "python",
|
|
1703
1713
|
)
|
|
1704
1714
|
)
|
|
1705
1715
|
)
|
|
@@ -1752,10 +1762,9 @@ class SQLAlchemyStorage:
|
|
|
1752
1762
|
"""
|
|
1753
1763
|
Try to acquire a system-level lock for coordinating background tasks.
|
|
1754
1764
|
|
|
1755
|
-
Uses
|
|
1756
|
-
1.
|
|
1757
|
-
2.
|
|
1758
|
-
3. If available, acquire lock; otherwise return False
|
|
1765
|
+
Uses atomic UPDATE pattern to avoid race conditions:
|
|
1766
|
+
1. Ensure row exists (INSERT OR IGNORE / ON CONFLICT DO NOTHING)
|
|
1767
|
+
2. Atomic UPDATE with WHERE condition (rowcount determines success)
|
|
1759
1768
|
|
|
1760
1769
|
Note: ALWAYS uses separate session (not external session).
|
|
1761
1770
|
"""
|
|
@@ -1764,51 +1773,78 @@ class SQLAlchemyStorage:
|
|
|
1764
1773
|
current_time = datetime.now(UTC)
|
|
1765
1774
|
lock_expires_at = current_time + timedelta(seconds=timeout_seconds)
|
|
1766
1775
|
|
|
1767
|
-
#
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
)
|
|
1771
|
-
|
|
1776
|
+
# Get dialect name
|
|
1777
|
+
dialect_name = self.engine.dialect.name
|
|
1778
|
+
|
|
1779
|
+
# 1. Ensure row exists (idempotent INSERT)
|
|
1780
|
+
if dialect_name == "sqlite":
|
|
1781
|
+
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
|
1782
|
+
|
|
1783
|
+
stmt: Any = (
|
|
1784
|
+
sqlite_insert(SystemLock)
|
|
1785
|
+
.values(
|
|
1786
|
+
lock_name=lock_name,
|
|
1787
|
+
locked_by=None,
|
|
1788
|
+
locked_at=None,
|
|
1789
|
+
lock_expires_at=None,
|
|
1790
|
+
)
|
|
1791
|
+
.on_conflict_do_nothing(index_elements=["lock_name"])
|
|
1792
|
+
)
|
|
1793
|
+
elif dialect_name == "postgresql":
|
|
1794
|
+
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
|
1772
1795
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1796
|
+
stmt = (
|
|
1797
|
+
pg_insert(SystemLock)
|
|
1798
|
+
.values(
|
|
1799
|
+
lock_name=lock_name,
|
|
1800
|
+
locked_by=None,
|
|
1801
|
+
locked_at=None,
|
|
1802
|
+
lock_expires_at=None,
|
|
1803
|
+
)
|
|
1804
|
+
.on_conflict_do_nothing(index_elements=["lock_name"])
|
|
1805
|
+
)
|
|
1806
|
+
else: # mysql
|
|
1807
|
+
from sqlalchemy.dialects.mysql import insert as mysql_insert
|
|
1808
|
+
|
|
1809
|
+
stmt = (
|
|
1810
|
+
mysql_insert(SystemLock)
|
|
1811
|
+
.values(
|
|
1812
|
+
lock_name=lock_name,
|
|
1813
|
+
locked_by=None,
|
|
1814
|
+
locked_at=None,
|
|
1815
|
+
lock_expires_at=None,
|
|
1816
|
+
)
|
|
1817
|
+
.on_duplicate_key_update(lock_name=lock_name)
|
|
1818
|
+
) # No-op update
|
|
1819
|
+
|
|
1820
|
+
await session.execute(stmt)
|
|
1821
|
+
await session.commit()
|
|
1822
|
+
|
|
1823
|
+
# 2. Atomic UPDATE to acquire lock (rowcount == 1 means success)
|
|
1824
|
+
# Use SQL-side datetime comparison for cross-DB compatibility
|
|
1825
|
+
current_time_expr = self._get_current_time_expr()
|
|
1826
|
+
result = await session.execute(
|
|
1827
|
+
update(SystemLock)
|
|
1828
|
+
.where(
|
|
1829
|
+
and_(
|
|
1830
|
+
SystemLock.lock_name == lock_name,
|
|
1831
|
+
or_(
|
|
1832
|
+
SystemLock.locked_by == None, # noqa: E711
|
|
1833
|
+
SystemLock.locked_by == worker_id, # Allow renewal by same worker
|
|
1834
|
+
self._make_datetime_comparable(SystemLock.lock_expires_at)
|
|
1835
|
+
<= current_time_expr,
|
|
1836
|
+
),
|
|
1837
|
+
)
|
|
1838
|
+
)
|
|
1839
|
+
.values(
|
|
1777
1840
|
locked_by=worker_id,
|
|
1778
1841
|
locked_at=current_time,
|
|
1779
1842
|
lock_expires_at=lock_expires_at,
|
|
1780
1843
|
)
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
return True
|
|
1784
|
-
|
|
1785
|
-
# Lock exists - check if available
|
|
1786
|
-
if lock.locked_by is None:
|
|
1787
|
-
# Unlocked - acquire
|
|
1788
|
-
lock.locked_by = worker_id
|
|
1789
|
-
lock.locked_at = current_time
|
|
1790
|
-
lock.lock_expires_at = lock_expires_at
|
|
1791
|
-
await session.commit()
|
|
1792
|
-
return True
|
|
1793
|
-
|
|
1794
|
-
# Check if expired (use SQL-side comparison for cross-DB compatibility)
|
|
1795
|
-
if lock.lock_expires_at is not None:
|
|
1796
|
-
# Handle timezone-naive datetime from SQLite
|
|
1797
|
-
lock_expires = (
|
|
1798
|
-
lock.lock_expires_at.replace(tzinfo=UTC)
|
|
1799
|
-
if lock.lock_expires_at.tzinfo is None
|
|
1800
|
-
else lock.lock_expires_at
|
|
1801
|
-
)
|
|
1802
|
-
if lock_expires <= current_time:
|
|
1803
|
-
# Expired - acquire
|
|
1804
|
-
lock.locked_by = worker_id
|
|
1805
|
-
lock.locked_at = current_time
|
|
1806
|
-
lock.lock_expires_at = lock_expires_at
|
|
1807
|
-
await session.commit()
|
|
1808
|
-
return True
|
|
1844
|
+
)
|
|
1845
|
+
await session.commit()
|
|
1809
1846
|
|
|
1810
|
-
|
|
1811
|
-
return False
|
|
1847
|
+
return bool(result.rowcount == 1) # type: ignore[attr-defined]
|
|
1812
1848
|
|
|
1813
1849
|
async def release_system_lock(self, lock_name: str, worker_id: str) -> None:
|
|
1814
1850
|
"""
|
|
@@ -2033,7 +2069,7 @@ class SQLAlchemyStorage:
|
|
|
2033
2069
|
result = await session.execute(
|
|
2034
2070
|
select(WorkflowCompensation)
|
|
2035
2071
|
.where(WorkflowCompensation.instance_id == instance_id)
|
|
2036
|
-
.order_by(WorkflowCompensation.created_at.desc())
|
|
2072
|
+
.order_by(WorkflowCompensation.created_at.desc(), WorkflowCompensation.id.desc())
|
|
2037
2073
|
)
|
|
2038
2074
|
rows = result.scalars().all()
|
|
2039
2075
|
|
|
@@ -2178,6 +2214,7 @@ class SQLAlchemyStorage:
|
|
|
2178
2214
|
self._make_datetime_comparable(WorkflowTimerSubscription.expires_at)
|
|
2179
2215
|
<= self._get_current_time_expr(),
|
|
2180
2216
|
WorkflowInstance.status == "waiting_for_timer",
|
|
2217
|
+
WorkflowInstance.framework == "python",
|
|
2181
2218
|
)
|
|
2182
2219
|
)
|
|
2183
2220
|
)
|
|
@@ -2252,7 +2289,7 @@ class SQLAlchemyStorage:
|
|
|
2252
2289
|
|
|
2253
2290
|
# Send NOTIFY for new outbox event
|
|
2254
2291
|
await self._send_notify(
|
|
2255
|
-
"
|
|
2292
|
+
"workflow_outbox_pending",
|
|
2256
2293
|
{"evt_id": event_id, "evt_type": event_type},
|
|
2257
2294
|
)
|
|
2258
2295
|
|
|
@@ -2764,6 +2801,7 @@ class SQLAlchemyStorage:
|
|
|
2764
2801
|
ChannelSubscription.activity_id.isnot(None), # Only waiting subscriptions
|
|
2765
2802
|
self._make_datetime_comparable(ChannelSubscription.timeout_at)
|
|
2766
2803
|
<= self._get_current_time_expr(),
|
|
2804
|
+
WorkflowInstance.framework == "python",
|
|
2767
2805
|
)
|
|
2768
2806
|
)
|
|
2769
2807
|
)
|
|
@@ -2902,6 +2940,7 @@ class SQLAlchemyStorage:
|
|
|
2902
2940
|
and_(
|
|
2903
2941
|
WorkflowInstance.status == "running",
|
|
2904
2942
|
WorkflowInstance.locked_by.is_(None),
|
|
2943
|
+
WorkflowInstance.framework == "python",
|
|
2905
2944
|
)
|
|
2906
2945
|
)
|
|
2907
2946
|
if limit is not None:
|
|
@@ -3002,12 +3041,9 @@ class SQLAlchemyStorage:
|
|
|
3002
3041
|
session.add(msg)
|
|
3003
3042
|
await self._commit_if_not_in_transaction(session)
|
|
3004
3043
|
|
|
3005
|
-
# Send NOTIFY for message published (channel
|
|
3006
|
-
import hashlib
|
|
3007
|
-
|
|
3008
|
-
channel_hash = hashlib.sha256(channel.encode()).hexdigest()[:16]
|
|
3044
|
+
# Send NOTIFY for message published (unified channel name)
|
|
3009
3045
|
await self._send_notify(
|
|
3010
|
-
|
|
3046
|
+
"workflow_channel_message",
|
|
3011
3047
|
{"ch": channel, "msg_id": message_id},
|
|
3012
3048
|
)
|
|
3013
3049
|
|
|
@@ -3571,7 +3607,7 @@ class SQLAlchemyStorage:
|
|
|
3571
3607
|
|
|
3572
3608
|
# Send NOTIFY for workflow resumable
|
|
3573
3609
|
await self._send_notify(
|
|
3574
|
-
"
|
|
3610
|
+
"workflow_resumable",
|
|
3575
3611
|
{"wf_id": instance_id, "wf_name": workflow_name},
|
|
3576
3612
|
)
|
|
3577
3613
|
|
edda/viewer_ui/app.py
CHANGED
|
@@ -309,6 +309,10 @@ def start_viewer(edda_app: EddaApp, port: int = 8080, reload: bool = False) -> N
|
|
|
309
309
|
all_workflows = service.get_all_workflows()
|
|
310
310
|
workflow_names = list(all_workflows.keys())
|
|
311
311
|
|
|
312
|
+
workflow_select: Any = None
|
|
313
|
+
params_container: Any = None
|
|
314
|
+
param_fields: dict[str, Any] = {}
|
|
315
|
+
|
|
312
316
|
if not workflow_names:
|
|
313
317
|
ui.label("No workflows registered").classes("text-red-500")
|
|
314
318
|
ui.button("Close", on_click=start_dialog.close)
|
|
@@ -323,9 +327,6 @@ def start_viewer(edda_app: EddaApp, port: int = 8080, reload: bool = False) -> N
|
|
|
323
327
|
# Container for dynamic parameter fields
|
|
324
328
|
params_container = ui.column().classes("w-full mb-4")
|
|
325
329
|
|
|
326
|
-
# Store input field references
|
|
327
|
-
param_fields: dict[str, Any] = {}
|
|
328
|
-
|
|
329
330
|
# Factory functions for creating field managers with proper closures
|
|
330
331
|
# These must be defined outside the loop to avoid closure issues
|
|
331
332
|
|
|
@@ -778,6 +779,8 @@ def start_viewer(edda_app: EddaApp, port: int = 8080, reload: bool = False) -> N
|
|
|
778
779
|
|
|
779
780
|
def update_parameter_fields() -> None:
|
|
780
781
|
"""Update parameter input fields based on selected workflow."""
|
|
782
|
+
if workflow_select is None or params_container is None:
|
|
783
|
+
return
|
|
781
784
|
selected_workflow = workflow_select.value
|
|
782
785
|
if not selected_workflow:
|
|
783
786
|
return
|
|
@@ -1106,13 +1109,17 @@ def start_viewer(edda_app: EddaApp, port: int = 8080, reload: bool = False) -> N
|
|
|
1106
1109
|
update_parameter_fields()
|
|
1107
1110
|
|
|
1108
1111
|
# Update fields when workflow selection changes
|
|
1109
|
-
workflow_select
|
|
1112
|
+
if workflow_select is not None:
|
|
1113
|
+
workflow_select.on_value_change(lambda _: update_parameter_fields())
|
|
1110
1114
|
|
|
1111
1115
|
# Action buttons
|
|
1112
1116
|
with ui.row().classes("w-full gap-2"):
|
|
1113
1117
|
|
|
1114
1118
|
async def handle_start() -> None:
|
|
1115
1119
|
"""Handle workflow start."""
|
|
1120
|
+
if workflow_select is None:
|
|
1121
|
+
ui.notify("No workflows available", type="negative")
|
|
1122
|
+
return
|
|
1116
1123
|
try:
|
|
1117
1124
|
selected_workflow = workflow_select.value
|
|
1118
1125
|
if not selected_workflow:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: edda-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -286,6 +286,46 @@ pip install "git+https://github.com/i2y/edda.git[postgresql,viewer]"
|
|
|
286
286
|
|
|
287
287
|
> **Tip**: For PostgreSQL, install the `postgres-notify` extra for near-instant event delivery using LISTEN/NOTIFY instead of polling.
|
|
288
288
|
|
|
289
|
+
### Database Schema Migration
|
|
290
|
+
|
|
291
|
+
**Automatic Migration (Default)**
|
|
292
|
+
|
|
293
|
+
Edda automatically applies database migrations at startup. No manual commands needed:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from edda import EddaApp
|
|
297
|
+
|
|
298
|
+
# Migrations are applied automatically
|
|
299
|
+
app = EddaApp(db_url="postgresql://user:pass@localhost/dbname")
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
This is safe in multi-worker environments - Edda handles concurrent startup gracefully.
|
|
303
|
+
|
|
304
|
+
**Manual Migration with dbmate (Optional)**
|
|
305
|
+
|
|
306
|
+
For explicit schema control, you can disable auto-migration and use [dbmate](https://github.com/amacneil/dbmate):
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
# Disable auto-migration
|
|
310
|
+
app = EddaApp(
|
|
311
|
+
db_url="postgresql://...",
|
|
312
|
+
auto_migrate=False # Use dbmate-managed schema
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Install dbmate
|
|
318
|
+
brew install dbmate # macOS
|
|
319
|
+
|
|
320
|
+
# Add schema submodule
|
|
321
|
+
git submodule add https://github.com/durax-io/schema.git schema
|
|
322
|
+
|
|
323
|
+
# Run migration manually
|
|
324
|
+
DATABASE_URL="postgresql://user:pass@localhost/dbname" dbmate -d ./schema/db/migrations/postgresql up
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
> **Note**: Edda's auto-migration uses the same SQL files as dbmate, maintaining full compatibility.
|
|
328
|
+
|
|
289
329
|
### Development Installation
|
|
290
330
|
|
|
291
331
|
If you want to contribute to Edda or modify the framework itself:
|
|
@@ -293,7 +333,7 @@ If you want to contribute to Edda or modify the framework itself:
|
|
|
293
333
|
```bash
|
|
294
334
|
# Clone repository
|
|
295
335
|
git clone https://github.com/i2y/edda.git
|
|
296
|
-
cd
|
|
336
|
+
cd edda
|
|
297
337
|
uv sync --all-extras
|
|
298
338
|
```
|
|
299
339
|
|
|
@@ -333,7 +373,7 @@ async def user_signup(ctx: WorkflowContext, email: str):
|
|
|
333
373
|
return {"status": "completed"}
|
|
334
374
|
```
|
|
335
375
|
|
|
336
|
-
**Activity IDs**: Activities are automatically identified with IDs like `"send_email:1"` for deterministic replay. Manual IDs are only needed for concurrent execution (e.g., `asyncio.gather`).
|
|
376
|
+
**Activity IDs**: Activities are automatically identified with IDs like `"send_email:1"` for deterministic replay. Manual IDs are only needed for concurrent execution (e.g., `asyncio.gather`).
|
|
337
377
|
|
|
338
378
|
### Durable Execution
|
|
339
379
|
|
|
@@ -1,14 +1,14 @@
|
|
|
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=
|
|
4
|
-
edda/channels.py,sha256=
|
|
3
|
+
edda/app.py,sha256=ITTc7x5S4ykCP3KPZXKxuNczXkPtbn04ZQaxcem46Hw,68406
|
|
4
|
+
edda/channels.py,sha256=CosFoB9HVHBKRmhU_t6qoCV3l6egAGt3sqpakfgZLKc,36596
|
|
5
5
|
edda/compensation.py,sha256=iKLlnTxiF1YSatmYQW84EkPB1yMKUEZBtgjuGnghLtY,11824
|
|
6
|
-
edda/context.py,sha256=
|
|
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
9
|
edda/locking.py,sha256=NAFJmw-JaSVsXn4Y4czJyv_s9bWG8cdrzDBWIEag5X8,13661
|
|
10
10
|
edda/pydantic_utils.py,sha256=dGVPNrrttDeq1k233PopCtjORYjZitsgASPfPnO6R10,9056
|
|
11
|
-
edda/replay.py,sha256=
|
|
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
|
|
@@ -24,27 +24,31 @@ edda/integrations/mirascope/types.py,sha256=vgEAu8EFTLSd92XSAxtZpMoe5gv93fe4Rm0D
|
|
|
24
24
|
edda/integrations/opentelemetry/__init__.py,sha256=x1_PyyygGDW-rxQTwoIrGzyjKErXHOOKdquFAMlCOAo,906
|
|
25
25
|
edda/integrations/opentelemetry/hooks.py,sha256=rCb6K_gJJMxjQ-UoJnbIOWsafapipzu7w-YPROZKxDA,21330
|
|
26
26
|
edda/outbox/__init__.py,sha256=azXG1rtheJEjOyoWmMsBeR2jp8Bz02R3wDEd5tQnaWA,424
|
|
27
|
-
edda/outbox/relayer.py,sha256=
|
|
27
|
+
edda/outbox/relayer.py,sha256=b14BaFnBODg4bz0ft8UZxqORlTQON5SHu4wrfYs3Lx4,11467
|
|
28
28
|
edda/outbox/transactional.py,sha256=LFfUjunqRlGibaINi-efGXFFivWGO7v3mhqrqyGW6Nw,3808
|
|
29
29
|
edda/serialization/__init__.py,sha256=hnOVJN-mJNIsSa_XH9jwhIydOsWvIfCaFaSd37HUplg,216
|
|
30
30
|
edda/serialization/base.py,sha256=xJy2CY9gdJDCF0tmCor8NomL2Lr_w7cveVvxccuc-tA,1998
|
|
31
31
|
edda/serialization/json.py,sha256=Dq96V4n1yozexjCPd_CL6Iuvh1u3jJhef6sTcNxXZeA,2842
|
|
32
32
|
edda/storage/__init__.py,sha256=NjvAzYV3SknACrC16ZQOA-xCKOj1-s3rBIWOS1ZGCaM,407
|
|
33
|
-
edda/storage/
|
|
33
|
+
edda/storage/migrations.py,sha256=KrceouVODct9WWDBhmjAW0IYptDWd2mqJmhrHnee59M,13704
|
|
34
|
+
edda/storage/models.py,sha256=axXGJ-Orwcd_AsEUwIyFfDyg3NQxMcOQ2mrTzXkNv3g,12284
|
|
34
35
|
edda/storage/notify_base.py,sha256=gUb-ypG1Bo0c-KrleYmC7eKtdwQNUeqGS5k7UILlSsQ,5055
|
|
35
|
-
edda/storage/pg_notify.py,sha256=
|
|
36
|
+
edda/storage/pg_notify.py,sha256=myzJ9xX86uiro9aaiA1SW1sN3E-zYafn7_lpeAy1jOg,11830
|
|
36
37
|
edda/storage/protocol.py,sha256=vdB5GvBen8lgUA0qEfBXfQTLbVfGKeBTQuEwSUqLZtI,39463
|
|
37
|
-
edda/storage/sqlalchemy_storage.py,sha256=
|
|
38
|
+
edda/storage/sqlalchemy_storage.py,sha256=HREK7fHmq3DGx6w4jA03_NrQu9HbyMomyIawMuOQLYQ,146246
|
|
38
39
|
edda/viewer_ui/__init__.py,sha256=N1-T33SXadOXcBsDSgJJ9Iqz4y4verJngWryQu70c5c,517
|
|
39
|
-
edda/viewer_ui/app.py,sha256=
|
|
40
|
+
edda/viewer_ui/app.py,sha256=xZdIIGX5D2efNWQSVpPdldxLukHHpJD7JiAa_YKG5Uw,97084
|
|
40
41
|
edda/viewer_ui/components.py,sha256=A0IxLwgj_Lu51O57OfzOwME8jzoJtKegEVvSnWc7uPo,45174
|
|
41
42
|
edda/viewer_ui/data_service.py,sha256=KOqnWr-Y8seH_dkJH_ejHRfxQqn7aY8Ni5C54tx2Z-E,36621
|
|
42
43
|
edda/viewer_ui/theme.py,sha256=mrXoXLRzgSnvE2a58LuMcPJkhlvHEDMWVa8Smqtk4l0,8118
|
|
43
44
|
edda/visualizer/__init__.py,sha256=DOpDstNhR0VcXAs_eMKxaL30p_0u4PKZ4o2ndnYhiRo,343
|
|
44
45
|
edda/visualizer/ast_analyzer.py,sha256=plmx7C9X_X35xLY80jxOL3ljg3afXxBePRZubqUIkxY,13663
|
|
45
46
|
edda/visualizer/mermaid_generator.py,sha256=XWa2egoOTNDfJEjPcwoxwQmblUqXf7YInWFjFRI1QGo,12457
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
edda_framework-0.
|
|
50
|
-
edda_framework-0.
|
|
47
|
+
edda/migrations/mysql/20251217000000_initial_schema.sql,sha256=LpINasESRhadOeqABwDk4JZ0OZ4_zQw_opnhIR4Xe9U,12367
|
|
48
|
+
edda/migrations/postgresql/20251217000000_initial_schema.sql,sha256=hCaGMWeptpzpnsjfNKVsMYuwPRe__fK9E0VZpClAumQ,11732
|
|
49
|
+
edda/migrations/sqlite/20251217000000_initial_schema.sql,sha256=Wq9gCnQ0K9SOt0PY_8f1MG4va8rLVWIIcf2lnRzSK5g,11906
|
|
50
|
+
edda_framework-0.13.0.dist-info/METADATA,sha256=K-ar-0liixJ38d34eDvPGYPyWxUSKjgsQ_xwZPCbZ2A,37567
|
|
51
|
+
edda_framework-0.13.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
52
|
+
edda_framework-0.13.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
|
|
53
|
+
edda_framework-0.13.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
|
|
54
|
+
edda_framework-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|