Distribuindo binários Go como sqlite-scanner por meio de PyPI usando go-to-wheel

Distribuindo binários Go como sqlite-scanner por meio de PyPI usando go-to-wheel


Distribuindo binários Go como sqlite-scanner por meio de PyPI usando go-to-wheel

4 de fevereiro de 2026

Recentemente, tenho explorado o Go para criar aplicativos binários pequenos, rápidos e independentes. Estou gostando de como geralmente existe uma maneira óbvia de fazer as coisas e o código resultante é enfadonho e legível – e algo que os LLMs são muito competentes em escrever. O único problema é a distribuição, mas acontece que publicar binários Go no PyPI significa que qualquer binário Go pode ser apenas um uvx package-name ligue.

scanner sqlite

sqlite-scanner é minha nova ferramenta Go CLI para verificar um sistema de arquivos em busca de arquivos de banco de dados SQLite.

Funciona verificando se os primeiros 16 bytes do arquivo correspondem exatamente à sequência numérica mágica do SQLite SQLite format 3\x00. Ele pode pesquisar uma ou mais pastas recursivamente, ativando goroutines simultâneas para acelerar a verificação. Ele transmite os resultados à medida que os encontra em texto simples, JSON ou JSON delimitado por nova linha. Opcionalmente, ele também pode exibir os tamanhos dos arquivos.

Para experimentá-lo, você pode baixar uma versão das versões do GitHub – e depois passar pelos bastidores do macOS para executar um binário “inseguro”. Ou você pode clonar o repositório e compilá-lo com Go. Ou… você pode executar o binário assim:

uvx sqlite-scanner

Por padrão, isso irá pesquisar em seu diretório atual por bancos de dados SQLite. Você pode passar um ou mais diretórios como argumentos:

uvx sqlite-scanner ~ /tmp

Adicionar --json para saída JSON, --size para incluir tamanhos de arquivo ou --jsonl para JSON delimitado por nova linha. Aqui está uma demonstração:

uvx sqlite-scanner ~ --jsonl --size

executar esse comando produz uma sequência de objetos JSON, cada um com um caminho e uma chave de tamanho

Se você ainda não recebeu pílula UV, você pode instalar sqlite-scanner usando pip install sqlite-scanner e então corra sqlite-scanner.

Para obter uma cópia permanente com uv usar uv tool install sqlite-scanner.

Como funciona o pacote Python

A razão pela qual vale a pena fazer isso é que pip, uv e o PyPI trabalharão juntos para identificar o binário compilado correto para seu sistema operacional e arquitetura.

Isso é motivado por nomes de arquivos. Se você visitar os downloads do PyPI para sqlite-scanner, verá os seguintes arquivos:

  • sqlite_scanner-0.1.1-py3-none-win_arm64.whl
  • sqlite_scanner-0.1.1-py3-none-win_amd64.whl
  • sqlite_scanner-0.1.1-py3-none-musllinux_1_2_x86_64.whl
  • sqlite_scanner-0.1.1-py3-none-musllinux_1_2_aarch64.whl
  • sqlite_scanner-0.1.1-py3-none-manylinux_2_17_x86_64.whl
  • sqlite_scanner-0.1.1-py3-none-manylinux_2_17_aarch64.whl
  • sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl
  • sqlite_scanner-0.1.1-py3-none-macosx_10_9_x86_64.whl

Quando eu corro pip install sqlite-scanner ou uvx sqlite-scanner no meu laptop Apple Silicon Mac, a magia da embalagem do Python garante que eu consiga isso macosx_11_0_arm64.whl variante.

Aqui está o que está na roda, que é um arquivo zip com um .whl extensão.

Além do bin/sqlite-scanner o arquivo mais importante é sqlite_scanner/__init__.py que inclui o seguinte:

