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:
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 -------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
31
uv.lock
generated
31
uv.lock
generated
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user