fix: clear all mypy errors

Annotate untyped containers and Optional attributes, type the stdlib-shaped
metric dicts in the client plugins, and add types-PyYAML/types-psutil to the
dev extras plus a mypy override for nio/mattermostdriver (no stubs).

Behaviour fixes found on the way: enable_kernel_timestamps() now returns False
when SO_TIMESTAMP is unknown instead of raising TypeError, send_shutdown is
defined at module level so cleanup() cannot hit a NameError, and a failed
subprocess returncode falls back to UNKNOWN.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 05:04:54 -04:00
co-authored by Claude Sonnet 5.5
parent 748349a7b9
commit 445ff6b474
20 changed files with 63 additions and 49 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import os
try:
import yaml
except Exception:
yaml = None
yaml = None # type: ignore[assignment]
SERVER_DEFAULTS = {
# Network settings
+1 -1
View File
@@ -1,4 +1,4 @@
msgs = [] # in-memory list of recent messages for new websocket clients; also logged to file via notify.eventlog
msgs: list = [] # in-memory list of recent messages for new websocket clients; also logged to file via notify.eventlog
class Data:
+5 -5
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from subprocess import Popen, PIPE, STDOUT
from typing import Optional
from typing import Any, Callable, Optional
import asyncio
import logging
@@ -79,8 +79,8 @@ async def dns_update_worker(
hbdclass,
cfg: dict,
async_queue=None,
log: Optional[callable] = None,
pushmsg: Optional[callable] = None,
log: Optional[Callable[..., Any]] = None,
pushmsg: Optional[Callable[..., Any]] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
):
"""Pure async DNS worker that processes updates from asyncio.Queue.
@@ -164,7 +164,7 @@ async def dns_update_worker(
def start_dns_worker(
hbdclass,
cfg: dict,
log: Optional[callable] = None,
log: Optional[Callable[..., Any]] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
):
"""Start the async DNS worker and return the Task.
@@ -176,7 +176,7 @@ def start_dns_worker(
loop = asyncio.get_event_loop()
# Create asyncio.Queue and wrap in a bridge for thread-safe puts
async_q = asyncio.Queue()
async_q: asyncio.Queue[Any] = asyncio.Queue()
class _QueueBridge:
"""Thread-safe wrapper around asyncio.Queue for synchronous callers."""
+4 -3
View File
@@ -8,6 +8,7 @@ import time
import json
import copy
import queue
from typing import Any, Dict
num = 0
@@ -24,7 +25,7 @@ def log(host, m):
class Connection:
# map of addrs to names
htab = {}
htab: Dict[str, Any] = {}
UNKNOWN = "unknown"
UP = "up"
DOWN = "down"
@@ -278,8 +279,8 @@ class Connection:
#
class Host:
# Table of Hosts
hosts = {}
dnsQ = queue.Queue()
hosts: Dict[str, "Host"] = {}
dnsQ: "queue.Queue[Any]" = queue.Queue()
def __init__(self, name):
global num
+2 -2
View File
@@ -11,7 +11,7 @@ import os
import asyncio
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, Callable, Iterable, List, Tuple, Union
from typing import Dict, Any, Optional, Callable, Iterable, List, TextIO, Tuple, Union
logger = logging.getLogger(__name__)
@@ -52,7 +52,7 @@ class MessageJournal:
self.enabled = self.config.get('journal_enabled', True)
# Runtime state
self._file_handle = None
self._file_handle: Optional[TextIO] = None
self._current_size = 0
self._lock = asyncio.Lock()
self._initialized = False
+1 -1
View File
@@ -128,7 +128,7 @@ def get_providers(config: dict) -> list[ResolvedProvider]:
Entries with missing required fields or unknown types are skipped with
a warning log. Order follows config declaration order.
"""
result = []
result: list = []
oauth_cfg = config.get("oauth", {})
if not isinstance(oauth_cfg, dict):
return result
+9 -9
View File
@@ -53,16 +53,16 @@ class AlertState:
self.metric_path = metric_path
self.level = AlertLevel.OK
self.since = time.time()
self.last_value = None
self.last_value: Any = None
self.last_check = time.time()
self.notification_count = 0
self.last_notification = None
self.threshold_value = None # The threshold value that triggered alert
self.operator = None # The comparison operator (>, <, >=, etc.)
self.last_notification: Optional[float] = None
self.threshold_value: Optional[float] = None # The threshold value that triggered alert
self.operator: Optional[str] = None # The comparison operator (>, <, >=, etc.)
self.hysteresis: Optional[float] = None # Hysteresis fraction used for recovery
self.formatted_message = None # Formatted display message for UI
self.formatted_message: Optional[str] = None # Formatted display message for UI
self.acknowledged = False # Whether alert has been acknowledged
self.acknowledged_at = None # Timestamp when acknowledged
self.acknowledged_at: Optional[float] = None # Timestamp when acknowledged
self.consecutive_count = 0 # Consecutive exceedances while still OK (for count gating)
self.pending_since: Optional[float] = None # non-None while waiting out grace period before notifying
@@ -362,13 +362,13 @@ class ThresholdChecker:
journal: Optional MessageJournal instance for logging threshold events
"""
# Named threshold configurations (pre-merged: defaults + overrides): {config_name: {metric_path: ThresholdConfig}}
self.threshold_configs = {}
self.threshold_configs: Dict[str, Dict[str, ThresholdConfig]] = {}
# Raw overrides only for each named config (no defaults baked in): {config_name: {metric_path: ThresholdConfig}}
self.threshold_raw_configs: Dict[str, Dict[str, ThresholdConfig]] = {}
# Single threshold set for backward compatibility: {metric_path: ThresholdConfig}
self.thresholds = {}
self.thresholds: Dict[str, ThresholdConfig] = {}
# Host to ordered list of config names: {host_name: [config_name, ...]}
self.host_config_mapping: Dict[str, List[str]] = {}
@@ -1276,7 +1276,7 @@ class ThresholdChecker:
def _format_display(
self,
display_format: str,
display_format: Optional[str],
value: Any,
threshold_value: Optional[float],
op_symbol: str,
+7 -5
View File
@@ -5,6 +5,7 @@ import socket
import struct
import time
import zlib
from typing import Optional
import logging
from platform import system as platform_system
@@ -25,6 +26,7 @@ _STALE_INTERVAL_MULTIPLIER = 3
# Supported on Linux, FreeBSD, and macOS. The constant is not exposed by
# Python's socket module on all platforms
platform = platform_system()
_SO_TIMESTAMP: Optional[int]
if platform == "Darwin":
_SO_TIMESTAMP = 1024 # SO_TIMESTAMP on macOS (not in Python's socket module)
elif platform == "Linux":
@@ -45,6 +47,8 @@ def enable_kernel_timestamps(sock) -> bool:
Returns True if the kernel will supply receive timestamps, False otherwise
(unsupported platform, older kernel, or insufficient permissions).
"""
if _SO_TIMESTAMP is None:
return False
try:
sock.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMP, 1)
return True
@@ -430,7 +434,7 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
return
cfg = ctx.get("config", {})
hbdcls = ctx.get("hbdclass")
hbdcls = ctx["hbdclass"]
msg_to_websockets = ctx.get("msg_to_websockets")
DEBUG = ctx.get("DEBUG", 0)
@@ -459,10 +463,8 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
newh = False
cid = msg.get("id", 0)
try:
rtt = float(msg.get("rtt"))
except TypeError:
rtt = None
rtt_raw = msg.get("rtt")
rtt = float(rtt_raw) if rtt_raw is not None else None
if msg.get("ID") == "HTB":
host.doesack = msg.get("acks", -1)