from __future__ import annotations from pathlib import Path import os import shutil import subprocess import sys class Application: def __init__(self, *args, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) self.base_path = Path(__file__).resolve().parents[1] self.venv_path = self.base_path / ".venv" # uv default self._hello() # -------- UX -------- def _hello(self): print(f"🚀 Iniciando o {getattr(self, 'package', '')} {getattr(self, 'version', '')}...") def _print(self, msg: str): print(f"[{getattr(self, 'package', '')}] {msg}", flush=True) # -------- main -------- def run(self): os.chdir(self.base_path) uv = self.ensure_uv() self.ensure_venv(uv) self.ensure_dependencies(uv) self.launch_neuro(uv) # -------- uv / env -------- def ensure_uv(self) -> str: """ Garante que o comando `uv` existe no host. Estratégia: 1) PATH 2) `python -m uv` (se uv estiver instalado no python do host) 3) fallback: instala uv via pip (host python) e tenta de novo """ if shutil.which("uv"): self._print("uv encontrado no PATH.") return "uv" # tenta via `python -m uv` (muitas vezes funciona em ambientes dev) try: subprocess.run([sys.executable, "-m", "uv", "--version"], check=True, capture_output=True, text=True) self._print("uv disponível via `python -m uv`.") return f"{sys.executable} -m uv" except Exception: pass # fallback pragmatico: pip install uv (host python) self._print("uv não encontrado. Tentando instalar via pip no Python do host...") try: subprocess.run([sys.executable, "-m", "pip", "install", "--user", "uv"], check=True) except subprocess.CalledProcessError as e: raise RuntimeError( "Não consegui instalar o `uv`. Instala manualmente e tenta novamente.\n" "Sugestões:\n" " - pip install --user uv\n" " - ou usa o instalador oficial do uv\n" ) from e if shutil.which("uv"): self._print("uv instalado e disponível no PATH.") return "uv" # última tentativa: python -m uv subprocess.run([sys.executable, "-m", "uv", "--version"], check=True) self._print("uv instalado e disponível via `python -m uv`.") return f"{sys.executable} -m uv" def _uv(self, uv_cmd: str, *args: str) -> None: """ Executa um comando uv, aceitando uv_cmd como: - "uv" - " -m uv" """ cmd = uv_cmd.split() + list(args) self._print(" ".join(cmd)) subprocess.run(cmd, check=True) def ensure_venv(self, uv_cmd: str) -> None: """ Garante que existe um venv gerido pelo uv em .venv. """ if self.venv_path.exists(): self._print("Ambiente .venv já existente.") return self._print("Criando ambiente .venv via uv...") # uv cria .venv por default; este comando respeita pyproject self._uv(uv_cmd, "venv") def ensure_dependencies(self, uv_cmd: str) -> None: """ Instala/sincroniza deps. Com uv.lock presente, o correto é uv sync. """ lock = self.base_path / "uv.lock" pyproject = self.base_path / "pyproject.toml" if not pyproject.exists(): raise RuntimeError("pyproject.toml não encontrado na raiz do projeto.") if lock.exists(): self._print("Sincronizando dependências via uv.lock (uv sync)...") self._uv(uv_cmd, "sync") else: # Se ainda não há lock, tenta resolver e criar self._print("uv.lock não encontrado. Resolvendo dependências (uv lock + uv sync)...") self._uv(uv_cmd, "lock") self._uv(uv_cmd, "sync") # -------- launch -------- def launch_neuro(self, uv_cmd: str) -> None: """ Entry point: roda a app dentro do ambiente uv. Preferência: módulo -m neuro (usa src layout corretamente). """ # Se tens __main__.py em neuro, isto é perfeito: self._print("Iniciando neuro via uv run...") self._uv(uv_cmd, "run", "python", "-m", "neuro")