Skip to content

fix(deps): declara fastapi e uvicorn no requirements — clone novo passa a subir - #57

Merged
caioross merged 2 commits into
mainfrom
auto/issue-56-requirements-fastapi-uvicorn
Jul 31, 2026
Merged

caioross merged 2 commits into
mainfrom
auto/issue-56-requirements-fastapi-uvicorn

Conversation

@caioross

Copy link
Copy Markdown
Owner

Contexto

laguna_server.py:23-26 importa uvicorn e fastapi no topo do módulo, mas nenhum dos dois estava declarado em requirements.txt nem em requirements.lock. Um clone novo seguindo o Quick start do README ao pé da letra — e pulando o bloco rotulado "(opcional)" — instalava um ambiente incapaz de subir o app.

Pior: install_shortcuts.ps1:8 aponta o atalho para o Laguna.vbs, que roda pythonw.exe (sem console). O ModuleNotFoundError: No module named 'fastapi' ia para um stdout que ninguém vê — falha 100% silenciosa, sem janela e sem erro. Quem já tem o ambiente montado (o dono, as worktrees da frota) nunca viu o problema.

O que mudou

Arquivo Mudança
requirements.txt +fastapi>=0.129,<1, +uvicorn>=0.40,<1 — bounds no mesmo estilo dos vizinhos, fora de qualquer bloco opcional
requirements.lock +fastapi==0.129.0, +uvicorn==0.40.0 na seção "diretas" — versões exatas do ambiente canônico, comprovadas via pip show no C:\Python313
README.md (PT :315-316 / EN :864-865) o bloco "(opcional)" passa a instalar só pywebview, e o comentário diz o que ele realmente entrega (janela nativa; sem ele, laguna_app.py cai no navegador padrão — fallback explícito em laguna_app.py:10-11)
tests_unit/conftest.py:29-35 comentário afirmava que fastapi/uvicorn "não estão em requirements.txt" — atualizado. O stub continua necessário, hermético e inalterado: o job tests-unit do CI instala só pytest numpy

Os dois arquivos de requirements foram mexidos no mesmo PR — a #41 existe justamente para impedir que divirjam.

Sobre a doutrina (HANDBOOK §7.2/§8): não é dependência nova

fastapi e uvicorn já são importados em produção desde sempre e já estão instalados no ambiente canônico. Este PR apenas declara o que o app já usa — não adiciona nada ao ambiente de ninguém, e não consome a cota de "≤1 dependência leve nova por semana". Ainda assim, por tocar requirements.*:

Solicito quórum (HANDBOOK §7)

Gate (resultado real)

  • T1 — verde. python -m compileall -q . → COMPILE_OK; python -c "import fase0_poc, laguna_core, laguna_server" → IMPORTS_OK.
  • pytest tests_unit/ — verde. 48 passed in 3.98s (com o conftest atualizado; stub hermético segue ativo mesmo com fastapi real instalado).
  • Verificação da AC 4: python -c "import fastapi, uvicorn" → DEPS_OK 0.129.0 0.40.0. Auditoria dos imports de terceiro no topo dos módulos de produção, todos agora com linha correspondente no requirements.txt:
    • laguna_server.py → sounddevice ✅, uvicorn ✅ (novo), fastapi ✅ (novo)
    • laguna_core.py → numpy ✅, sounddevice ✅
    • laguna_pipeline.py → numpy ✅
    • laguna_devices.py → sounddevice ✅
    • fase0_poc.py → numpy ✅, sounddevice ✅
  • T2 dispensado — não toca laguna_core.py/fase0_poc.py/laguna_pipeline.py (zero linhas de código de produção no diff).
  • T3 dispensado — não toca static/.
  • Sem mudança de constante de VAD/latência ou default de modelo → benchmark não se aplica.

Riscos

Baixos. Nenhuma linha de código executável mudou (só requirements, README e um comentário). Não há impacto em latência nem na promessa 100% local. O único efeito colateral possível é pip install -r requirements.txt passar a puxar starlette/pydantic/h11/click em ambientes que não os tinham — que é exatamente o comportamento correto, já que o app não roda sem eles.

Closes #56

…sa a subir

`laguna_server.py` importa `uvicorn`/`fastapi` no topo do módulo, mas nenhum
dos dois estava em `requirements.txt` nem em `requirements.lock`. Quem seguia o
Quick start do README ao pé da letra (e pulava o bloco rotulado "opcional")
instalava um ambiente incapaz de subir o app: o atalho do `install_shortcuts.ps1`
aponta para o `Laguna.vbs`, que roda `pythonw.exe` — o `ModuleNotFoundError` ia
para um stdout invisível, falha 100% silenciosa.

