Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .coveragerc-memory
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Coverage configuration for memory backend testing
# CLI tests are skipped with memory:// URLs, so exclude CLI from coverage

[run]
branch = true
parallel = true
omit =
src/docket/__main__.py
src/docket/cli.py
tests/cli/test_*.py
22 changes: 15 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,29 @@ jobs:
# Python 3.10 hangs when we include the CLI tests, so ignore it and
# use a lower threshold to compensate
- python-version: "3.10"
cov-threshold: 75
pytest-ignore: "--ignore tests/cli"
cov-threshold: 100
pytest-args: ""
# Python 3.11 coverage reporting is unstable, so use 98% threshold
- python-version: "3.11"
cov-threshold: 98
pytest-ignore: ""
pytest-args: ""
- python-version: "3.12"
cov-threshold: 100
pytest-ignore: ""
pytest-args: ""
- python-version: "3.13"
cov-threshold: 100
pytest-ignore: ""
pytest-args: ""
- python-version: "3.14"
cov-threshold: 100
pytest-ignore: ""
pytest-args: ""
# Memory backend: CLI tests are skipped via pytest skip markers because
# CLI rejects memory:// URLs. Use separate coverage config to exclude CLI.
- backend:
name: "Memory (in-memory backend)"
redis-version: "memory"
redis-py-version: ">=5"
cov-threshold: 98 # CLI tests are excluded from coverage and some lines are only covered by CLI tests
pytest-args: "--cov-config=.coveragerc-memory"

steps:
- uses: actions/checkout@v4
Expand All @@ -63,7 +71,7 @@ jobs:
- name: Run tests
env:
REDIS_VERSION: ${{ matrix.backend.redis-version }}
run: uv run pytest --cov-branch --cov-fail-under=${{ matrix.cov-threshold }} --cov-report=xml --cov-report=term-missing:skip-covered ${{ matrix.pytest-ignore }}
run: uv run pytest --cov-branch --cov-fail-under=${{ matrix.cov-threshold }} --cov-report=xml --cov-report=term-missing:skip-covered ${{ matrix.pytest-args }}

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
Expand Down
8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ docs = [
"mkdocstrings>=0.24.1",
"mkdocstrings-python>=1.8.0",
]
examples = [
"fastapi>=0.120.0",
"pydantic>=2.11.10",
"uvicorn>=0.38.0",
]
examples = ["fastapi>=0.120.0", "pydantic>=2.11.10", "uvicorn>=0.38.0"]

[project.scripts]
docket = "docket.__main__:app"
Expand Down Expand Up @@ -109,6 +105,8 @@ filterwarnings = ["error"]

[tool.coverage.run]
omit = ["src/docket/__main__.py"]
branch = true
parallel = true

[tool.pyright]
include = ["src", "tests"]
Expand Down
7 changes: 7 additions & 0 deletions sitecustomize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file ensures that we can collect coverage data for the CLI when it's running in a subprocess
import os

if os.getenv("COVERAGE_PROCESS_START"):
import coverage

coverage.process_startup()
26 changes: 26 additions & 0 deletions src/docket/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ def set_logging_level(level: LogLevel) -> None:
logging.getLogger().setLevel(level.value)


def validate_url(url: str) -> str:
"""
Validate that the provided URL is compatible with the CLI.

The memory:// backend is not compatible with the CLI as it doesn't persist
across processes.
"""
if url.startswith("memory://"):
raise typer.BadParameter(
"The memory:// URL scheme is not supported by the CLI.\n"
"The memory backend does not persist across processes.\n"
"Please use a persistent backend like Redis or Valkey."
)
return url


def handle_strike_wildcard(value: str) -> str | None:
if value in ("", "*"):
return None
Expand Down Expand Up @@ -178,6 +194,7 @@ def worker(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
name: Annotated[
Expand Down Expand Up @@ -336,6 +353,7 @@ def strike(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
) -> None:
Expand Down Expand Up @@ -373,6 +391,7 @@ def clear(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
) -> None:
Expand Down Expand Up @@ -425,6 +444,7 @@ def restore(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
) -> None:
Expand Down Expand Up @@ -468,6 +488,7 @@ def trace(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
message: Annotated[
Expand Down Expand Up @@ -511,6 +532,7 @@ def fail(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
message: Annotated[
Expand Down Expand Up @@ -554,6 +576,7 @@ def sleep(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
seconds: Annotated[
Expand Down Expand Up @@ -688,6 +711,7 @@ def snapshot(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
stats: Annotated[
Expand Down Expand Up @@ -840,6 +864,7 @@ def list_workers(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
) -> None:
Expand Down Expand Up @@ -876,6 +901,7 @@ def workers_for_task(
typer.Option(
help="The URL of the Redis server",
envvar="DOCKET_URL",
callback=validate_url,
),
] = "redis://localhost:6379/0",
) -> None:
Expand Down
8 changes: 0 additions & 8 deletions tests/cli/conftest.py

This file was deleted.

Loading