refactor: modernize code quality tooling with Ruff

- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:37:38 +01:00
parent 21c13ca39b
commit 238c1ec9b8
169 changed files with 2183 additions and 1784 deletions

View File

@@ -17,8 +17,9 @@ The module uses the following technologies:
import logging
import os
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, Optional
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
from typing import Any
from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials
@@ -26,10 +27,14 @@ from jose import jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from app.exceptions import (AdminRequiredException,
InsufficientPermissionsException,
InvalidCredentialsException, InvalidTokenException,
TokenExpiredException, UserNotActiveException)
from app.exceptions import (
AdminRequiredException,
InsufficientPermissionsException,
InvalidCredentialsException,
InvalidTokenException,
TokenExpiredException,
UserNotActiveException,
)
from models.database.user import User
logger = logging.getLogger(__name__)
@@ -97,7 +102,7 @@ class AuthManager:
def authenticate_user(
self, db: Session, username: str, password: str
) -> Optional[User]:
) -> User | None:
"""Authenticate user credentials against the database.
Supports authentication using either username or email address.
@@ -129,7 +134,7 @@ class AuthManager:
# Authentication successful, return user object
return user
def create_access_token(self, user: User) -> Dict[str, Any]:
def create_access_token(self, user: User) -> dict[str, Any]:
"""Create a JWT access token for an authenticated user.
The token includes user identity and role information in the payload.
@@ -146,7 +151,7 @@ class AuthManager:
"""
# Calculate token expiration time
expires_delta = timedelta(minutes=self.token_expire_minutes)
expire = datetime.now(timezone.utc) + expires_delta
expire = datetime.now(UTC) + expires_delta
# Build JWT payload with user information
payload = {
@@ -155,7 +160,7 @@ class AuthManager:
"email": user.email, # User email address
"role": user.role, # User role for authorization
"exp": expire, # Expiration time (JWT standard claim)
"iat": datetime.now(timezone.utc), # Issued at time (JWT standard claim)
"iat": datetime.now(UTC), # Issued at time (JWT standard claim)
}
# Encode the payload into a JWT token
@@ -168,7 +173,7 @@ class AuthManager:
"expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds
}
def verify_token(self, token: str) -> Dict[str, Any]:
def verify_token(self, token: str) -> dict[str, Any]:
"""Verify and decode a JWT token, returning the user data.
Validates the token signature, expiration, and required claims.
@@ -199,8 +204,8 @@ class AuthManager:
raise InvalidTokenException("Token missing expiration")
# Check if token has expired (additional check beyond jwt.decode)
if datetime.now(timezone.utc) > datetime.fromtimestamp(
exp, tz=timezone.utc
if datetime.now(UTC) > datetime.fromtimestamp(
exp, tz=UTC
):
raise TokenExpiredException()

View File

@@ -70,7 +70,7 @@ class ContextManager:
host = host.split(":")[0]
logger.debug(
f"[CONTEXT] Detecting context",
"[CONTEXT] Detecting context",
extra={
"original_path": request.url.path,
"clean_path": getattr(request.state, "clean_path", "NOT SET"),

View File

@@ -9,7 +9,7 @@ This module provides classes and functions for:
import logging
import time
from typing import Callable
from collections.abc import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

View File

@@ -9,8 +9,7 @@ This module provides classes and functions for:
import logging
from collections import defaultdict, deque
from datetime import datetime, timedelta, timezone
from typing import Dict
from datetime import UTC, datetime, timedelta
logger = logging.getLogger(__name__)
@@ -21,9 +20,9 @@ class RateLimiter:
def __init__(self):
"""Class constructor."""
# Dictionary to store request timestamps for each client
self.clients: Dict[str, deque] = defaultdict(lambda: deque())
self.clients: dict[str, deque] = defaultdict(lambda: deque())
self.cleanup_interval = 3600 # Clean up old entries every hour
self.last_cleanup = datetime.now(timezone.utc)
self.last_cleanup = datetime.now(UTC)
def allow_request(
self, client_id: str, max_requests: int, window_seconds: int
@@ -33,7 +32,7 @@ class RateLimiter:
Uses sliding window algorithm
"""
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
window_start = now - timedelta(seconds=window_seconds)
# Clean up old entries periodically
@@ -60,7 +59,7 @@ class RateLimiter:
def _cleanup_old_entries(self):
"""Clean up old entries to prevent memory leaks."""
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=24)
cutoff_time = datetime.now(UTC) - timedelta(hours=24)
clients_to_remove = []
for client_id, requests in self.clients.items():
@@ -80,11 +79,11 @@ class RateLimiter:
f"Rate limiter cleanup completed. Removed {len(clients_to_remove)} inactive clients"
)
def get_client_stats(self, client_id: str) -> Dict[str, int]:
def get_client_stats(self, client_id: str) -> dict[str, int]:
"""Get statistics for a specific client."""
client_requests = self.clients.get(client_id, deque())
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
hour_ago = now - timedelta(hours=1)
day_ago = now - timedelta(days=1)

View File

@@ -114,7 +114,7 @@ class ThemeContextMiddleware(BaseHTTPMiddleware):
request.state.theme = theme
logger.debug(
f"[THEME] Theme loaded for vendor",
"[THEME] Theme loaded for vendor",
extra={
"vendor_id": vendor.id,
"vendor_name": vendor.name,

View File

@@ -12,7 +12,6 @@ Also extracts clean_path for nested routing patterns.
"""
import logging
from typing import Optional
from fastapi import Request
from sqlalchemy import func
@@ -31,7 +30,7 @@ class VendorContextManager:
"""Manages vendor context detection for multi-tenant routing."""
@staticmethod
def detect_vendor_context(request: Request) -> Optional[dict]:
def detect_vendor_context(request: Request) -> dict | None:
"""
Detect vendor context from request.
@@ -106,7 +105,7 @@ class VendorContextManager:
return None
@staticmethod
def get_vendor_from_context(db: Session, context: dict) -> Optional[Vendor]:
def get_vendor_from_context(db: Session, context: dict) -> Vendor | None:
"""
Get vendor from database using context information.
@@ -142,11 +141,10 @@ class VendorContextManager:
f"[OK] Vendor found via custom domain: {domain}{vendor.name}"
)
return vendor
else:
logger.warning(
f"No active vendor found for custom domain: {domain}"
)
return None
logger.warning(
f"No active vendor found for custom domain: {domain}"
)
return None
# Method 2 & 3: Subdomain or path-based lookup
if "subdomain" in context:
@@ -169,7 +167,7 @@ class VendorContextManager:
return vendor
@staticmethod
def extract_clean_path(request: Request, vendor_context: Optional[dict]) -> str:
def extract_clean_path(request: Request, vendor_context: dict | None) -> str:
"""
Extract clean path without vendor prefix for routing.
@@ -217,7 +215,7 @@ class VendorContextManager:
return request.url.path.startswith("/api/v1/shop/")
@staticmethod
def extract_vendor_from_referer(request: Request) -> Optional[dict]:
def extract_vendor_from_referer(request: Request) -> dict | None:
"""
Extract vendor context from Referer header.
@@ -250,7 +248,7 @@ class VendorContextManager:
referer_host = referer_host.split(":")[0]
logger.debug(
f"[VENDOR] Extracting vendor from Referer",
"[VENDOR] Extracting vendor from Referer",
extra={
"referer": referer,
"referer_host": referer_host,
@@ -449,7 +447,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
request.state.clean_path = request.url.path
logger.debug(
f"[VENDOR_CONTEXT] Vendor detected from Referer for shop API",
"[VENDOR_CONTEXT] Vendor detected from Referer for shop API",
extra={
"vendor_id": vendor.id,
"vendor_name": vendor.name,
@@ -463,7 +461,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
)
else:
logger.warning(
f"[WARNING] Vendor context from Referer but vendor not found",
"[WARNING] Vendor context from Referer but vendor not found",
extra={
"context": vendor_context,
"detection_method": vendor_context.get(
@@ -479,7 +477,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
db.close()
else:
logger.warning(
f"[VENDOR] Shop API request without Referer header",
"[VENDOR] Shop API request without Referer header",
extra={"path": request.url.path},
)
request.state.vendor = None
@@ -518,7 +516,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
)
logger.debug(
f"[VENDOR_CONTEXT] Vendor detected",
"[VENDOR_CONTEXT] Vendor detected",
extra={
"vendor_id": vendor.id,
"vendor_name": vendor.name,
@@ -530,7 +528,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
)
else:
logger.warning(
f"[WARNING] Vendor context detected but vendor not found",
"[WARNING] Vendor context detected but vendor not found",
extra={
"context": vendor_context,
"detection_method": vendor_context.get("detection_method"),
@@ -543,7 +541,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
db.close()
else:
logger.debug(
f"[VENDOR] No vendor context detected",
"[VENDOR] No vendor context detected",
extra={
"path": request.url.path,
"host": request.headers.get("host", ""),
@@ -557,7 +555,7 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
return await call_next(request)
def get_current_vendor(request: Request) -> Optional[Vendor]:
def get_current_vendor(request: Request) -> Vendor | None:
"""Helper function to get current vendor from request state."""
return getattr(request.state, "vendor", None)