Add logging and doc

This commit is contained in:
2025-09-05 11:08:40 +02:00
parent 89ff54acfe
commit afd27a811a
2 changed files with 107 additions and 27 deletions

View File

@@ -1,5 +1,8 @@
from typing import Any, cast from typing import Any, cast
import ovh import ovh
import logging
logger = logging.getLogger("ovh_factures.fetcher")
def fetch_api(app_key: str, app_secret: str, consumer_key: str) -> list[str]: def fetch_api(app_key: str, app_secret: str, consumer_key: str) -> list[str]:
@@ -12,12 +15,15 @@ def fetch_api(app_key: str, app_secret: str, consumer_key: str) -> list[str]:
data: Any = client.get("/me/bill/") data: Any = client.get("/me/bill/")
if data is None: if data is None:
logger.warning("Réponse vide pour /me/bill/")
return [] return []
if not isinstance(data, list) or not all(isinstance(x, str) for x in data): if not isinstance(data, list) or not all(isinstance(x, str) for x in data):
logger.error("Réponse inattendue pour /me/bill/: %r", data)
raise TypeError("Réponse OVH inattendue pour /me/bill/: liste de str requise") raise TypeError("Réponse OVH inattendue pour /me/bill/: liste de str requise")
bills: list[str] = cast(list[str], data) bills: list[str] = cast(list[str], data)
logger.info("%d factures détectées dans /me/bill/", len(bills))
return bills return bills
@@ -32,5 +38,7 @@ def fetch_invoice_content(
) )
bill = client.get(f"/me/bill/{id}") bill = client.get(f"/me/bill/{id}")
if bill is None: if bill is None:
logger.error("Facture %s introuvable", id)
raise RuntimeError(f"Facture {id} introuvable") raise RuntimeError(f"Facture {id} introuvable")
logger.debug("Facture %s récupérée avec succès", id)
return bill return bill

136
main.py
View File

