forktex-flow 0.2.0__tar.gz → 0.2.1__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.
Files changed (36) hide show
  1. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/PKG-INFO +1 -1
  2. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/pyproject.toml +1 -1
  3. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/runs.py +37 -2
  4. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/LICENSE +0 -0
  5. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/NOTICE +0 -0
  6. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/README.md +0 -0
  7. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/__init__.py +0 -0
  8. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/__init__.py +0 -0
  9. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/config.py +0 -0
  10. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/definition.py +0 -0
  11. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/filter.py +0 -0
  12. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/node.py +0 -0
  13. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/state.py +0 -0
  14. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/domain/types.py +0 -0
  15. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/errors.py +0 -0
  16. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/extension.py +0 -0
  17. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/flow.py +0 -0
  18. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/__init__.py +0 -0
  19. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/mappers.py +0 -0
  20. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/migrations/__init__.py +0 -0
  21. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/migrations/v0001__schema.sql +0 -0
  22. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/models.py +0 -0
  23. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/steps.py +0 -0
  24. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/persist/store.py +0 -0
  25. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/py.typed +0 -0
  26. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/read/__init__.py +0 -0
  27. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/read/instance.py +0 -0
  28. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/read/query.py +0 -0
  29. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/__init__.py +0 -0
  30. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/compiler.py +0 -0
  31. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/context.py +0 -0
  32. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/ctx.py +0 -0
  33. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/driver.py +0 -0
  34. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/execution.py +0 -0
  35. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/runtime/registry.py +0 -0
  36. {forktex_flow-0.2.0 → forktex_flow-0.2.1}/src/forktex_flow/testing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forktex-flow
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Postgres-native durable workflow execution — pipelines and cron-scheduled runs, built on the forktex substrate.
5
5
  License-Expression: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
6
6
  License-File: LICENSE
@@ -23,7 +23,7 @@
23
23
 
24
24
  [project]
25
25
  name = "forktex-flow"
26
- version = "0.2.0"
26
+ version = "0.2.1"
27
27
  description = "Postgres-native durable workflow execution — pipelines and cron-scheduled runs, built on the forktex substrate."
28
28
  authors = [
29
29
  {name = "FORKTEX",email = "info@forktex.com"}
@@ -43,7 +43,7 @@ from sqlalchemy.orm import selectinload
43
43
 
44
44
  from forktex_flow.domain.types import TERMINAL_STATUSES, RunInfo, RunStatus
45
45
  from forktex_flow.persist.mappers import to_run_info
46
- from forktex_flow.persist.models import Run, RunEvent
46
+ from forktex_flow.persist.models import Run, RunEvent, StepRun
47
47
 
48
48
  if TYPE_CHECKING:
49
49
  from sqlalchemy.ext.asyncio import AsyncConnection
@@ -210,10 +210,45 @@ async def claim_pending_runs(store: FlowStore, limit: int) -> list[tuple[UUID, s
210
210
  instead of racing over it: ``with_for_update(skip_locked=True)`` on a scalar
211
211
  subquery renders the locking clause *inside* the subselect, which is where
212
212
  Postgres needs it for the atomicity to hold.
213
+
214
+ **Runs waiting on a retry backoff are skipped.** A step that asks to be
215
+ retried sets ``next_attempt_at`` and the run goes straight back to
216
+ ``pending`` (``execution.py``, ``StepRetryRequested``). Claiming on status
217
+ alone therefore re-dispatched it on the very next driver tick — a second
218
+ later — and the backoff a step declared was never waited. `@step(backoff=(15,
219
+ 30, 60), max_attempts=720)` reads as hours of patience and delivered twelve
220
+ minutes of busy-looping against whatever the step was polling.
221
+
222
+ A run is skipped only while it is *actually* blocked: it has a pending step
223
+ scheduled in the future and no pending step that is due. A fresh run with no
224
+ step rows, and a resumed run whose next step has not been created yet, both
225
+ still claim — the guard must not turn a backoff into a stall.
213
226
  """
227
+ blocked_until_later = (
228
+ select(StepRun.id)
229
+ .where(
230
+ StepRun.run_id == Run.id,
231
+ StepRun.status == "pending",
232
+ StepRun.next_attempt_at.isnot(None),
233
+ StepRun.next_attempt_at > sa.func.now(),
234
+ )
235
+ .exists()
236
+ )
237
+ ready_now = (
238
+ select(StepRun.id)
239
+ .where(
240
+ StepRun.run_id == Run.id,
241
+ StepRun.status == "pending",
242
+ sa.or_(
243
+ StepRun.next_attempt_at.is_(None),
244
+ StepRun.next_attempt_at <= sa.func.now(),
245
+ ),
246
+ )
247
+ .exists()
248
+ )
214
249
  claimable = (
215
250
  select(Run.id)
216
- .where(Run.status == "pending")
251
+ .where(Run.status == "pending", sa.or_(~blocked_until_later, ready_now))
217
252
  .order_by(Run.started_at.asc())
218
253
  .limit(limit)
219
254
  .with_for_update(skip_locked=True)
File without changes
File without changes
File without changes