""" Launcher de la app GENPACT V2. Arranca un servidor Voila local en background y abre el browser apuntando al app.ipynb. Cuando se cierra la consola, el servidor de Voila se mata. Funciona tanto desde Python directo como empaquetado con PyInstaller. """ import os import sys import time import socket import threading import webbrowser import subprocess from pathlib import Path # ------------------------------------------------------------------ # Localizar app.ipynb y .env # ------------------------------------------------------------------ def base_dir() -> Path: """Carpeta donde estan los recursos de la app. - En modo PyInstaller (--onedir), sys._MEIPASS apunta a la carpeta temporal. - En modo dev, es la carpeta del script. """ if getattr(sys, 'frozen', False): # PyInstaller --onedir: los datos quedan al lado del .exe return Path(sys.executable).parent return Path(__file__).resolve().parent BASE = base_dir() APP_NB = BASE / 'app.ipynb' # Si el .env esta un nivel arriba (proyecto), copialo o setea el cwd if not (BASE / '.env').exists() and (BASE.parent / '.env').exists(): os.chdir(BASE.parent) else: os.chdir(BASE) # ------------------------------------------------------------------ # Buscar puerto libre # ------------------------------------------------------------------ def find_free_port(preferido: int = 8866) -> int: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.bind(('127.0.0.1', preferido)) sock.close() return preferido except OSError: sock.close() # Si el preferido esta ocupado, deja que el SO asigne uno s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('127.0.0.1', 0)) p = s.getsockname()[1] s.close() return p # ------------------------------------------------------------------ # Arrancar Voila # ------------------------------------------------------------------ def main(): if not APP_NB.exists(): print(f'ERROR: no se encuentra {APP_NB}') sys.exit(1) port = find_free_port(8866) # Cuando Voila apunta a un notebook especifico, lo sirve en la raiz "/" url = f'http://127.0.0.1:{port}/' print('=' * 60) print(' GENPACT V2 - Sistema de Descargas SCAII') print('=' * 60) print(f' Iniciando servidor Voila en puerto {port}...') print(f' URL: {url}') print(' Cierra esta ventana para detener la app.') print('=' * 60) # Construir comando de Voila if getattr(sys, 'frozen', False): # En modo empaquetado, llamamos al binario de python integrado cmd = [sys.executable, '-m', 'voila', str(APP_NB), f'--port={port}', '--no-browser', '--Voila.ip=127.0.0.1', '--strip_sources=True'] else: cmd = [sys.executable, '-m', 'voila', str(APP_NB), f'--port={port}', '--no-browser', '--Voila.ip=127.0.0.1', '--strip_sources=True'] # Lanzar proceso proc = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) # Abrir browser cuando el server este listo def _open(): for _ in range(60): try: with socket.create_connection(('127.0.0.1', port), timeout=0.5): break except OSError: time.sleep(0.5) webbrowser.open(url) threading.Thread(target=_open, daemon=True).start() try: proc.wait() except KeyboardInterrupt: print('\nDeteniendo Voila...') proc.terminate() if __name__ == '__main__': main()