Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/scheduler/scheduler_service.py: 86%

56 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 03:04 +0000

1import asyncio 

2from datetime import UTC, datetime, timedelta 

3from pathlib import Path 

4 

5from mealie.core import root_logger 

6from mealie.core.config import get_app_settings 

7from mealie.services.scheduler.runner import repeat_every 

8 

9from .scheduler_registry import SchedulerRegistry 

10 

11logger = root_logger.get_logger() 

12 

13CWD = Path(__file__).parent 

14 

15MINUTES_DAY = 1440 

16MINUTES_5 = 5 

17MINUTES_HOUR = 60 

18 

19 

20class SchedulerService: 

21 @staticmethod 

22 async def start(): 

23 await run_minutely() 

24 await run_hourly() 

25 

26 # Wait to trigger our daily run until our given "daily time", so having asyncio handle it. 

27 asyncio.create_task(schedule_daily()) 

28 

29 

30async def schedule_daily(): 

31 now = datetime.now(UTC) 

32 daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME_UTC 

33 logger.debug(f"Current time is {now} and DAILY_SCHEDULE_TIME (in UTC) is {daily_schedule_time}") 

34 

35 next_schedule = now.replace( 

36 hour=daily_schedule_time.hour, minute=daily_schedule_time.minute, second=0, microsecond=0 

37 ) 

38 delta = next_schedule - now 

39 if delta < timedelta(0): 39 ↛ 40line 39 didn't jump to line 40 because the condition on line 39 was never true

40 next_schedule = next_schedule + timedelta(days=1) 

41 delta = next_schedule - now 

42 

43 hours_until, seconds_reminder = divmod(delta.total_seconds(), 3600) 

44 minutes_until, seconds_reminder = divmod(seconds_reminder, 60) 

45 seconds_until = round(seconds_reminder) 

46 logger.debug("Time left: %02d:%02d:%02d", hours_until, minutes_until, seconds_until) 

47 

48 target_time = next_schedule.replace(microsecond=0, second=0) 

49 logger.info("Daily tasks scheduled for %s", str(target_time)) 

50 

51 wait_seconds = delta.total_seconds() 

52 await asyncio.sleep(wait_seconds) 

53 await run_daily() 

54 

55 

56def _scheduled_task_wrapper(callable): 

57 try: 

58 callable() 

59 except Exception as e: 

60 logger.error("Error in scheduled task func='%s': exception='%s'", callable.__name__, e) 

61 

62 

63@repeat_every(minutes=MINUTES_DAY, wait_first=False, logger=logger) 

64def run_daily(): 

65 logger.debug("Running daily callbacks") 

66 for func in SchedulerRegistry._daily: 

67 _scheduled_task_wrapper(func) 

68 

69 

70@repeat_every(minutes=MINUTES_HOUR, wait_first=True, logger=logger) 

71def run_hourly(): 

72 logger.debug("Running hourly callbacks") 

73 for func in SchedulerRegistry._hourly: 

74 _scheduled_task_wrapper(func) 

75 

76 

77@repeat_every(minutes=MINUTES_5, wait_first=True, logger=logger) 

78def run_minutely(): 

79 logger.debug("Running minutely callbacks") 

80 for func in SchedulerRegistry._minutely: 

81 _scheduled_task_wrapper(func)