From 86588aff09c625a495464914bff0e56fedbd0f7e Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 10 Mar 2026 13:56:38 +0100 Subject: [PATCH] Fix SQLAlchemy 2.x compatibility: wrap raw SQL in text() SQLAlchemy 2.x requires raw SQL strings to be explicitly wrapped in text(). Fixed 16 instances across 5 route files. Co-Authored-By: Claude Opus 4.6 --- .../compliance/api/compliance_scope_routes.py | 13 ++++++------ .../compliance/api/import_routes.py | 19 +++++++++-------- .../compliance/api/screening_routes.py | 21 ++++++++++--------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/backend-compliance/compliance/api/compliance_scope_routes.py b/backend-compliance/compliance/api/compliance_scope_routes.py index 408ed3d..1b94173 100644 --- a/backend-compliance/compliance/api/compliance_scope_routes.py +++ b/backend-compliance/compliance/api/compliance_scope_routes.py @@ -15,6 +15,7 @@ from typing import Any, Optional from fastapi import APIRouter, HTTPException, Header from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -75,13 +76,13 @@ async def get_compliance_scope( db = SessionLocal() try: row = db.execute( - """SELECT tenant_id, + text("""SELECT tenant_id, state->'compliance_scope' AS scope, created_at, updated_at FROM sdk_states WHERE tenant_id = :tid - AND state ? 'compliance_scope'""", + AND state ? 'compliance_scope'"""), {"tid": tid}, ).fetchone() @@ -106,22 +107,22 @@ async def upsert_compliance_scope( db = SessionLocal() try: db.execute( - """INSERT INTO sdk_states (tenant_id, state) + text("""INSERT INTO sdk_states (tenant_id, state) VALUES (:tid, jsonb_build_object('compliance_scope', :scope::jsonb)) ON CONFLICT (tenant_id) DO UPDATE SET state = sdk_states.state || jsonb_build_object('compliance_scope', :scope::jsonb), - updated_at = NOW()""", + updated_at = NOW()"""), {"tid": tid, "scope": scope_json}, ) db.commit() row = db.execute( - """SELECT tenant_id, + text("""SELECT tenant_id, state->'compliance_scope' AS scope, created_at, updated_at FROM sdk_states - WHERE tenant_id = :tid""", + WHERE tenant_id = :tid"""), {"tid": tid}, ).fetchone() diff --git a/backend-compliance/compliance/api/import_routes.py b/backend-compliance/compliance/api/import_routes.py index 14d8044..25ab62d 100644 --- a/backend-compliance/compliance/api/import_routes.py +++ b/backend-compliance/compliance/api/import_routes.py @@ -15,6 +15,7 @@ from typing import Optional import httpx from fastapi import APIRouter, File, Form, Header, UploadFile, HTTPException from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -291,11 +292,11 @@ async def analyze_document( db = SessionLocal() try: db.execute( - """INSERT INTO compliance_imported_documents + text("""INSERT INTO compliance_imported_documents (id, tenant_id, filename, file_type, file_size, detected_type, detection_confidence, extracted_text, extracted_entities, recommendations, status, analyzed_at) VALUES (:id, :tenant_id, :filename, :file_type, :file_size, :detected_type, :confidence, - :text, :entities::jsonb, :recommendations::jsonb, 'analyzed', NOW())""", + :text, :entities::jsonb, :recommendations::jsonb, 'analyzed', NOW())"""), { "id": doc_id, "tenant_id": tenant_id, @@ -313,9 +314,9 @@ async def analyze_document( if total_gaps > 0: import json db.execute( - """INSERT INTO compliance_gap_analyses + text("""INSERT INTO compliance_gap_analyses (tenant_id, document_id, total_gaps, critical_gaps, high_gaps, medium_gaps, low_gaps, gaps, recommended_packages) - VALUES (:tenant_id, :document_id, :total, :critical, :high, :medium, :low, :gaps::jsonb, :packages::jsonb)""", + VALUES (:tenant_id, :document_id, :total, :critical, :high, :medium, :low, :gaps::jsonb, :packages::jsonb)"""), { "tenant_id": tenant_id, "document_id": doc_id, @@ -358,7 +359,7 @@ async def get_gap_analysis( db = SessionLocal() try: result = db.execute( - "SELECT * FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid", + text("SELECT * FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ).fetchone() if not result: @@ -374,11 +375,11 @@ async def list_documents(tenant_id: str = "default"): db = SessionLocal() try: result = db.execute( - """SELECT id, filename, file_type, file_size, detected_type, detection_confidence, + text("""SELECT id, filename, file_type, file_size, detected_type, detection_confidence, extracted_entities, recommendations, status, analyzed_at, created_at FROM compliance_imported_documents WHERE tenant_id = :tenant_id - ORDER BY created_at DESC""", + ORDER BY created_at DESC"""), {"tenant_id": tenant_id}, ) rows = result.fetchall() @@ -424,11 +425,11 @@ async def delete_document( try: # Delete gap analysis first (FK dependency) db.execute( - "DELETE FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid", + text("DELETE FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ) result = db.execute( - "DELETE FROM compliance_imported_documents WHERE id = :doc_id AND tenant_id = :tid", + text("DELETE FROM compliance_imported_documents WHERE id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ) db.commit() diff --git a/backend-compliance/compliance/api/screening_routes.py b/backend-compliance/compliance/api/screening_routes.py index 307ca67..9b9ee16 100644 --- a/backend-compliance/compliance/api/screening_routes.py +++ b/backend-compliance/compliance/api/screening_routes.py @@ -17,6 +17,7 @@ from typing import Optional import httpx from fastapi import APIRouter, File, Form, UploadFile, HTTPException from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -366,13 +367,13 @@ async def scan_dependencies( db = SessionLocal() try: db.execute( - """INSERT INTO compliance_screenings + text("""INSERT INTO compliance_screenings (id, tenant_id, status, sbom_format, sbom_version, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, sbom_data, started_at, completed_at) VALUES (:id, :tenant_id, 'completed', 'CycloneDX', '1.5', :total_components, :total_issues, :critical, :high, :medium, :low, - :sbom_data::jsonb, :started_at, :completed_at)""", + :sbom_data::jsonb, :started_at, :completed_at)"""), { "id": screening_id, "tenant_id": tenant_id, @@ -391,11 +392,11 @@ async def scan_dependencies( # Persist security issues for issue in issues: db.execute( - """INSERT INTO compliance_security_issues + text("""INSERT INTO compliance_security_issues (id, screening_id, severity, title, description, cve, cvss, affected_component, affected_version, fixed_in, remediation, status) VALUES (:id, :screening_id, :severity, :title, :description, :cve, :cvss, - :component, :version, :fixed_in, :remediation, :status)""", + :component, :version, :fixed_in, :remediation, :status)"""), { "id": issue["id"], "screening_id": screening_id, @@ -486,10 +487,10 @@ async def get_screening(screening_id: str): db = SessionLocal() try: result = db.execute( - """SELECT id, status, sbom_format, sbom_version, + text("""SELECT id, status, sbom_format, sbom_version, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, sbom_data, started_at, completed_at - FROM compliance_screenings WHERE id = :id""", + FROM compliance_screenings WHERE id = :id"""), {"id": screening_id}, ) row = result.fetchone() @@ -498,9 +499,9 @@ async def get_screening(screening_id: str): # Fetch issues issues_result = db.execute( - """SELECT id, severity, title, description, cve, cvss, + text("""SELECT id, severity, title, description, cve, cvss, affected_component, affected_version, fixed_in, remediation, status - FROM compliance_security_issues WHERE screening_id = :id""", + FROM compliance_security_issues WHERE screening_id = :id"""), {"id": screening_id}, ) issues_rows = issues_result.fetchall() @@ -566,12 +567,12 @@ async def list_screenings(tenant_id: str = "default"): db = SessionLocal() try: result = db.execute( - """SELECT id, status, total_components, total_issues, + text("""SELECT id, status, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, started_at, completed_at, created_at FROM compliance_screenings WHERE tenant_id = :tenant_id - ORDER BY created_at DESC""", + ORDER BY created_at DESC"""), {"tenant_id": tenant_id}, ) rows = result.fetchall()