O payload de boot do Modo A¶
No Modo A, o seu Python roda dentro do navegador, no Pyodide. Antes da
primeira pintura, o browser baixa o tempestweb-pkg.zip, descompacta no
filesystem virtual do Pyodide e faz import app. Esta página mostra as duas
decisões que o tempestweb build --mode wasm toma sobre esse zip — e como você
ajusta cada uma no tempestweb.toml.
Resumo em uma linha
Por default, o zip leva bytecode pré-compilado para o Python exato do
Pyodide e só os subpacotes do tempestweb que o seu app alcança. Os dois
têm saída segura: sem o Python certo na máquina de build, sai fonte (com
aviso); se o walk de imports não enxergar algo, você força com keep.
O problema: compilar tudo, em toda visita¶
O Pyodide liga sys.dont_write_bytecode. Na prática, isso quer dizer que todo
módulo é compilado a partir do fonte em todo carregamento de página — o
__pycache__ que o CPython normalmente grava nunca existe ali, e nada sobrevive
a um reload.
Medido no painel do tempest-webtunnel (Pyodide v314.0.0, em Node, mesmo zip):
com o zip antigo, 159 módulos do app, do tempestweb e do tempest_core
passavam pelo compilador a cada boot. Com o bytecode no zip, zero.
1. Bytecode no zip — [wasm] bytecode¶
| Valor | O que vai no zip | Quando usar |
|---|---|---|
"alongside" (default) |
.py e um __pycache__/<módulo>.cpython-314.pyc ao lado |
Quase sempre. Traceback com a linha de código, e o boot pula a compilação. |
"sourceless" |
Só o .pyc, no lugar do .py |
Quando o tamanho do download pesa mais que o traceback com fonte. |
"off" |
Só .py, como antes |
Para desligar o recurso (e o aviso abaixo). |
O app.py é especial: o bootstrap o baixa à parte e grava ao lado do zip, então
o bytecode dele vai como __pycache__/app.cpython-314.pyc dentro do zip — e o
importlib do Pyodide o encontra ali.
Por que é seguro¶
Um .pyc só serve para um CPython. O de outra versão é recusado — ou, pior,
lido errado. Então o build não confia em "parece 3.14":
- O alvo está fixado em
tempestweb.core.constants:WASM_PYODIDE_PYTHON = "3.14"eWASM_PYODIDE_PYC_MAGIC(o magic number lido do próprio runtime do Pyodide v314.0.0). - O build procura um intérprete com exatamente esse magic: o que está
rodando o build, depois
uv python find 3.14, depoispython3.14noPATH. Uma versão alpha do 3.14 tem outro magic e é recusada. - Compila em modo checked-hash (PEP 552): o Pyodide confere o hash do fonte
antes de usar o
.pyc. Se umapp.pynovo chegar com um zip velho (cache), o módulo é recompilado em vez de rodar código antigo. - Confere o cabeçalho de todo
.pycescrito. Qualquer divergência descarta o bytecode inteiro.
Sem intérprete compatível, o build não falha: emite um
WasmBytecodeWarning e empacota só o fonte — o app boota igual, só que mais
devagar.
Instalando o Python do Pyodide na máquina de build
Se o seu ambiente roda outro Python (3.11, 3.12, 3.13), deixe um 3.14 disponível para o build achar:
O uv python find só procura — o build nunca baixa um Python sozinho.
O tempestweb dev não compila
O loop de desenvolvimento rebuilda a cada save e prefere ser rápido: ele
empacota só fonte. O bytecode é coisa de build, run e deploy.
2. Só o que o app alcança — [wasm] trim¶
O zip carregava todo subpacote do tempestweb que roda no browser
(tabular, vision, query, export, access, ...), importado ou não.
Agora o build faz um walk estático de imports e deixa de fora o que ninguém
alcança.
[wasm]
trim = true # default
keep = ["tabular"] # escape: subpacotes que vão mesmo sem import visível
O walk começa em três lugares — o app.py, os seus [wasm] modules e o
tempestweb.runtime (que o bootstrap importa) — e segue cada subpacote
alcançado. Ele conta, de propósito, mais do que o Python executa:
importefrom ... importem qualquer lugar do arquivo — dentro de função e sobTYPE_CHECKINGtambém;- import relativo (
from ..tabular import x); tempestweb.htmlcomo atributo;- string pontilhada, como em
importlib.import_module("tempestweb.transports.sse").
Docstring não conta: mencionar tempestweb.tabular na documentação não é
importar.
O que o walk não enxerga
Um nome de módulo montado em runtime — import_module(f"tempestweb.{nome}")
— não aparece em lugar nenhum do fonte. Se o seu app faz isso, diga ao build:
keep = ["tabular"], ou desligue de vez com trim = false. Um nome errado em
keep falha o build com a lista dos válidos, em vez de ser ignorado.
tempest_core vai sempre inteiro: o __init__ dele importa quase tudo na hora,
então cortar arquivo ali quebraria o boot.
Quanto muda — medido¶
Painel do tempest-webtunnel (app.py + o cliente api/ gerado pelo
gen api), tempestweb build --mode wasm:
| Zip | Tamanho | Entradas | .py |
.pyc |
|---|---|---|---|---|
| Antes (tudo, só fonte) | 487 KB | 198 | 197 | 0 |
bytecode = "off", trim = true |
416 KB | 168 | 167 | 0 |
Default (alongside + trim) |
1.16 MB | 336 | 167 | 168 |
sourceless + trim |
739 KB | 169 | 0 | 168 |
O trim tirou access, components, export, query, tabular e vision
deste app. O bytecode custa download — .pyc comprime pior que fonte —, e em
troca o boot deixa de compilar.
E o tempo do import app (o lado Python do boot, depois do zip baixado), no
mesmo Pyodide v314.0.0 rodando em Node num desktop, 10 rodadas intercaladas
por variante numa máquina carregada — mediana (mínimo):
| Zip | import app |
|---|---|
| Antes | 1454 ms (1267) |
off + trim |
1436 ms (1168) |
Default (alongside + trim) |
1247 ms (1068) |
sourceless + trim |
1176 ms (1016) |
Ou seja, ~15–20% do lado Python. O resto não é compilação: perfilado, cerca
de dois terços do import app é o pydantic construindo os ~260 models do
tempest_core e do app — trabalho que bytecode nenhum evita.
E o memory snapshot do Pyodide?
O Pyodide tem uma API privada de snapshot de memória
(loadPyodide({ _makeSnapshot: true }) → makeMemorySnapshot() →
loadPyodide({ _loadSnapshot })) que pularia o boot do interpretador e os
imports de uma vez. Testamos na v314.0.0 e não dá para usar aqui:
- qualquer
loadPackage— inclusive o dopydantic, que o core exige — deixa estado no lado JS e omakeMemorySnapshot()recusa (Unexpected hiwire entry at index 6); - instalando as wheels à mão para contornar, o snapshot sai (36 MB), mas o
restore falha (
RangeError: offset is out of bounds): a extensão nativapydantic_coreé uma biblioteca dinâmica carregada fora da memória que o snapshot captura; - só um interpretador sem extensão nativa restaura (≈30 MB, em ~80 ms) —
e é justamente o
pydanticque custa caro no boot.
Sem o pydantic no snapshot não há ganho que pague 30 MB de download, então
o recurso ficou de fora.
Recap¶
- O Pyodide não guarda bytecode: sem
.pycno zip, todo módulo compila a cada visita. [wasm] bytecode="alongside"(default) ·"sourceless"·"off". O.pycsó sai de um intérprete com o magic exato do Pyodide fixado; sem ele, fonte +WasmBytecodeWarning.[wasm] trim = true(default) deixa de fora os subpacotes que o walk de imports não alcança;keep = [...]força,trim = falsedesliga.- O
tempestweb devempacota só fonte, para o loop continuar rápido.