Skip to content

Commit e629b5f

Browse files
knifecakeclaude
andcommitted
Add Sphinx documentation site with ReadTheDocs setup (#13)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ae5f5d4 commit e629b5f

16 files changed

Lines changed: 1323 additions & 1 deletion

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ wheels/
1818
# temporary files
1919
tmp/
2020
.mypy_cache/
21+
22+
# Sphinx docs build output
23+
docs/_build/

.readthedocs.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
3+
build:
4+
os: ubuntu-22.04
5+
tools:
6+
python: "3.13"
7+
commands:
8+
- asdf plugin add uv
9+
- asdf install uv 0.10.2
10+
- asdf global uv 0.10.2
11+
- uv sync --group docs --frozen
12+
- uv run -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Unreleased
44

5+
**Added:**
6+
7+
- Sphinx documentation site covering installation, getting started,
8+
configuration, API reference, internals and alternatives (#13). Published
9+
to Read The Docs.
10+
511
**Fixed:**
612

713
- Fixed a bug where the process would hang on shutdown and require `kill -9`.

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ lint:
1313
uv run ruff check --fix
1414
uv run ruff format
1515

16+
.PHONY: docs
17+
docs:
18+
uv run --group docs -m sphinx -b html docs docs/_build/html
19+
1620
.PHONY: force-kill
1721
force-kill:
1822
ps | grep steady_queue | cut -f 1 -d ' ' | xargs kill -9

docs/Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Minimal makefile for Sphinx documentation
2+
3+
SPHINXOPTS ?=
4+
SPHINXBUILD ?= uv run sphinx-build
5+
SOURCEDIR = .
6+
BUILDDIR = _build
7+
8+
help:
9+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
10+
11+
.PHONY: help Makefile
12+
13+
%: Makefile
14+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/alternatives.rst

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
============
2+
Alternatives
3+
============
4+
5+
.. contents:: On this page
6+
:local:
7+
:depth: 2
8+
9+
vs django-tasks
10+
---------------
11+
12+
`django-tasks <https://github.com/RealOrangeOne/django-tasks>`_ is the
13+
third-party package that defined the ``django.tasks`` interface (DEP 0014) and
14+
served as its reference implementation. It ships a ``DatabaseBackend`` that
15+
stores and executes tasks using your application's database.
16+
17+
The ``django-tasks`` ``DatabaseBackend`` is intentionally minimal. Steady
18+
Queue is also a database-backed implementation of the same interface, but adds:
19+
20+
- **Cron-style recurring tasks** via the ``@recurring`` decorator.
21+
- **Concurrency controls** via ``@limits_concurrency``.
22+
- **Operational visibility**: inspect, retry and discard failed tasks from the
23+
Django admin.
24+
- **Queue pausing**: pause and resume individual queues from the admin.
25+
- **Horizontal scaling**: run multiple worker or dispatcher processes across
26+
machines.
27+
- **Configurable process topology**: control the number of workers, threads per
28+
worker, polling intervals and which queues each worker handles.
29+
- **Heartbeat-based liveness checks** with automatic task recovery when a
30+
worker process dies.
31+
32+
If you just need simple background task execution and none of the above
33+
features matter to you, ``django-tasks`` is a fine choice. Steady Queue is
34+
the better fit when you need more operational control or advanced features.
35+
36+
Because both implement the same ``django.tasks`` interface, you can switch
37+
between them by changing a single line in ``settings.py``.
38+
39+
vs Celery
40+
---------
41+
42+
Celery is the most widely used task queue library in the Python ecosystem. It
43+
is mature, has a huge community and supports a wide range of brokers (Redis,
44+
RabbitMQ, Amazon SQS, etc.).
45+
46+
The main trade-off is operational complexity: Celery requires a separate broker
47+
process (typically Redis or RabbitMQ) that must be deployed, monitored and
48+
maintained. Steady Queue uses your existing database, so there's nothing new to
49+
operate.
50+
51+
Celery also has its own task definition interface (``@app.task`` / ``@shared_task``),
52+
whereas Steady Queue follows the standard ``django.tasks`` interface (DEP 0014),
53+
which keeps your task code portable across compliant backends.
54+
55+
Consider Celery if you need features like task routing to heterogeneous worker
56+
pools, support for non-relational brokers, or the rich ecosystem of
57+
third-party Celery extensions. Consider Steady Queue if you'd rather not
58+
introduce a broker dependency and your workloads fit comfortably within a
59+
relational database.
60+
61+
vs django-rq
62+
------------
63+
64+
`django-rq <https://github.com/rq/django-rq>`_ integrates the `RQ
65+
<https://python-rq.org/>`_ task queue (which uses Redis as a broker) with
66+
Django. Like Celery, it requires Redis to be running. It is simpler than
67+
Celery and easier to set up, but still adds operational overhead compared to a
68+
pure database solution.
69+
70+
vs Solid Queue
71+
--------------
72+
73+
`Solid Queue <https://github.com/rails/solid_queue>`_ is the Ruby on Rails
74+
library that Steady Queue is ported from. If you're coming from a Rails
75+
background, the concepts and configuration will look familiar. The main
76+
differences in the external interface are:
77+
78+
- **Task definition.** Solid Queue works with Active Job classes; Steady Queue
79+
uses the ``@task`` decorator from DEP 0014.
80+
- **Priority ordering.** Steady Queue follows Django's convention where *larger*
81+
numbers mean *higher* priority (e.g. priority 10 runs before priority 0).
82+
Solid Queue uses the inverse.
83+
- **Recurring tasks.** Solid Queue supports command-based recurring tasks
84+
(arbitrary shell commands on a schedule). Steady Queue only supports
85+
recurring Python task functions.
86+
- **Instrumentation.** Solid Queue emits rich ``ActiveSupport::Notifications``
87+
events. Steady Queue uses standard Python logging and the ``django.tasks``
88+
signals instead.

docs/api.rst

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
=============
2+
API Reference
3+
=============
4+
5+
Steady Queue's public API is intentionally small. Most of the interface comes
6+
from Django's ``django.tasks`` module (see the `Django tasks documentation
7+
<https://docs.djangoproject.com/en/stable/ref/tasks/>`_). Steady Queue adds
8+
two decorators and a handful of module-level settings.
9+
10+
.. contents:: On this page
11+
:local:
12+
:depth: 2
13+
14+
15+
.. _api-recurring:
16+
17+
@recurring
18+
----------
19+
20+
.. autofunction:: steady_queue.recurring_task.recurring
21+
22+
The ``@recurring`` decorator registers a task to be enqueued automatically on
23+
a cron schedule. It must be applied *outside* the ``@task()`` decorator:
24+
25+
.. code-block:: python
26+
27+
from django.tasks import task
28+
from steady_queue.recurring_task import recurring
29+
30+
@recurring(schedule="0 9 * * 1-5", key="weekly_report")
31+
@task()
32+
def weekly_report():
33+
...
34+
35+
The same task can have multiple recurring schedules:
36+
37+
.. code-block:: python
38+
39+
@recurring(schedule="0 9 * * *", args=("Alice",), key="greet_alice")
40+
@recurring(schedule="0 12 * * *", args=("Bob",), key="greet_bob")
41+
@task()
42+
def greet(name: str):
43+
print(f"Hello, {name}!")
44+
45+
Parameters:
46+
47+
``schedule``
48+
A crontab expression (anything understood by the `crontab
49+
<https://pypi.org/project/crontab/>`_ library). For example:
50+
51+
- ``"* * * * *"`` — every minute
52+
- ``"0 9 * * 1-5"`` — 9am on weekdays
53+
- ``"@daily"`` — once a day at midnight
54+
55+
``key``
56+
A unique string identifier for this recurring configuration. Must be
57+
unique across all ``@recurring`` decorators in your codebase. Used to
58+
prevent duplicate runs when multiple schedulers are active.
59+
60+
``args``
61+
Positional arguments to pass to the task when it is enqueued. Defaults to
62+
no arguments.
63+
64+
``kwargs``
65+
Keyword arguments to pass to the task when it is enqueued.
66+
67+
``queue_name``
68+
The queue to enqueue the task on. If omitted, uses the queue from the
69+
``@task()`` decorator or the default queue.
70+
71+
``priority``
72+
Numeric priority for the enqueued task. If omitted, uses the priority from
73+
the ``@task()`` decorator or ``0``.
74+
75+
``description``
76+
Optional human-readable description. Currently unused but stored for
77+
future tooling.
78+
79+
80+
.. _api-limits-concurrency:
81+
82+
@limits_concurrency
83+
-------------------
84+
85+
.. autofunction:: steady_queue.concurrency.limits_concurrency
86+
87+
The ``@limits_concurrency`` decorator restricts how many instances of a task
88+
can run at the same time. It must be applied *outside* the ``@task()``
89+
decorator:
90+
91+
.. code-block:: python
92+
93+
from django.tasks import task
94+
from steady_queue.concurrency import limits_concurrency
95+
96+
@limits_concurrency(key=lambda user_id: str(user_id), to=1)
97+
@task()
98+
def generate_report(user_id: int):
99+
...
100+
101+
Parameters:
102+
103+
``key``
104+
**Required.** A string or a callable that accepts the same arguments as
105+
the task and returns a string. Tasks with the same key value are counted
106+
together for the concurrency limit.
107+
108+
``to``
109+
Maximum number of tasks with the same key that may run simultaneously.
110+
Defaults to ``1``.
111+
112+
``duration``
113+
How long the concurrency guarantee is held. If a task holds a concurrency
114+
slot for longer than this, the slot may be released by the dispatcher's
115+
maintenance pass. Defaults to
116+
``steady_queue.default_concurrency_control_period`` (3 minutes).
117+
118+
``group``
119+
A string used to apply a shared concurrency limit across different task
120+
types. Tasks from different functions that share the same ``group`` and
121+
``key`` value count against the same limit. Defaults to the task's module
122+
path.
123+
124+
125+
Argument serialization
126+
----------------------
127+
128+
Task functions accept almost any argument type as positional or keyword
129+
arguments. Beyond the standard DEP 0014 serializable types, Steady Queue adds
130+
support for:
131+
132+
- ``datetime`` and ``date`` objects
133+
- ``timedelta`` objects
134+
- Django model instances (serialized as content type + primary key)
135+
136+
If a model instance cannot be found in the database when the task is executed,
137+
a ``steady_queue.arguments.DeserializationError`` is raised.
138+
139+
140+
Backend limitations
141+
-------------------
142+
143+
The ``SteadyQueueBackend`` does not support the following features defined by
144+
the Django task backend interface:
145+
146+
- **Async enqueueing** — tasks cannot be enqueued from async code.
147+
- **Result fetching** — ``task_result.return_value`` is not supported. Store
148+
results directly in your database or file storage if they need to be
149+
persisted.
150+
151+
These limitations are advertised via the
152+
`Django task feature flags
153+
<https://docs.djangoproject.com/en/stable/ref/tasks/#feature-flags>`_.

docs/conf.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import os
2+
import sys
3+
4+
import django
5+
6+
project = "Steady Queue"
7+
copyright = "2025, Elias Hernandis"
8+
author = "Elias Hernandis"
9+
10+
# Make source available for autodoc
11+
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"
12+
sys.path.insert(0, os.path.abspath(".."))
13+
django.setup()
14+
15+
import steady_queue # noqa: E402
16+
17+
release = steady_queue.__version__
18+
19+
20+
# -- General configuration ---------------------------------------------------
21+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
22+
23+
extensions = [
24+
"sphinx.ext.autodoc",
25+
"sphinx.ext.napoleon",
26+
]
27+
28+
templates_path = ["_templates"]
29+
30+
31+
# -- Options for HTML output -------------------------------------------------
32+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
33+
34+
html_theme = "alabaster"
35+
html_static_path = ["_static"]
36+
37+
html_theme_options = {
38+
"description": "A database-backed task backend for Django",
39+
"github_user": "knifecake",
40+
"github_repo": "steady-queue",
41+
"github_button": True,
42+
"github_type": "star",
43+
}
44+
45+
# -- Napoleon extension configuration ----------------------------------------
46+
47+
napoleon_google_docstring = True
48+
napoleon_numpy_docstring = False

0 commit comments

Comments
 (0)