Source code for action0.celery_sched.toml_loader
"""
Parse TOML schedule files, with the standard library's :py:mod:`tomllib`.
An entry in TOML has exactly the keys and values it has in YAML:
.. code-block:: toml
["Nightly report"]
task = "myapp.reports.tasks.nightly"
params = ["daily"]
kw = { recipients = ["ops@example.com"] }
schedule = { crontab = "0 3 * * *" }
options = { queue = "reports" }
TOML has no tags, so the ``!ENV`` substitution is written as a *prefix* of
a string: ``crontab = "!ENV ${REPORT_CRON:-0 3 * * *}"``. The rest of the
string, after ``!ENV`` and the whitespace following it, is substituted
exactly like YAML's ``!ENV`` tag does it (see
:py:mod:`~action0.celery_sched.envvars`). Only string values are
substituted, never keys.
TOML itself forbids declaring a key twice, so a duplicate entry is a syntax
error here.
"""
import re
import tomllib
from typing import Any
from action0.celery_sched.envvars import substitute_env
from action0.celery_sched.errors import DefinitionError
from action0.celery_sched.errors import located
# the TOML spelling of the !ENV tag: at the very start of a string
_ENV_PREFIX = re.compile(r"!ENV[ \t]+")
[docs]
def load_toml(text: str) -> dict[str, Any]:
"""
Parse TOML text and substitute its ``!ENV``-prefixed strings.
>>> load_toml('["Poll feed"]\\nschedule = { every = "!ENV ${POLL_EVERY:-5m}" }')
{'Poll feed': {'schedule': {'every': '5m'}}}
:param text: the TOML document
:returns: the parsed document
:raises DefinitionError: on a syntax error, or an unset environment variable
"""
try:
document = tomllib.loads(text)
except tomllib.TOMLDecodeError as error:
raise DefinitionError(f"invalid TOML: {error}") from None
substituted: dict[str, Any] = {}
for name, value in document.items():
with located(entry=name):
substituted[name] = _substitute(value)
return substituted
def _substitute(value: Any) -> Any:
"""Substitute every ``!ENV``-prefixed string in a value, locating failures."""
if isinstance(value, str):
prefix = _ENV_PREFIX.match(value)
return substitute_env(value[prefix.end() :]) if prefix else value
if isinstance(value, dict):
substituted: dict[str, Any] = {}
for key, item in value.items():
with located(key):
substituted[key] = _substitute(item)
return substituted
if isinstance(value, list):
items = []
for index, item in enumerate(value):
with located(str(index)):
items.append(_substitute(item))
return items
return value