Action0-Celery-Sched
Celery beat schedules defined in YAML or TOML files: which task runs when, with which arguments, kept out of the code and validated when the app starts.
Requires Python 3.11 or newer and Celery 5.3 or newer.
Full documentation including the API reference: https://laughinjar.github.io/action0-celery-sched/
Status: early. The file format in YAML and TOML, all three kinds of Celery schedule, the validation and the task check work and are covered by tests; the API may still move.
Installation
pip install action0-celery-sched # TOML schedules
pip install "action0-celery-sched[yaml]" # YAML schedules too (PyYAML)
pip install "action0-celery-sched[solar]" # solar schedules (ephem)
pip install "action0-celery-sched[yaml,solar]" # all of it
TOML needs nothing beyond the standard library's tomllib; YAML needs PyYAML,
which the yaml extra brings along. (uv add works the same way.)
Usage
Write the schedule down, in YAML or in TOML — the entries are the same in both:
# beat.yaml
"Poll feed":
task: myapp.feeds.tasks.poll
schedule:
every: 5m # or 300, 90s, 1h30m, {minutes: 5}
"Nightly report":
task: myapp.reports.tasks.nightly
kw: # keyword arguments
recipients: [ops@example.com]
params: # positional arguments
- daily
schedule:
crontab: "0 3 * * *" # or {minute: 0, hour: 3}, or @daily
options: # passed on to apply_async()
queue: reports
"Sunset lights":
task: myapp.home.tasks.lights_on
schedule:
solar: {event: sunset, lat: 48.21, lon: 16.37}
# beat.toml
["Poll feed"]
task = "myapp.feeds.tasks.poll"
schedule = { every = "5m" } # or 300, "90s", "1h30m", { minutes = 5 }
["Nightly report"]
task = "myapp.reports.tasks.nightly"
kw = { recipients = ["ops@example.com"] } # keyword arguments
params = ["daily"] # positional arguments
schedule = { crontab = "0 3 * * *" } # or { minute = 0, hour = 3 }, or "@daily"
options = { queue = "reports" } # passed on to apply_async()
["Sunset lights"]
task = "myapp.home.tasks.lights_on"
schedule = { solar = { event = "sunset", lat = 48.21, lon = 16.37 } }
And hand it to Celery:
from celery import Celery
from action0.celery_sched import load_beat_schedule
app = Celery("myapp")
app.conf.beat_schedule = load_beat_schedule("beat.yaml")
# or
app.conf.beat_schedule = load_beat_schedule("beat.toml")
The suffix decides the format (format="yaml" or format="toml" for anything
else). A mapping works too, e.g. the beat part of a larger YAML or TOML
config: load_beat_schedule(settings["beat"]).
The result is plain Celery, a beat_schedule dict of schedule, crontab
and solar objects:
{'Poll feed': {'task': 'myapp.feeds.tasks.poll',
'schedule': <freq: 5.00 minutes>,
'args': (),
'kwargs': {},
'options': {}},
...}
Everything is validated while loading. An unknown key such as a misspelled
shedule, an impossible crontab field, or an entry name used twice is an
error naming the file, the entry and the key — the same message for both
formats, here for the crontab "0 25 * * *":
beat.yaml: entry 'Nightly report': schedule.crontab.hour: invalid value '25': Invalid end range: 25 > 23.
beat.toml: entry 'Nightly report': schedule.crontab.hour: invalid value '25': Invalid end range: 25 > 23.
Values that differ per environment can come from environment variables, and
entries can be switched off without deleting them. In YAML !ENV is a tag;
TOML has no tags, so there it is a prefix of the string:
"Nightly report":
task: myapp.reports.tasks.nightly
schedule:
crontab: !ENV ${REPORT_CRON:-0 3 * * *}
enabled: !ENV ${REPORTS_ENABLED:-true}
["Nightly report"]
task = "myapp.reports.tasks.nightly"
schedule = { crontab = "!ENV ${REPORT_CRON:-0 3 * * *}" }
enabled = "!ENV ${REPORTS_ENABLED:-true}"
Several files merge in order, whatever their format. With replace=True a
later file may override or disable entries of an earlier one:
app.conf.beat_schedule = load_beat_schedule(
"beat/common.yaml", f"beat/{environment}.toml", replace=True
)
A misspelled task name would only surface when the task is first due. Catch
it when beat starts instead:
from celery.signals import beat_init
from action0.celery_sched import check_tasks
@beat_init.connect
def check_schedule(sender, **kwargs):
check_tasks(sender.app) # raises UnknownTaskError listing every unknown name
See the usage guide
for the full file format, every example in both YAML and TOML: every interval
and crontab spelling, relative intervals, how the two formats differ,
templates and anchors, and the error types.
The action0 namespace is simply the one the author likes to use for
personal projects.
Development
uv run pytest # tests (incl. doctests in src/)
uv run ruff check # lint
uv run ruff format # format
uv run mypy # type-check (strict)
uv run pyright # type-check
uv run ty check # type-check
AI disclosure
This library is developed with heavy use of AI coding tools: the code, tests, and documentation are largely written by Claude Code, working from the author's design brief and reviewed by the author. If that changes how much you want to rely on this package, that's a fair call — read the source, it's small.
License
MIT — see LICENSE.
Release files for action0-celery-sched 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| action0_celery_sched-0.1.0.tar.gz | 129.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| action0_celery_sched-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 157.6 kB
Release files / action0_celery_sched-0.1.0.tar.gz
| Download URL | action0_celery_sched-0.1.0.tar.gz |
|---|---|
| Size | 129.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4d8c6c79e0afacef0fe5d01c20e066b33efb48ed808f1bdeb1e7050deaf38363
|
|
BLAKE2b-256 checksum How to use checksums |
ef8629929d051a9b8f8cb27dbc0352910371a1fbd7b38b9eef5946f90ff205e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / action0_celery_sched-0.1.0-py3-none-any.whl
| Download URL | action0_celery_sched-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4262d919f20bf3ec11e92785155a06cd435214ad384e4f3998770ae11a91ad79
|
|
BLAKE2b-256 checksum How to use checksums |
5fdec32937e7052291f50f039c99069007301afe085ed09b97e3e4347d781f02
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log