From f5af03b540d9f870ff0aba94bfd891e5ca269277 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 18 May 2026 21:06:20 +0000 Subject: [PATCH] docs: add full inline comments to utils.py --- ns8_backup_monitor/utils.py | 75 ++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/ns8_backup_monitor/utils.py b/ns8_backup_monitor/utils.py index c182571..7f04dbe 100644 --- a/ns8_backup_monitor/utils.py +++ b/ns8_backup_monitor/utils.py @@ -1,6 +1,21 @@ #!/usr/bin/env python3 -""" -utils.py - Shared utilities: config loading, logging setup. +"""Shared utilities for ns8-backup-monitor. + +This module provides two thin helpers used by every other module in the package: + +load_config() + Locate and parse the YAML configuration file. The caller may supply an + explicit path; when omitted, a built-in list of well-known locations is + probed in order. + +setup_logging() + Initialise the root Python logger from the ``logging`` section of the + parsed configuration. A StreamHandler (stdout) is always added so that + journald captures output when the service runs under systemd. An optional + rotating file handler is added when ``logging.file`` is set. + +Both functions are called once during start-up from ``__main__.py`` before any +other module-level loggers are used. """ import logging @@ -11,21 +26,47 @@ from pathlib import Path try: import yaml except ImportError: + # PyYAML is the only non-stdlib dependency. We defer the error to + # load_config() so that a helpful message is shown at runtime instead of + # a bare ImportError traceback. yaml = None +# --------------------------------------------------------------------------- +# Default config search paths +# --------------------------------------------------------------------------- +# Tried in order when no explicit --config argument is given. +# The last entry (config/config.yml) allows running directly from the repo +# root during development without a system-wide installation. DEFAULT_CONFIG_PATHS = [ - "/etc/ns8-backup-monitor/config.yml", - "/opt/ns8-backup-monitor/config/config.yml", - "config/config.yml", + "/etc/ns8-backup-monitor/config.yml", # production install path + "/opt/ns8-backup-monitor/config/config.yml", # alternative install layout + "config/config.yml", # development / repo root ] def load_config(path: str = None) -> dict: + """Locate and parse the YAML configuration file. + + Args: + path: Explicit filesystem path to ``config.yml``. When ``None``, + ``DEFAULT_CONFIG_PATHS`` is probed in order and the first + existing file is used. + + Returns: + Parsed configuration as a plain Python dict. Returns an empty dict + if the file exists but is empty. + + Raises: + ImportError: PyYAML is not installed. + FileNotFoundError: No config file found at any of the probed paths. + """ if yaml is None: raise ImportError("PyYAML not installed. Run: pip3 install pyyaml") + # Use the caller-supplied path or fall back to the default search list. paths = [path] if path else DEFAULT_CONFIG_PATHS + for p in paths: if p and Path(p).exists(): with open(p) as f: @@ -38,12 +79,36 @@ def load_config(path: str = None) -> dict: def setup_logging(config: dict): + """Initialise the root logger from the ``logging`` section of *config*. + + Called once from ``__main__.py`` before any module-level loggers emit + records. Subsequent calls are no-ops because ``basicConfig`` is + idempotent once handlers are attached. + + Configuration keys (all optional): + logging.level : Python log level name, e.g. ``INFO``, ``DEBUG``. + Defaults to ``INFO``. + logging.file : Absolute path for the rotating log file. + When empty or absent, only stdout is used. + + The rotating file handler caps each file at 5 MB and keeps 3 backups, + which limits total disk usage to ~20 MB regardless of uptime. + + Args: + config: Parsed configuration dictionary (output of ``load_config``). + """ log_cfg = config.get("logging", {}) + + # Resolve the log level; fall back to INFO for unknown names. level = getattr(logging, log_cfg.get("level", "INFO").upper(), logging.INFO) log_file = log_cfg.get("file", "") + # Always log to stdout so journald captures output via + # StandardOutput=journal in the systemd unit. handlers = [logging.StreamHandler(sys.stdout)] + if log_file: + # Rotating file handler: 5 MB per file, 3 rotated backups kept. handlers.append( logging.handlers.RotatingFileHandler( log_file, maxBytes=5 * 1024 * 1024, backupCount=3