- `requirements.txt`: `fastapi>=0.129,<1` e `uvicorn>=0.40,<1`, no estilo dos vizinhos.
- `requirements.lock`: versões exatas do ambiente canônico (`pip show` em C:\Python313).
- README PT/EN: o bloco "(opcional)" passa a instalar só `pywebview`, que é o
  único de fato opcional (fallback explícito em `laguna_app.py`).
- `tests_unit/conftest.py`: comentário desatualizado; o stub segue necessário e
  hermético (o job da CI instala só `pytest numpy`).

Refs #56
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

O `uvicorn` puro traz só `click` e `h11` (confirmado por `pip show`). Sem
`websockets` nem `wsproto`, `uvicorn/protocols/websockets/auto.py` resolve
`AutoWebSocketsProtocol = None`, o upgrade do `/ws` nunca acontece e o
Starlette passa a rotear `GET /ws` como HTTP comum → 404.

Prova no app real (`uvicorn.Config(app, ws="none")`, que é exatamente o que
o auto-resolve faz sem lib de WS):
    ws_protocol_class resolvido: None
    HTTP/1.1 404 Not Found

Efeito para quem clona: a UI carrega (o mount de `static/` funciona) e nunca
atualiza — `static/app.js:710` cai em `scheduleReconnect()` num laço infinito,
sem erro acionável na tela. `laguna_server.py:305` (`@app.websocket("/ws")`) é
a espinha da UI live, então declarar fastapi+uvicorn sem uma lib de WS conserta
o "importa" e deixa o "funciona" quebrado.

Não é dependência nova de verdade: `websockets==15.0.1` já está no ambiente
canônico e já é exigida em runtime desde que o `/ws` existe — este commit
declara o que o app já usa, na mesma linha do resto desta PR.

Refs #56
@caioross

Copy link
Copy Markdown
Owner Author

Parecer do PR Doctor — quórum §7.2 concluído, 3× APROVA (merge)

Balde B (área de quórum): toca requirements.*, e o corpo pede quórum explicitamente. Pré-requisitos conferidos antes de convocar: CI verde, mergeable: CLEAN, diff lido inteiro.

1ª rodada — 2 APROVA / 1 VETO

Lente Pipeline/Latência — APROVA. Zero linha executável no diff; laguna_pipeline.py/laguna_core.py não importam fastapi/uvicorn nem transitivamente (o acoplamento é só laguna_server.py:23-26, fora de qualquer callback de áudio). Testou o vetor mais plausível — conflito de resolver em pydantic: spacy 3.8.11 (pydantic!=1.8,<3.0.0,>=1.7.4) ∩ thinc 8.3.10 (>=2.0.0,<3.0.0) ∩ fastapi 0.129.0 (>=2.7.0) = interseção não-vazia (env roda 2.12.5). click e anyio idem. E confirmou que requirements.lock:1-11 se declara não exaustivo ("Inclui as transitivas críticas: ctranslate2 e onnxruntime"), então starlette/pydantic ficarem de fora é a política declarada.

Lente Produto/UX — APROVA. Paridade PT/EN exata (README.md:315-316 / :864-865); grep de fastapi|uvicorn no README não achou contradição residual — e o subgraph OPT em README.md:288-290 já listava só pywebview + VB-CABLE, ou seja, a prosa é que estava errada. A frase nova é fiel a laguna_app.py:63-70 (except Exception → webbrowser.open(URL)). Achado de produto: Laguna.vbs:16 e Laguna.bat:4 chamam laguna_server.py, logo o atalho do Desktop — o fluxo principal — estava quebrado em clone novo.

Lente Privacidade/Robustez — VETO. uvicorn puro só requer click+h11; sem websockets/wsproto o app sobe e o /ws fica morto — e o dono nunca viu porque o ambiente dele já tem websockets 15.0.1 e wsproto 1.3.2 instaladas por fora, sem serem dep de nada declarado.

Veto confirmado na fonte e no app real

Não aceitei o veto no argumento: verifiquei. uvicorn/protocols/websockets/auto.py resolve AutoWebSocketsProtocol = None quando os dois imports falham, e h11_impl responde "No supported WebSocket library detected.". Subindo o app real com uvicorn.Config(app, ws="none") — que é exatamente o que o auto-resolve produz num clone novo:

