""" Authentification Drive via un compte Google PERSONNEL (OAuth, refresh token). Pourquoi ce module existe : un compte de service (GOOGLE_SERVICE_ACCOUNT_JSON) n'a AUCUN quota de stockage Drive qui lui appartient. Il peut lire et modifier des fichiers déjà partagés avec lui (c'est ce que fait drive_enrichment.py, en lecture seule, sans problème), mais dès qu'il essaie de CRÉER un nouveau fichier (copier un template, uploader une image, créer un dossier) en dehors d'un Drive partagé, Google le refuse avec une erreur 403 "storageQuotaExceeded" — le compte de service n'a nulle part où "ranger" ce nouveau fichier. Ce module fournit donc un accès Drive/Sheets authentifié avec VOTRE compte Google personnel (qui a un vrai quota), en utilisant un refresh token obtenu UNE SEULE FOIS en local — jamais d'interaction navigateur nécessaire sur le serveur ensuite. ⚠️ NOTE POUR PLUS TARD (2026-07-08) : si ce projet passe un jour sur un compte Google Workspace (professionnel/payant), il devient possible d'utiliser un Drive partagé ("Shared Drive") à la place. Dans un Drive partagé, les fichiers appartiennent au Drive lui-même, pas à un compte individuel : le compte de service peut alors y créer des fichiers directement (avec supportsAllDrives=True), sans jamais toucher de quota personnel — et ce module OAuth devient inutile. Les Drive partagés n'existent pas sur les comptes Gmail personnels gratuits, d'où le choix actuel de cette solution OAuth en attendant. ═══════════════════════════════════════════════════════════════════════ COMMENT OBTENIR OAUTH_REFRESH_TOKEN (à faire UNE SEULE FOIS, en local, jamais sur le Space HF) : ═══════════════════════════════════════════════════════════════════════ 1. Google Cloud Console → APIs & Services → Identifiants → Créer des identifiants → ID client OAuth → type "Application de bureau". Téléchargez le fichier JSON (ex: oauth_credentials.json). 2. Sur votre machine (pas le Space), avec google-auth-oauthlib installé : from google_auth_oauthlib.flow import InstalledAppFlow flow = InstalledAppFlow.from_client_secrets_file( "oauth_credentials.json", ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets"], ) creds = flow.run_local_server(port=0) print("CLIENT_ID:", creds.client_id) print("CLIENT_SECRET:", creds.client_secret) print("REFRESH_TOKEN:", creds.refresh_token) Un navigateur s'ouvre : connectez-vous avec le compte Google qui possède le Drive "BDD Claim", autorisez l'accès. 3. Copiez les 3 valeurs affichées dans les secrets du Space HF : OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REFRESH_TOKEN. Le refresh_token n'expire pas (sauf révocation manuelle dans les paramètres du compte Google) : cette étape ne sera plus jamais nécessaire ensuite, le serveur s'en sert silencieusement à chaque appel. """ import os from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from googleapiclient.discovery import build OAUTH_CLIENT_ID = (os.environ.get("OAUTH_CLIENT_ID") or "").strip() OAUTH_CLIENT_SECRET = (os.environ.get("OAUTH_CLIENT_SECRET") or "").strip() OAUTH_REFRESH_TOKEN = (os.environ.get("OAUTH_REFRESH_TOKEN") or "").strip() SCOPES = [ "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets", ] _user_credentials = None _user_drive_service = None def _get_user_credentials(): """Construit (une seule fois) les credentials OAuth du compte personnel, en échangeant silencieusement le refresh token contre un access token (aucune interaction navigateur nécessaire sur le serveur).""" global _user_credentials if _user_credentials is None: if not (OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and OAUTH_REFRESH_TOKEN): raise EnvironmentError( "OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET / OAUTH_REFRESH_TOKEN " "non définis. Voir le docstring de oauth_drive.py pour la " "procédure d'obtention (à faire une seule fois, en local)." ) creds = Credentials( token=None, refresh_token=OAUTH_REFRESH_TOKEN, client_id=OAUTH_CLIENT_ID, client_secret=OAUTH_CLIENT_SECRET, token_uri="https://oauth2.googleapis.com/token", scopes=SCOPES, ) creds.refresh(Request()) _user_credentials = creds return _user_credentials def get_user_credentials(): """Retourne les credentials bruts (utilisables par gspread.authorize()).""" return _get_user_credentials() def get_user_drive_service(): """Service Drive authentifié avec le compte personnel (quota réel) — à utiliser pour toute opération qui CRÉE un fichier (copy, create, upload). Pour la lecture seule, continuez à utiliser le compte de service via drive_enrichment.get_drive_service().""" global _user_drive_service if _user_drive_service is None: _user_drive_service = build("drive", "v3", credentials=_get_user_credentials()) return _user_drive_service