From 79abf23ea81570d6d7352a91ff8bd62d35c21dde Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 30 Jun 2026 10:19:28 +0200 Subject: [PATCH 1/2] fix(api): evidence_routes F821-Regression beheben (Extract-Service-Halb-Refactor) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a638d0e5 ("extract EvidenceService") stellte Signaturen auf service=Depends um, ließ aber Bodies + Imports auf dem alten Stand → 43 F821 (NameError zur Laufzeit). - gelöschte stdlib-Imports restauriert (os/json/hashlib/uuid/datetime/timedelta) - db: Session = Depends(get_db) an den betroffenen Endpoints restauriert - translate_domain_errors + _update_risks_impl (=evidence_service._update_risks) importiert - unerreichbaren toten Block (alte get_ci_evidence_status-Impl nach dem return) entfernt - dsms_cid=None no-op in create/review/reject (DSMS-Commit-Copy-Paste) Verifiziert: ruff F821 0, py_compile, test_evidence_routes.py 35 passed. Co-Authored-By: Claude Opus 4.7 --- .../compliance/api/evidence_routes.py | 84 ++++--------------- 1 file changed, 18 insertions(+), 66 deletions(-) diff --git a/backend-compliance/compliance/api/evidence_routes.py b/backend-compliance/compliance/api/evidence_routes.py index 29acba1b..1b7f201e 100644 --- a/backend-compliance/compliance/api/evidence_routes.py +++ b/backend-compliance/compliance/api/evidence_routes.py @@ -16,6 +16,11 @@ from the legacy path. """ import logging +import os +import json +import hashlib +import uuid as uuid_module +from datetime import datetime, timedelta from typing import Any, Optional from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile @@ -30,14 +35,15 @@ from ..db import ( EvidenceConfidenceEnum, EvidenceTruthStatusEnum, ) -from ..db.models import EvidenceDB, ControlDB, AuditTrailDB +from ..db.models import EvidenceDB, AuditTrailDB from ..services.auto_risk_updater import AutoRiskUpdater -from ..services.evidence_service import EvidenceService +from ..services.evidence_service import EvidenceService, _update_risks as _update_risks_impl from .schemas import ( EvidenceCreate, EvidenceResponse, EvidenceListResponse, EvidenceRejectRequest, ) from .audit_trail_utils import log_audit_trail +from ._http_errors import translate_domain_errors logger = logging.getLogger(__name__) router = APIRouter(tags=["compliance-evidence"]) @@ -146,6 +152,7 @@ async def list_evidence( status: Optional[str] = None, page: Optional[int] = Query(None, ge=1, description="Page number (1-based)"), limit: Optional[int] = Query(None, ge=1, le=500, description="Items per page"), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceListResponse: """List evidence with optional filters and pagination.""" @@ -186,9 +193,11 @@ async def list_evidence( @router.post("/evidence", response_model=EvidenceResponse) async def create_evidence( evidence_data: EvidenceCreate, + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceResponse: """Create new evidence record.""" + dsms_cid = None repo = EvidenceRepository(db) # Get control UUID @@ -257,6 +266,7 @@ async def create_evidence( @router.delete("/evidence/{evidence_id}") async def delete_evidence( evidence_id: str, + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> dict[str, Any]: """Delete an evidence record.""" @@ -275,6 +285,7 @@ async def upload_evidence( title: str = Query(...), file: UploadFile = File(...), description: Optional[str] = Query(None), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceResponse: """Upload evidence file.""" @@ -674,6 +685,7 @@ async def collect_ci_evidence( async def get_ci_evidence_status( control_id: Optional[str] = Query(None, description="Filter by control ID"), days: int = Query(30, description="Look back N days"), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> dict[str, Any]: """Get CI/CD evidence collection status overview.""" @@ -681,70 +693,8 @@ async def get_ci_evidence_status( return service.ci_status(control_id, days) -# ---------------------------------------------------------------------------- -# Legacy re-exports for tests that import helpers directly. -# ---------------------------------------------------------------------------- - - if control_id: - ctrl_repo = ControlRepository(db) - control = ctrl_repo.get_by_control_id(control_id) - if control: - query = query.filter(EvidenceDB.control_id == control.id) - - evidence_list = query.order_by(EvidenceDB.collected_at.desc()).limit(100).all() - - # Group by control and calculate stats - control_stats = defaultdict(lambda: { - "total": 0, - "valid": 0, - "failed": 0, - "last_collected": None, - "evidence": [], - }) - - for e in evidence_list: - # Get control_id string - control = db.query(ControlDB).filter(ControlDB.id == e.control_id).first() - ctrl_id = control.control_id if control else "unknown" - - stats = control_stats[ctrl_id] - stats["total"] += 1 - if e.status: - if e.status.value == "valid": - stats["valid"] += 1 - elif e.status.value == "failed": - stats["failed"] += 1 - if not stats["last_collected"] or e.collected_at > stats["last_collected"]: - stats["last_collected"] = e.collected_at - - # Add evidence summary - stats["evidence"].append({ - "id": e.id, - "type": e.evidence_type, - "status": e.status.value if e.status else None, - "collected_at": e.collected_at.isoformat() if e.collected_at else None, - "ci_job_id": e.ci_job_id, - }) - - # Convert to list and sort - result = [] - for ctrl_id, stats in control_stats.items(): - result.append({ - "control_id": ctrl_id, - "total_evidence": stats["total"], - "valid_count": stats["valid"], - "failed_count": stats["failed"], - "last_collected": stats["last_collected"].isoformat() if stats["last_collected"] else None, - "recent_evidence": stats["evidence"][:5], - }) - - result.sort(key=lambda x: x["last_collected"] or "", reverse=True) - - return { - "period_days": days, - "total_evidence": len(evidence_list), - "controls": result, - } +# (Alte CI-Status-Implementierung entfernt — unerreichbarer Code nach `return +# service.ci_status(...)`; durch den Service ersetzt, `query` war nie initialisiert.) # ============================================================================ @@ -772,6 +722,7 @@ async def review_evidence( approval_status='first_approved'. A second (different) reviewer then sets second_reviewer and approval_status='approved'. """ + dsms_cid = None evidence = db.query(EvidenceDB).filter(EvidenceDB.id == evidence_id).first() if not evidence: raise HTTPException(status_code=404, detail=f"Evidence {evidence_id} not found") @@ -851,6 +802,7 @@ async def reject_evidence( db: Session = Depends(get_db), ): """Reject evidence (sets approval_status='rejected').""" + dsms_cid = None evidence = db.query(EvidenceDB).filter(EvidenceDB.id == evidence_id).first() if not evidence: raise HTTPException(status_code=404, detail=f"Evidence {evidence_id} not found") From 3389fa3e7a5c39b2c0ea0c195621291b4af066a5 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 30 Jun 2026 10:51:00 +0200 Subject: [PATCH 2/2] fix(api): F821-Regression in 6 weiteren Route-Dateien beheben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gleiche Wurzel wie evidence_routes (Extract-Service-Refactor a638d0e5 ff.): Signaturen/Imports halb umgestellt → undefined names → NameError beim Aufruf. - routes.py: db-Param in get_control/update_control/review_control + EvidenceDB-Import - dsfa_routes.py: db-Param in create_dsfa + HTTPException/text-Import - dashboard_routes.py: timezone-Import - canonical_control_routes.py: logger-Definition - ai_routes.py: timezone in den lokalen datetime-Imports - vvt_routes.py: HTTPException-Import Verifiziert: ruff F821 0 über das gesamte compliance/api/, alle 6 py_compile, 294 Tests grün auf den betroffenen Modulen (die 2 dsfa-invalid-status/risk-Failures sind vorbestehend = 400-vs-422, unabhängig von diesem Fix). Co-Authored-By: Claude Opus 4.7 --- backend-compliance/compliance/api/ai_routes.py | 4 ++-- backend-compliance/compliance/api/canonical_control_routes.py | 2 ++ backend-compliance/compliance/api/dashboard_routes.py | 2 +- backend-compliance/compliance/api/dsfa_routes.py | 4 +++- backend-compliance/compliance/api/routes.py | 4 ++++ backend-compliance/compliance/api/vvt_routes.py | 2 +- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend-compliance/compliance/api/ai_routes.py b/backend-compliance/compliance/api/ai_routes.py index 6e2997f9..5e31818b 100644 --- a/backend-compliance/compliance/api/ai_routes.py +++ b/backend-compliance/compliance/api/ai_routes.py @@ -162,7 +162,7 @@ async def update_ai_system( db: Session = Depends(get_db), ): """Update an AI system.""" - from datetime import datetime + from datetime import datetime, timezone system = db.query(AISystemDB).filter(AISystemDB.id == system_id).first() if not system: @@ -226,7 +226,7 @@ async def assess_ai_system( db: Session = Depends(get_db), ): """Run AI Act risk assessment for an AI system.""" - from datetime import datetime + from datetime import datetime, timezone system = db.query(AISystemDB).filter(AISystemDB.id == system_id).first() if not system: diff --git a/backend-compliance/compliance/api/canonical_control_routes.py b/backend-compliance/compliance/api/canonical_control_routes.py index a8f80cdd..cac566fb 100644 --- a/backend-compliance/compliance/api/canonical_control_routes.py +++ b/backend-compliance/compliance/api/canonical_control_routes.py @@ -47,6 +47,8 @@ from compliance.services.canonical_control_service import ( _control_row, # re-exported for legacy test imports ) +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/v1/canonical", tags=["canonical-controls"]) diff --git a/backend-compliance/compliance/api/dashboard_routes.py b/backend-compliance/compliance/api/dashboard_routes.py index a4d2fd79..88034085 100644 --- a/backend-compliance/compliance/api/dashboard_routes.py +++ b/backend-compliance/compliance/api/dashboard_routes.py @@ -14,7 +14,7 @@ Endpoints: """ import logging -from datetime import datetime, date, timedelta +from datetime import datetime, date, timedelta, timezone from calendar import month_abbr from typing import Optional, Dict, Any, List from decimal import Decimal diff --git a/backend-compliance/compliance/api/dsfa_routes.py b/backend-compliance/compliance/api/dsfa_routes.py index 024737fe..1bc0fe0c 100644 --- a/backend-compliance/compliance/api/dsfa_routes.py +++ b/backend-compliance/compliance/api/dsfa_routes.py @@ -26,10 +26,11 @@ versions). Module-level helpers re-exported for legacy tests. import logging from typing import Any, List, Optional -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from fastapi.responses import Response from sqlalchemy.orm import Session +from sqlalchemy import text from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors @@ -484,6 +485,7 @@ async def list_dsfas( async def create_dsfa( request: DSFACreate, tenant_id: Optional[str] = Query(None), + db: Session = Depends(get_db), service: DSFAService = Depends(get_dsfa_service), ) -> dict[str, Any]: """Neue DSFA erstellen.""" diff --git a/backend-compliance/compliance/api/routes.py b/backend-compliance/compliance/api/routes.py index a3693475..c0a20760 100644 --- a/backend-compliance/compliance/api/routes.py +++ b/backend-compliance/compliance/api/routes.py @@ -24,6 +24,7 @@ from fastapi.responses import FileResponse from sqlalchemy.orm import Session from classroom_engine.database import get_db +from ..db.models import EvidenceDB from .audit_trail_utils import log_audit_trail from ..db import ( @@ -310,6 +311,7 @@ async def list_controls_paginated( ) async def get_control( control_id: str, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Get a specific control by control_id.""" @@ -354,6 +356,7 @@ async def get_control( async def update_control( control_id: str, update: ControlUpdate, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Update a control.""" @@ -443,6 +446,7 @@ async def update_control( async def review_control( control_id: str, review: ControlReviewRequest, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Mark a control as reviewed with new status.""" diff --git a/backend-compliance/compliance/api/vvt_routes.py b/backend-compliance/compliance/api/vvt_routes.py index 66e44f6e..0cbb8d83 100644 --- a/backend-compliance/compliance/api/vvt_routes.py +++ b/backend-compliance/compliance/api/vvt_routes.py @@ -21,7 +21,7 @@ Phase 1 Step 4 refactor: handlers delegate to VVTService. import logging from typing import Any, List, Optional -from fastapi import APIRouter, Depends, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session