From d42bfecab43b56489c5d568292a75bda0fb05e05 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Thu, 11 Jun 2026 10:20:36 +0200 Subject: [PATCH] chore: ajoute ruff (lint) et corrige les violations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- pyproject.toml | 23 ++++++++++++++ src/plesna_gerance/api/app.py | 13 ++++---- src/plesna_gerance/api/routes/__init__.py | 12 +++---- src/plesna_gerance/api/routes/analytics.py | 16 +++++----- src/plesna_gerance/api/routes/config.py | 8 ++--- src/plesna_gerance/api/routes/dashboard.py | 10 +++--- src/plesna_gerance/api/routes/documents.py | 14 ++++----- src/plesna_gerance/api/routes/extraction.py | 2 +- src/plesna_gerance/api/routes/ia.py | 3 +- src/plesna_gerance/api/routes/revenus.py | 7 ++--- src/plesna_gerance/api/routes/tags.py | 2 +- src/plesna_gerance/api/schemas/__init__.py | 8 ++--- src/plesna_gerance/api/schemas/models.py | 1 - src/plesna_gerance/cli.py | 10 +++--- src/plesna_gerance/database/__init__.py | 6 ++-- src/plesna_gerance/database/connection.py | 2 +- src/plesna_gerance/database/models.py | 13 ++++---- src/plesna_gerance/database/service.py | 5 ++- src/plesna_gerance/database/storage.py | 2 +- src/plesna_gerance/extractor.py | 4 +-- src/plesna_gerance/parsers/__init__.py | 4 +-- src/plesna_gerance/parsers/locataires.py | 4 +-- src/plesna_gerance/parsers/metadata.py | 2 +- src/plesna_gerance/scripts/seed_tags.py | 5 ++- src/plesna_gerance/services/ollama_service.py | 4 +-- src/plesna_gerance/services/sql_executor.py | 1 - src/plesna_gerance/services/tag_predictor.py | 12 +++---- src/plesna_gerance/utils/__init__.py | 2 +- tests/test_amounts.py | 2 +- tests/test_database_service.py | 6 ++-- tests/test_sql_executor.py | 15 +++++---- tests/test_storage.py | 2 +- tests/test_uploads.py | 2 +- uv.lock | 31 ++++++++++++++++++- 34 files changed, 148 insertions(+), 105 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dfb506b..1eadc16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ plesna-gerance = "plesna_gerance.cli:main" [dependency-groups] dev = [ "pytest>=8.0", + "ruff>=0.6", ] [tool.pytest.ini_options] @@ -25,6 +26,28 @@ testpaths = ["tests"] pythonpath = ["src"] addopts = "-q" +[tool.ruff] +target-version = "py310" +line-length = 88 +src = ["src", "tests"] + +[tool.ruff.lint] +# E/W: pycodestyle, F: pyflakes, I: isort, UP: pyupgrade, B: bugbear +select = ["E", "W", "F", "I", "UP", "B"] +ignore = [ + "E501", # longueur de ligne gérée par le formateur, pas bloquante + "B904", # raise sans `from` : on traduit volontairement les exceptions en HTTP +] + +[tool.ruff.lint.flake8-bugbear] +# Appels idiomatiques FastAPI en argument par défaut (injection de dépendances) +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.File", + "fastapi.Form", + "fastapi.Query", +] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/src/plesna_gerance/api/app.py b/src/plesna_gerance/api/app.py index e3b849c..1f76e2c 100644 --- a/src/plesna_gerance/api/app.py +++ b/src/plesna_gerance/api/app.py @@ -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.""" diff --git a/src/plesna_gerance/api/routes/__init__.py b/src/plesna_gerance/api/routes/__init__.py index 8099ab7..0b4255e 100644 --- a/src/plesna_gerance/api/routes/__init__.py +++ b/src/plesna_gerance/api/routes/__init__.py @@ -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", diff --git a/src/plesna_gerance/api/routes/analytics.py b/src/plesna_gerance/api/routes/analytics.py index 70e6653..a1500b1 100644 --- a/src/plesna_gerance/api/routes/analytics.py +++ b/src/plesna_gerance/api/routes/analytics.py @@ -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"]) diff --git a/src/plesna_gerance/api/routes/config.py b/src/plesna_gerance/api/routes/config.py index 33b4391..1e8e8d1 100644 --- a/src/plesna_gerance/api/routes/config.py +++ b/src/plesna_gerance/api/routes/config.py @@ -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] diff --git a/src/plesna_gerance/api/routes/dashboard.py b/src/plesna_gerance/api/routes/dashboard.py index ecfde7b..87854cf 100644 --- a/src/plesna_gerance/api/routes/dashboard.py +++ b/src/plesna_gerance/api/routes/dashboard.py @@ -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"]) diff --git a/src/plesna_gerance/api/routes/documents.py b/src/plesna_gerance/api/routes/documents.py index 70dfdfe..30ff4b8 100644 --- a/src/plesna_gerance/api/routes/documents.py +++ b/src/plesna_gerance/api/routes/documents.py @@ -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"]) diff --git a/src/plesna_gerance/api/routes/extraction.py b/src/plesna_gerance/api/routes/extraction.py index cb3e8e6..9e9ee2d 100644 --- a/src/plesna_gerance/api/routes/extraction.py +++ b/src/plesna_gerance/api/routes/extraction.py @@ -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"]) diff --git a/src/plesna_gerance/api/routes/ia.py b/src/plesna_gerance/api/routes/ia.py index d9f7489..6428f78 100644 --- a/src/plesna_gerance/api/routes/ia.py +++ b/src/plesna_gerance/api/routes/ia.py @@ -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__) diff --git a/src/plesna_gerance/api/routes/revenus.py b/src/plesna_gerance/api/routes/revenus.py index 38c3c7d..1d5f9fb 100644 --- a/src/plesna_gerance/api/routes/revenus.py +++ b/src/plesna_gerance/api/routes/revenus.py @@ -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, ) diff --git a/src/plesna_gerance/api/routes/tags.py b/src/plesna_gerance/api/routes/tags.py index 565b0fa..ecacdd1 100644 --- a/src/plesna_gerance/api/routes/tags.py +++ b/src/plesna_gerance/api/routes/tags.py @@ -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 diff --git a/src/plesna_gerance/api/schemas/__init__.py b/src/plesna_gerance/api/schemas/__init__.py index 14eb462..85aa88d 100644 --- a/src/plesna_gerance/api/schemas/__init__.py +++ b/src/plesna_gerance/api/schemas/__init__.py @@ -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__ = [ diff --git a/src/plesna_gerance/api/schemas/models.py b/src/plesna_gerance/api/schemas/models.py index 7070491..7facc00 100644 --- a/src/plesna_gerance/api/schemas/models.py +++ b/src/plesna_gerance/api/schemas/models.py @@ -5,7 +5,6 @@ from typing import Any from pydantic import BaseModel - # ============================================================ # Requests # ============================================================ diff --git a/src/plesna_gerance/cli.py b/src/plesna_gerance/cli.py index c26a783..9a44cbc 100644 --- a/src/plesna_gerance/cli.py +++ b/src/plesna_gerance/cli.py @@ -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}") diff --git a/src/plesna_gerance/database/__init__.py b/src/plesna_gerance/database/__init__.py index 867ff4f..61f0678 100644 --- a/src/plesna_gerance/database/__init__.py +++ b/src/plesna_gerance/database/__init__.py @@ -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", diff --git a/src/plesna_gerance/database/connection.py b/src/plesna_gerance/database/connection.py index d2985ed..84bd0e2 100644 --- a/src/plesna_gerance/database/connection.py +++ b/src/plesna_gerance/database/connection.py @@ -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 diff --git a/src/plesna_gerance/database/models.py b/src/plesna_gerance/database/models.py index bcadbc1..a30d441 100644 --- a/src/plesna_gerance/database/models.py +++ b/src/plesna_gerance/database/models.py @@ -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 diff --git a/src/plesna_gerance/database/service.py b/src/plesna_gerance/database/service.py index 047ed43..385565e 100644 --- a/src/plesna_gerance/database/service.py +++ b/src/plesna_gerance/database/service.py @@ -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): diff --git a/src/plesna_gerance/database/storage.py b/src/plesna_gerance/database/storage.py index 74a959d..0cef62c 100644 --- a/src/plesna_gerance/database/storage.py +++ b/src/plesna_gerance/database/storage.py @@ -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) diff --git a/src/plesna_gerance/extractor.py b/src/plesna_gerance/extractor.py index 7a6111d..1bd799d 100644 --- a/src/plesna_gerance/extractor.py +++ b/src/plesna_gerance/extractor.py @@ -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: diff --git a/src/plesna_gerance/parsers/__init__.py b/src/plesna_gerance/parsers/__init__.py index 52c422d..7ac59be 100644 --- a/src/plesna_gerance/parsers/__init__.py +++ b/src/plesna_gerance/parsers/__init__.py @@ -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", diff --git a/src/plesna_gerance/parsers/locataires.py b/src/plesna_gerance/parsers/locataires.py index c754e4f..8cd70ba 100644 --- a/src/plesna_gerance/parsers/locataires.py +++ b/src/plesna_gerance/parsers/locataires.py @@ -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 diff --git a/src/plesna_gerance/parsers/metadata.py b/src/plesna_gerance/parsers/metadata.py index b225185..dda7ac2 100644 --- a/src/plesna_gerance/parsers/metadata.py +++ b/src/plesna_gerance/parsers/metadata.py @@ -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: diff --git a/src/plesna_gerance/scripts/seed_tags.py b/src/plesna_gerance/scripts/seed_tags.py index f605442..6f24ca8 100644 --- a/src/plesna_gerance/scripts/seed_tags.py +++ b/src/plesna_gerance/scripts/seed_tags.py @@ -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") diff --git a/src/plesna_gerance/services/ollama_service.py b/src/plesna_gerance/services/ollama_service.py index b8f0cfb..6526598 100644 --- a/src/plesna_gerance/services/ollama_service.py +++ b/src/plesna_gerance/services/ollama_service.py @@ -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__) diff --git a/src/plesna_gerance/services/sql_executor.py b/src/plesna_gerance/services/sql_executor.py index d3f7d45..0d3ea60 100644 --- a/src/plesna_gerance/services/sql_executor.py +++ b/src/plesna_gerance/services/sql_executor.py @@ -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"} diff --git a/src/plesna_gerance/services/tag_predictor.py b/src/plesna_gerance/services/tag_predictor.py index b9be900..55a539e 100644 --- a/src/plesna_gerance/services/tag_predictor.py +++ b/src/plesna_gerance/services/tag_predictor.py @@ -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. diff --git a/src/plesna_gerance/utils/__init__.py b/src/plesna_gerance/utils/__init__.py index d3cdcc8..3986ffc 100644 --- a/src/plesna_gerance/utils/__init__.py +++ b/src/plesna_gerance/utils/__init__.py @@ -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"] diff --git a/tests/test_amounts.py b/tests/test_amounts.py index b09edf3..e629e67 100644 --- a/tests/test_amounts.py +++ b/tests/test_amounts.py @@ -2,7 +2,7 @@ import pytest -from plesna_gerance.utils.amounts import parse_amount, extract_amounts_from_line +from plesna_gerance.utils.amounts import extract_amounts_from_line, parse_amount @pytest.mark.parametrize( diff --git a/tests/test_database_service.py b/tests/test_database_service.py index 4a6bde0..313fbfd 100644 --- a/tests/test_database_service.py +++ b/tests/test_database_service.py @@ -4,16 +4,16 @@ from datetime import date import pytest -from plesna_gerance.database.service import DatabaseService, DuplicateDocumentError from plesna_gerance.database.models import ( + Depense, Document, Immeuble, - Lot, Locataire, + Lot, Revenu, - Depense, Tag, ) +from plesna_gerance.database.service import DatabaseService, DuplicateDocumentError def test_save_document_creates_full_graph(db_session, sample_data): diff --git a/tests/test_sql_executor.py b/tests/test_sql_executor.py index 0304dc7..8320c6b 100644 --- a/tests/test_sql_executor.py +++ b/tests/test_sql_executor.py @@ -5,19 +5,18 @@ import sqlite3 import pytest from plesna_gerance.services.sql_executor import ( - execute_readonly_sql, - _validate_sql, - _ensure_limit, - _authorizer, - _SQLITE_OK, _SQLITE_DENY, - _SQLITE_SELECT, - _SQLITE_READ, + _SQLITE_OK, _SQLITE_PRAGMA, + _SQLITE_READ, + _SQLITE_SELECT, MAX_ROWS, + _authorizer, + _ensure_limit, + _validate_sql, + execute_readonly_sql, ) - # --- Garde lexicale ------------------------------------------------------- diff --git a/tests/test_storage.py b/tests/test_storage.py index cc54714..3580e27 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -5,9 +5,9 @@ from datetime import date import pytest from plesna_gerance.database.storage import ( + compute_document_paths, extract_street_letter, sanitize_filename, - compute_document_paths, ) diff --git a/tests/test_uploads.py b/tests/test_uploads.py index eccdffc..2aeb5c1 100644 --- a/tests/test_uploads.py +++ b/tests/test_uploads.py @@ -6,7 +6,7 @@ import io import pytest from fastapi import UploadFile -from plesna_gerance.utils.uploads import read_upload_limited, UploadTooLargeError +from plesna_gerance.utils.uploads import UploadTooLargeError, read_upload_limited def _upload(content: bytes) -> UploadFile: diff --git a/uv.lock b/uv.lock index c54d76b..a47ee35 100644 --- a/uv.lock +++ b/uv.lock @@ -263,6 +263,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pytest" }, + { name = "ruff" }, ] [package.metadata] @@ -276,7 +277,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8.0" }] +dev = [ + { name = "pytest", specifier = ">=8.0" }, + { name = "ruff", specifier = ">=0.6" }, +] [[package]] name = "pluggy" @@ -529,6 +533,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "ruff" +version = "0.15.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.45"