pydocket 0.26.0__tar.gz → 0.26.2__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.
- {pydocket-0.26.0 → pydocket-0.26.2}/.coveragerc-core +2 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/.gitignore +4 -0
- pydocket-0.26.2/.pre-commit-config.yaml +16 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/PKG-INFO +13 -16
- {pydocket-0.26.0 → pydocket-0.26.2}/README.md +11 -14
- {pydocket-0.26.0 → pydocket-0.26.2}/pyproject.toml +12 -19
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_execution_progress.py +1 -17
- pydocket-0.26.2/src/docket/_execution_scripts.py +97 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_lua.py +70 -32
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_redelivery.py +1 -8
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_redis.py +57 -4
- pydocket-0.26.2/src/docket/_redis_credentials.py +115 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_redis_sentinel.py +8 -2
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_result_store.py +1 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_concurrency.py +4 -305
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_debounce.py +1 -39
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_ratelimit.py +1 -30
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/docket.py +10 -1
- pydocket-0.26.2/src/docket/lua/acquire_or_park.lua +97 -0
- pydocket-0.26.2/src/docket/lua/cancel_cleanup.lua +20 -0
- pydocket-0.26.2/src/docket/lua/cancel_task.lua +56 -0
- pydocket-0.26.2/src/docket/lua/claim.lua +109 -0
- pydocket-0.26.2/src/docket/lua/debounce.lua +42 -0
- pydocket-0.26.2/src/docket/lua/progress_write.lua +18 -0
- pydocket-0.26.2/src/docket/lua/ratelimit.lua +33 -0
- pydocket-0.26.2/src/docket/lua/refresh_lease.lua +8 -0
- pydocket-0.26.2/src/docket/lua/release_and_wake.lua +111 -0
- pydocket-0.26.2/src/docket/lua/scavenge_and_wake.lua +105 -0
- pydocket-0.26.2/src/docket/lua/schedule.lua +216 -0
- pydocket-0.26.2/src/docket/lua/stream_due_tasks.lua +65 -0
- pydocket-0.26.2/src/docket/lua/terminal.lua +66 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/strikelist.py +5 -1
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/worker.py +20 -66
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/_container.py +14 -9
- pydocket-0.26.2/tests/_rotating_provider.py +42 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cluster/Dockerfile +1 -1
- pydocket-0.26.2/tests/concurrency_limits/test_parked_status.py +40 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_waiter_queue.py +11 -21
- pydocket-0.26.2/tests/concurrency_limits/waiters.py +21 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/conftest.py +59 -17
- pydocket-0.26.2/tests/lua/calc.lua +11 -0
- pydocket-0.26.2/tests/lua/count_argv.lua +5 -0
- pydocket-0.26.2/tests/lua/echo_bool.lua +4 -0
- pydocket-0.26.2/tests/lua/echo_described_key.lua +3 -0
- pydocket-0.26.2/tests/lua/echo_dict.lua +4 -0
- pydocket-0.26.2/tests/lua/echo_keys_and_args.lua +6 -0
- pydocket-0.26.2/tests/lua/echo_list.lua +4 -0
- pydocket-0.26.2/tests/lua/echo_numbers.lua +5 -0
- pydocket-0.26.2/tests/lua/echo_tuple.lua +4 -0
- pydocket-0.26.2/tests/lua/extra_binding.lua +4 -0
- pydocket-0.26.2/tests/lua/join_variadic.lua +10 -0
- pydocket-0.26.2/tests/lua/misdecoded_flag.lua +4 -0
- pydocket-0.26.2/tests/lua/noscript_echo.lua +3 -0
- pydocket-0.26.2/tests/test_credential_provider.py +164 -0
- pydocket-0.26.2/tests/test_credential_rotation.py +265 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_lua_decorator.py +52 -100
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_pubsub_resp_version.py +2 -2
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_results_storage.py +1 -1
- pydocket-0.26.2/tests/test_reused_keys.py +113 -0
- pydocket-0.26.2/tests/worker/test_strikes_at_startup.py +68 -0
- pydocket-0.26.0/.claude/skills/audit-docs/SKILL.md +0 -95
- pydocket-0.26.0/.codespellrc +0 -3
- pydocket-0.26.0/.dockerignore +0 -11
- pydocket-0.26.0/.github/codecov.yml +0 -33
- pydocket-0.26.0/.github/dependabot.yml +0 -23
- pydocket-0.26.0/.github/workflows/chaos.yml +0 -56
- pydocket-0.26.0/.github/workflows/ci.yml +0 -276
- pydocket-0.26.0/.github/workflows/docs.yml +0 -38
- pydocket-0.26.0/.github/workflows/publish.yml +0 -46
- pydocket-0.26.0/.pre-commit-config.yaml +0 -47
- pydocket-0.26.0/.readthedocs.yaml +0 -11
- pydocket-0.26.0/AGENTS.md +0 -164
- pydocket-0.26.0/Dockerfile +0 -15
- pydocket-0.26.0/SECURITY.md +0 -22
- pydocket-0.26.0/chaos/README.md +0 -6
- pydocket-0.26.0/chaos/driver.py +0 -348
- pydocket-0.26.0/chaos/producer.py +0 -51
- pydocket-0.26.0/chaos/redis.py +0 -46
- pydocket-0.26.0/chaos/run +0 -8
- pydocket-0.26.0/chaos/signals.py +0 -345
- pydocket-0.26.0/chaos/tasks.py +0 -42
- pydocket-0.26.0/docker-compose.yml +0 -17
- pydocket-0.26.0/docs/api-reference.md +0 -3
- pydocket-0.26.0/docs/dependency-injection.md +0 -607
- pydocket-0.26.0/docs/getting-started.md +0 -284
- pydocket-0.26.0/docs/index.md +0 -82
- pydocket-0.26.0/docs/observability.md +0 -352
- pydocket-0.26.0/docs/production.md +0 -577
- pydocket-0.26.0/docs/task-behaviors.md +0 -655
- pydocket-0.26.0/docs/task-patterns.md +0 -318
- pydocket-0.26.0/docs/testing.md +0 -422
- pydocket-0.26.0/examples/agenda_scatter.py +0 -128
- pydocket-0.26.0/examples/common.py +0 -67
- pydocket-0.26.0/examples/concurrency_control.py +0 -114
- pydocket-0.26.0/examples/fallback_arithmetic.py +0 -109
- pydocket-0.26.0/examples/fastapi_background_tasks.py +0 -204
- pydocket-0.26.0/examples/find_and_flood.py +0 -41
- pydocket-0.26.0/examples/local_development.py +0 -98
- pydocket-0.26.0/examples/self_contained_service.py +0 -62
- pydocket-0.26.0/examples/self_perpetuating.py +0 -36
- pydocket-0.26.0/examples/task_progress.py +0 -109
- pydocket-0.26.0/loq.toml +0 -37
- pydocket-0.26.0/mkdocs.yml +0 -81
- pydocket-0.26.0/src/docket/_execution_scripts.py +0 -481
- pydocket-0.26.0/telemetry/.gitignore +0 -1
- pydocket-0.26.0/telemetry/start +0 -9
- pydocket-0.26.0/telemetry/stop +0 -8
- pydocket-0.26.0/tests/__init__.py +0 -0
- pydocket-0.26.0/tests/cli/__init__.py +0 -0
- pydocket-0.26.0/tests/fundamentals/CLAUDE.md +0 -1
- pydocket-0.26.0/uv.lock +0 -2327
- {pydocket-0.26.0 → pydocket-0.26.2}/.coveragerc-cli +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/LICENSE +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/__main__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_cancellation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_docket_snapshot.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_prometheus_exporter.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_redis_memory.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_telemetry.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/_uuid7.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/agenda.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/annotations.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/cli/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/cli/_support.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_base.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_contextual.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_cooldown.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_cron.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_functional.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_perpetual.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_progress.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_resolution.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_retry.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/dependencies/_timeout.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/execution.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/instrumentation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/py.typed +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/tasks.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/src/docket/testing.py +0 -0
- {pydocket-0.26.0/chaos → pydocket-0.26.2/tests}/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/_key_leak_checker.py +0 -0
- {pydocket-0.26.0/examples → pydocket-0.26.2/tests/cli}/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/run.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_clear.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_iterate_with_timeout.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_module.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_parsing.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_snapshot.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_striking.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_tasks.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_url_validation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_version.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_watch.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_worker.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/test_workers.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cli/waiting.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cluster/entrypoint.sh +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/cluster/start-cluster.sh +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/__init__.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/overlap.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_annotated.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_basic.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_errors_and_resilience.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_execution_patterns.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_redelivery.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_waiter_safeguard.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/concurrency_limits/test_worker_mechanics.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/AGENTS.md +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2/tests/fundamentals}/CLAUDE.md +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/conftest.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_async_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_builtin_tasks.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_call_arguments.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_cancellation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_context_injection.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_cron.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_errors.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_idempotency.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_logging.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_perpetual.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_progress_state.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_results.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_retries.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_scheduling.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_self_perpetuation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_shared_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_striking.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_sync_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/fundamentals/test_timeouts.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/instrumentation/test_counters.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/instrumentation/test_export.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/instrumentation/test_tracing.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/sitecustomize.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_agenda.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_annotated_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_automatic_perpetual_recovery.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_batch_scheduling.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_call_arguments.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_cancel_queued_task.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_cancellation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_claim_supersedes.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_cooldown.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_debounce.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_dependencies_advanced.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_dependencies_core.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_dependency_uniqueness.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_depends_typing.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_clear.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_execution.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_isolation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_keys.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_registration.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_docket_snapshot.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_execution.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_execution_state.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_exponential_retry_overflow.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_fallback_task.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_get_result_cancellation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_handler_semantics.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_immediate_retry_state.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_interrupted_tasks.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_key_leak_protection.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_lease_renewal.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_memory_backend.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_payload_escaping.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_perpetual_handoff.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_perpetual_race.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_perpetual_reliability.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_perpetual_reschedule.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_perpetual_state.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_progress_basics.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_progress_clobber.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_progress_pubsub.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_ratelimit.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_redelivery.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_redelivery_sweep.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_redis_connection.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_reschedule_clears_worker.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_results_retrieval.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_sentinel_urls.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_socket_timeout.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_strikelist.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_striking.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_task_cycle_round_trips.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_terminal_acked.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_testing.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_uncalled_for_contract.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_uuid7.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_worker_cancellation.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_worker_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_worker_single_dependencies.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/test_worker_task_deps_interaction.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_bootstrap.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_core.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_crash.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_delivery_batch.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_invariants.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_lifecycle.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_liveness.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_reconnection.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_scheduling.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_task_factory.py +0 -0
- {pydocket-0.26.0 → pydocket-0.26.2}/tests/worker/test_ttl_zero.py +0 -0
|
@@ -10,11 +10,13 @@ omit =
|
|
|
10
10
|
**/conftest.py
|
|
11
11
|
tests/_key_leak_checker.py
|
|
12
12
|
tests/_container.py
|
|
13
|
+
tests/_rotating_provider.py
|
|
13
14
|
tests/sitecustomize.py
|
|
14
15
|
tests/cli/*.py
|
|
15
16
|
# real-Redis-only; skips on the memory and cluster legs
|
|
16
17
|
tests/test_socket_timeout.py
|
|
17
18
|
tests/test_pubsub_resp_version.py
|
|
19
|
+
tests/test_credential_rotation.py
|
|
18
20
|
|
|
19
21
|
[report]
|
|
20
22
|
exclude_also =
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# The root config's hooks, ruff among them, also cover this directory.
|
|
2
|
+
repos:
|
|
3
|
+
- repo: local
|
|
4
|
+
hooks:
|
|
5
|
+
- id: pyright
|
|
6
|
+
name: pyright (docket package)
|
|
7
|
+
entry: uv run pyright --verifytypes docket --ignoreexternal
|
|
8
|
+
language: system
|
|
9
|
+
types: [python]
|
|
10
|
+
pass_filenames: false
|
|
11
|
+
- id: pyright
|
|
12
|
+
name: pyright (tests and conformance agent)
|
|
13
|
+
entry: uv run pyright tests conformance-agent
|
|
14
|
+
language: system
|
|
15
|
+
types: [python]
|
|
16
|
+
pass_filenames: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pydocket
|
|
3
|
-
Version: 0.26.
|
|
3
|
+
Version: 0.26.2
|
|
4
4
|
Summary: A distributed background task system for Python functions
|
|
5
5
|
Project-URL: Homepage, https://docket.lol/
|
|
6
6
|
Project-URL: Documentation, https://docket.lol/en/latest/
|
|
@@ -36,7 +36,7 @@ Requires-Dist: opentelemetry-api>=1.33.0
|
|
|
36
36
|
Requires-Dist: prometheus-client>=0.21.1
|
|
37
37
|
Requires-Dist: py-key-value-aio[memory,redis]>=0.3.0
|
|
38
38
|
Requires-Dist: python-json-logger>=2.0.7
|
|
39
|
-
Requires-Dist: redis>=
|
|
39
|
+
Requires-Dist: redis>=6
|
|
40
40
|
Requires-Dist: rich>=13.9.4
|
|
41
41
|
Requires-Dist: taskgroup>=0.2.2; python_version < '3.11'
|
|
42
42
|
Requires-Dist: typer>=0.15.1
|
|
@@ -52,7 +52,7 @@ on the scheduling of future work as seamlessly and efficiently as immediate work
|
|
|
52
52
|
|
|
53
53
|
[](https://pypi.org/project/pydocket/)
|
|
54
54
|
[](https://pypi.org/project/pydocket/)
|
|
55
|
-
[](https://github.com/chrisguidry/docket/actions
|
|
55
|
+
[](https://github.com/chrisguidry/docket/actions)
|
|
56
56
|
[](https://app.codecov.io/gh/chrisguidry/docket)
|
|
57
57
|
[](https://github.com/chrisguidry/docket/blob/main/LICENSE)
|
|
58
58
|
[](https://docket.lol/)
|
|
@@ -130,11 +130,11 @@ With `pip`:
|
|
|
130
130
|
pip install pydocket
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
Docket requires
|
|
134
|
-
|
|
133
|
+
Docket requires [Redis](https://redis.io/) 6.2 or later, or
|
|
134
|
+
[Valkey](https://valkey.io/) 8.0 or later. Docket is tested with:
|
|
135
135
|
|
|
136
|
-
- Redis 6.2
|
|
137
|
-
-
|
|
136
|
+
- Redis 6.2 and 8.10, and Redis 8.10 in cluster mode
|
|
137
|
+
- Valkey 8.0 and 9.1
|
|
138
138
|
- In-memory backend via [burner-redis](https://github.com/prefectlabs/burner-redis) for testing
|
|
139
139
|
|
|
140
140
|
For testing without Redis, use the in-memory backend:
|
|
@@ -151,17 +151,14 @@ See [Testing with Docket](https://docket.lol/en/latest/testing/#using-in-memory-
|
|
|
151
151
|
|
|
152
152
|
# Hacking on `docket`
|
|
153
153
|
|
|
154
|
-
We use [`uv`](https://docs.astral.sh/uv/) for project management
|
|
155
|
-
|
|
154
|
+
We use [`uv`](https://docs.astral.sh/uv/) for project management. The
|
|
155
|
+
repository is a uv workspace, and the Python package lives in its `python/`
|
|
156
|
+
directory. From a clone of the repository:
|
|
156
157
|
|
|
157
158
|
```bash
|
|
158
159
|
uv sync
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
The to run the test suite:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
pytest
|
|
160
|
+
cd python
|
|
161
|
+
uv run pytest
|
|
165
162
|
```
|
|
166
163
|
|
|
167
164
|
We aim to maintain 100% test coverage, which is required for all PRs to `docket`. We
|
|
@@ -170,7 +167,7 @@ begins with testing all the dusty branches and corners. This will give us the
|
|
|
170
167
|
confidence to upgrade dependencies quickly and to adapt to new versions of Redis over
|
|
171
168
|
time.
|
|
172
169
|
|
|
173
|
-
To work on the documentation locally:
|
|
170
|
+
To work on the documentation locally, from the repository root:
|
|
174
171
|
|
|
175
172
|
```bash
|
|
176
173
|
uv sync
|
|
@@ -3,7 +3,7 @@ on the scheduling of future work as seamlessly and efficiently as immediate work
|
|
|
3
3
|
|
|
4
4
|
[](https://pypi.org/project/pydocket/)
|
|
5
5
|
[](https://pypi.org/project/pydocket/)
|
|
6
|
-
[](https://github.com/chrisguidry/docket/actions
|
|
6
|
+
[](https://github.com/chrisguidry/docket/actions)
|
|
7
7
|
[](https://app.codecov.io/gh/chrisguidry/docket)
|
|
8
8
|
[](https://github.com/chrisguidry/docket/blob/main/LICENSE)
|
|
9
9
|
[](https://docket.lol/)
|
|
@@ -81,11 +81,11 @@ With `pip`:
|
|
|
81
81
|
pip install pydocket
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Docket requires
|
|
85
|
-
|
|
84
|
+
Docket requires [Redis](https://redis.io/) 6.2 or later, or
|
|
85
|
+
[Valkey](https://valkey.io/) 8.0 or later. Docket is tested with:
|
|
86
86
|
|
|
87
|
-
- Redis 6.2
|
|
88
|
-
-
|
|
87
|
+
- Redis 6.2 and 8.10, and Redis 8.10 in cluster mode
|
|
88
|
+
- Valkey 8.0 and 9.1
|
|
89
89
|
- In-memory backend via [burner-redis](https://github.com/prefectlabs/burner-redis) for testing
|
|
90
90
|
|
|
91
91
|
For testing without Redis, use the in-memory backend:
|
|
@@ -102,17 +102,14 @@ See [Testing with Docket](https://docket.lol/en/latest/testing/#using-in-memory-
|
|
|
102
102
|
|
|
103
103
|
# Hacking on `docket`
|
|
104
104
|
|
|
105
|
-
We use [`uv`](https://docs.astral.sh/uv/) for project management
|
|
106
|
-
|
|
105
|
+
We use [`uv`](https://docs.astral.sh/uv/) for project management. The
|
|
106
|
+
repository is a uv workspace, and the Python package lives in its `python/`
|
|
107
|
+
directory. From a clone of the repository:
|
|
107
108
|
|
|
108
109
|
```bash
|
|
109
110
|
uv sync
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
The to run the test suite:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
pytest
|
|
111
|
+
cd python
|
|
112
|
+
uv run pytest
|
|
116
113
|
```
|
|
117
114
|
|
|
118
115
|
We aim to maintain 100% test coverage, which is required for all PRs to `docket`. We
|
|
@@ -121,7 +118,7 @@ begins with testing all the dusty branches and corners. This will give us the
|
|
|
121
118
|
confidence to upgrade dependencies quickly and to adapt to new versions of Redis over
|
|
122
119
|
time.
|
|
123
120
|
|
|
124
|
-
To work on the documentation locally:
|
|
121
|
+
To work on the documentation locally, from the repository root:
|
|
125
122
|
|
|
126
123
|
```bash
|
|
127
124
|
uv sync
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
"prometheus-client>=0.21.1",
|
|
33
33
|
"py-key-value-aio[memory,redis]>=0.3.0",
|
|
34
34
|
"python-json-logger>=2.0.7",
|
|
35
|
-
"redis>=
|
|
35
|
+
"redis>=6",
|
|
36
36
|
"rich>=13.9.4",
|
|
37
37
|
"typer>=0.15.1",
|
|
38
38
|
"typing_extensions>=4.12.0",
|
|
@@ -47,12 +47,9 @@ metrics = [
|
|
|
47
47
|
|
|
48
48
|
[dependency-groups]
|
|
49
49
|
dev = [
|
|
50
|
-
"codespell>=2.4.1",
|
|
51
50
|
"docker>=7.1.0",
|
|
52
51
|
# Security: urllib3 >=2.6.3 fixes decompression bomb vulnerabilities
|
|
53
52
|
"urllib3>=2.6.3",
|
|
54
|
-
"ipython>=8.0.0",
|
|
55
|
-
"loq>=0.1.0a3",
|
|
56
53
|
"opentelemetry-distro>=0.60b0",
|
|
57
54
|
"opentelemetry-exporter-otlp>=1.33.0",
|
|
58
55
|
"opentelemetry-exporter-prometheus>=0.60b0",
|
|
@@ -60,7 +57,6 @@ dev = [
|
|
|
60
57
|
"opentelemetry-instrumentation-logging>=0.60b0",
|
|
61
58
|
"opentelemetry-instrumentation-redis>=0.60b0",
|
|
62
59
|
"opentelemetry-sdk>=1.33.0",
|
|
63
|
-
"prek>=0.3.1",
|
|
64
60
|
"pyright>=1.1.408",
|
|
65
61
|
"pytest>=9.0.2",
|
|
66
62
|
"pytest-asyncio>=0.24.0",
|
|
@@ -71,16 +67,6 @@ dev = [
|
|
|
71
67
|
"pytest-timeout>=2.4.0",
|
|
72
68
|
"pytest-xdist>=3.6.1",
|
|
73
69
|
"ruff>=0.14.14",
|
|
74
|
-
"zensical>=0.0.32",
|
|
75
|
-
"mkdocstrings>=1.0.2",
|
|
76
|
-
"mkdocstrings-python>=2.0.1",
|
|
77
|
-
]
|
|
78
|
-
examples = [
|
|
79
|
-
"fastapi>=0.120.0",
|
|
80
|
-
"pydantic>=2.12.5",
|
|
81
|
-
# Security: starlette >=0.49.1 fixes session middleware vulnerability
|
|
82
|
-
"starlette>=0.49.1",
|
|
83
|
-
"uvicorn>=0.40.0",
|
|
84
70
|
]
|
|
85
71
|
|
|
86
72
|
[project.scripts]
|
|
@@ -94,6 +80,11 @@ docket = "docket.__main__:app"
|
|
|
94
80
|
|
|
95
81
|
[tool.hatch.version]
|
|
96
82
|
source = "vcs"
|
|
83
|
+
# Releases before the monorepo are tagged bare (0.26.0), and later ones
|
|
84
|
+
# python/v0.27.0. Matching only these keeps another language's tag, like
|
|
85
|
+
# rust/v0.1.0, out of pydocket's version.
|
|
86
|
+
tag-pattern = "^(?:python/v)?(?P<version>\\d+\\.\\d+\\.\\d+.*)$"
|
|
87
|
+
raw-options = { root = "..", scm = { git = { describe_command = "git describe --dirty --tags --long --match python/v* --match [0-9]*" } } }
|
|
97
88
|
|
|
98
89
|
[tool.hatch.metadata]
|
|
99
90
|
allow-direct-references = true
|
|
@@ -102,8 +93,8 @@ allow-direct-references = true
|
|
|
102
93
|
[tool.hatch.build.targets.wheel]
|
|
103
94
|
packages = ["src/docket"]
|
|
104
95
|
|
|
105
|
-
[tool.
|
|
106
|
-
|
|
96
|
+
[tool.hatch.build.targets.sdist]
|
|
97
|
+
exclude = ["conformance-agent"]
|
|
107
98
|
|
|
108
99
|
[tool.pytest.ini_options]
|
|
109
100
|
addopts = [
|
|
@@ -160,9 +151,11 @@ parallel = true
|
|
|
160
151
|
exclude_also = ["\\.\\.\\.$"]
|
|
161
152
|
|
|
162
153
|
[tool.pyright]
|
|
163
|
-
include = ["src", "tests", "
|
|
154
|
+
include = ["src", "tests", "conformance-agent"]
|
|
155
|
+
# The agent runs as a directory, so its modules import each other from there.
|
|
156
|
+
executionEnvironments = [{ root = "conformance-agent" }]
|
|
164
157
|
typeCheckingMode = "strict"
|
|
165
|
-
venvPath = "
|
|
158
|
+
venvPath = ".."
|
|
166
159
|
venv = ".venv"
|
|
167
160
|
reportUnnecessaryTypeIgnoreComment = "error"
|
|
168
161
|
reportUnnecessaryCast = "error"
|
|
@@ -32,23 +32,7 @@ async def _progress_write(
|
|
|
32
32
|
payload: Arg[str],
|
|
33
33
|
clear_message: Arg[bool],
|
|
34
34
|
fields: Args[dict[str, str]],
|
|
35
|
-
) -> bytes:
|
|
36
|
-
"""
|
|
37
|
-
local hset_args = {}
|
|
38
|
-
for i = fields_start, #ARGV, 2 do
|
|
39
|
-
hset_args[#hset_args + 1] = ARGV[i]
|
|
40
|
-
hset_args[#hset_args + 1] = ARGV[i + 1]
|
|
41
|
-
end
|
|
42
|
-
if #hset_args > 0 then
|
|
43
|
-
redis.call('HSET', progress_key, unpack(hset_args))
|
|
44
|
-
end
|
|
45
|
-
if clear_message then
|
|
46
|
-
redis.call('HDEL', progress_key, 'message')
|
|
47
|
-
end
|
|
48
|
-
redis.call('PUBLISH', progress_key, payload)
|
|
49
|
-
return 'OK'
|
|
50
|
-
"""
|
|
51
|
-
...
|
|
35
|
+
) -> bytes: ...
|
|
52
36
|
|
|
53
37
|
|
|
54
38
|
class ProgressEvent(TypedDict):
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""The Lua that moves one task key between lifecycle states.
|
|
2
|
+
|
|
3
|
+
Every transition in a task's life is one atomic script against the keys for
|
|
4
|
+
a single task: ``_schedule`` puts it on the stream or the queue, ``_claim``
|
|
5
|
+
takes it for a worker, ``_terminal`` records how it finished, and
|
|
6
|
+
``_cancel_task`` takes it back off. The first three each check the
|
|
7
|
+
``generation`` counter in the runs hash, so a stale attempt can never
|
|
8
|
+
overwrite a newer one.
|
|
9
|
+
|
|
10
|
+
``Execution`` and ``Docket`` call these; the scripts hold no Python logic of
|
|
11
|
+
their own.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from ._lua import Arg, Args, Key, redis_script
|
|
17
|
+
from ._redis import RedisClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@redis_script
|
|
21
|
+
async def _schedule(
|
|
22
|
+
redis: RedisClient,
|
|
23
|
+
*,
|
|
24
|
+
stream_key: Key[str],
|
|
25
|
+
known_key: Key[str],
|
|
26
|
+
parked_key: Key[str],
|
|
27
|
+
queue_key: Key[str],
|
|
28
|
+
stream_id_key: Key[str],
|
|
29
|
+
runs_key: Key[str],
|
|
30
|
+
state_channel: Key[str],
|
|
31
|
+
task_key: Arg[str],
|
|
32
|
+
when_timestamp: Arg[float],
|
|
33
|
+
is_immediate: Arg[bool],
|
|
34
|
+
replace: Arg[bool],
|
|
35
|
+
reschedule_message_id: Arg[bytes],
|
|
36
|
+
expected_generation: Arg[int],
|
|
37
|
+
worker_group_name: Arg[str],
|
|
38
|
+
state_payload: Arg[str],
|
|
39
|
+
message: Args[dict[bytes, bytes]],
|
|
40
|
+
) -> bytes | str: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@redis_script
|
|
44
|
+
async def _claim(
|
|
45
|
+
redis: RedisClient,
|
|
46
|
+
*,
|
|
47
|
+
runs_key: Key[str],
|
|
48
|
+
progress_key: Key[str],
|
|
49
|
+
known_key: Key[str],
|
|
50
|
+
stream_id_key: Key[str],
|
|
51
|
+
state_channel: Key[str],
|
|
52
|
+
stream_key: Key[str],
|
|
53
|
+
worker: Arg[str],
|
|
54
|
+
started_at: Arg[str],
|
|
55
|
+
generation: Arg[int],
|
|
56
|
+
state_payload: Arg[str],
|
|
57
|
+
key_json: Arg[str],
|
|
58
|
+
worker_group_name: Arg[str],
|
|
59
|
+
message_id: Arg[bytes],
|
|
60
|
+
) -> list[Any]: ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@redis_script
|
|
64
|
+
async def _terminal(
|
|
65
|
+
redis: RedisClient,
|
|
66
|
+
*,
|
|
67
|
+
runs_key: Key[str],
|
|
68
|
+
state_channel: Key[str],
|
|
69
|
+
progress_key: Key[str],
|
|
70
|
+
stream_key: Key[str],
|
|
71
|
+
generation: Arg[int],
|
|
72
|
+
state: Arg[str],
|
|
73
|
+
completed_at: Arg[str],
|
|
74
|
+
ttl_seconds: Arg[int],
|
|
75
|
+
state_payload: Arg[str],
|
|
76
|
+
worker_group_name: Arg[str],
|
|
77
|
+
message_id: Arg[bytes],
|
|
78
|
+
extra_fields: Args[list[str]],
|
|
79
|
+
) -> bytes: ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@redis_script
|
|
83
|
+
async def _cancel_task(
|
|
84
|
+
redis: RedisClient,
|
|
85
|
+
*,
|
|
86
|
+
stream_key: Key[str],
|
|
87
|
+
known_key: Key[str],
|
|
88
|
+
parked_key: Key[str],
|
|
89
|
+
queue_key: Key[str],
|
|
90
|
+
stream_id_key: Key[str],
|
|
91
|
+
runs_key: Key[str],
|
|
92
|
+
progress_key: Key[str],
|
|
93
|
+
state_channel: Key[str],
|
|
94
|
+
task_key: Arg[str],
|
|
95
|
+
completed_at: Arg[str],
|
|
96
|
+
state_payload: Arg[str],
|
|
97
|
+
) -> bytes: ...
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The ``@redis_script`` decorator collapses each Lua-backed Redis operation
|
|
4
4
|
into a single callable :class:`RedisScript` whose signature *is* the
|
|
5
|
-
declared function's calling contract
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
declared function's calling contract. The Lua source lives in
|
|
6
|
+
``protocol/`` at the repository root, shared by every language's docket,
|
|
7
|
+
and a copy ships in this package's ``lua/`` directory. Compared with the
|
|
8
|
+
hand-rolled ``redis.register_script`` + lazy-singleton pattern, every
|
|
9
|
+
script gains:
|
|
8
10
|
|
|
9
11
|
* SHA1 computed once at decoration time and reused forever -- no
|
|
10
12
|
per-call ``register_script`` hash work.
|
|
@@ -34,25 +36,36 @@ Authoring shape:
|
|
|
34
36
|
worker: Arg[str],
|
|
35
37
|
started_at: Arg[str],
|
|
36
38
|
generation: Arg[int],
|
|
37
|
-
) -> bytes:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
) -> bytes: ...
|
|
40
|
+
|
|
41
|
+
The stub's name picks its script: ``_claim`` runs ``lua/claim.lua``. Each
|
|
42
|
+
script starts with a header that binds ``KEYS`` and ``ARGV`` to local
|
|
43
|
+
names, then a blank line, then the body:
|
|
44
|
+
|
|
45
|
+
.. code-block:: lua
|
|
46
|
+
|
|
47
|
+
local runs_key = KEYS[1]
|
|
48
|
+
local progress_key = KEYS[2]
|
|
49
|
+
local worker = ARGV[1]
|
|
50
|
+
local started_at = ARGV[2]
|
|
51
|
+
local generation = tonumber(ARGV[3])
|
|
52
|
+
|
|
53
|
+
-- ... Lua body ...
|
|
54
|
+
return 'OK'
|
|
55
|
+
|
|
56
|
+
The header is the calling contract that every language follows, so the
|
|
57
|
+
decorator checks it against the signature when the module loads, and a
|
|
58
|
+
script whose header and signature disagree fails at import.
|
|
49
59
|
"""
|
|
50
60
|
|
|
51
61
|
from __future__ import annotations
|
|
52
62
|
|
|
53
63
|
import functools
|
|
54
64
|
import inspect
|
|
65
|
+
import sys
|
|
66
|
+
from importlib.resources import files
|
|
55
67
|
from typing import (
|
|
68
|
+
TYPE_CHECKING,
|
|
56
69
|
Annotated,
|
|
57
70
|
Any,
|
|
58
71
|
Awaitable,
|
|
@@ -73,6 +86,12 @@ from redis.commands.core import AsyncScript
|
|
|
73
86
|
|
|
74
87
|
from ._redis import Pipeline, RedisClient
|
|
75
88
|
|
|
89
|
+
if TYPE_CHECKING:
|
|
90
|
+
if sys.version_info >= (3, 11):
|
|
91
|
+
from importlib.resources.abc import Traversable
|
|
92
|
+
else:
|
|
93
|
+
from importlib.abc import Traversable
|
|
94
|
+
|
|
76
95
|
|
|
77
96
|
# Marker classes used as ``Annotated`` metadata. The class objects
|
|
78
97
|
# themselves go into ``__metadata__`` -- no instances needed -- and the
|
|
@@ -323,23 +342,29 @@ class RedisScript(Generic[_P, _R]):
|
|
|
323
342
|
pipeline.eval(self.lua, len(keys), *keys, *argv)
|
|
324
343
|
|
|
325
344
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
) -> RedisScript[_P, _R]:
|
|
329
|
-
"""Wrap an async function declaring a Lua script as its docstring.
|
|
345
|
+
class ScriptDirectory:
|
|
346
|
+
"""A directory of Lua scripts, used as the ``@redis_script`` decorator.
|
|
330
347
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
348
|
+
Decorating a stub returns a :class:`RedisScript` that runs the file
|
|
349
|
+
named after the stub, without its leading underscores: ``_claim`` runs
|
|
350
|
+
``claim.lua``. Call it exactly like the declared function to execute
|
|
351
|
+
immediately, or use ``.enqueue(pipeline, ...)`` to queue it on a
|
|
352
|
+
pipeline. See the module docstring for the authoring contract and the
|
|
353
|
+
encoding rules applied to ``Arg`` / ``Args`` parameters.
|
|
336
354
|
"""
|
|
337
|
-
body = inspect.getdoc(fn)
|
|
338
|
-
if not body:
|
|
339
|
-
raise TypeError(
|
|
340
|
-
f"@redis_script function {fn.__qualname__} needs a Lua body in its docstring"
|
|
341
|
-
)
|
|
342
355
|
|
|
356
|
+
def __init__(self, directory: Traversable) -> None:
|
|
357
|
+
self._directory = directory
|
|
358
|
+
|
|
359
|
+
def __call__(
|
|
360
|
+
self, fn: Callable[Concatenate[RedisClient, _P], Awaitable[_R]]
|
|
361
|
+
) -> RedisScript[_P, _R]:
|
|
362
|
+
return _wrap(fn, self._directory)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _wrap(
|
|
366
|
+
fn: Callable[Concatenate[RedisClient, _P], Awaitable[_R]], directory: Traversable
|
|
367
|
+
) -> RedisScript[_P, _R]:
|
|
343
368
|
sig = inspect.signature(fn)
|
|
344
369
|
hints = get_type_hints(fn, include_extras=True)
|
|
345
370
|
|
|
@@ -390,10 +415,22 @@ def redis_script(
|
|
|
390
415
|
f"Args[...] must be the last parameter"
|
|
391
416
|
)
|
|
392
417
|
|
|
393
|
-
|
|
394
|
-
|
|
418
|
+
# Every language binds KEYS and ARGV the way this header says, so a
|
|
419
|
+
# header that drifts from the signature would send values to the wrong
|
|
420
|
+
# slots. The blank line ends the header, so an extra binding fails too.
|
|
421
|
+
script_name = fn.__name__.lstrip("_")
|
|
422
|
+
source = (directory / f"{script_name}.lua").read_text(encoding="utf-8")
|
|
423
|
+
header = _generate_preamble(key_params, arg_params)
|
|
424
|
+
if not source.startswith(f"{header}\n\n"):
|
|
425
|
+
raise TypeError(
|
|
426
|
+
f"@redis_script: the Lua header for {fn.__qualname__} must be "
|
|
427
|
+
f"exactly these lines, then a blank line:\n{header}"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return RedisScript(fn, source, key_params, arg_params)
|
|
431
|
+
|
|
395
432
|
|
|
396
|
-
|
|
433
|
+
redis_script = ScriptDirectory(files("docket") / "lua")
|
|
397
434
|
|
|
398
435
|
|
|
399
436
|
__all__ = [
|
|
@@ -401,5 +438,6 @@ __all__ = [
|
|
|
401
438
|
"Args",
|
|
402
439
|
"Key",
|
|
403
440
|
"RedisScript",
|
|
441
|
+
"ScriptDirectory",
|
|
404
442
|
"redis_script",
|
|
405
443
|
]
|
|
@@ -85,14 +85,7 @@ async def _refresh_lease(
|
|
|
85
85
|
lease_key: Key[str],
|
|
86
86
|
holder: Arg[str],
|
|
87
87
|
duration_ms: Arg[int],
|
|
88
|
-
) -> int:
|
|
89
|
-
"""
|
|
90
|
-
if redis.call('GET', lease_key) == holder then
|
|
91
|
-
return redis.call('PEXPIRE', lease_key, duration_ms)
|
|
92
|
-
end
|
|
93
|
-
return 0
|
|
94
|
-
"""
|
|
95
|
-
...
|
|
88
|
+
) -> int: ...
|
|
96
89
|
|
|
97
90
|
|
|
98
91
|
async def renew_leases(
|