edouardfoussier's picture
loader: diagnostiquer l'acces AVANT de telecharger
73e5fea
Raw
History Blame Contribute Delete
5.69 kB
"""loader.py — le Space public, le code privé.
Un Space public expose ses fichiers. Pour garder la méthode fermée tout en
laissant l'outil utilisable, ce fichier est le SEUL point d'entrée public : il
télécharge la charge utile depuis un dépôt Hugging Face **privé** au démarrage,
puis lance l'application.
Ce qui reste PUBLIC, délibérément :
- ce chargeur, le Dockerfile, requirements.txt ;
- `falrun.py` — les 100 lignes qui manipulent la clé fal du visiteur. C'est
l'argument qui fait qu'on ose coller une clé ; le cacher coûterait plus que
ce qu'il protégerait (il ne contient aucun savoir-faire) ;
- les vidéos d'exemple et les poids publics (GNM de Google, MediaPipe).
Ce qui part en PRIVÉ : tout ce qui encode la méthode — l'alignement
yeux-sur-yeux, le fondu de raccord, les compounds des presets, le scanner de
fenêtres, la calibration en boucle fermée, et le stage 3D.
Configuration (Settings → Variables and secrets) :
PRIVATE_REPO = "<user>/retakes-core" (variable)
HF_TOKEN = un token avec accès LECTURE à ce dépôt (secret)
Le token ne quitte jamais le conteneur : il n'est lu que par ce fichier, et la
page servie au visiteur ne contient que l'interface Gradio.
"""
from __future__ import annotations
import os
import runpy
import sys
APP_DIR = os.path.dirname(os.path.abspath(__file__))
REPO = os.environ.get('PRIVATE_REPO', '').strip()
TOKEN = (os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACE_TOKEN') or '').strip()
REVISION = os.environ.get('PRIVATE_REVISION', 'main').strip() or 'main'
def _placeholder(title: str, body: str) -> None:
"""Un Space mal configuré doit l'ANNONCER, pas mourir sur une trace de pile.
Sinon le visiteur voit « Runtime error » et le propriétaire cherche dans les
logs de build alors que le problème est un secret manquant.
"""
import gradio as gr
with gr.Blocks(title='Retakes — configuration') as demo:
gr.Markdown(f'# {title}\n\n{body}')
demo.launch(server_name='0.0.0.0', server_port=7860)
sys.exit(0)
def main() -> None:
app = os.path.join(APP_DIR, 'app.py')
# DÉVELOPPEMENT : si la charge utile est déjà là (dépôt complet en local), on
# la lance sans rien télécharger. L'image publique, elle, ne contient jamais
# app.py — deploy_split.sh l'exclut — donc elle passe toujours par le dépôt
# privé. Même commande dans les deux cas, aucun chemin de code divergent.
if os.path.exists(app):
sys.path.insert(0, APP_DIR)
runpy.run_path(app, run_name='__main__')
return
if not REPO or not TOKEN:
missing = ' et '.join(
x for x in (('`PRIVATE_REPO` (variable)' if not REPO else ''),
('`HF_TOKEN` (secret)' if not TOKEN else '')) if x)
_placeholder(
'⚙️ Configuration incomplète',
f'Ce Space charge son code depuis un dépôt privé. Il manque {missing} '
'dans *Settings → Variables and secrets*.\n\nAucune donnée visiteur '
"n'est concernée : l'application n'a pas démarré.")
from huggingface_hub import HfApi, snapshot_download
# VÉRIFIER L'ACCÈS D'ABORD. `snapshot_download` ne lève PAS quand `local_dir`
# existe déjà : il journalise « Returning existing local_dir » et rend la main
# comme si tout allait bien (constaté le 04/08). Sans ce contrôle, un token
# sans droits donnait le message « le dépôt ne contient pas app.py » — un
# diagnostic faux, qui envoie chercher le problème au mauvais endroit.
try:
HfApi().repo_info(repo_id=REPO, repo_type='model', token=TOKEN)
except Exception as e: # noqa: BLE001
name = type(e).__name__
denied = 'RepositoryNotFound' in name or 'GatedRepo' in name or '401' in str(e)[:80]
_placeholder(
'⚙️ Le code privé est inaccessible',
(f'Le Space ne peut pas lire `{REPO}`.\n\n'
'**Cause la plus fréquente** : un token *fine-grained* qui ne liste pas '
'ce dépôt. Il faut lui cocher explicitement l\'accès en **lecture** à '
'ce dépôt précis — ou utiliser un token *classic* avec la portée '
'`read`.\n\nVérifie aussi que `PRIVATE_REPO` est exact.'
if denied else
f'Erreur `{name}`. Vérifie `PRIVATE_REPO` et le token dans '
'*Settings → Variables and secrets*.')
+ '\n\nAucune donnée visiteur n\'est concernée : l\'application n\'a '
'pas démarré.')
try:
# local_dir=APP_DIR : la charge utile atterrit AU MÊME endroit que le
# chargeur, donc `import pipeline` et les chemins relatifs (redo/, derisk/)
# fonctionnent sans rien réécrire — le code privé est identique à celui
# qu'on exécute en local pendant le développement.
snapshot_download(repo_id=REPO, repo_type='model', revision=REVISION,
token=TOKEN, local_dir=APP_DIR)
except Exception as e: # noqa: BLE001
# On n'imprime QUE le type : les logs d'un Space public sont lisibles, et un
# message verbeux peut recracher une URL signée ou un en-tête d'autorisation.
_placeholder('⚙️ Téléchargement du code échoué',
f'`{type(e).__name__}`. Le dépôt est accessible mais le '
'téléchargement a échoué — réessaie en redémarrant le Space.')
if not os.path.exists(app):
_placeholder('⚙️ Charge utile incomplète',
f'`{REPO}` est lisible, mais ne contient pas `app.py` à sa '
'racine. Vérifie ce que `deploy_split.sh` y a poussé.')
sys.path.insert(0, APP_DIR)
runpy.run_path(app, run_name='__main__')
if __name__ == '__main__':
main()