def get_binary_path():
    """Return the path to the bundled binary."""
    binary = os.path.join(os.path.dirname(__file__), "bin", "sqlite-scanner")
 
    # Ensure binary is executable on Unix
    if sys.platform != "win32":
        current_mode = os.stat(binary).st_mode
        if not (current_mode & stat.S_IXUSR):
            os.chmod(binary, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
 
    return binary
 
 
def main():
    """Execute the bundled binary."""
    binary = get_binary_path()
 
    if sys.platform == "win32":
        # On Windows, use subprocess to properly handle signals
        sys.exit(subprocess.call((binary) + sys.argv(1:)))
    else:
        # On Unix, exec replaces the process
        os.execvp(binary, (binary) + sys.argv(1:))

Que main() método – também chamado de sqlite_scanner/__main__.py—localiza o binário e o executa quando o próprio pacote Python é executado, usando o sqlite-scanner = sqlite_scanner:main ponto de entrada definido na roda.

O que significa que podemos usá-lo como uma dependência

Usar o PyPI como plataforma de distribuição para binários Go parece um pouco abusivo, embora haja muitos precedentes.

Vou justificar salientando que isso significa podemos usar binários Go como dependências para outros pacotes Python agora.

Isso é genuinamente útil! Isso significa que qualquer funcionalidade disponível em um binário Go multiplataforma agora pode ser incluída em um pacote Python. Python é realmente bom na execução de subprocessos, então isso abre um mundo inteiro de truques úteis que podemos incorporar em nossas ferramentas Python.

Para demonstrar isso, criei o datasette-scan – um novo plugin do Datasette que depende de sqlite-scanner e então usa esse binário Go para verificar uma pasta em busca de bancos de dados SQLite e anexá-los a uma instância do Datasette.

Veja como usar isso (sem instalar nada primeiro, obrigado uv) para explorar qualquer banco de dados SQLite na sua pasta Downloads:

uv run --with datasette-scan datasette scan ~/Downloads

Se você espiar o código, verá que depende do sqlite-scanner em pyproject.toml e chama usando subprocess.run() contra sqlite_scanner.get_binary_path() em sua própria função scan_directories().

Tenho explorado esse padrão para outros binários não-Go recentemente – aqui está um script recente que depende do static-ffmpeg para garantir que ffmpeg está disponível para uso do script.

Construindo rodas Python a partir de pacotes Go com go-to-wheel

Depois de tentar esse padrão algumas vezes, percebi que seria útil ter uma ferramenta para automatizar o processo.

Primeiro fiz um brainstorming com Claude para verificar se não havia nenhuma ferramenta para fazer isso. Ele me indicou maturin bin, que ajuda a distribuir projetos Rust usando rodas Python, e pip-binary-factory, que agrupa todos os tipos de outros projetos, mas não identificou nada que abordasse exatamente o problema que eu estava procurando resolver.

Então, pedi ao Claude Code para construir a primeira versão na web e, em seguida, refinei o código localmente no meu laptop com a ajuda de mais Claude Code e um pouco do OpenAI Codex também, apenas para misturar as coisas.

A documentação completa está no repositório simonw/go-to-wheel. Publiquei essa ferramenta no PyPI, então agora você pode executá-la usando:

O sqlite-scanner pacote que você pode ver no PyPI foi construído usando go-to-wheel assim:

uvx go-to-wheel ~/dev/sqlite-scanner \
  --set-version-var main.version \
  --version 0.1.1 \
  --readme README.md \
  --author 'Simon Willison' \
  --url https://github.com/simonw/sqlite-scanner \
  --description 'Scan directories for SQLite databases'

Isso criou um conjunto de rodas no dist/ pasta. Testei um deles assim:

uv run --with dist/sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl \
  sqlite-scanner --version

Quando isso cuspiu o número de versão correto, eu estava confiante de que tudo havia funcionado conforme planejado, então empurrei todo o conjunto de rodas para PyPI usando twine upload assim:

Tive que colar um token da API PyPI que salvei anteriormente e bastou isso.

Espero usar muito esse padrão

sqlite-scanner é claramente uma prova de conceito para esse padrão mais amplo – o Python é muito capaz de rastrear recursivamente uma estrutura de diretórios em busca de arquivos que começam com um prefixo de byte específico por conta própria!

Dito isto, acho que há um muito a ser dito sobre esse padrão. Go é um ótimo complemento para Python: é rápido, compila em pequenos binários independentes, tem excelente suporte à simultaneidade e um rico ecossistema de bibliotecas.

Go é semelhante ao Python porque possui uma biblioteca padrão forte. Go é particularmente bom para ferramentas HTTP — eu criei vários proxies HTTP no passado usando o excelente net/http/httputil.ReverseProxy manipulador.

Também tenho experimentado o wazero, o tempo de execução WebAssembly robusto e maduro de dependência zero do Go, como parte de minha busca contínua pela sandbox ideal para executar código não confiável. Aqui está minha última experiência com essa biblioteca.

Ser capaz de integrar perfeitamente binários Go em projetos Python sem que o usuário final precise pensar em Go — eles pip install e tudo simplesmente funciona – parece uma adição valiosa à minha caixa de ferramentas.



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *