pyworkflow-engine 0.2.2__tar.gz → 0.2.5__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.
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/PKG-INFO +1 -1
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyproject.toml +1 -1
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/__init__.py +1 -1
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/app.py +7 -23
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/tasks.py +35 -80
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/local.py +13 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/step.py +3 -2
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/base.py +58 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/cassandra.py +320 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/citus.py +111 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/dynamodb.py +304 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/file.py +266 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/memory.py +57 -5
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/migrations/base.py +20 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/mysql.py +341 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/postgres.py +483 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/sqlite.py +425 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/context.py +20 -2
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/decorator.py +20 -3
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/dispatcher.py +98 -13
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/emit.py +18 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/signal.py +1 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_schema_migrations.py +1 -1
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/CLAUDE.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/DISTRIBUTED.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/LICENSE +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/MANIFEST.in +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/README.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/RELEASING.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/architecture.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/cancellation.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/continue-as-new.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/events.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/fault-tolerance.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/hooks.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/limitations.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/schedules.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/sleep.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/step-context.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/steps.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/workflows.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/conventions.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/brokers.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/cli.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/configuration.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/harness-gaps.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/introduction.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/layers.md +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/quickstart.mdx +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/docker-compose.yml +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/pyworkflow.config.yaml +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/basic.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/batch_processing.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/cancellation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflow_from_step.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflow_patterns.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflows.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/continue_as_new.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/fault_tolerance.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/hooks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/idempotency.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/long_running.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/retries.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/schedules.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/sleep_in_step.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/step_context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/01_basic_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/02_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/pyworkflow.config.yaml +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/01_basic_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/02_file_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/03_retries.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/04_long_running.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/05_event_log.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/06_idempotency.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/07_hooks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/08_cancellation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/09_child_workflows.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/10_child_workflow_patterns.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/11_continue_as_new.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/12_schedules.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/13_step_context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/14_child_workflow_from_step.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/01_quick_tasks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/02_retries.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/03_sleep.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/handler.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/testing.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/loop.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/scheduler.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/singleton.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/__main__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/hooks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/quickstart.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/runs.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/scheduler.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/schedules.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/setup.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/worker.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/workflows.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/formatters.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/styles.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/async_helpers.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/config.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/config_generator.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/discovery.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/docker_manager.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/interactive.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/config.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/aws.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/base.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/mock.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/step_context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/exceptions.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/registry.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/scheduled.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/validation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/discovery.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/events.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/executor.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/replay.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/observability/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/observability/logging.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/child_handle.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/child_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/continue_as_new.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/define_hook.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/hooks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/resume_hook.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/schedule.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/shield.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/sleep.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/step_checkpoint.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/step_hook.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/base.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/celery.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/factory.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/local.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/scheduler/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/scheduler/local.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/decoder.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/encoder.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/config.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/migrations/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/schemas.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/checkpoint.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/consumer.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/registry.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/step_context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/duration.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/helpers.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/schedule.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow_engine.egg-info/SOURCES.txt +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/setup.cfg +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_cancellation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_cassandra_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_child_workflows.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_continue_as_new.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_dynamodb_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_schedule_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_singleton.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_stream_e2e.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_workflow_suspended.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_cassandra_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_citus_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_dynamodb_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_postgres_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_sqlite_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/conftest.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/storage/__init__.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/storage/test_migrations.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_cancellation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_child_workflows.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_cli_worker.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_continue_as_new.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_emit.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_event_limits.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_executor.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_force_local.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_hooks.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_parent_run_id.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_primitives_from_steps.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_registry.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_replay.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_retention.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_schedule_schemas.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_schedule_utils.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_scheduled_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_signal.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_singleton.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_checkpoint.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_context.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_hook.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_stream_storage.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_stream_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_validation.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_workflow.py +0 -0
- {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_workflow_suspended.py +0 -0
|
@@ -7,7 +7,7 @@ packages = [{include = "pyworkflow"}]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pyworkflow-engine"
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.5"
|
|
11
11
|
description = "A Python implementation of durable, event-sourced workflows inspired by Vercel Workflow"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -304,33 +304,17 @@ def create_celery_app(
|
|
|
304
304
|
# Broker transport options - prevent task redelivery
|
|
305
305
|
# See: https://github.com/celery/celery/issues/5935
|
|
306
306
|
"broker_transport_options": final_broker_opts,
|
|
307
|
-
# Task routing
|
|
307
|
+
# Task routing — use direct exchange (each task specifies queue= explicitly)
|
|
308
308
|
"task_default_queue": "pyworkflow.default",
|
|
309
309
|
"task_default_exchange": "pyworkflow",
|
|
310
|
-
"task_default_exchange_type": "
|
|
311
|
-
"task_default_routing_key": "
|
|
310
|
+
"task_default_exchange_type": "direct",
|
|
311
|
+
"task_default_routing_key": "pyworkflow.default",
|
|
312
312
|
# Task queues
|
|
313
313
|
"task_queues": (
|
|
314
|
-
Queue(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
),
|
|
319
|
-
Queue(
|
|
320
|
-
"pyworkflow.steps",
|
|
321
|
-
Exchange("pyworkflow", type="topic"),
|
|
322
|
-
routing_key="workflow.step.#",
|
|
323
|
-
),
|
|
324
|
-
Queue(
|
|
325
|
-
"pyworkflow.workflows",
|
|
326
|
-
Exchange("pyworkflow", type="topic"),
|
|
327
|
-
routing_key="workflow.workflow.#",
|
|
328
|
-
),
|
|
329
|
-
Queue(
|
|
330
|
-
"pyworkflow.schedules",
|
|
331
|
-
Exchange("pyworkflow", type="topic"),
|
|
332
|
-
routing_key="workflow.schedule.#",
|
|
333
|
-
),
|
|
314
|
+
Queue("pyworkflow.default", Exchange("pyworkflow", type="direct")),
|
|
315
|
+
Queue("pyworkflow.steps", Exchange("pyworkflow", type="direct")),
|
|
316
|
+
Queue("pyworkflow.workflows", Exchange("pyworkflow", type="direct")),
|
|
317
|
+
Queue("pyworkflow.schedules", Exchange("pyworkflow", type="direct")),
|
|
334
318
|
),
|
|
335
319
|
# Task execution
|
|
336
320
|
"task_acks_late": True,
|
|
@@ -10,7 +10,6 @@ These tasks enable:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
|
-
import contextlib
|
|
14
13
|
import random
|
|
15
14
|
import uuid
|
|
16
15
|
from collections.abc import Callable
|
|
@@ -186,29 +185,6 @@ def execute_step_task(
|
|
|
186
185
|
)
|
|
187
186
|
return None
|
|
188
187
|
|
|
189
|
-
# Execution-time guard: prevent duplicate step execution on concurrent workers.
|
|
190
|
-
# The singleton lock (unique_on=["run_id", "step_id"]) prevents duplicate queueing,
|
|
191
|
-
# but broker-level re-delivery or race conditions can still result in two workers
|
|
192
|
-
# executing the same step. This Redis lock ensures only one proceeds.
|
|
193
|
-
_exec_lock_key = f"pyworkflow:step_exec:{run_id}:{step_id}"
|
|
194
|
-
_exec_lock_backend = self.singleton_backend
|
|
195
|
-
_exec_lock_acquired = False
|
|
196
|
-
if _exec_lock_backend is not None:
|
|
197
|
-
_exec_lock_acquired = _exec_lock_backend.lock(
|
|
198
|
-
_exec_lock_key, self.request.id or "unknown", expiry=self._lock_expiry
|
|
199
|
-
)
|
|
200
|
-
if not _exec_lock_acquired:
|
|
201
|
-
logger.warning(
|
|
202
|
-
"Step already being executed by another worker, skipping duplicate",
|
|
203
|
-
run_id=run_id,
|
|
204
|
-
step_id=step_id,
|
|
205
|
-
step_name=step_name,
|
|
206
|
-
existing_worker_task=_exec_lock_backend.get(_exec_lock_key),
|
|
207
|
-
)
|
|
208
|
-
return None
|
|
209
|
-
else:
|
|
210
|
-
_exec_lock_acquired = True # No Redis = no locking
|
|
211
|
-
|
|
212
188
|
# Deserialize arguments
|
|
213
189
|
args = deserialize_args(args_json)
|
|
214
190
|
kwargs = deserialize_kwargs(kwargs_json)
|
|
@@ -475,11 +451,6 @@ def execute_step_task(
|
|
|
475
451
|
raise FatalError(f"Step '{step_name}' failed after retries: {str(e)}") from e
|
|
476
452
|
|
|
477
453
|
finally:
|
|
478
|
-
# Release execution lock so retries or other tasks can proceed.
|
|
479
|
-
if _exec_lock_acquired and _exec_lock_backend is not None:
|
|
480
|
-
with contextlib.suppress(Exception):
|
|
481
|
-
_exec_lock_backend.unlock(_exec_lock_key)
|
|
482
|
-
|
|
483
454
|
# Clean up step execution context (checkpoint/hook primitives)
|
|
484
455
|
if step_exec_tokens is not None:
|
|
485
456
|
from pyworkflow.primitives.step_checkpoint import reset_step_execution_context
|
|
@@ -864,62 +835,33 @@ def start_workflow_task(
|
|
|
864
835
|
celery_task_id=self.request.id,
|
|
865
836
|
)
|
|
866
837
|
|
|
867
|
-
#
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
_exec_lock_key = f"pyworkflow:wf_start:{run_id}"
|
|
872
|
-
_exec_lock_backend = self.singleton_backend
|
|
873
|
-
_exec_lock_acquired = False
|
|
874
|
-
if _exec_lock_backend is not None:
|
|
875
|
-
_exec_lock_acquired = _exec_lock_backend.lock(
|
|
876
|
-
_exec_lock_key, self.request.id or "unknown", expiry=self._lock_expiry
|
|
877
|
-
)
|
|
878
|
-
if not _exec_lock_acquired:
|
|
879
|
-
logger.warning(
|
|
880
|
-
"Workflow start already being executed by another worker, skipping duplicate",
|
|
881
|
-
run_id=run_id,
|
|
882
|
-
workflow_name=workflow_name,
|
|
883
|
-
existing_worker_task=_exec_lock_backend.get(_exec_lock_key),
|
|
884
|
-
)
|
|
885
|
-
return run_id
|
|
886
|
-
else:
|
|
887
|
-
_exec_lock_acquired = True # No Redis = no locking
|
|
888
|
-
|
|
889
|
-
try:
|
|
890
|
-
# Get workflow metadata
|
|
891
|
-
workflow_meta = get_workflow(workflow_name)
|
|
892
|
-
if not workflow_meta:
|
|
893
|
-
raise ValueError(f"Workflow '{workflow_name}' not found in registry")
|
|
838
|
+
# Get workflow metadata
|
|
839
|
+
workflow_meta = get_workflow(workflow_name)
|
|
840
|
+
if not workflow_meta:
|
|
841
|
+
raise ValueError(f"Workflow '{workflow_name}' not found in registry")
|
|
894
842
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
843
|
+
# Deserialize arguments
|
|
844
|
+
args = deserialize_args(args_json)
|
|
845
|
+
kwargs = deserialize_kwargs(kwargs_json)
|
|
898
846
|
|
|
899
|
-
|
|
900
|
-
|
|
847
|
+
# Get storage backend
|
|
848
|
+
storage = _get_storage_backend(storage_config)
|
|
901
849
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
)
|
|
850
|
+
# Execute workflow directly on worker
|
|
851
|
+
result_run_id = run_async(
|
|
852
|
+
_start_workflow_on_worker(
|
|
853
|
+
workflow_meta=workflow_meta,
|
|
854
|
+
args=args,
|
|
855
|
+
kwargs=kwargs,
|
|
856
|
+
storage=storage,
|
|
857
|
+
storage_config=storage_config,
|
|
858
|
+
idempotency_key=idempotency_key,
|
|
859
|
+
run_id=run_id,
|
|
913
860
|
)
|
|
861
|
+
)
|
|
914
862
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
finally:
|
|
919
|
-
# Release execution lock so retries or re-dispatches can proceed.
|
|
920
|
-
if _exec_lock_acquired and _exec_lock_backend is not None:
|
|
921
|
-
with contextlib.suppress(Exception):
|
|
922
|
-
_exec_lock_backend.unlock(_exec_lock_key)
|
|
863
|
+
logger.info(f"Workflow execution initiated: {workflow_name}", run_id=result_run_id)
|
|
864
|
+
return result_run_id
|
|
923
865
|
|
|
924
866
|
|
|
925
867
|
@celery_app.task(
|
|
@@ -2520,6 +2462,9 @@ async def _resume_workflow_on_worker(
|
|
|
2520
2462
|
run_id=run_id,
|
|
2521
2463
|
step_id=step_id,
|
|
2522
2464
|
)
|
|
2465
|
+
resume_workflow_task.release_lock(
|
|
2466
|
+
task_args=[run_id], task_kwargs={"storage_config": storage_config}
|
|
2467
|
+
)
|
|
2523
2468
|
schedule_workflow_resumption(
|
|
2524
2469
|
run_id,
|
|
2525
2470
|
datetime.now(UTC),
|
|
@@ -2539,6 +2484,9 @@ async def _resume_workflow_on_worker(
|
|
|
2539
2484
|
run_id=run_id,
|
|
2540
2485
|
hook_id=hook_id,
|
|
2541
2486
|
)
|
|
2487
|
+
resume_workflow_task.release_lock(
|
|
2488
|
+
task_args=[run_id], task_kwargs={"storage_config": storage_config}
|
|
2489
|
+
)
|
|
2542
2490
|
schedule_workflow_resumption(
|
|
2543
2491
|
run_id,
|
|
2544
2492
|
datetime.now(UTC),
|
|
@@ -2550,6 +2498,13 @@ async def _resume_workflow_on_worker(
|
|
|
2550
2498
|
# Schedule automatic resumption if we have a resume_at time
|
|
2551
2499
|
resume_at = e.data.get("resume_at") if e.data else None
|
|
2552
2500
|
if resume_at:
|
|
2501
|
+
# Release the singleton lock BEFORE scheduling the next resume.
|
|
2502
|
+
# Without this, the new resume_workflow_task.apply_async() sees
|
|
2503
|
+
# the lock still held by the current (calling) task and silently
|
|
2504
|
+
# drops the schedule as a duplicate.
|
|
2505
|
+
resume_workflow_task.release_lock(
|
|
2506
|
+
task_args=[run_id], task_kwargs={"storage_config": storage_config}
|
|
2507
|
+
)
|
|
2553
2508
|
schedule_workflow_resumption(
|
|
2554
2509
|
run_id, resume_at, storage_config=storage_config, triggered_by="start_sleep_hook"
|
|
2555
2510
|
)
|
|
@@ -151,7 +151,16 @@ class LocalContext(WorkflowContext):
|
|
|
151
151
|
from pyworkflow.engine.events import EventType
|
|
152
152
|
from pyworkflow.serialization.decoder import deserialize
|
|
153
153
|
|
|
154
|
+
max_counter = 0
|
|
154
155
|
for event in events:
|
|
156
|
+
# Track step counter from sleep and hook IDs to avoid collisions
|
|
157
|
+
for key in ("sleep_id", "hook_id"):
|
|
158
|
+
id_val = event.data.get(key) if event.data else None
|
|
159
|
+
if id_val:
|
|
160
|
+
c = self._extract_counter_from_id(id_val)
|
|
161
|
+
if c > max_counter:
|
|
162
|
+
max_counter = c
|
|
163
|
+
|
|
155
164
|
if event.type == EventType.STEP_STARTED:
|
|
156
165
|
# Track step as in-progress (dispatched but not completed)
|
|
157
166
|
step_id = event.data.get("step_id")
|
|
@@ -271,6 +280,10 @@ class LocalContext(WorkflowContext):
|
|
|
271
280
|
}
|
|
272
281
|
self._pending_children.pop(child_id, None)
|
|
273
282
|
|
|
283
|
+
# Restore step counter so new sleeps/hooks don't collide with replayed ones
|
|
284
|
+
if max_counter > self._step_counter:
|
|
285
|
+
self._step_counter = max_counter
|
|
286
|
+
|
|
274
287
|
@property
|
|
275
288
|
def is_durable(self) -> bool:
|
|
276
289
|
return self._durable
|
|
@@ -669,7 +669,8 @@ async def _dispatch_step_to_celery(
|
|
|
669
669
|
|
|
670
670
|
# Defense-in-depth: check if STEP_STARTED was already recorded for this step.
|
|
671
671
|
# This guards against duplicate dispatch when two resume tasks race and both
|
|
672
|
-
# replay past the same step. If already started, re-suspend to wait
|
|
672
|
+
# replay past the same step. If already started, re-suspend to wait for
|
|
673
|
+
# the running task to complete via task_reject_on_worker_lost re-delivery.
|
|
673
674
|
# Exception: if STEP_SUSPENDED was recorded (step_hook suspension), the step
|
|
674
675
|
# needs re-dispatch so it can re-execute and find the HOOK_RECEIVED event.
|
|
675
676
|
events = await ctx.storage.get_events(ctx.run_id)
|
|
@@ -682,7 +683,7 @@ async def _dispatch_step_to_celery(
|
|
|
682
683
|
)
|
|
683
684
|
if already_started and not was_suspended:
|
|
684
685
|
logger.info(
|
|
685
|
-
f"Step {step_name} already has STEP_STARTED event, re-suspending",
|
|
686
|
+
f"Step {step_name} already has STEP_STARTED event, re-suspending (task running)",
|
|
686
687
|
run_id=ctx.run_id,
|
|
687
688
|
step_id=step_id,
|
|
688
689
|
)
|
|
@@ -755,6 +755,7 @@ class StorageBackend(ABC):
|
|
|
755
755
|
signal_type: str,
|
|
756
756
|
payload: dict,
|
|
757
757
|
source_run_id: str | None = None,
|
|
758
|
+
stream_run_id: str | None = None,
|
|
758
759
|
metadata: dict | None = None,
|
|
759
760
|
) -> int:
|
|
760
761
|
"""
|
|
@@ -766,6 +767,7 @@ class StorageBackend(ABC):
|
|
|
766
767
|
signal_type: Signal type (e.g., "task.created")
|
|
767
768
|
payload: Signal payload data
|
|
768
769
|
source_run_id: Optional source workflow run ID
|
|
770
|
+
stream_run_id: Optional stream run identifier for grouping signals
|
|
769
771
|
metadata: Optional signal metadata
|
|
770
772
|
|
|
771
773
|
Returns:
|
|
@@ -798,6 +800,40 @@ class StorageBackend(ABC):
|
|
|
798
800
|
"""
|
|
799
801
|
raise NotImplementedError("This storage backend does not support streams")
|
|
800
802
|
|
|
803
|
+
async def query_stream_signals(
|
|
804
|
+
self,
|
|
805
|
+
stream_id: str,
|
|
806
|
+
stream_run_id: str,
|
|
807
|
+
*,
|
|
808
|
+
source_run_id: str | None = None,
|
|
809
|
+
signal_type: str | None = None,
|
|
810
|
+
after_sequence: int | None = None,
|
|
811
|
+
before_sequence: int | None = None,
|
|
812
|
+
last_n: int | None = None,
|
|
813
|
+
limit: int = 50,
|
|
814
|
+
) -> list[dict]:
|
|
815
|
+
"""
|
|
816
|
+
Query signals from a stream with rich filtering.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
stream_id: Stream identifier.
|
|
820
|
+
source_run_id: Filter to signals from a specific workflow run.
|
|
821
|
+
stream_run_id: Filter to signals from a specific stream run.
|
|
822
|
+
signal_type: Filter by signal type (e.g. ``"task.completed"``).
|
|
823
|
+
after_sequence: Only include signals with ``sequence >= value``.
|
|
824
|
+
before_sequence: Only include signals with ``sequence <= value``.
|
|
825
|
+
last_n: Return the N most recent signals (overrides
|
|
826
|
+
``after_sequence`` and ``limit``).
|
|
827
|
+
limit: Maximum number of signals to return.
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
List of signal dicts ordered by sequence ASC.
|
|
831
|
+
|
|
832
|
+
Raises:
|
|
833
|
+
NotImplementedError: If backend doesn't support streams.
|
|
834
|
+
"""
|
|
835
|
+
raise NotImplementedError("This storage backend does not support streams")
|
|
836
|
+
|
|
801
837
|
# Subscription Operations
|
|
802
838
|
|
|
803
839
|
async def register_stream_subscription(
|
|
@@ -839,6 +875,28 @@ class StorageBackend(ABC):
|
|
|
839
875
|
"""
|
|
840
876
|
raise NotImplementedError("This storage backend does not support streams")
|
|
841
877
|
|
|
878
|
+
async def get_subscriptions_for_stream(
|
|
879
|
+
self,
|
|
880
|
+
stream_id: str,
|
|
881
|
+
signal_type: str,
|
|
882
|
+
) -> list[dict]:
|
|
883
|
+
"""
|
|
884
|
+
Get ALL subscriptions for a signal type on a stream, regardless of status.
|
|
885
|
+
|
|
886
|
+
Unlike ``get_waiting_steps`` which only returns ``"waiting"``
|
|
887
|
+
subscriptions, this returns subscriptions in any status (waiting,
|
|
888
|
+
running, etc.). Used to avoid creating duplicate subscriptions
|
|
889
|
+
when a step is currently executing.
|
|
890
|
+
|
|
891
|
+
Args:
|
|
892
|
+
stream_id: Stream identifier
|
|
893
|
+
signal_type: Signal type to match
|
|
894
|
+
|
|
895
|
+
Returns:
|
|
896
|
+
List of dicts with step subscription info
|
|
897
|
+
"""
|
|
898
|
+
raise NotImplementedError("This storage backend does not support streams")
|
|
899
|
+
|
|
842
900
|
async def update_subscription_status(
|
|
843
901
|
self,
|
|
844
902
|
stream_id: str,
|