Source code for action0.celery_sched.solar
"""
Parse the value of a ``solar`` schedule into a :py:class:`celery.schedules.solar`.
The value is a mapping of the event and the observer's position:
``solar: {event: sunset, lat: 48.21, lon: 16.37}`` in YAML,
``solar = { event = "sunset", lat = 48.21, lon = 16.37 }`` in TOML.
Celery computes the event times with `ephem <https://pypi.org/project/ephem/>`_,
which is only installed with the ``solar`` extra
(``pip install "action0-celery-sched[solar]"``).
"""
from enum import StrEnum
from typing import Any
from celery.schedules import solar
from action0.celery_sched.errors import DefinitionError
from action0.celery_sched.errors import located
from action0.celery_sched.values import as_number
from action0.celery_sched.values import check_keys
from action0.celery_sched.values import expect_mapping
[docs]
class SolarEvent(StrEnum):
"""
The events a solar schedule can fire on, as Celery names them.
>>> SolarEvent("sunset") is SolarEvent.SUNSET
True
"""
DAWN_ASTRONOMICAL = "dawn_astronomical"
DAWN_NAUTICAL = "dawn_nautical"
DAWN_CIVIL = "dawn_civil"
SUNRISE = "sunrise"
SOLAR_NOON = "solar_noon"
SUNSET = "sunset"
DUSK_CIVIL = "dusk_civil"
DUSK_NAUTICAL = "dusk_nautical"
DUSK_ASTRONOMICAL = "dusk_astronomical"
_KEYS = ("event", "lat", "lon")
[docs]
def parse_solar(value: object) -> solar:
"""
Turn the value of a ``solar`` key into a Celery solar schedule.
>>> parse_solar({"event": "sunset", "lat": 48.21, "lon": 16.37})
<solar: sunset at latitude 48.21, longitude: 16.37>
:param value: the parsed value, from either format
:returns: the solar schedule
:raises DefinitionError: if the value is malformed or out of range
:raises ImportError: if ephem (the ``solar`` extra) is not installed
"""
mapping = expect_mapping(value)
check_keys(mapping, allowed=_KEYS, required=_KEYS)
with located("event"):
event = _parse_event(mapping["event"])
with located("lat"):
lat = _parse_degrees(mapping["lat"], limit=90)
with located("lon"):
lon = _parse_degrees(mapping["lon"], limit=180)
return _build(event, lat, lon)
def _parse_event(value: Any) -> SolarEvent:
"""Check that the event is one Celery knows."""
try:
return SolarEvent(value)
except ValueError:
choices = ", ".join(event.value for event in SolarEvent)
raise DefinitionError(f"unknown event {value!r} (choose from: {choices})") from None
def _parse_degrees(value: Any, *, limit: int) -> int | float:
"""Check that a coordinate is a number within ``-limit..limit``."""
degrees = as_number(value)
if not -limit <= degrees <= limit:
raise DefinitionError(f"must be between {-limit} and {limit}, got {degrees!r}")
return degrees
def _build(event: SolarEvent, lat: int | float, lon: int | float) -> solar:
"""Construct the schedule, explaining a missing ephem."""
try:
return solar(event.value, lat, lon)
except ImportError as error:
raise ImportError(
"solar schedules need ephem: pip install 'action0-celery-sched[solar]'"
) from error