refactor: no functions or classes inside functions in app/

Merged
HA
7d ago
+317-275

app/ defined 33 functions inside other functions. This moves every one of them out, so an AST scan of app/ finds no def or class inside a function body.

The Kafka handlers in app/events/handlers.py are module-level functions now, each factory registers them with router.subscriber(...)(handler), and the names say what they do (start_saga, store_completed_result, notify_failed). FastStream wraps each registration separately and Dishka injects through the broker's call decorators, so the same function is safe to register on several routers. The exception handlers moved out of configure_exception_handlers the same way.

The ASGI wrappers in the cache, metrics, rate limit, security headers and request size middlewares are small classes holding one request's send or receive. The metrics one keeps the status and headers its response starts with. The memory and CPU gauge callbacks are module-level functions, and the process gauge is a class that keeps its psutil.Process, since cpu_percent measures from its last call.

The notification and replay retry loggers are methods. They read the notification or the replay config from RetryCallState.args, the arguments of the retried call, so nothing closes over them, and the replay dispatch is a method too. System notifications go out 20 users at a time with itertools.batched and gather instead of a semaphore in a closure. The user overview counts statuses with a generator, and the rate limit filters compare three precomputed sets instead of three lambdas. Three sort-key lambdas are left, since the issue covers def and class.

AGENTS.md now states the rule and shows the module-level handler, and the topic architecture doc shows the same pattern.

ruff, mypy, grimp, 569 unit tests and the Grafana contract tests pass, and the docs build in strict mode. The backend e2e suite gave 488 passed, 1 skipped on a fresh local stack with k3s and the worker running this branch, the same count as main.

Closes #383


Summary by cubic

Moves every def and class defined inside a function out to module level in app/, so an AST scan of app/ finds no nested functions or classes.

  • Kafka handlers, exception handlers, and retry loggers are now module-level functions or methods, registered with the subscriber or exception-handler decorators directly.
  • ASGI send/receive wrappers in the cache, metrics, rate limit, security headers, and request size middlewares became small classes that hold one request's state.
  • System notifications now batch 20 users at a time with itertools.batched and gather instead of a semaphore in a closure.
  • AGENTS.md and the Kafka topic architecture doc now document the rule and show the pattern.

Closes #383.