malikachaaban
app
22c0953
Raw
History Blame Contribute Delete
6.88 kB
#!/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()