pyworkflow-engine 0.3.6__tar.gz → 0.3.7__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.3.6 → pyworkflow_engine-0.3.7}/PKG-INFO +1 -1
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyproject.toml +1 -1
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/__init__.py +8 -2
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/singleton.py +5 -1
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/tasks.py +22 -3
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/step_hook.py +72 -9
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_singleton.py +38 -0
- pyworkflow_engine-0.3.7/tests/unit/test_step_hook.py +400 -0
- pyworkflow_engine-0.3.6/tests/unit/test_step_hook.py +0 -202
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/CLAUDE.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/DISTRIBUTED.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/LICENSE +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/MANIFEST.in +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/README.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/RELEASING.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/architecture.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/cancellation.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/continue-as-new.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/events.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/fault-tolerance.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/hooks.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/limitations.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/schedules.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/sleep.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/step-context.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/steps.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/stream-steps.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/stream-workflows.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/concepts/workflows.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/conventions.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/guides/brokers.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/guides/cli.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/guides/configuration.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/harness-gaps.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/introduction.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/layers.md +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/docs/quickstart.mdx +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/docker-compose.yml +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/pyworkflow.config.yaml +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/basic.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/batch_processing.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/cancellation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/child_workflow_from_step.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/child_workflow_patterns.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/child_workflows.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/continue_as_new.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/fault_tolerance.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/hooks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/idempotency.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/long_running.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/retries.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/schedules.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/sleep_in_step.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/durable/workflows/step_context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/transient/01_basic_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/transient/02_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/transient/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/celery/transient/pyworkflow.config.yaml +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/01_basic_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/02_file_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/03_retries.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/04_long_running.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/05_event_log.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/06_idempotency.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/07_hooks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/08_cancellation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/09_child_workflows.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/10_child_workflow_patterns.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/11_continue_as_new.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/12_schedules.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/13_step_context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/14_child_workflow_from_step.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/durable/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/transient/01_quick_tasks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/transient/02_retries.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/transient/03_sleep.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/examples/local/transient/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/aws/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/aws/context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/aws/handler.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/aws/testing.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/app.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/loop.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/reschedule.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/celery/scheduler.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/__main__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/hooks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/quickstart.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/runs.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/scheduler.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/schedules.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/setup.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/worker.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/commands/workflows.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/output/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/output/formatters.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/output/styles.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/async_helpers.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/config.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/config_generator.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/discovery.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/docker_manager.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/interactive.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/cli/utils/storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/config.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/aws.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/base.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/local.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/mock.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/context/step_context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/exceptions.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/registry.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/scheduled.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/step.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/validation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/core/workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/discovery.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/engine/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/engine/events.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/engine/executor.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/engine/replay.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/observability/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/observability/logging.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/child_handle.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/child_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/continue_as_new.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/define_hook.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/hooks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/resume_hook.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/schedule.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/shield.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/sleep.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/primitives/step_checkpoint.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/runtime/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/runtime/base.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/runtime/celery.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/runtime/factory.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/runtime/local.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/scheduler/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/scheduler/local.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/serialization/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/serialization/decoder.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/serialization/encoder.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/base.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/cassandra.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/citus.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/config.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/dynamodb.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/file.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/memory.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/migrations/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/migrations/base.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/mysql.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/postgres.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/schemas.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/storage/sqlite.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/checkpoint.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/consumer.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/decorator.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/dispatcher.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/emit.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/registry.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/runtime.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/signal.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/streams/step_context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/tracing/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/tracing/base.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/tracing/factory.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/tracing/langfuse.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/tracing/types.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/utils/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/utils/duration.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/utils/helpers.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow/utils/schedule.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/pyworkflow_engine.egg-info/SOURCES.txt +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/setup.cfg +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/_reschedule_e2e_app.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_cancellation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_cassandra_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_child_workflows.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_continue_as_new.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_dynamodb_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_list_runs_pagination.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_reschedule.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_reschedule_e2e.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_schedule_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_schema_migrations.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_singleton.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_stream_e2e.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/integration/test_workflow_suspended.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/test_cassandra_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/test_citus_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/test_dynamodb_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/test_postgres_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/backends/test_sqlite_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/conftest.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/storage/__init__.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/storage/test_migrations.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_cancellation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_child_workflows.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_cli_worker.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_continue_as_new.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_emit.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_event_limits.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_executor.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_fault_tolerance.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_force_local.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_hooks.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_parent_run_id.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_primitives_from_steps.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_registry.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_replay.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_reschedule.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_retention.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_schedule_schemas.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_schedule_utils.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_scheduled_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_signal.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_step.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_step_checkpoint.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_step_context.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_stream_storage.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_stream_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_validation.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/tests/unit/test_workflow.py +0 -0
- {pyworkflow_engine-0.3.6 → pyworkflow_engine-0.3.7}/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.3.
|
|
10
|
+
version = "0.3.7"
|
|
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"
|
|
@@ -29,7 +29,7 @@ Quick Start:
|
|
|
29
29
|
>>> run_id = await start(my_workflow, "Alice")
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "0.3.
|
|
32
|
+
__version__ = "0.3.7"
|
|
33
33
|
|
|
34
34
|
# Configuration
|
|
35
35
|
from pyworkflow.config import (
|
|
@@ -140,7 +140,11 @@ from pyworkflow.primitives.step_checkpoint import (
|
|
|
140
140
|
load_step_checkpoint,
|
|
141
141
|
save_step_checkpoint,
|
|
142
142
|
)
|
|
143
|
-
from pyworkflow.primitives.step_hook import
|
|
143
|
+
from pyworkflow.primitives.step_hook import (
|
|
144
|
+
STEP_HOOK_TIMEOUT,
|
|
145
|
+
StepHookTimeout,
|
|
146
|
+
step_hook,
|
|
147
|
+
)
|
|
144
148
|
|
|
145
149
|
# Runtime
|
|
146
150
|
from pyworkflow.runtime import LocalRuntime, Runtime, get_runtime, register_runtime
|
|
@@ -290,6 +294,8 @@ __all__ = [
|
|
|
290
294
|
"load_step_checkpoint",
|
|
291
295
|
"delete_step_checkpoint",
|
|
292
296
|
"step_hook",
|
|
297
|
+
"StepHookTimeout",
|
|
298
|
+
"STEP_HOOK_TIMEOUT",
|
|
293
299
|
# Streams
|
|
294
300
|
"stream_workflow",
|
|
295
301
|
"stream_step",
|
|
@@ -323,7 +323,11 @@ class SingletonWorkflowTask(Task):
|
|
|
323
323
|
|
|
324
324
|
# Bind arguments to function signature
|
|
325
325
|
sig = inspect.signature(self.run)
|
|
326
|
-
|
|
326
|
+
bound_sig = sig.bind(*task_args, **task_kwargs)
|
|
327
|
+
# Include declared defaults so unique_on may reference parameters
|
|
328
|
+
# the caller did not pass explicitly (e.g. dedup_scope).
|
|
329
|
+
bound_sig.apply_defaults()
|
|
330
|
+
bound = bound_sig.arguments
|
|
327
331
|
|
|
328
332
|
unique_args: list[Any] = []
|
|
329
333
|
for key in unique_on:
|
|
@@ -371,6 +371,19 @@ def execute_step_task(
|
|
|
371
371
|
hook_id=hook_id or "",
|
|
372
372
|
)
|
|
373
373
|
)
|
|
374
|
+
# step_hook(on_timeout="return") carries the hook deadline: schedule
|
|
375
|
+
# a resume at expiry so the workflow wakes even if nobody ever calls
|
|
376
|
+
# resume_hook (the step re-executes and receives STEP_HOOK_TIMEOUT).
|
|
377
|
+
# A racing resume_hook is harmless — duplicate resumes are dropped
|
|
378
|
+
# by try_claim_run.
|
|
379
|
+
resume_at = e.data.get("resume_at")
|
|
380
|
+
if resume_at is not None:
|
|
381
|
+
schedule_workflow_resumption(
|
|
382
|
+
run_id,
|
|
383
|
+
resume_at,
|
|
384
|
+
storage_config,
|
|
385
|
+
triggered_by="step_hook_timeout",
|
|
386
|
+
)
|
|
374
387
|
return None
|
|
375
388
|
|
|
376
389
|
# Other SuspensionSignals are not supported from steps
|
|
@@ -2025,12 +2038,13 @@ async def _start_workflow_on_worker(
|
|
|
2025
2038
|
name="pyworkflow.resume_workflow",
|
|
2026
2039
|
base=SingletonWorkflowTask,
|
|
2027
2040
|
queue="pyworkflow.schedules",
|
|
2028
|
-
unique_on=["run_id"],
|
|
2041
|
+
unique_on=["run_id", "dedup_scope"],
|
|
2029
2042
|
)
|
|
2030
2043
|
def resume_workflow_task(
|
|
2031
2044
|
run_id: str,
|
|
2032
2045
|
storage_config: dict[str, Any] | None = None,
|
|
2033
2046
|
triggered_by_hook_id: str | None = None,
|
|
2047
|
+
dedup_scope: str = "immediate",
|
|
2034
2048
|
) -> Any | None:
|
|
2035
2049
|
"""
|
|
2036
2050
|
Resume a suspended workflow.
|
|
@@ -2703,10 +2717,15 @@ def schedule_workflow_resumption(
|
|
|
2703
2717
|
triggered_by=triggered_by,
|
|
2704
2718
|
)
|
|
2705
2719
|
|
|
2706
|
-
# Schedule the resume task
|
|
2720
|
+
# Schedule the resume task. Delayed (deadline) resumes hold their
|
|
2721
|
+
# singleton lock for the whole countdown; scoping them separately keeps
|
|
2722
|
+
# them from swallowing immediate resumes (e.g. resume_hook wake-ups)
|
|
2723
|
+
# enqueued while the countdown is pending. Extra resume executions are
|
|
2724
|
+
# harmless: try_claim_run drops all but the first.
|
|
2725
|
+
dedup_scope = "deadline" if delay_seconds > 0 else "immediate"
|
|
2707
2726
|
resume_workflow_task.apply_async(
|
|
2708
2727
|
args=[run_id],
|
|
2709
|
-
kwargs={"storage_config": storage_config},
|
|
2728
|
+
kwargs={"storage_config": storage_config, "dedup_scope": dedup_scope},
|
|
2710
2729
|
countdown=delay_seconds,
|
|
2711
2730
|
)
|
|
2712
2731
|
|
|
@@ -25,7 +25,8 @@ Usage:
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from collections.abc import Awaitable, Callable
|
|
28
|
-
from
|
|
28
|
+
from datetime import UTC, datetime, timedelta
|
|
29
|
+
from typing import Any, Literal
|
|
29
30
|
|
|
30
31
|
from loguru import logger
|
|
31
32
|
from pydantic import BaseModel
|
|
@@ -35,12 +36,29 @@ from pyworkflow.core.exceptions import SuspensionSignal
|
|
|
35
36
|
from pyworkflow.primitives.step_checkpoint import get_step_run_id
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
class StepHookTimeout:
|
|
40
|
+
"""Sentinel returned by ``step_hook(on_timeout="return")`` when the hook expires.
|
|
41
|
+
|
|
42
|
+
Check with ``isinstance(result, StepHookTimeout)`` or compare against the
|
|
43
|
+
``STEP_HOOK_TIMEOUT`` singleton.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
__slots__ = ()
|
|
47
|
+
|
|
48
|
+
def __repr__(self) -> str:
|
|
49
|
+
return "STEP_HOOK_TIMEOUT"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
STEP_HOOK_TIMEOUT = StepHookTimeout()
|
|
53
|
+
|
|
54
|
+
|
|
38
55
|
async def step_hook(
|
|
39
56
|
name: str,
|
|
40
57
|
*,
|
|
41
58
|
timeout: str | int | None = None,
|
|
42
59
|
on_created: Callable[[str], Awaitable[None]] | None = None,
|
|
43
60
|
payload_schema: type[BaseModel] | None = None,
|
|
61
|
+
on_timeout: Literal["suspend", "return"] = "suspend",
|
|
44
62
|
) -> Any:
|
|
45
63
|
"""
|
|
46
64
|
Wait for an external event from within a @step function.
|
|
@@ -58,9 +76,14 @@ async def step_hook(
|
|
|
58
76
|
timeout: Optional max wait time (str duration or seconds)
|
|
59
77
|
on_created: Optional async callback with the hook token
|
|
60
78
|
payload_schema: Optional Pydantic model for payload validation
|
|
79
|
+
on_timeout: What to do when ``timeout`` elapses without a resume.
|
|
80
|
+
"suspend" (default): keep waiting for resume_hook() — legacy behavior.
|
|
81
|
+
"return": the runtime schedules a resume at the deadline and this
|
|
82
|
+
call returns the ``STEP_HOOK_TIMEOUT`` sentinel on re-execution.
|
|
61
83
|
|
|
62
84
|
Returns:
|
|
63
|
-
Payload from resume_hook()
|
|
85
|
+
Payload from resume_hook(), or ``STEP_HOOK_TIMEOUT`` when the hook
|
|
86
|
+
expired and ``on_timeout="return"``
|
|
64
87
|
|
|
65
88
|
Raises:
|
|
66
89
|
RuntimeError: If called outside a step context
|
|
@@ -107,11 +130,11 @@ async def step_hook(
|
|
|
107
130
|
|
|
108
131
|
events = await storage.get_events(ctx.run_id)
|
|
109
132
|
hook_received = None
|
|
110
|
-
|
|
133
|
+
hook_created_event = None
|
|
111
134
|
|
|
112
135
|
for event in events:
|
|
113
136
|
if event.type == EventType.HOOK_CREATED and event.data.get("hook_id") == hook_id:
|
|
114
|
-
|
|
137
|
+
hook_created_event = event
|
|
115
138
|
elif event.type == EventType.HOOK_RECEIVED and event.data.get("hook_id") == hook_id:
|
|
116
139
|
hook_received = event
|
|
117
140
|
|
|
@@ -127,17 +150,53 @@ async def step_hook(
|
|
|
127
150
|
)
|
|
128
151
|
return payload
|
|
129
152
|
|
|
130
|
-
# If hook was already created but not received, re-suspend
|
|
131
|
-
if
|
|
153
|
+
# If hook was already created but not received, check expiry, else re-suspend
|
|
154
|
+
if hook_created_event is not None:
|
|
155
|
+
expires_at: datetime | None = None
|
|
156
|
+
expires_at_raw = hook_created_event.data.get("expires_at")
|
|
157
|
+
if expires_at_raw:
|
|
158
|
+
expires_at = datetime.fromisoformat(expires_at_raw)
|
|
159
|
+
|
|
160
|
+
if on_timeout == "return" and expires_at is not None and datetime.now(UTC) >= expires_at:
|
|
161
|
+
from pyworkflow.engine.events import create_hook_expired_event
|
|
162
|
+
from pyworkflow.storage.schemas import HookStatus
|
|
163
|
+
|
|
164
|
+
await storage.record_event(
|
|
165
|
+
create_hook_expired_event(run_id=ctx.run_id, hook_id=hook_id)
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
await storage.update_hook_status(hook_id, HookStatus.EXPIRED)
|
|
169
|
+
except Exception:
|
|
170
|
+
# Best-effort: the HOOK_EXPIRED event is authoritative for replay;
|
|
171
|
+
# a failed status update must not fail the step.
|
|
172
|
+
logger.warning(
|
|
173
|
+
f"Step hook '{name}' expired but status update failed",
|
|
174
|
+
run_id=ctx.run_id,
|
|
175
|
+
hook_id=hook_id,
|
|
176
|
+
)
|
|
177
|
+
logger.info(
|
|
178
|
+
f"Step hook '{name}' expired, returning timeout sentinel",
|
|
179
|
+
run_id=ctx.run_id,
|
|
180
|
+
hook_id=hook_id,
|
|
181
|
+
)
|
|
182
|
+
return STEP_HOOK_TIMEOUT
|
|
183
|
+
|
|
132
184
|
logger.debug(
|
|
133
185
|
f"Step hook '{name}' already created, re-suspending",
|
|
134
186
|
run_id=ctx.run_id,
|
|
135
187
|
hook_id=hook_id,
|
|
136
188
|
)
|
|
189
|
+
# Re-arm the deadline resume only for on_timeout="return" with a future
|
|
190
|
+
# deadline: the runtime schedules a resume at resume_at, and scheduling
|
|
191
|
+
# one in the past would busy-loop resume → re-suspend.
|
|
192
|
+
resume_data: dict[str, Any] = {}
|
|
193
|
+
if on_timeout == "return" and expires_at is not None and datetime.now(UTC) < expires_at:
|
|
194
|
+
resume_data["resume_at"] = expires_at
|
|
137
195
|
raise SuspensionSignal(
|
|
138
196
|
reason=f"step_hook:{hook_id}",
|
|
139
197
|
hook_id=hook_id,
|
|
140
198
|
step_id=step_run_id,
|
|
199
|
+
**resume_data,
|
|
141
200
|
)
|
|
142
201
|
|
|
143
202
|
# Parse timeout
|
|
@@ -179,8 +238,6 @@ async def step_hook(
|
|
|
179
238
|
payload_schema=payload_schema.__name__ if payload_schema else None,
|
|
180
239
|
)
|
|
181
240
|
if timeout_seconds:
|
|
182
|
-
from datetime import UTC, datetime, timedelta
|
|
183
|
-
|
|
184
241
|
hook_record.expires_at = datetime.now(UTC) + timedelta(seconds=timeout_seconds)
|
|
185
242
|
|
|
186
243
|
await storage.create_hook(hook_record)
|
|
@@ -197,9 +254,15 @@ async def step_hook(
|
|
|
197
254
|
if on_created:
|
|
198
255
|
await on_created(token)
|
|
199
256
|
|
|
200
|
-
# Raise SuspensionSignal to suspend the step
|
|
257
|
+
# Raise SuspensionSignal to suspend the step. For on_timeout="return",
|
|
258
|
+
# carry the deadline so the runtime schedules a resume at expiry — without
|
|
259
|
+
# it, an expired hook nobody resumes would suspend the workflow forever.
|
|
260
|
+
suspend_data: dict[str, Any] = {}
|
|
261
|
+
if on_timeout == "return" and hook_record.expires_at is not None:
|
|
262
|
+
suspend_data["resume_at"] = hook_record.expires_at
|
|
201
263
|
raise SuspensionSignal(
|
|
202
264
|
reason=f"step_hook:{hook_id}",
|
|
203
265
|
hook_id=hook_id,
|
|
204
266
|
step_id=step_run_id,
|
|
267
|
+
**suspend_data,
|
|
205
268
|
)
|
|
@@ -1007,3 +1007,41 @@ class TestReleaseLockKeyReproduction:
|
|
|
1007
1007
|
key_prefix="pyworkflow:lock:",
|
|
1008
1008
|
)
|
|
1009
1009
|
backend.unlock.assert_called_once_with(expected_key)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
class TestResumeDedupScope:
|
|
1013
|
+
"""The resume task's singleton key must separate immediate vs deadline resumes.
|
|
1014
|
+
|
|
1015
|
+
A deadline (countdown) resume holds its singleton lock for the whole
|
|
1016
|
+
countdown; without scope separation it swallows every immediate resume
|
|
1017
|
+
(e.g. resume_hook wake-ups) enqueued meanwhile.
|
|
1018
|
+
"""
|
|
1019
|
+
|
|
1020
|
+
def test_lock_key_differs_by_dedup_scope(self):
|
|
1021
|
+
from pyworkflow.celery.tasks import resume_workflow_task
|
|
1022
|
+
|
|
1023
|
+
immediate = resume_workflow_task.generate_lock(
|
|
1024
|
+
resume_workflow_task.name,
|
|
1025
|
+
["run_x"],
|
|
1026
|
+
{"storage_config": None, "dedup_scope": "immediate"},
|
|
1027
|
+
)
|
|
1028
|
+
deadline = resume_workflow_task.generate_lock(
|
|
1029
|
+
resume_workflow_task.name,
|
|
1030
|
+
["run_x"],
|
|
1031
|
+
{"storage_config": None, "dedup_scope": "deadline"},
|
|
1032
|
+
)
|
|
1033
|
+
assert immediate != deadline
|
|
1034
|
+
|
|
1035
|
+
def test_lock_key_defaults_to_immediate_scope(self):
|
|
1036
|
+
"""Callers not passing dedup_scope must match explicit 'immediate'."""
|
|
1037
|
+
from pyworkflow.celery.tasks import resume_workflow_task
|
|
1038
|
+
|
|
1039
|
+
default_key = resume_workflow_task.generate_lock(
|
|
1040
|
+
resume_workflow_task.name, ["run_x"], {"storage_config": None}
|
|
1041
|
+
)
|
|
1042
|
+
explicit_key = resume_workflow_task.generate_lock(
|
|
1043
|
+
resume_workflow_task.name,
|
|
1044
|
+
["run_x"],
|
|
1045
|
+
{"storage_config": None, "dedup_scope": "immediate"},
|
|
1046
|
+
)
|
|
1047
|
+
assert default_key == explicit_key
|