From 019e43b18c2974af62c3ba57e1415b12a28463bc Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 13 Oct 2026 09:49:50 +0200 Subject: [PATCH] Added Ruff linting --- pyproject.toml | 28 ++++++++++++++++++++++++++++ requirements-dev.txt | 1 + src/main.py | 41 +++++++++++++++++++++-------------------- src/metrics_server.py | 2 +- src/rabbitmq_handler.py | 16 +++++++++------- 5 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9f8f13d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.ruff] +line-length = 120 +target-version = "py312" +[tool.ruff.lint] +select = [ + "E", "F", # pycodestyle errors, pyflakes (unused imports/variables, undefined names) + "I", # import sorting (replaces isort) + "B", # bugbear: likely bugs + "ASYNC", # async mistakes + "RUF006", # un-referenced asyncio.create_task (tasks can be garbage-collected) +] +ignore = ["E501"] + +[tool.ruff.lint.isort] +known-first-party = ["db_module", "metrics_server", "rabbitmq_handler", "secret_manager", + "simple_logger_handler", "uvicorn_logger_config", "validator"] + +[tool.ruff.format] +indent-style = "tab" + +[tool.ruff.lint.flake8-bugbear] +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.Query", + "fastapi.Header", + "fastapi.Body", + "fastapi.Security", +] \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..92a80f4 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +ruff==0.17.0 diff --git a/src/main.py b/src/main.py index 568eb57..5e37112 100644 --- a/src/main.py +++ b/src/main.py @@ -1,22 +1,23 @@ -from fastapi import FastAPI, Depends, HTTPException, Request, Response +import asyncio +import os +from contextlib import asynccontextmanager +from typing import Dict + +import httpx +import uvicorn +from fastapi import Depends, FastAPI, HTTPException, Request from fastapi.responses import JSONResponse from fastapi.security.api_key import APIKeyHeader -from starlette.exceptions import HTTPException as StarletteHTTPException -from typing import Dict from pydantic import BaseModel, Field -from validator import verify_api_key -from db_module import get_db, create_connection_pool, close_connection_pool, start_healthcheck_thread -from simple_logger_handler import setup_logger, LOG_LEVEL -from rabbitmq_handler import RabbitMQProducer -import uvicorn -from contextlib import asynccontextmanager -from metrics_server import REQUEST_COUNTER -import asyncio -from uvicorn_logger_config import LOGGING_CONFIG -from secret_manager import cleanup_secret_files -import httpx -import os +from starlette.exceptions import HTTPException as StarletteHTTPException +from db_module import close_connection_pool, create_connection_pool, get_db, start_healthcheck_thread +from metrics_server import REQUEST_COUNTER +from rabbitmq_handler import RabbitMQProducer +from secret_manager import cleanup_secret_files +from simple_logger_handler import LOG_LEVEL, setup_logger +from uvicorn_logger_config import LOGGING_CONFIG +from validator import verify_api_key logger = setup_logger(__name__) api_key_header_internal = APIKeyHeader(name="X-API-Key-Internal") @@ -104,10 +105,10 @@ async def prometheus_middleware(request: Request, call_next): return response def verify_api_key_dependency_internal(db=Depends(get_db), api_key: str = Depends(api_key_header_internal)) -> str: - logger.debug(f"Verifying internal API key: {api_key}") - cursor = db.cursor() - cursor.execute("SELECT program_name, api_key FROM internal_api_keys WHERE status = 'active'") - for program_name, hashed_key in cursor.fetchall(): + with db.cursor() as cursor: + cursor.execute("SELECT program_name, api_key FROM internal_api_keys WHERE status = 'active'") + rows = cursor.fetchall() + for program_name, hashed_key in rows: if verify_api_key(api_key=api_key, hashed=hashed_key): logger.debug(f"API key verified for program: {program_name}") return program_name @@ -170,7 +171,7 @@ async def pull_notifications( except (httpx.HTTPError, ValueError) as e: logger.error(f"[FastAPI] Pull service failed for user_id={user_id}: {e!r}") status = 504 if isinstance(e, httpx.TimeoutException) else 502 - raise HTTPException(status_code=status, detail="Notification service unavailable") + raise HTTPException(status_code=status, detail="Notification service unavailable") from e logger.info(f"[FastAPI] Returning {len(messages)} message(s) for user_id={user_id}, has_more={has_more}") return PullResponse(messages=messages, count=len(messages), has_more=has_more) diff --git a/src/metrics_server.py b/src/metrics_server.py index c3fdc9b..15e7fd1 100644 --- a/src/metrics_server.py +++ b/src/metrics_server.py @@ -1,5 +1,5 @@ from fastapi import FastAPI, Response -from prometheus_client import generate_latest, CONTENT_TYPE_LATEST, Counter +from prometheus_client import CONTENT_TYPE_LATEST, Counter, generate_latest metrics_api = FastAPI(title="Metrics Server", description="Prometheus metrics endpoint") diff --git a/src/rabbitmq_handler.py b/src/rabbitmq_handler.py index ce372ee..ae57bdd 100644 --- a/src/rabbitmq_handler.py +++ b/src/rabbitmq_handler.py @@ -1,13 +1,15 @@ import asyncio +import json +import os +from enum import IntEnum +from typing import NamedTuple +from urllib.parse import quote + import aio_pika from aio_pika.exceptions import AMQPException, DeliveryError + from secret_manager import return_credentials -import os from simple_logger_handler import setup_logger -import json -from urllib.parse import quote -from typing import NamedTuple -from enum import IntEnum logger = setup_logger(__name__) MAX_UNROUTABLE_ATTEMPTS = 3 @@ -105,10 +107,10 @@ class RabbitMQProducer: - Declares the user's pull queue first when the key is a pull key. - Returned/unroutable messages (DeliveryError) are retried up to - MAX_UNROUTABLE_ATTEMPTS times, then dropped with an error log. + MAX_UNROUTABLE_ATTEMPTS times, then dropped with an error log. - Connection errors are retried without limit after a 2 second pause. - Any other error (e.g. non-serializable payload) drops the message so the - loop keeps running. + loop keeps running. """ logger.debug("[RabbitMQ] Waiting for ready signal before flushing queue") await self._ready.wait()