@@ -4,44 +4,100 @@ import dotenv
import ovh import ovh
import fetcher as ft import fetcher as ft
from urllib.request import urlretrieve from urllib.request import urlretrieve
import logging
from logging.handlers import RotatingFileHandler
# --- Configuration du logging ---
logging.addLevelName(logging.DEBUG, "DÉBOGAGE")
logging.addLevelName(logging.INFO, "INFO")
logging.addLevelName(logging.WARNING, "AVERTISSEMENT")
logging.addLevelName(logging.ERROR, "ERREUR")
logging.addLevelName(logging.CRITICAL, "CRITIQUE")
logger = logging.getLogger("ovh_factures")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Console
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
# Fichier
fh = RotatingFileHandler(
"ovh_factures.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
fh.setFormatter(formatter)
logger.addHandler(fh)
# Chargement des variables d'environnement (.env)
dotenv.load_dotenv() dotenv.load_dotenv()
APP_KEY = os.environ["APP_KEY"] APP_KEY = os.environ["APP_KEY"]
APP_SECRET = os.environ["APP_SECRET"] APP_SECRET = os.environ["APP_SECRET"]
CONSUMER_KEY = os.environ["CONSUMER_KEY"] CONSUMER_KEY = os.environ["CONSUMER_KEY"]
PATH_OVH = os.environ["OVH_PATH"] PATH_OVH = os.environ["OVH_PATH"]
YEAR = datetime.now().year YEAR = datetime.now().year # Année courante (int)
def indexer(ids: list[str]) -> list[str]: def indexer(ids: list[str]) -> list[str]:
ids_already_in = os.listdir(f"{PATH_OVH}/{YEAR}") """
missing = [x for x in ids if f"{x}.pdf" not in ids_already_in] Parcourt le répertoire de l'année courante et compare les factures déjà présentes
result = [] avec la liste d'IDs renvoyée par OVH. Ne conserve que les factures absentes
for x in missing: ET datées de l'année courante.
date_str = ft.fetch_invoice_content( """
x, logger.info("Indexation des factures pour l'année %s", YEAR)
app_secret=APP_SECRET, target_dir = f"{PATH_OVH}{YEAR}"
app_key=APP_KEY,
consumer_key=CONSUMER_KEY,
)["date"]
if datetime.fromisoformat(date_str).year >= int(YEAR):
result.append(x)
return result
def get_ids() -> list[str]:
try: try:
ids = ft.fetch_api( ids_already_in = os.listdir(target_dir)
except FileNotFoundError:
logger.warning("Dossier %s inexistant, aucune facture locale", target_dir)
ids_already_in = []
missing = [x for x in ids if f"{x}.pdf" not in ids_already_in]
logger.info("%d factures absentes détectées", len(missing))
result: list[str] = []
for bill_id in missing:
try:
meta = ft.fetch_invoice_content(
bill_id,
app_key=APP_KEY,
app_secret=APP_SECRET,
consumer_key=CONSUMER_KEY,
)
except Exception as e:
logger.error("Impossible de récupérer la méta pour %s : %s", bill_id, e)
continue
bill_year = datetime.fromisoformat(meta["date"]).year
if bill_year == YEAR:
result.append(bill_id)
logger.info("%d factures retenues pour téléchargement", len(result))
return result
def get_ids() -> list[str]:
"""
Interroge lAPI OVH et renvoie la liste des IDs de toutes les factures.
"""
logger.info("Récupération de la liste des factures via API OVH")
try:
return ft.fetch_api(
app_key=APP_KEY, app_key=APP_KEY,
app_secret=APP_SECRET, app_secret=APP_SECRET,
consumer_key=CONSUMER_KEY, consumer_key=CONSUMER_KEY,
) )
return ids
except ovh.exceptions.APIError as e: except ovh.exceptions.APIError as e:
raise RuntimeError(f"Échec récupération IDs factures: {e}") from e logger.error("Échec récupération des IDs de factures : %s", e)
raise RuntimeError(f"Échec de la récupération des IDs de factures : {e}") from e
def get_bill(bill_id: str) -> dict: def get_bill(bill_id: str) -> dict:
"""
Récupère, via lAPI OVH, les informations détaillées dune facture (JSON).
"""
logger.debug("Récupération de la facture %s", bill_id)
try: try:
return ft.fetch_invoice_content( return ft.fetch_invoice_content(
bill_id, bill_id,
@@ -50,23 +106,39 @@ def get_bill(bill_id: str) -> dict:
consumer_key=CONSUMER_KEY, consumer_key=CONSUMER_KEY,
) )
except ovh.exceptions.APIError as e: except ovh.exceptions.APIError as e:
raise RuntimeError(f"Échec récupération facture {bill_id}: {e}") from e logger.error("Échec récupération de la facture %s : %s", bill_id, e)
raise RuntimeError(
f"Échec de la récupération de la facture {bill_id} : {e}"
) from e
def save_pdf(bill: dict): def save_pdf(bill: dict) -> None:
"""
Télécharge le PDF dune facture dans un sous-dossier par année.
Noms de fichiers : <billId>.pdf
"""
date = datetime.fromisoformat(bill["date"]).date() date = datetime.fromisoformat(bill["date"]).date()
path = f"{PATH_OVH}/{date.year}" path = f"{PATH_OVH}{date.year}/"
os.makedirs(path, exist_ok=True)
if not os.path.isdir(path):
os.mkdir(path)
url = bill["pdfUrl"] url = bill["pdfUrl"]
urlretrieve(url, f"{PATH_OVH}{bill['billId']}.pdf") dest = f"{path}{bill['billId']}.pdf"
try:
urlretrieve(url, dest)
logger.info("Facture %s sauvegardée dans %s", bill["billId"], dest)
except Exception as e:
logger.error("Impossible de télécharger la facture %s : %s", bill["billId"], e)
raise
if __name__ == "__main__": if __name__ == "__main__":
if not os.path.isdir(PATH_OVH): logger.info("Démarrage du traitement des factures OVH pour %s", YEAR)
os.mkdir(PATH_OVH) os.makedirs(f"{PATH_OVH}{YEAR}", exist_ok=True)
ids = indexer(get_ids())
if ids: ids_candidats = indexer(get_ids())
for id in ids:
save_pdf(get_bill(id)) for bill_id in ids_candidats:
save_pdf(get_bill(bill_id))
logger.info("Traitement terminé : %d factures téléchargées", len(ids_candidats))