ws_protocol_class resolvido: None
GET /ws (Upgrade: websocket)  ->  HTTP/1.1 404 Not Found

Uma correção ao veto: o sintoma é 404, não 400 — sem upgrade, o Starlette roteia GET /ws como HTTP comum e nenhuma rota responde nesse path. O efeito prático é o previsto: laguna_server.py:305 (@app.websocket("/ws")) é a espinha da UI live, e static/app.js:710 (ws.onclose = () => scheduleReconnect()) entra em laço infinito de reconexão — UI carregada, painel eternamente parado, nenhum erro acionável na tela. Declarar fastapi+uvicorn sem lib de WS consertava o importa e deixava o funciona quebrado.

Não acatei o 2º ponto do veto (starlette/pydantic sem pin no lock): requirements.lock:10-11 delimita o próprio escopo — diretas + transitivas de engine de STT/TTS. Fecho transitivo completo é outra decisão, não pendência desta PR. A lente retirou o ponto na 2ª rodada.

Reparo (e9af420, por união, sem force)

websockets>=15,<16 em requirements.txt (com comentário explicando a armadilha) e websockets==15.0.1 no requirements.lock, na seção das diretas. Escolhi websockets explícito em vez de uvicorn[standard], que arrastaria httptools, watchfiles, python-dotenv, PyYAML e colorama — irrelevantes aqui, contra a regra de diff mínimo. Não consome a cota de "≤1 dep leve nova/semana" (§8) pelo mesmo argumento que a PR já fazia: é requisito de runtime desde que o /ws existe, só não estava declarado.

2ª rodada — 3× APROVA

  • Pipeline/Latência: varreu requires() de todas as deps declaradas — nenhuma menciona websockets; único constraint é uvicorn (websockets>=10.4; extra=='standard', sem teto). pip show websockets → Requires: vazio, zero transitiva nova. packaging.Requirement parseia 10/10 linhas do .txt e 12/12 do .lock (o comentário é ignorado). Achado extra: o <16 é load-bearing, não cosmético — websockets_impl.py:5,11 importa websockets.legacy sem guarda, e legacy sai na série 16; sem teto, um uvicorn 0.40 futuro explodiria com ImportError na subida.
  • Privacidade/Robustez: validou num venv limpo só com os pins do lock → auto -> WebSocketProtocol e FRESH HANDSHAKE OK -> {"kind":"hello","running":[]}. Bind intacto (laguna_server.py:34, laguna_app.py:22 seguem 127.0.0.1); import websockets não puxa socket/ssl/urllib. Stub do conftest.py:38 segue hermético (instala em sys.modules incondicionalmente, sem try/except) — 48 passed com o pacote real presente.
  • Produto/UX: nenhuma das 6 caixas de troubleshooting PT (README.md:487-535) nem das 4 EN (:920-947) menciona WebSocket/reconexão, então nada ficou obsoleto. git diff origin/main...HEAD -- static/ volta vazio: contrato REST/WS e i18n intocados. Retirou o nit de bounds neste pacote (websockets é semver normal; <1 em fastapi/uvicorn fica para PR separado, se o dono quiser).

Gate (re-rodado na worktree após o reparo)

compileall → COMPILE_OK · import fase0_poc, laguna_core, laguna_server → IMPORTS_OK · pytest tests_unit/ -q → 48 passed. T2/T3 dispensados corretamente (zero código de produção, static/ intocado). CI verde nos dois jobs em e9af420.

Closes #56 está certo: as 5 ACs foram cumpridas, e o reparo entrega a promessa que o título faz.

Fica para o backlog (fora do escopo, sem veto)

A auditoria da AC 4 varreu imports de topo, e por isso não pegou este caso — o buraco não era um ModuleNotFoundError, era uma capability que o pacote só ganha com extra. Fica um irmão menor: laguna_app.py:63 importa webview (pywebview), ausente do requirements.txt. Não é bug — está sob try/except com fallback para webbrowser e é opt-in documentado —, mas vale ao Curador avaliar um extra laguna[gui] ou uma nota no requirements.

Merge por squash.

@caioross
caioross merged commit c0cea0a into main Jul 31, 2026
2 checks passed
@caioross
caioross deleted the auto/issue-56-requirements-fastapi-uvicorn branch July 31, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

infra: fastapi e uvicorn nao estao no requirements — clone novo seguindo o README nao sobe, e o atalho .vbs falha em silencio

1 participant