Spaces:
Sleeping
Sleeping
File size: 6,879 Bytes
22c0953 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | #!/usr/bin/env python3
"""
ClearDDI — lanceur de démo
Installe les dépendances si besoin, démarre le serveur, affiche un QR code
pour que n'importe qui (sur le même réseau, ou partout avec --public) puisse
ouvrir l'app sur son téléphone, et ouvre le navigateur local.
Usage :
python3 run.py → accès réseau local (Wi-Fi de la salle)
python3 run.py --public → accès public via un tunnel (n'importe où)
python3 run.py --port 9000 → changer le port
python3 run.py --no-browser → ne pas ouvrir le navigateur automatiquement
"""
import argparse
import socket
import subprocess
import sys
import time
import urllib.request
import webbrowser
from pathlib import Path
ROOT = Path(__file__).resolve().parent
BACKEND_DIR = ROOT / "backend"
QR_PATH = ROOT / "qr_acces.png"
REQUIRED_PACKAGES = [
"fastapi", "uvicorn", "rdkit", "shap", "xgboost",
"scikit-learn", "joblib", "pandas", "numpy", "pyarrow", "pydantic",
"qrcode", "pillow",
]
def ensure_dependencies(need_ngrok: bool):
print("Vérification des dépendances Python...")
packages = list(REQUIRED_PACKAGES) + (["pyngrok"] if need_ngrok else [])
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "--quiet", *packages],
capture_output=True, text=True, check=False,
)
if result.returncode != 0:
# Systèmes Debian/Ubuntu récents (PEP 668) refusent pip install hors venv.
# On retente avec --break-system-packages plutôt que de planter au démarrage,
# sans alarmer l'utilisateur avec le message pip brut si ça se résout seul.
retry = subprocess.run(
[sys.executable, "-m", "pip", "install", "--quiet", "--break-system-packages", *packages],
capture_output=True, text=True, check=False,
)
if retry.returncode != 0:
print(
"⚠️ Installation automatique des dépendances impossible.\n"
" Essayez manuellement :\n"
f" {sys.executable} -m pip install --break-system-packages {' '.join(packages)}\n"
" ou créez un environnement virtuel (python3 -m venv .venv) avant de relancer."
)
def get_lan_ip():
"""Adresse IP de cette machine sur le réseau local (celle que les autres
appareils du même Wi-Fi doivent utiliser pour nous joindre). N'envoie
aucun paquet : ouvre juste une socket UDP pour lire l'IP de sortie."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(("8.8.8.8", 80))
return s.getsockname()[0]
finally:
s.close()
except OSError:
return None
def wait_for_server(port, timeout=25.0):
"""Attend que /api/health réponde, au lieu d'un sleep() arbitraire."""
deadline = time.time() + timeout
url = f"http://127.0.0.1:{port}/api/health"
while time.time() < deadline:
try:
with urllib.request.urlopen(url, timeout=1.5) as resp:
if resp.status == 200:
return True
except Exception:
pass
time.sleep(0.5)
return False
def start_public_tunnel(port):
"""Ouvre un tunnel public (ngrok) vers le serveur local. Nécessite un
compte ngrok gratuit + un authtoken configuré (voir README) — sans quoi
ngrok refuse désormais les tunnels anonymes."""
try:
from pyngrok import ngrok
except ImportError:
print("⚠️ pyngrok n'est pas installé — accès public désactivé.")
return None
try:
tunnel = ngrok.connect(port, "http")
return tunnel.public_url
except Exception as e:
print(
"⚠️ Impossible d'ouvrir un tunnel public via ngrok.\n"
" Cause probable : pas d'authtoken configuré.\n"
" → Créez un compte gratuit sur https://dashboard.ngrok.com/signup\n"
" → Récupérez votre token puis lancez une fois :\n"
" python3 -m pyngrok config add-authtoken VOTRE_TOKEN\n"
f" Détail technique : {e}\n"
" → On continue avec l'accès réseau local à la place."
)
return None
def show_qr(url):
try:
import qrcode
except ImportError:
print("⚠️ Le paquet 'qrcode' n'est pas installé : QR code non généré.")
return
qr = qrcode.QRCode(border=1)
qr.add_data(url)
qr.make()
print("\nScannez ce QR code pour ouvrir ClearDDI :\n")
qr.print_ascii(invert=True)
try:
img = qr.make_image(fill_color="black", back_color="white")
img.save(QR_PATH)
print(f"\n(QR code aussi enregistré dans : {QR_PATH})")
except Exception:
pass # l'ASCII ci-dessus suffit si l'image ne peut pas être sauvegardée
def main():
parser = argparse.ArgumentParser(description="Lanceur de démo ClearDDI")
parser.add_argument("--port", type=int, default=8765, help="Port du serveur (défaut : 8765)")
parser.add_argument("--public", action="store_true",
help="Rendre l'app accessible publiquement (tunnel ngrok), pas seulement sur le réseau local")
parser.add_argument("--no-browser", action="store_true",
help="Ne pas ouvrir automatiquement le navigateur local")
args = parser.parse_args()
port = args.port
ensure_dependencies(need_ngrok=args.public)
print(f"Démarrage du serveur ClearDDI sur le port {port} ...")
proc = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", str(port)],
cwd=str(BACKEND_DIR),
)
if not wait_for_server(port):
print("⚠️ Le serveur ne répond pas encore après 25s — vérifiez les erreurs ci-dessus.")
local_url = f"http://localhost:{port}"
share_url = local_url
access_note = "réseau local uniquement"
if args.public:
public_url = start_public_tunnel(port)
if public_url:
share_url = public_url
access_note = "accessible depuis n'importe où sur Internet"
if share_url == local_url:
lan_ip = get_lan_ip()
if lan_ip:
share_url = f"http://{lan_ip}:{port}"
access_note = "accessible à toute personne sur le même réseau Wi-Fi"
else:
access_note = "IP locale non détectée — partagez plutôt l'URL locale manuellement"
print(f"\n✅ Serveur prêt : {local_url} (sur cette machine)")
print(f"📱 À partager ({access_note}) : {share_url}")
show_qr(share_url)
if not args.no_browser:
webbrowser.open(local_url)
print("\nServeur lancé. Appuyez sur Ctrl+C pour arrêter.")
try:
proc.wait()
except KeyboardInterrupt:
proc.terminate()
if __name__ == "__main__":
main()
|