refactor: improve backend type safety and import organization

- Add TYPE_CHECKING guards for runtime-expensive imports (aiohttp, aiosqlite)
- Reorganize imports to follow PEP 8 conventions
- Convert TypeAlias to modern PEP 695 type syntax (where appropriate)
- Use Sequence/Mapping from collections.abc for type hints (covariant)
- Replace string literals with cast() for improved type inference
- Fix casting of Fail2BanResponse and TypedDict patterns
- Add IpLookupResult TypedDict for precise return type annotation
- Reformat overlong lines for readability (120 char limit)
- Add asyncio_mode and filterwarnings to pytest config
- Update test fixtures with improved type hints

This improves mypy type checking and makes type relationships explicit.
This commit is contained in:
2026-03-20 13:44:14 +01:00
parent 6515164d53
commit 250bb1a2e5
30 changed files with 431 additions and 644 deletions

View File

@@ -21,34 +21,52 @@ import contextlib
import errno
import socket
import time
from collections.abc import Mapping, Sequence, Set
from pickle import HIGHEST_PROTOCOL, dumps, loads
from typing import TYPE_CHECKING, TypeAlias
from typing import TYPE_CHECKING
import structlog
# ---------------------------------------------------------------------------
# Types
# ---------------------------------------------------------------------------
Fail2BanToken: TypeAlias = str | int | float | bool | None | dict[str, object] | list[object]
# Use covariant container types so callers can pass ``list[int]`` / ``dict[str, str]``
# without needing to cast. At runtime we only accept the basic built-in
# containers supported by fail2ban's protocol (list/dict/set) and stringify
# anything else.
#
# NOTE: ``Sequence`` will also accept tuples, but tuples are stringified at
# runtime because fail2ban only understands lists.
type Fail2BanToken = (
str
| int
| float
| bool
| None
| Mapping[str, object]
| Sequence[object]
| Set[object]
)
"""A single token in a fail2ban command.
Fail2ban accepts simple types (str/int/float/bool) plus compound types
(list/dict). Complex objects are stringified before being sent.
(list/dict/set). Complex objects are stringified before being sent.
"""
Fail2BanCommand: TypeAlias = list[Fail2BanToken]
type Fail2BanCommand = Sequence[Fail2BanToken]
"""A command sent to fail2ban over the socket.
Commands are pickle serialised lists of tokens.
Commands are pickle serialised sequences of tokens.
"""
Fail2BanResponse: TypeAlias = tuple[int, object]
type Fail2BanResponse = tuple[int, object]
"""A typical fail2ban response containing a status code and payload."""
if TYPE_CHECKING:
from types import TracebackType
import structlog
log: structlog.stdlib.BoundLogger = structlog.get_logger()
# fail2ban protocol constants — inline to avoid a hard import dependency
@@ -200,7 +218,7 @@ def _send_command_sync(
) from last_oserror
def _coerce_command_token(token: Fail2BanToken) -> Fail2BanToken:
def _coerce_command_token(token: object) -> Fail2BanToken:
"""Coerce a command token to a type that fail2ban understands.
fail2ban's ``CSocket.convert`` accepts ``str``, ``bool``, ``int``,