mirror of
https://github.com/Fare-spec/get_ovh_bills.git
synced 2025-12-07 10:20:36 +00:00
Add logging and doc
This commit is contained in:
@@ -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
136
main.py
@@ -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 l’API 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 l’API OVH, les informations détaillées d’une 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 d’une 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))
|
||||||
|
|||||||
Reference in New Issue
Block a user