Bradley Kirton's Blog

Published on June 5, 2025

Go home

A reconciliation loop template

"""Reconciliation loop template."""

import logging
import signal
import time
import typing as t

from django.core.management.base import BaseCommand
from django.utils import timezone

from abacus.db import models as db_models

logger = logging.getLogger(__name__)


class LoopState:
    """Provides an interruptible sleeper."""

    def __init__(self) -> None:
        self.running = True
        signal.signal(signal.SIGINT, self.stop_soon)

    def stop_soon(self, *args: t.Any) -> None:
        logger.info("Loop will stop soon.")
        self.running = False

    def sleep(self, secs: float) -> None:
        """Sleep for the provided number of seconds."""

        downtime = 0.0
        sleep_secs = 0.2

        while downtime < secs and self.running:
            downtime += sleep_secs
            time.sleep(sleep_secs)


class Command(BaseCommand):
    """Event handler fired when a motion event ends."""

    def handle(self, **options: t.Any) -> None:
        """Run the loop."""

        tzinfo = timezone.get_current_timezone()
        sysuser = db_models.get_sysuser()

        counter = 0
        max_sleep_secs = 32
        sleep_sec_mapping = {
            0: 1,
            1: 2,
            2: 4,
            3: 8,
            4: 16,
            5: max_sleep_secs,
        }

        state = LoopState()

        while state.running:
            tasks = []
            task_len = 0

            if task_len == 0:
                counter += 1
            else:
                counter = 0

            while tasks:
                tasks.pop(0)

            sleep_secs = sleep_sec_mapping.get(counter, max_sleep_secs)
            logger.info(f"Processed {task_len} record(s), sleeping for {sleep_secs} secs")
            state.sleep(sleep_secs)