From b62fa3c2e8912ecc5d95454edb09b6ddd9e44f8c Mon Sep 17 00:00:00 2001 From: Thioye97 Date: Wed, 22 Jul 2026 09:43:44 +0000 Subject: [PATCH] Lint ruff : rendre le job vert (app/ et tests/) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le job « lint (ruff) » du CI était rouge de longue date sur main : 66 violations préexistantes dans app/ et tests/ (les tests, eux, passaient). Corrigées ici pour rétablir un CI vert. - Imports et variables inutiles retirés (F401, F841, F811, F541). - Instructions multiples sur une ligne éclatées (E701, E702). - Imports de routers volontairement après setup_logging marqués # noqa: E402, suivant la convention déjà en place dans app/main.py. - Variables au nom ambigu « l » renommées (E741). Aucun changement de comportement : reformatage et suppression de code mort. --- app/api/deps/db_admin.py | 3 +-- app/api/routes/admin.py | 7 +------ app/api/routes/analyst.py | 13 ++++++------- app/api/routes/cases.py | 23 +++++++++++++++-------- app/api/routes/documents.py | 15 +++++++++------ app/api/routes/settings_routes.py | 3 +-- app/main.py | 6 +++--- app/models/offshore.py | 1 - app/services/blacklist_service.py | 2 +- app/services/cascading_screening.py | 4 ++-- app/services/documents_service.py | 12 ++++++++---- app/services/export_pdf_service.py | 10 +++++----- app/services/local_ocr_service.py | 12 ++++++++---- app/services/matching.py | 2 +- app/services/sanctions_sources/un.py | 1 - app/services/simple_screening_engine.py | 2 +- tests/test_lbcft_modules.py | 18 +++++++++--------- tests/unit/test_pagination_cursor.py | 3 ++- 18 files changed, 73 insertions(+), 64 deletions(-) diff --git a/app/api/deps/db_admin.py b/app/api/deps/db_admin.py index 11f6738..a46c554 100644 --- a/app/api/deps/db_admin.py +++ b/app/api/deps/db_admin.py @@ -1,8 +1,7 @@ -from fastapi import Depends, Header, HTTPException +from fastapi import Depends, Header from sqlalchemy.orm import Session from app.api.deps.db import get_db_rls -from app.core.db import set_tenant_context def get_db_admin( db: Session = Depends(get_db_rls), diff --git a/app/api/routes/admin.py b/app/api/routes/admin.py index c3a06be..58d44c4 100644 --- a/app/api/routes/admin.py +++ b/app/api/routes/admin.py @@ -1,5 +1,4 @@ from __future__ import annotations -import math from typing import Optional from uuid import UUID from app.api.deps.auth import get_current_user @@ -8,11 +7,7 @@ from fastapi import APIRouter, Depends, Query, HTTPException from sqlalchemy.orm import Session from sqlalchemy import text -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session -from sqlalchemy import text, select, func -from uuid import UUID -from datetime import date, timedelta +from sqlalchemy import select, func from app.api.deps.db import get_db_rls as get_db from app.models.screening_db import ( diff --git a/app/api/routes/analyst.py b/app/api/routes/analyst.py index 1038ab5..e72cf6c 100644 --- a/app/api/routes/analyst.py +++ b/app/api/routes/analyst.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional from uuid import UUID from io import StringIO import csv @@ -11,19 +11,16 @@ from fastapi.responses import StreamingResponse from pydantic import BaseModel, Field -from sqlalchemy import func, select, text +from sqlalchemy import select, text from sqlalchemy.orm import Session -from sqlalchemy.sql import desc from app.api.deps.auth import get_current_user from app.api.deps.db import get_db_rls as get_db -from app.models.case import Case from app.models.screening_db import ( Entity, ScreeningMatch, ScreeningRequest, - ScreeningResult, SourceRecord, ) @@ -1041,7 +1038,8 @@ def iter_csv(): writer.writerow(HEADERS) yield buf.getvalue() - buf.seek(0); buf.truncate() + buf.seek(0) + buf.truncate() for row in result.mappings(): payload = row.get("request_payload") or {} @@ -1109,7 +1107,8 @@ def iter_csv(): row.get("completed_at") or "", ]) yield buf.getvalue() - buf.seek(0); buf.truncate() + buf.seek(0) + buf.truncate() risk_part = risk_norm.lower() if risk_norm else "all" status_part = status_norm.lower() if status_norm else "all" diff --git a/app/api/routes/cases.py b/app/api/routes/cases.py index 44af6b8..f44fdfb 100644 --- a/app/api/routes/cases.py +++ b/app/api/routes/cases.py @@ -102,8 +102,10 @@ def list_cases( ).mappings().all() risk_by_case = {r["case_id"]: r["risk_level"] for r in rows} except Exception: - try: db.rollback() - except Exception: pass + try: + db.rollback() + except Exception: + pass # ── Bulk: client_name depuis screening_requests ────────────────── name_by_case: dict[str, str] = {} @@ -135,17 +137,22 @@ def list_cases( if name: name_by_case[r["case_id"]] = name except Exception: - try: db.rollback() - except Exception: pass + try: + db.rollback() + except Exception: + pass # ── Sérialisation manuelle (bypass Pydantic) ───────────────────── def _str(v) -> str | None: return str(v) if v is not None else None def _dt(v) -> str | None: - if v is None: return None - try: return v.isoformat() - except Exception: return str(v) + if v is None: + return None + try: + return v.isoformat() + except Exception: + return str(v) items = [] for c in cases: @@ -285,7 +292,7 @@ def update_case_status( candidates = STATUS_CANDIDATES.get(new_status) if not candidates: - raise HTTPException(422, f"Status invalide. Valeurs: PENDING, IN_PROGRESS, CLOSED") + raise HTTPException(422, "Status invalide. Valeurs: PENDING, IN_PROGRESS, CLOSED") # Premier candidat présent dans l'enum réel mapped = next((c for c in candidates if c in valid_values), None) diff --git a/app/api/routes/documents.py b/app/api/routes/documents.py index 9f9069a..93daff2 100644 --- a/app/api/routes/documents.py +++ b/app/api/routes/documents.py @@ -4,7 +4,6 @@ from uuid import UUID from pathlib import Path from typing import Dict, Any -from sqlalchemy import text from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, Query from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session @@ -61,10 +60,14 @@ def _guess_mime(doc: Document) -> str: if getattr(doc, "mime_type", None): return str(doc.mime_type) name = (getattr(doc, "original_filename", None) or getattr(doc, "object_key", None) or "").lower() - if name.endswith(".pdf"): return "application/pdf" - if name.endswith(".png"): return "image/png" - if name.endswith(".jpg") or name.endswith(".jpeg"): return "image/jpeg" - if name.endswith(".webp"): return "image/webp" + if name.endswith(".pdf"): + return "application/pdf" + if name.endswith(".png"): + return "image/png" + if name.endswith(".jpg") or name.endswith(".jpeg"): + return "image/jpeg" + if name.endswith(".webp"): + return "image/webp" return "application/octet-stream" @@ -244,7 +247,7 @@ def _run_ocr_background(doc_id: UUID, tenant_id: str) -> None: f"prefill={list(prefill.keys())}" ) - except Exception as e: + except Exception: import traceback print(f"[ocr background] ❌ doc={doc_id} tenant={tenant_id}") print(traceback.format_exc()) diff --git a/app/api/routes/settings_routes.py b/app/api/routes/settings_routes.py index 2ac8d62..b5f031f 100644 --- a/app/api/routes/settings_routes.py +++ b/app/api/routes/settings_routes.py @@ -8,7 +8,6 @@ from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi.responses import JSONResponse -from pydantic import BaseModel from sqlalchemy import text from sqlalchemy.orm import Session @@ -401,7 +400,7 @@ def list_users( ).mappings().all() return JSONResponse(content=[dict(r) for r in rows]) - except Exception as e: + except Exception: # Retry without role/is_active if columns don't exist try: db.rollback() diff --git a/app/main.py b/app/main.py index 12c49be..ebaa437 100644 --- a/app/main.py +++ b/app/main.py @@ -22,7 +22,7 @@ # --- Routers (import après setup_logging) --- from app.api.routes import admin # noqa: E402 from app.api.routes.admin_tenants import router as admin_tenants_router # noqa: E402 -from app.api.routes.adverse_media import router as adverse_media_router +from app.api.routes.adverse_media import router as adverse_media_router # noqa: E402 from app.api.routes.blacklist import router as blacklist_router # noqa: E402 from app.api.routes.alerting import router as alerting_router # noqa: E402 from app.api.routes.analyst import router as analyst_router # noqa: E402 @@ -32,8 +32,8 @@ from app.api.routes.documents import router as documents_router # noqa: E402 from app.api.routes.health import router as health_router # noqa: E402 from app.api.routes.integration import router as integration_router # noqa: E402 -from app.api.routes.kyt import router as kyt_router -from app.api.routes.ubo import router as ubo_router +from app.api.routes.kyt import router as kyt_router # noqa: E402 +from app.api.routes.ubo import router as ubo_router # noqa: E402 from app.api.routes.offshore import router as offshore_router # noqa: E402 from app.api.routes.rbac import router as rbac_router # noqa: E402 from app.api.routes.referentiel import router as referentiel_router # noqa: E402 diff --git a/app/models/offshore.py b/app/models/offshore.py index 1ec3f32..8262bf3 100644 --- a/app/models/offshore.py +++ b/app/models/offshore.py @@ -20,7 +20,6 @@ from __future__ import annotations import enum -import uuid from sqlalchemy import Column, DateTime, Enum, Index, Integer, String, Text from sqlalchemy.dialects.postgresql import JSONB diff --git a/app/services/blacklist_service.py b/app/services/blacklist_service.py index 65f24d3..8f77b45 100644 --- a/app/services/blacklist_service.py +++ b/app/services/blacklist_service.py @@ -26,7 +26,7 @@ from sqlalchemy import text from sqlalchemy.orm import Session -from app.services import list_ingest, list_refresh +from app.services import list_refresh logger = logging.getLogger("simandou.blacklist") diff --git a/app/services/cascading_screening.py b/app/services/cascading_screening.py index 320cc69..a9b214d 100644 --- a/app/services/cascading_screening.py +++ b/app/services/cascading_screening.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime, timezone -from typing import Any, Callable, Optional +from typing import Any, Callable import json from sqlalchemy import text @@ -110,7 +110,7 @@ def start_post_kyc_cascade(case_id: str, db: Session) -> str: # 3) Run now (synchronous MVP) try: _run_screening_request_now(request_id=request_id, db=db) - except Exception as e: + except Exception: # Optionnel: marquer FAILED si tu veux un état DB clair # (si ton runner gère déjà FAILED, tu peux enlever ce bloc) try: diff --git a/app/services/documents_service.py b/app/services/documents_service.py index 4194348..96e7dae 100644 --- a/app/services/documents_service.py +++ b/app/services/documents_service.py @@ -63,10 +63,14 @@ def _local_path(object_key: str) -> Path: def _ocr_fields_to_prefill(fields: Dict[str, Any]) -> Dict[str, Any]: prefill: Dict[str, Any] = {} - if fields.get("last_name"): prefill["nom"] = fields["last_name"] - if fields.get("first_name"): prefill["prenom"] = fields["first_name"] - if fields.get("date_of_birth"): prefill["dob"] = fields["date_of_birth"] - if fields.get("document_number"): prefill["card_number"] = fields["document_number"] + if fields.get("last_name"): + prefill["nom"] = fields["last_name"] + if fields.get("first_name"): + prefill["prenom"] = fields["first_name"] + if fields.get("date_of_birth"): + prefill["dob"] = fields["date_of_birth"] + if fields.get("document_number"): + prefill["card_number"] = fields["document_number"] return prefill diff --git a/app/services/export_pdf_service.py b/app/services/export_pdf_service.py index b16727a..885c8b9 100644 --- a/app/services/export_pdf_service.py +++ b/app/services/export_pdf_service.py @@ -14,7 +14,7 @@ from reportlab.lib.units import mm from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle -from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER +from reportlab.lib.enums import TA_LEFT, TA_RIGHT from reportlab.platypus import ( SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak, HRFlowable, KeepTogether, @@ -26,7 +26,7 @@ from app.core.db import set_tenant_context from app.models.screening_db import ( - ScreeningMatch, SourceRecord, Entity, + SourceRecord, Entity, ) # ─── Constants ──────────────────────────────────────────────── @@ -234,9 +234,9 @@ def _run() -> tuple[dict | None, list[dict]]: rows = db.execute( text(Q.format(col="request_id", p="rid")), {"rid": request_id} ).mappings().all() - l, h = _rows(rows) - if l: - return l, h + lignes, h = _rows(rows) + if lignes: + return lignes, h if case_id: rows = db.execute( diff --git a/app/services/local_ocr_service.py b/app/services/local_ocr_service.py index bd30246..b3535b8 100644 --- a/app/services/local_ocr_service.py +++ b/app/services/local_ocr_service.py @@ -146,10 +146,14 @@ class OCRResult: def ocr_to_prefill(fields: dict) -> dict: prefill: Dict[str, Any] = {} - if fields.get("last_name"): prefill["nom"] = fields["last_name"] - if fields.get("first_name"): prefill["prenom"] = fields["first_name"] - if fields.get("date_of_birth"): prefill["dob"] = fields["date_of_birth"] - if fields.get("document_number"): prefill["card_number"] = fields["document_number"] + if fields.get("last_name"): + prefill["nom"] = fields["last_name"] + if fields.get("first_name"): + prefill["prenom"] = fields["first_name"] + if fields.get("date_of_birth"): + prefill["dob"] = fields["date_of_birth"] + if fields.get("document_number"): + prefill["card_number"] = fields["document_number"] return prefill diff --git a/app/services/matching.py b/app/services/matching.py index 0e27415..cb0b2ec 100644 --- a/app/services/matching.py +++ b/app/services/matching.py @@ -12,7 +12,7 @@ from app.core.cache import cache, make_key from app.core.config import settings from app.core.logging import get_logger -from app.models.screening_db import ScreeningMatch, SourceRecord +from app.models.screening_db import ScreeningMatch logger = get_logger("simandou.matching") diff --git a/app/services/sanctions_sources/un.py b/app/services/sanctions_sources/un.py index 3fbb437..37a32cc 100644 --- a/app/services/sanctions_sources/un.py +++ b/app/services/sanctions_sources/un.py @@ -6,7 +6,6 @@ from typing import List from xml.etree import ElementTree as ET -import httpx from .config import UN_LIST_URL from .http_client import fetch_bytes diff --git a/app/services/simple_screening_engine.py b/app/services/simple_screening_engine.py index 6d7098c..8ee5b4a 100644 --- a/app/services/simple_screening_engine.py +++ b/app/services/simple_screening_engine.py @@ -9,7 +9,7 @@ from sqlalchemy import text from sqlalchemy.orm import Session -from app.models.screening_db import ScreeningMatch, ScreeningRequest, ScreeningResult +from app.models.screening_db import ScreeningMatch, ScreeningRequest from app.services.matching import ( band, normalize_name, diff --git a/tests/test_lbcft_modules.py b/tests/test_lbcft_modules.py index 3c4ccbe..2608420 100644 --- a/tests/test_lbcft_modules.py +++ b/tests/test_lbcft_modules.py @@ -505,7 +505,6 @@ def _make_tenant(db) -> str: def test_adverse_media_releve_le_risque_sans_jamais_bloquer(db): """Effet sur la vérification d'une personne morale, mesuré en isolant le signalement : le même nom doit passer de LOW/PASS à un examen imposé.""" - from sqlalchemy import text from app.core.db import set_tenant_context from app.services import simple_screening_engine as eng @@ -531,7 +530,6 @@ def test_adverse_media_releve_le_risque_sans_jamais_bloquer(db): @pytest.mark.integration def test_adverse_media_ne_sapplique_pas_aux_personnes_physiques(db): - from sqlalchemy import text from app.core.db import set_tenant_context from app.services import simple_screening_engine as eng @@ -837,7 +835,6 @@ def ids(): def test_filtrage_exclut_les_entites_radiees(db): """Sans cette exclusion, une levée de sanction n'aurait aucun effet : l'entité resterait rapprochée indéfiniment.""" - from sqlalchemy import text from app.services import list_refresh from app.services.matching import normalize_name, retrieve_candidates, tokenize @@ -1101,7 +1098,6 @@ def test_dedupe_compare_les_graphies_sans_tenir_compte_de_la_casse(db): bruts étaient identiques.""" from sqlalchemy import text from app.scripts import dedupe_entities - from app.services.matching import tokenize officielle = _entite(db, "IBRAHIMA SOW", avec_source=True) copie = _entite(db, "Ibrahima Sow") @@ -1129,7 +1125,8 @@ def test_import_csv_accepte_les_libelles_francais(db): from sqlalchemy import text from app.services import adverse_media_service as ams - db.execute(text("DELETE FROM adverse_media_records")); db.commit() + db.execute(text("DELETE FROM adverse_media_records")) + db.commit() csv = ("entity_name,category,source,summary\n" "Global Mining SARL,Corruption,OCCRP,Licences minières\n" "Atlas Trading Ltd,Blanchiment,ICIJ,Réseau présumé\n" @@ -1151,7 +1148,8 @@ def test_import_csv_est_idempotent(db): from sqlalchemy import text from app.services import adverse_media_service as ams - db.execute(text("DELETE FROM adverse_media_records")); db.commit() + db.execute(text("DELETE FROM adverse_media_records")) + db.commit() csv = b"entity_name,category\nSociete Test SA,Fraude\n" assert ams.importer_csv(db, csv)["crees"] == 1 r2 = ams.importer_csv(db, csv) @@ -1165,7 +1163,8 @@ def test_import_csv_supporte_les_exports_de_tableur(db): from sqlalchemy import text from app.services import adverse_media_service as ams - db.execute(text("DELETE FROM adverse_media_records")); db.commit() + db.execute(text("DELETE FROM adverse_media_records")) + db.commit() bom = "entity_name,category\r\nSociété Accentuée SA,Fraude\r\n".encode("utf-8") assert ams.importer_csv(db, bom)["crees"] == 1 latin = "entity_name,category\nSociété Latin SA,Corruption\n".encode("latin-1") @@ -1186,7 +1185,8 @@ def test_un_signalement_desactive_ne_pese_plus_sur_le_risque(db): from sqlalchemy import text from app.services import adverse_media_service as ams - db.execute(text("DELETE FROM adverse_media_records")); db.commit() + db.execute(text("DELETE FROM adverse_media_records")) + db.commit() ams.importer_csv(db, b"entity_name,category\nOmega Trading Ltd,Blanchiment\n") assert ams.assess_company(db, "Omega Trading Ltd")["hit"] is True @@ -1231,7 +1231,7 @@ def test_liste_noire_une_levee_produit_son_effet(db): bl.importer(db, _BL_CSV.encode("utf-8")) sans_mamadou = "\n".join( - l for l in _BL_CSV.splitlines() if "BCRG-A" not in l) + "\n" + ligne for ligne in _BL_CSV.splitlines() if "BCRG-A" not in ligne) + "\n" r = bl.importer(db, sans_mamadou.encode("utf-8")) assert r["delisted"] == 1 diff --git a/tests/unit/test_pagination_cursor.py b/tests/unit/test_pagination_cursor.py index 93c1a8c..ef5cbef 100644 --- a/tests/unit/test_pagination_cursor.py +++ b/tests/unit/test_pagination_cursor.py @@ -47,7 +47,8 @@ def test_b64_but_not_json(self): assert decode_cursor(garbage) == [] def test_b64_json_but_not_list(self): - import base64, json + import base64 + import json payload = base64.urlsafe_b64encode(json.dumps({"not": "list"}).encode()).decode().rstrip("=") assert decode_cursor(payload) == []