chore: eliminate all deprecation warnings from tests and linter

Warnings fixed:
- datetime.utcnow() → datetime.now(UTC) across 48+ files (src/ + tests/)
- datetime.utcnow (callback ref) → lambda: datetime.now(UTC) in model fields (18 files)
- Pydantic class Config → model_config = ConfigDict(...) (16 files)
- Pydantic .dict() → .model_dump() (8 files)
- ConfigDict(allow_population_by_field_name=True) → validate_by_name=True
- SQLAlchemy declarative_base() import path updated
- FastAPI on_event → lifespan context manager (app.py)
- Import sorting (ruff I001) auto-fixed across all files
- Fixed broken re-export chains that ruff F401 cleanup broke:
  _validate_bcp47: service.py now imports from dictionary_validation directly
  job_to_response: _job_routes.py and test imports from service_utils directly
  fetch_datasource_metadata: restored re-export in service.py
- Added missing TranslateJobService import in _job_routes.py (was deleted by F401)
- Added ConfigDict(protected_namespaces=()) for DashboardDatasetItem schema field
- pytest.ini: replaced deprecated importmode with asyncio_mode

All 440 tests pass with zero deprecation warnings.
This commit is contained in:
2026-05-26 19:18:28 +03:00
parent 65340c079e
commit 54779a636a
181 changed files with 779 additions and 897 deletions

View File

@@ -10,13 +10,15 @@
# @DATA_CONTRACT [HTTP Request | WS Message] -> [HTTP Response | JSON Log Stream]
# @RATIONALE Duplicate @RELATION and @INVARIANT lines removed from header. Import sorting unified via ruff isort (I) rule across src/ — 139 fixes applied.
import asyncio
from contextlib import asynccontextmanager
import os
from pathlib import Path
# project_root is used for static files mounting
project_root = Path(__file__).resolve().parent.parent.parent
import asyncio
from alembic.config import Config as AlembicConfig
from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
@@ -24,6 +26,8 @@ from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.sessions import SessionMiddleware
from alembic import command as alembic_command
from .api import auth
from .api.routes import (
admin,
@@ -50,9 +54,6 @@ from .api.routes import (
translate,
validation,
)
from alembic.config import Config as AlembicConfig
from alembic import command as alembic_command
from .core.auth.security import get_password_hash
from .core.cot_logger import seed_trace_id
from .core.database import AuthSessionLocal, init_db
@@ -62,6 +63,28 @@ from .core.utils.network import NetworkError
from .dependencies import get_scheduler_service, get_task_manager
from .models.auth import Role, User
# #region lifespan [C:3] [TYPE Function]
# @BRIEF Async context manager for FastAPI startup/shutdown lifecycle.
# @RELATION CALLS -> [run_alembic_migrations]
# @RELATION CALLS -> [init_db]
# @RELATION CALLS -> [AppDependencies]
# @POST On startup: schema up-to-date, admin exists, scheduler started. On shutdown: scheduler stopped.
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
seed_trace_id()
with belief_scope("startup_event"):
ensure_encryption_key()
run_alembic_migrations()
init_db()
ensure_initial_admin_user()
scheduler = get_scheduler_service()
scheduler.start()
yield
# Shutdown
scheduler.stop()
# #endregion lifespan
# #region FastAPI_App [C:3] [TYPE Global] [SEMANTICS app, fastapi, instance, route-registry]
# @BRIEF Canonical FastAPI application instance for route, middleware, and websocket registration.
# @RELATION DEPENDS_ON -> [ApiRoutesModule]
@@ -70,6 +93,7 @@ app = FastAPI(
title="Superset Tools API",
description="API for managing Superset automation tools and plugins.",
version="1.0.0",
lifespan=lifespan,
)
# #endregion FastAPI_App
# #region ensure_initial_admin_user [C:3] [TYPE Function]
@@ -152,37 +176,7 @@ def run_alembic_migrations() -> None:
# #endregion run_alembic_migrations
# #region startup_event [C:3] [TYPE Function]
# @BRIEF Handles application startup: Alembic → create_all → admin bootstrap → scheduler.
# @RELATION CALLS -> [run_alembic_migrations]
# @RELATION CALLS -> [init_db]
# @POST Schema is up-to-date via Alembic, missing tables created, admin exists, scheduler started.
@app.on_event("startup")
async def startup_event():
seed_trace_id()
with belief_scope("startup_event"):
ensure_encryption_key()
# 1. Alembic — applies ALL pending migrations (add/drop/rename columns, data migrations)
run_alembic_migrations()
# 2. init_db — creates new tables via create_all() (does NOT alter existing ones)
init_db()
ensure_initial_admin_user()
scheduler = get_scheduler_service()
scheduler.start()
# #endregion startup_event
# #region shutdown_event [C:3] [TYPE Function]
# @BRIEF Handles application shutdown tasks, such as stopping the scheduler.
# @RELATION CALLS -> [AppDependencies]
# @PRE None.
# @POST Scheduler is stopped.
# Shutdown event
@app.on_event("shutdown")
async def shutdown_event():
seed_trace_id()
with belief_scope("shutdown_event"):
scheduler = get_scheduler_service()
scheduler.stop()
# #endregion shutdown_event
# #region app_middleware [TYPE Block]
# @BRIEF Configure application-wide middleware (Session, CORS).
# @RATIONALE SessionMiddleware uses SESSION_SECRET_KEY independent of JWT SECRET_KEY (see [SEC:H-4]).
@@ -349,8 +343,8 @@ async def _authenticate_websocket(websocket: WebSocket, endpoint_name: str) -> b
# Try JWT first, fallback to API key
try:
from .core.auth.jwt import decode_token
from .core.auth.api_key import hash_api_key
from .core.auth.jwt import decode_token
from .core.database import SessionLocal
# Attempt JWT validation
@@ -479,7 +473,7 @@ async def websocket_endpoint(
initial_sent = 0
for log_entry in initial_logs:
if matches_filters(log_entry):
log_dict = log_entry.dict()
log_dict = log_entry.model_dump()
log_dict["timestamp"] = log_dict["timestamp"].isoformat()
await websocket.send_json(log_dict)
initial_sent += 1
@@ -510,7 +504,7 @@ async def websocket_endpoint(
log_entry = await queue.get()
if not matches_filters(log_entry):
continue
log_dict = log_entry.dict()
log_dict = log_entry.model_dump()
log_dict["timestamp"] = log_dict["timestamp"].isoformat()
await websocket.send_json(log_dict)
logger.reflect(