chore: ajoute ruff (lint) et corrige les violations
- Config ruff dans pyproject.toml : règles E/W/F/I/UP/B, whitelist des appels d'injection FastAPI (Depends/File/Form/Query) pour B008, B904 ignoré (traduction volontaire des exceptions en réponses HTTP) - Auto-fixes : tri des imports, suppression d'imports inutilisés, annotations PEP 604, f-strings sans placeholder, modes open redondants - Suppression de variables inutilisées (config.reset_setting, parser locataires) ruff check . : All checks passed ; 61 tests OK Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,16 +10,17 @@ from fastapi.staticfiles import StaticFiles
|
||||
from .. import __version__
|
||||
from ..database import init_db
|
||||
from .routes import (
|
||||
extraction_router,
|
||||
documents_router,
|
||||
tags_router,
|
||||
analytics_router,
|
||||
dashboard_router,
|
||||
revenus_router,
|
||||
ia_router,
|
||||
config_router,
|
||||
dashboard_router,
|
||||
documents_router,
|
||||
extraction_router,
|
||||
ia_router,
|
||||
revenus_router,
|
||||
tags_router,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Initialize database on application startup."""
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""API Routes package."""
|
||||
|
||||
from .extraction import router as extraction_router
|
||||
from .documents import router as documents_router
|
||||
from .tags import router as tags_router
|
||||
from .analytics import router as analytics_router
|
||||
from .dashboard import router as dashboard_router
|
||||
from .revenus import router as revenus_router
|
||||
from .ia import router as ia_router
|
||||
from .config import router as config_router
|
||||
from .dashboard import router as dashboard_router
|
||||
from .documents import router as documents_router
|
||||
from .extraction import router as extraction_router
|
||||
from .ia import router as ia_router
|
||||
from .revenus import router as revenus_router
|
||||
from .tags import router as tags_router
|
||||
|
||||
__all__ = [
|
||||
"extraction_router",
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
"""Analytics routes - Data analysis and reporting endpoints."""
|
||||
|
||||
from datetime import date
|
||||
from collections import defaultdict
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import distinct, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select, func, distinct, extract
|
||||
|
||||
from ...database import get_session
|
||||
from ...database.models import Depense, Document, Immeuble, Lot, Tag
|
||||
from ..schemas.models import (
|
||||
ImmeubleResponse,
|
||||
LotResponse,
|
||||
CategorySummary,
|
||||
DepenseDetail,
|
||||
DepensesSummary,
|
||||
CategorySummary,
|
||||
TagSummary,
|
||||
MonthlySummary,
|
||||
FournisseurSummary,
|
||||
FournisseurResponse,
|
||||
FournisseurSummary,
|
||||
ImmeubleResponse,
|
||||
LotResponse,
|
||||
MonthlySummary,
|
||||
TagResponse,
|
||||
TagSummary,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["analytics"])
|
||||
|
||||
@@ -4,13 +4,13 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session, DatabaseService
|
||||
from ...database import DatabaseService, get_session
|
||||
from ...database.models import Tag
|
||||
from ...services.settings_service import (
|
||||
SETTINGS_REGISTRY,
|
||||
delete_setting,
|
||||
get_all_settings,
|
||||
set_setting,
|
||||
delete_setting,
|
||||
SETTINGS_REGISTRY,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/config", tags=["config"])
|
||||
@@ -67,7 +67,7 @@ async def reset_setting(
|
||||
"""Reset un setting au défaut (supprime l'override DB)."""
|
||||
if key not in SETTINGS_REGISTRY:
|
||||
raise HTTPException(status_code=404, detail=f"Setting inconnu : {key}")
|
||||
deleted = delete_setting(session, key)
|
||||
delete_setting(session, key)
|
||||
# Return the resolved value after deletion
|
||||
all_settings = get_all_settings(session)
|
||||
return all_settings[key]
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"""Dashboard routes - Aggregated data for the home page."""
|
||||
|
||||
from datetime import date, timedelta
|
||||
from collections import defaultdict
|
||||
from datetime import date, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select, func, desc
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import desc, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session
|
||||
from ...database.models import (
|
||||
Depense,
|
||||
Document,
|
||||
Immeuble,
|
||||
Lot,
|
||||
Locataire,
|
||||
Lot,
|
||||
Revenu,
|
||||
Depense,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
|
||||
|
||||
@@ -4,17 +4,17 @@ import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Form
|
||||
from fastapi.responses import Response, JSONResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session, DatabaseService, storage
|
||||
from ...database.models import Document, Immeuble, Lot, Locataire, Revenu, Depense
|
||||
from ...database import DatabaseService, get_session, storage
|
||||
from ...database.models import Depense, Document, Immeuble, Locataire, Lot, Revenu
|
||||
from ...database.service import DuplicateDocumentError
|
||||
from ...extractor import extract_compte_rendu
|
||||
from ...utils.uploads import read_upload_limited, UploadTooLargeError
|
||||
from ..schemas import SaveRequest, SaveResponse, DocumentSummary
|
||||
from ...utils.uploads import UploadTooLargeError, read_upload_limited
|
||||
from ..schemas import DocumentSummary, SaveRequest, SaveResponse
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["documents"])
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from fastapi import APIRouter, File, HTTPException, UploadFile
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from ...extractor import extract_compte_rendu
|
||||
from ...utils.uploads import read_upload_limited, UploadTooLargeError
|
||||
from ...utils.uploads import UploadTooLargeError, read_upload_limited
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["extraction"])
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
import httpx
|
||||
|
||||
from ...services.ollama_service import ask_ollama, check_ollama_health
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
"""Revenus routes - Dedicated endpoints for rental income analytics."""
|
||||
|
||||
from datetime import date, timedelta
|
||||
from collections import defaultdict
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select, func, desc, and_
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import and_, desc, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session
|
||||
from ...database.models import (
|
||||
Document,
|
||||
Immeuble,
|
||||
Lot,
|
||||
Locataire,
|
||||
Lot,
|
||||
Revenu,
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session, DatabaseService
|
||||
from ...database import DatabaseService, get_session
|
||||
from ...services.tag_predictor import TagPredictor
|
||||
from ..schemas import PredictTagsRequest
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"""Pydantic schemas for API request/response models."""
|
||||
|
||||
from .models import (
|
||||
SaveRequest,
|
||||
SaveResponse,
|
||||
PredictTagsRequest,
|
||||
DocumentSummary,
|
||||
DepenseDetail,
|
||||
DepensesSummary,
|
||||
DocumentSummary,
|
||||
ImmeubleResponse,
|
||||
LotResponse,
|
||||
PredictTagsRequest,
|
||||
SaveRequest,
|
||||
SaveResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Requests
|
||||
# ============================================================
|
||||
|
||||
@@ -188,10 +188,12 @@ def db_info(db_path: Path | None) -> None:
|
||||
|
||||
Montre le nombre de documents, immeubles, lots, etc.
|
||||
"""
|
||||
from .database import init_db as do_init_db, get_session_factory
|
||||
from .database.models import Document, Immeuble, Lot, Locataire, Revenu, Depense
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from .database import get_session_factory
|
||||
from .database import init_db as do_init_db
|
||||
from .database.models import Depense, Document, Immeuble, Locataire, Lot, Revenu
|
||||
|
||||
# Initialize/connect to database
|
||||
result_path = do_init_db(db_path)
|
||||
SessionLocal = get_session_factory()
|
||||
@@ -206,8 +208,8 @@ def db_info(db_path: Path | None) -> None:
|
||||
dep_count = session.execute(select(func.count(Depense.id))).scalar()
|
||||
|
||||
click.echo(f"Base de donnees: {result_path}")
|
||||
click.echo(f"")
|
||||
click.echo(f"Statistiques:")
|
||||
click.echo("")
|
||||
click.echo("Statistiques:")
|
||||
click.echo(f" - Documents: {doc_count}")
|
||||
click.echo(f" - Immeubles: {imm_count}")
|
||||
click.echo(f" - Lots: {lot_count}")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Database module for plesna-gerance."""
|
||||
|
||||
from .connection import get_engine, get_session, get_session_factory, init_db
|
||||
from .models import Base, Document, Immeuble, Lot, Locataire, Revenu, Depense, Setting
|
||||
from .service import DatabaseService, DuplicateDocumentError
|
||||
from . import storage
|
||||
from .connection import get_engine, get_session, get_session_factory, init_db
|
||||
from .models import Base, Depense, Document, Immeuble, Locataire, Lot, Revenu, Setting
|
||||
from .service import DatabaseService, DuplicateDocumentError
|
||||
|
||||
__all__ = [
|
||||
"get_engine",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Database connection management for SQLite."""
|
||||
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
"""SQLAlchemy models for plesna-gerance database."""
|
||||
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import Optional
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
Float,
|
||||
Date,
|
||||
DateTime,
|
||||
Text,
|
||||
Float,
|
||||
ForeignKey,
|
||||
UniqueConstraint,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase, relationship
|
||||
|
||||
|
||||
@@ -6,11 +6,10 @@ from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from .models import Document, Immeuble, Lot, Locataire, Revenu, Depense, Tag
|
||||
from . import storage
|
||||
from ..utils.amounts import parse_amount
|
||||
from . import storage
|
||||
from .models import Depense, Document, Immeuble, Locataire, Lot, Revenu, Tag
|
||||
|
||||
|
||||
class DuplicateDocumentError(Exception):
|
||||
|
||||
@@ -240,7 +240,7 @@ def read_json(relative_path: str, storage_root: Path | None = None) -> dict[str,
|
||||
FileNotFoundError: If file doesn't exist.
|
||||
"""
|
||||
full_path = get_absolute_path(relative_path, storage_root)
|
||||
with open(full_path, "r", encoding="utf-8") as f:
|
||||
with open(full_path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Orchestrateur principal pour l'extraction des comptes rendus de gérance."""
|
||||
|
||||
from .parsers.pdf import extract_text_from_pdf
|
||||
from .parsers.metadata import extract_metadata
|
||||
from .parsers.locataires import extract_situation_locataires
|
||||
from .parsers.metadata import extract_metadata
|
||||
from .parsers.operations import extract_recapitulatif_operations
|
||||
from .parsers.pdf import extract_text_from_pdf
|
||||
|
||||
|
||||
def extract_compte_rendu(pdf_path: str) -> dict:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Parsers pour les différentes sections des PDFs de gérance."""
|
||||
|
||||
from .pdf import extract_text_from_pdf
|
||||
from .metadata import extract_metadata
|
||||
from .locataires import extract_situation_locataires
|
||||
from .metadata import extract_metadata
|
||||
from .operations import extract_recapitulatif_operations
|
||||
from .pdf import extract_text_from_pdf
|
||||
|
||||
__all__ = [
|
||||
"extract_text_from_pdf",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import re
|
||||
|
||||
from ..utils.dates import parse_french_date
|
||||
from ..utils.amounts import extract_amounts_from_line
|
||||
from ..utils.dates import parse_french_date
|
||||
|
||||
|
||||
def _preprocess_locataires_text(text: str) -> str:
|
||||
@@ -21,7 +21,6 @@ def _preprocess_locataires_text(text: str) -> str:
|
||||
lines = text.split("\n")
|
||||
cleaned_lines = []
|
||||
first_situation_found = False
|
||||
in_situation_section = False
|
||||
|
||||
# Patterns à ignorer (en-têtes répétés sur chaque page)
|
||||
skip_patterns = [
|
||||
@@ -64,7 +63,6 @@ def _preprocess_locataires_text(text: str) -> str:
|
||||
if "SITUATION DES LOCATAIRES" in stripped:
|
||||
if not first_situation_found:
|
||||
first_situation_found = True
|
||||
in_situation_section = True
|
||||
cleaned_lines.append(line)
|
||||
# Ignorer les occurrences suivantes
|
||||
continue
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import re
|
||||
|
||||
from ..utils.dates import parse_french_date
|
||||
from ..utils.amounts import parse_amount
|
||||
from ..utils.dates import parse_french_date
|
||||
|
||||
|
||||
def extract_metadata(text: str) -> dict:
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from ..database import init_db, get_session
|
||||
from ..database import get_session, init_db
|
||||
from ..database.models import Tag
|
||||
|
||||
|
||||
# Liste des tags prédéfinis
|
||||
PREDEFINED_TAGS = [
|
||||
"Ascenseur",
|
||||
@@ -57,7 +56,7 @@ def seed_tags():
|
||||
|
||||
session.commit()
|
||||
|
||||
print(f"\n📊 Résumé:")
|
||||
print("\n📊 Résumé:")
|
||||
print(f" - {created_count} tags créés")
|
||||
print(f" - {skipped_count} tags existants")
|
||||
print(f" - Total: {len(PREDEFINED_TAGS)} tags")
|
||||
|
||||
@@ -5,9 +5,9 @@ import logging
|
||||
|
||||
import httpx
|
||||
|
||||
from .sql_executor import execute_readonly_sql, get_schema_description
|
||||
from .settings_service import get_setting
|
||||
from ..database.connection import get_session_factory
|
||||
from .settings_service import get_setting
|
||||
from .sql_executor import execute_readonly_sql, get_schema_description
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import sqlite3
|
||||
|
||||
from ..database.connection import get_db_path
|
||||
|
||||
|
||||
# PRAGMA autorisés (lecture de métadonnées uniquement)
|
||||
_ALLOWED_PRAGMAS = {"table_info", "database_list", "table_list"}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Service de prédiction de tags pour les dépenses basé sur l'historique."""
|
||||
|
||||
from typing import Optional
|
||||
from collections import Counter
|
||||
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database.models import Depense, Tag
|
||||
@@ -14,8 +12,8 @@ class TagPrediction:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tag_id: Optional[int],
|
||||
tag_name: Optional[str],
|
||||
tag_id: int | None,
|
||||
tag_name: str | None,
|
||||
confidence: float,
|
||||
reason: str,
|
||||
):
|
||||
@@ -72,7 +70,7 @@ class TagPredictor:
|
||||
reason="Aucun historique trouvé",
|
||||
)
|
||||
|
||||
def _predict_by_fournisseur(self, fournisseur: str) -> Optional[TagPrediction]:
|
||||
def _predict_by_fournisseur(self, fournisseur: str) -> TagPrediction | None:
|
||||
"""Prédit le tag basé sur le fournisseur.
|
||||
|
||||
Retourne le tag le plus fréquemment utilisé pour ce fournisseur.
|
||||
@@ -111,7 +109,7 @@ class TagPredictor:
|
||||
|
||||
def _predict_by_sous_categorie(
|
||||
self, sous_categorie: str
|
||||
) -> Optional[TagPrediction]:
|
||||
) -> TagPrediction | None:
|
||||
"""Prédit le tag basé sur la sous-catégorie.
|
||||
|
||||
Retourne le tag le plus fréquemment utilisé pour cette sous-catégorie.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Utilitaires pour le parsing des données de gérance."""
|
||||
|
||||
from .amounts import extract_amounts_from_line, parse_amount
|
||||
from .dates import parse_french_date
|
||||
from .amounts import parse_amount, extract_amounts_from_line
|
||||
|
||||
__all__ = ["parse_french_date", "parse_amount", "extract_amounts_from_line"]
|
||||
|
||||
Reference in New Issue
Block a user