Spaces:
Sleeping
Sleeping
| #!/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() | |