Fix SQLAlchemy 2.x compatibility: wrap raw SQL in text()
Some checks failed
Deploy to Coolify / deploy (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-10 13:56:38 +01:00
parent eb260db431
commit 2c8df1433d
5 changed files with 42 additions and 38 deletions

View File

@@ -15,6 +15,7 @@ from typing import Optional
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import text
from database import SessionLocal from database import SessionLocal
@@ -224,9 +225,9 @@ def log_audit(db, tenant_id: str, action: str, changed_fields: Optional[dict], c
"""Write an audit log entry.""" """Write an audit log entry."""
try: try:
db.execute( db.execute(
"""INSERT INTO compliance_company_profile_audit text("""INSERT INTO compliance_company_profile_audit
(tenant_id, action, changed_fields, changed_by) (tenant_id, action, changed_fields, changed_by)
VALUES (:tenant_id, :action, :fields::jsonb, :changed_by)""", VALUES (:tenant_id, :action, :fields::jsonb, :changed_by)"""),
{ {
"tenant_id": tenant_id, "tenant_id": tenant_id,
"action": action, "action": action,
@@ -252,7 +253,7 @@ async def get_company_profile(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tenant_id", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tenant_id"),
{"tenant_id": tid}, {"tenant_id": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -276,7 +277,7 @@ async def upsert_company_profile(
try: try:
# Check if profile exists # Check if profile exists
existing = db.execute( existing = db.execute(
"SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid", text("SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()
@@ -285,7 +286,7 @@ async def upsert_company_profile(
completed_at_clause = ", completed_at = NOW()" if profile.is_complete else ", completed_at = NULL" completed_at_clause = ", completed_at = NOW()" if profile.is_complete else ", completed_at = NULL"
db.execute( db.execute(
f"""INSERT INTO compliance_company_profiles text(f"""INSERT INTO compliance_company_profiles
(tenant_id, company_name, legal_form, industry, founded_year, (tenant_id, company_name, legal_form, industry, founded_year,
business_model, offerings, company_size, employee_count, annual_revenue, business_model, offerings, company_size, employee_count, annual_revenue,
headquarters_country, headquarters_city, has_international_locations, headquarters_country, headquarters_city, has_international_locations,
@@ -344,7 +345,7 @@ async def upsert_company_profile(
supervisory_authority = EXCLUDED.supervisory_authority, supervisory_authority = EXCLUDED.supervisory_authority,
review_cycle_months = EXCLUDED.review_cycle_months, review_cycle_months = EXCLUDED.review_cycle_months,
updated_at = NOW() updated_at = NOW()
{completed_at_clause}""", {completed_at_clause}"""),
{ {
"tid": tid, "tid": tid,
"company_name": profile.company_name, "company_name": profile.company_name,
@@ -392,7 +393,7 @@ async def upsert_company_profile(
# Fetch and return # Fetch and return
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -415,7 +416,7 @@ async def delete_company_profile(
db = SessionLocal() db = SessionLocal()
try: try:
existing = db.execute( existing = db.execute(
"SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid", text("SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()
@@ -423,7 +424,7 @@ async def delete_company_profile(
raise HTTPException(status_code=404, detail="Company profile not found") raise HTTPException(status_code=404, detail="Company profile not found")
db.execute( db.execute(
"DELETE FROM compliance_company_profiles WHERE tenant_id = :tid", text("DELETE FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
@@ -451,7 +452,7 @@ async def get_template_context(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -513,11 +514,11 @@ async def get_audit_log(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
"""SELECT id, action, changed_fields, changed_by, created_at text("""SELECT id, action, changed_fields, changed_by, created_at
FROM compliance_company_profile_audit FROM compliance_company_profile_audit
WHERE tenant_id = :tid WHERE tenant_id = :tid
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 100""", LIMIT 100"""),
{"tid": tid}, {"tid": tid},
) )
rows = result.fetchall() rows = result.fetchall()

View File

@@ -15,6 +15,7 @@ from typing import Any, Optional
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import text
from database import SessionLocal from database import SessionLocal
@@ -75,13 +76,13 @@ async def get_compliance_scope(
db = SessionLocal() db = SessionLocal()
try: try:
row = db.execute( row = db.execute(
"""SELECT tenant_id, text("""SELECT tenant_id,
state->'compliance_scope' AS scope, state->'compliance_scope' AS scope,
created_at, created_at,
updated_at updated_at
FROM sdk_states FROM sdk_states
WHERE tenant_id = :tid WHERE tenant_id = :tid
AND state ? 'compliance_scope'""", AND state ? 'compliance_scope'"""),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()
@@ -106,22 +107,22 @@ async def upsert_compliance_scope(
db = SessionLocal() db = SessionLocal()
try: try:
db.execute( 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)) VALUES (:tid, jsonb_build_object('compliance_scope', :scope::jsonb))
ON CONFLICT (tenant_id) DO UPDATE ON CONFLICT (tenant_id) DO UPDATE
SET state = sdk_states.state || jsonb_build_object('compliance_scope', :scope::jsonb), SET state = sdk_states.state || jsonb_build_object('compliance_scope', :scope::jsonb),
updated_at = NOW()""", updated_at = NOW()"""),
{"tid": tid, "scope": scope_json}, {"tid": tid, "scope": scope_json},
) )
db.commit() db.commit()
row = db.execute( row = db.execute(
"""SELECT tenant_id, text("""SELECT tenant_id,
state->'compliance_scope' AS scope, state->'compliance_scope' AS scope,
created_at, created_at,
updated_at updated_at
FROM sdk_states FROM sdk_states
WHERE tenant_id = :tid""", WHERE tenant_id = :tid"""),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()

View File

@@ -39,7 +39,7 @@ def _get_template_context(db, tid: str) -> dict:
cp_db = SessionLocal() cp_db = SessionLocal()
try: try:
result = cp_db.execute( result = cp_db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()

View File

@@ -15,6 +15,7 @@ from typing import Optional
import httpx import httpx
from fastapi import APIRouter, File, Form, Header, UploadFile, HTTPException from fastapi import APIRouter, File, Form, Header, UploadFile, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import text
from database import SessionLocal from database import SessionLocal
@@ -291,11 +292,11 @@ async def analyze_document(
db = SessionLocal() db = SessionLocal()
try: try:
db.execute( 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, (id, tenant_id, filename, file_type, file_size, detected_type, detection_confidence,
extracted_text, extracted_entities, recommendations, status, analyzed_at) extracted_text, extracted_entities, recommendations, status, analyzed_at)
VALUES (:id, :tenant_id, :filename, :file_type, :file_size, :detected_type, :confidence, 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, "id": doc_id,
"tenant_id": tenant_id, "tenant_id": tenant_id,
@@ -313,9 +314,9 @@ async def analyze_document(
if total_gaps > 0: if total_gaps > 0:
import json import json
db.execute( 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) (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, "tenant_id": tenant_id,
"document_id": doc_id, "document_id": doc_id,
@@ -358,7 +359,7 @@ async def get_gap_analysis(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( 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}, {"doc_id": document_id, "tid": tid},
).fetchone() ).fetchone()
if not result: if not result:
@@ -374,11 +375,11 @@ async def list_documents(tenant_id: str = "default"):
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( 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 extracted_entities, recommendations, status, analyzed_at, created_at
FROM compliance_imported_documents FROM compliance_imported_documents
WHERE tenant_id = :tenant_id WHERE tenant_id = :tenant_id
ORDER BY created_at DESC""", ORDER BY created_at DESC"""),
{"tenant_id": tenant_id}, {"tenant_id": tenant_id},
) )
rows = result.fetchall() rows = result.fetchall()
@@ -424,11 +425,11 @@ async def delete_document(
try: try:
# Delete gap analysis first (FK dependency) # Delete gap analysis first (FK dependency)
db.execute( 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}, {"doc_id": document_id, "tid": tid},
) )
result = db.execute( 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}, {"doc_id": document_id, "tid": tid},
) )
db.commit() db.commit()

View File

@@ -17,6 +17,7 @@ from typing import Optional
import httpx import httpx
from fastapi import APIRouter, File, Form, UploadFile, HTTPException from fastapi import APIRouter, File, Form, UploadFile, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import text
from database import SessionLocal from database import SessionLocal
@@ -366,13 +367,13 @@ async def scan_dependencies(
db = SessionLocal() db = SessionLocal()
try: try:
db.execute( db.execute(
"""INSERT INTO compliance_screenings text("""INSERT INTO compliance_screenings
(id, tenant_id, status, sbom_format, sbom_version, (id, tenant_id, status, sbom_format, sbom_version,
total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues,
sbom_data, started_at, completed_at) sbom_data, started_at, completed_at)
VALUES (:id, :tenant_id, 'completed', 'CycloneDX', '1.5', VALUES (:id, :tenant_id, 'completed', 'CycloneDX', '1.5',
:total_components, :total_issues, :critical, :high, :medium, :low, :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, "id": screening_id,
"tenant_id": tenant_id, "tenant_id": tenant_id,
@@ -391,11 +392,11 @@ async def scan_dependencies(
# Persist security issues # Persist security issues
for issue in issues: for issue in issues:
db.execute( db.execute(
"""INSERT INTO compliance_security_issues text("""INSERT INTO compliance_security_issues
(id, screening_id, severity, title, description, cve, cvss, (id, screening_id, severity, title, description, cve, cvss,
affected_component, affected_version, fixed_in, remediation, status) affected_component, affected_version, fixed_in, remediation, status)
VALUES (:id, :screening_id, :severity, :title, :description, :cve, :cvss, VALUES (:id, :screening_id, :severity, :title, :description, :cve, :cvss,
:component, :version, :fixed_in, :remediation, :status)""", :component, :version, :fixed_in, :remediation, :status)"""),
{ {
"id": issue["id"], "id": issue["id"],
"screening_id": screening_id, "screening_id": screening_id,
@@ -486,10 +487,10 @@ async def get_screening(screening_id: str):
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( 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, total_components, total_issues, critical_issues, high_issues,
medium_issues, low_issues, sbom_data, started_at, completed_at 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}, {"id": screening_id},
) )
row = result.fetchone() row = result.fetchone()
@@ -498,9 +499,9 @@ async def get_screening(screening_id: str):
# Fetch issues # Fetch issues
issues_result = db.execute( 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 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}, {"id": screening_id},
) )
issues_rows = issues_result.fetchall() issues_rows = issues_result.fetchall()
@@ -566,12 +567,12 @@ async def list_screenings(tenant_id: str = "default"):
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( 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, critical_issues, high_issues, medium_issues, low_issues,
started_at, completed_at, created_at started_at, completed_at, created_at
FROM compliance_screenings FROM compliance_screenings
WHERE tenant_id = :tenant_id WHERE tenant_id = :tenant_id
ORDER BY created_at DESC""", ORDER BY created_at DESC"""),
{"tenant_id": tenant_id}, {"tenant_id": tenant_id},
) )
rows = result.fetchall() rows = result.fetchall()