Empacotando projetos Python

Este tutorial mostra como empacotar um projeto Python simples. Ele mostrará como adicionar os arquivos e a estrutura necessários para criar o pacote, como construir o pacote e como enviá-lo para o Índice de Pacotes do Python.

Dica

Se você tiver problemas para executar os comandos neste tutorial, copie o comando e sua saída e, em seguida, relate um problema no repositório packaging-problems no GitHub. Faremos o nosso melhor para ajudá-lo!

Alguns comandos exigem uma nova versão de pip, então comece certificando-se de que você tenha a versão mais recente instalada:

python3 -m pip install --upgrade pip
py -m pip install --upgrade pip

Um projeto simples

Este tutorial usa um projeto simples chamado example_package. Recomendamos seguir este tutorial como está usando este projeto, antes de empacotar seu próprio projeto.

Crie a seguinte estrutura de arquivos localmente:

packaging_tutorial/
└── src/
    └── example_package/
        ├── __init__.py
        └── example.py

__init__.py é necessário para importar o diretório como um pacote, e deve estar vazio.

example.py é um exemplo de módulo dentro do pacote que pode conter a lógica (funções, classes, constantes, etc.) do seu pacote. Abra esse arquivo e insira o seguinte conteúdo:

def add_one(number):
    return number + 1

Se você não está familiarizado com módulos e pacotes de importação do Python, reserve alguns minutos para ler a documentação do Python para pacotes e módulos.

Depois de criar esta estrutura, você desejará executar todos os comandos neste tutorial dentro do diretório packaging_tutorial.

Criando arquivos do pacote

Agora você adicionará arquivos que são usados para preparar o projeto para distribuição. Quando terminar, a estrutura do projeto ficará assim:

packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
├── src/
│   └── example_package/
│       ├── __init__.py
│       └── example.py
└── tests/

Criando um diretório de teste

tests/ é um espaço reservado, ou placeholder, para arquivos de teste. Deixe-o vazio por enquanto.

Criando pyproject.toml

pyproject.toml informa as ferramentas de construção (como pip e construir) o que é necessário para construir seu projeto. Este tutorial usa setuptools, então abra pyproject.toml e insira o seguinte conteúdo:

[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"

build-system.requires fornece uma lista de pacotes que são necessários para construir seu pacote. Listar algo aqui apenas o tornará disponível durante a construção, não depois de instalado.

build-system.build-backend é o nome do objeto Python que será usado para realizar a construção. Se você fosse usar um sistema de construção diferente, como flit ou poetry, eles iriam aqui, e os detalhes de configuração seriam completamente diferentes da configuração do setuptools descrita abaixo.

Veja PEP 517 e PEP 518 para um informações e detalhes.

Configurando metadados

Há dois tipos de metadados: estáticos e dinâmicos.

  • Metadados estáticos (setup.cfg): garantidamente os mesmos sempre. Isso é mais simples, fácil de ler e evita muitos erros comuns, como erros de codificação.

  • Metadados dinâmicos (setup.py): possivelmente não determinísticos. Quaisquer itens que são dinâmicos ou determinados no momento da instalação, bem como módulos de extensão ou extensões para setuptools, precisam ir para setup.py.

Metadados estáticos (setup.cfg) devem ser preferidos. Metadados dinâmicos (setup.py) devem ser usados apenas como uma saída de emergência quando for absolutamente necessário. setup.py costumava ser necessário, mas pode ser omitido com as versões mais recentes de setuptools e pip.

setup.cfg é o arquivo de configuração para setuptools. Ele informa ao setuptools sobre o seu pacote (como o nome e a versão), bem como os arquivos de código a serem incluídos. Eventualmente, grande parte desta configuração pode ser movida para pyproject.toml.

Abra setup.cfg e insira o seguinte conteúdo. Mude o name para incluir seu nome de usuário; isso garante que você tenha um nome de pacote exclusivo e que seu pacote não entre em conflito com os pacotes carregados por outras pessoas que seguem este tutorial.

[metadata]
name = example-package-YOUR-USERNAME-HERE
version = 0.0.1
author = Example Author
author_email = author@example.com
description = A small example package
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/pypa/sampleproject
project_urls =
    Bug Tracker = https://github.com/pypa/sampleproject/issues
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent

[options]
package_dir =
    = src
packages = find:
python_requires = >=3.6

[options.packages.find]
where = src

Há uma variedade de metadados e opções suportados aqui. Isso está no formato configparser; não coloque aspas em torno dos valores. Este pacote de exemplo usa um conjunto relativamente mínimo de metadata:

  • name é o nome da distribuição do seu pacote. Pode ser qualquer nome, desde que contenha apenas letras, números, _ e -. Também não deve ser feito em pypi.org. Certifique-se de atualizá-lo com seu nome de usuário, pois isso garante que você não tentará enviar um pacote com o mesmo nome de um que já existe.

  • version é a versão do pacote. Consulte a PEP 440 para obter mais detalhes sobre as versões. Você pode usar as diretivas file: ou attr: para ler um arquivo ou atributo de pacote.

  • author e author_email são usados para identificar o autor do pacote.

  • description é um resumo curto do pacote contendo apenas uma frase.

  • long_description é uma descrição detalhada do pacote. Isso é mostrado na página de detalhes do pacote no Índice do Pacotes Python. Neste caso, a descrição longa é carregada de README.md (que é um padrão comum) usando a diretiva file:.

  • long_description_content_type diz ao índice que tipo de marcação é usado para a descrição longa. Neste caso, é Markdown.

  • url é a URL da página inicial do projeto. Para muitos projetos, será apenas um link para GitHub, GitLab, Bitbucket ou serviço de hospedagem de código semelhante.

  • project_urls permite listar qualquer número de links extras para mostrar no PyPI. Geralmente, isso pode ser para documentação, rastreadores de problemas, etc.

  • classifiers fornece o índice e pip alguns metadados adicionais sobre seu pacote. Nesse caso, o pacote é compatível apenas com Python 3, está licenciado sob a licença MIT e é independente do sistema operacional. Você deve sempre incluir pelo menos a(s) versão(ões) do Python em que seu pacote funciona, em que licença seu pacote está disponível e em quais sistemas operacionais seu pacote funcionará. Para obter uma lista completa de classificadores, consulte https://pypi.org/classifiers/.

Na categoria options, temos controles para o próprio setuptools:

  • package_dir é um mapeamento de nomes e diretórios de pacotes. Um nome de pacote vazio representa o “pacote raiz” – o diretório no projeto que contém todos os arquivos fonte Python para o pacote – então, neste caso, o diretório src é designado o pacote raiz.

  • packages é uma lista de todos os Python pacotes de importação que devem ser incluídos no pacote de distribuição. Em vez de listar cada pacote manualmente, podemos usar a diretiva find: para descobrir automaticamente todos os pacotes e subpacotes e options.packages.find para especificar o package_dir a ser usado. Neste caso, a lista de pacotes será example_package visto que é o único pacote presente.

  • python_requires fornece as versões do Python suportadas pelo seu projeto. Instaladores como pip olharão para as versões anteriores dos pacotes até encontrar um que tenha uma versão correspondente do Python.

Existem muitos mais do que os mencionados aqui. Veja Empacotando e distribuindo projetos para mais detalhes.

setup.py é o script de construção para setuptools. Ele informa ao setuptools sobre o seu pacote (como o nome e a versão), bem como os arquivos de código a serem incluídos.

Abra setup.py e insira o seguinte conteúdo. Mude o name para incluir seu nome de usuário; isso garante que você tenha um nome de pacote exclusivo e que seu pacote não entre em conflito com os pacotes carregados por outras pessoas que seguem este tutorial.

import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="example-package-YOUR-USERNAME-HERE",
    version="0.0.1",
    author="Example Author",
    author_email="author@example.com",
    description="A small example package",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/pypa/sampleproject",
    project_urls={
        "Bug Tracker": "https://github.com/pypa/sampleproject/issues",
    },
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    package_dir={"": "src"},
    packages=setuptools.find_packages(where="src"),
    python_requires=">=3.6",
)

setup() recebe vários argumentos. Este pacote de exemplo usa um conjunto relativamente mínimo:

  • name é o nome da distribuição do seu pacote. Pode ser qualquer nome, desde que contenha apenas letras, números, _ e -. Também não deve ser feito em pypi.org. Certifique-se de atualizá-lo com seu nome de usuário, pois isso garante que você não tentará enviar um pacote com o mesmo nome de um que já existe.

  • version é a versão do pacote. Consulte PEP 440 para obter mais detalhes sobre as versões.

  • author e author_email são usados para identificar o autor do pacote.

  • description é um resumo curto do pacote contendo apenas uma frase.

  • long_description é uma descrição detalhada do pacote. Isso é mostrado na página de detalhes do pacote no Índice do Pacotes do Python. Neste caso, a descrição longa é carregada de README.md, que é um padrão comum.

  • long_description_content_type diz ao índice que tipo de marcação é usado para a descrição longa. Neste caso, é Markdown.

  • url é a URL da página inicial do projeto. Para muitos projetos, será apenas um link para GitHub, GitLab, Bitbucket ou serviço de hospedagem de código semelhante.

  • project_urls permite listar qualquer número de links extras para mostrar no PyPI. Geralmente, isso pode ser para documentação, rastreadores de problemas, etc.

  • classifiers fornece o índice e pip alguns metadados adicionais sobre seu pacote. Nesse caso, o pacote é compatível apenas com Python 3, está licenciado sob a licença MIT e é independente do sistema operacional. Você deve sempre incluir pelo menos a(s) versão(ões) do Python em que seu pacote funciona, em que licença seu pacote está disponível e em quais sistemas operacionais seu pacote funcionará. Para obter uma lista completa de classificadores, consulte https://pypi.org/classifiers/.

  • package_dir é um dicionário com nomes de pacotes para chaves e diretórios para valores. Um nome de pacote vazio representa o “pacote raiz” – o diretório no projeto que contém todos os arquivos fonte Python para o pacote – então, neste caso, o diretório src é designado o pacote raiz.

  • packages é uma lista de todos os pacotes de importação do Python que devem ser incluídos no pacote de distribuição. Em vez de listar cada pacote manualmente, podemos usar find_packages() para descobrir automaticamente todos os pacotes e subpacotes em package_dir. Neste caso, a lista de pacotes será example_package visto que é o único pacote presente.

  • python_requires fornece as versões do Python suportadas pelo seu projeto. Instaladores como pip olharão para as versões anteriores dos pacotes até encontrar um que tenha uma versão correspondente do Python.

Existem muitos mais do que os mencionados aqui. Veja Empacotando e distribuindo projetos para mais detalhes.

Aviso

Você pode ver alguns projetos existentes ou outros tutoriais de empacotamento Python que importam sua função setup de distutils.core em vez de setuptools. Esta é uma abordagem legada que os instaladores oferece suporte a compatibilidade com versões anteriores 1, mas usar a API legada do distutils diretamente em novos projetos é fortemente desencorajado, uma vez que distutils foi descontinuado de acordo com :pep:`632 `e será removido da biblioteca padrão em Python 3.12.

Criando README.md

Abra README.md e insira o seguinte conteúdo. Você pode personalizar isso se desejar.

# Example Package

This is a simple example package. You can use
[Github-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
to write your content.

Porque nossa configuração carrega README.md para fornecer um long_description, README.md deve ser incluído junto com seu código quando você gerar uma distribuição fonte. Versões mais recentes de setuptools farão isso automaticamente.

Criando um LICENSE

É importante que cada pacote enviado para o Índice de Pacotes do Python inclua uma licença. Isso informa aos usuários que instalam seu pacote os termos sob os quais eles podem usá-lo. Para obter ajuda na escolha de uma licença, consulte https://choosealicense.com/. Depois de escolher uma licença, abra LICENSE e digite o texto da licença. Por exemplo, se você escolheu a licença MIT:

Copyright (c) 2018 The Python Packaging Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Incluindo outros arquivos

Os arquivos listados acima serão incluídos automaticamente em sua distribuição fonte. Se você deseja controlar o que entra explicitamente, consulte Incluindo arquivos em distribuições de código-fonte com MANIFEST.in.

A distribuição construída final terá os arquivos Python nos pacotes descobertos ou listados Python. Se você deseja controlar o que acontece aqui, como adicionar arquivos de dados, consulte Incluindo Arquivos de Dados da documentação do setuptools.

Gerando arquivos de distribuição

O próximo passo é gerar pacotes de distribuição para o pacote. Estes são arquivos que são enviados para o Índice de Pacotes do Python e podem ser instalados pelo pip.

Certifique-se de que você tenha a versão mais recente da construção do PyPA instalada:

python3 -m pip install --upgrade build
py -m pip install --upgrade build

Dica

Se você tiver problemas ao instalá-los, veja o tutorial Instalando pacotes.

Agora, execute este comando a partir do mesmo diretório no qual pyproject.toml está localizado:

python3 -m build
py -m build

Este comando deve produzir muito texto e, uma vez concluído, deve gerar dois arquivos no diretório dist:

dist/
  example-package-YOUR-USERNAME-HERE-0.0.1-py3-none-any.whl
  example-package-YOUR-USERNAME-HERE-0.0.1.tar.gz

O arquivo tar.gz é um arquivo fonte enquanto o arquivo .whl é uma distribuição construída. As versões pip mais recentes instalam preferencialmente distribuições construídas, mas irão recorrer aos arquivos fonte se necessário. Você deve sempre enviar um arquivo fonte e fornecer arquivos construídos para as plataformas com as quais seu projeto é compatível. Neste caso, nosso pacote de exemplo é compatível com Python em qualquer plataforma, portanto, apenas uma distribuição construída é necessária.

Enviando os arquivos de distribuição

Finalmente, é hora de enviar seu pacote para o Índice de Pacotes do Python!

A primeira coisa que você precisa fazer é registrar uma conta no Test PyPI, que é uma instância separada do índice do pacote destinada a teste e experimentação. É ótimo para coisas como este tutorial, em que não queremos necessariamente enviar para o índice real. Para registrar uma conta, vá em https://test.pypi.org/account/register/ e conclua as etapas nessa página. Você também precisará verificar seu endereço de e-mail antes de enviar qualquer pacote. Para mais detalhes, veja Usando TestPyPI.

Para enviar seu projeto com segurança, você precisará de um token de API do PyPI. Crie um em https://test.pypi.org/manage/account/#api-tokens, definindo o “Escopo” como “Toda a conta”. Não feche a página antes de copiar e salvar o token – você não verá o token novamente.

Agora que você está registrado, você pode usar twine para enviar os pacotes de distribuição. Você precisará instalar o Twine:

python3 -m pip install --upgrade twine
py -m pip install --upgrade twine

Uma vez instalado, execute o Twine para enviar de todos os arquivos em dist:

python3 -m twine upload --repository testpypi dist/*
py -m twine upload --repository testpypi dist/*

Você será solicitado a fornecer um nome de usuário e uma senha. Para o nome de usuário, use __token__. Para a senha, use o valor do token, incluindo o prefixo pypi-.

Depois que o comando for concluído, você deverá ver uma saída semelhante a esta:

Uploading distributions to https://test.pypi.org/legacy/
Enter your username: [your username]
Enter your password:
Uploading example-package-YOUR-USERNAME-HERE-0.0.1-py3-none-any.whl
100%|█████████████████████| 4.65k/4.65k [00:01<00:00, 2.88kB/s]
Uploading example-package-YOUR-USERNAME-HERE-0.0.1.tar.gz
100%|█████████████████████| 4.25k/4.25k [00:01<00:00, 3.05kB/s]

Depois de enviado, seu pacote pode ser visto no Test PyPI, por exemplo, em https://test.pypi.org/project/example-package-YOUR-USERNAME-HERE

Instalando seu pacote recém-enviado

Você pode usar o pip para instalar seu pacote e verificar se ele funciona. Crie um ambiente virtual e instale seu pacote a partir do Test PyPI:

python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps example-package-YOUR-USERNAME-HERE
py -m pip install --index-url https://test.pypi.org/simple/ --no-deps example-package-YOUR-USERNAME-HERE

Certifique-se de especificar seu nome de usuário no nome do pacote!

pip deve instalar o pacote de Test PyPI e a saída deve ser semelhante a esta:

Collecting example-package-YOUR-USERNAME-HERE
  Downloading https://test-files.pythonhosted.org/packages/.../example-package-YOUR-USERNAME-HERE-0.0.1-py3-none-any.whl
Installing collected packages: example-package-YOUR-USERNAME-HERE
Successfully installed example-package-YOUR-USERNAME-HERE-0.0.1

Nota

Este exemplo usa o sinalizador --index-url para especificar Test PyPI em vez do PyPI real. Além disso, ele especifica --no-deps. Como o Test PyPI não tem os mesmos pacotes que o PyPI real, é possível que a tentativa de instalação de dependências falhe ou instale algo inesperado. Embora nosso pacote de exemplo não tenha nenhuma dependência, é uma boa prática evitar instalar dependências ao usar o Test PyPI.

Você pode testar se ele foi instalado corretamente importando o pacote. Certifique-se de que ainda está em seu ambiente virtual e execute o Python:

python3
py

e importe o pacote:

>>> from example_package import example
>>> example.add_one(2)
3

Note que o pacote de importação é example_package independente de qual name você deu ao seu pacote de distribuição no setup.cfg ou setup.py (neste caso, example-package-YOUR-USERNAME-HERE).

Próximos passos

Parabéns, você empacotou e distribuiu um projeto Python! ✨ 🍰 ✨

Lembre-se de que este tutorial mostrou como enviar o seu pacote para Test PyPI, que não é um armazenamento permanente. O sistema de teste ocasionalmente exclui pacotes e contas. É melhor usar Test PyPI para testes e experimentos como este tutorial.

Quando estiver pronto para carregar um pacote real para o Índice de Pacotes do Python, você pode fazer quase o mesmo que fez neste tutorial, mas com estas diferenças importantes:

  • Escolha um nome único e memorável para o seu pacote. Você não precisa acrescentar seu nome de usuário como fez no tutorial.

  • Registre uma conta em https://pypi.org – observe que esses são dois servidores separados e os detalhes de login do servidor de teste não são compartilhados com o servidor principal.

  • Use twine upload dist/* para enviar seu pacote e insira suas credenciais para a conta que você registrou no PyPI real. Agora que você está enviando o pacote em produção, você não precisa especificar --repository; o pacote será enviado para https://pypi.org/ por padrão.

  • Instale seu pacote a partir do PyPI real usando python3 -m pip install [seu-pacote].

Neste ponto, se você quiser ler mais sobre o empacotamento de bibliotecas Python, aqui estão algumas coisas que você pode fazer:


1

Alguns ambientes Python legados podem não ter o setuptools pré-instalado, e os operadores desses ambientes ainda podem exigir que os usuários instalem pacotes executando comandos setup.py install, em vez de fornecer um instalador como o pip que instala automaticamente as dependências de compilação necessárias. Esses ambientes não serão capazes de usar muitos pacotes publicados até que o ambiente seja atualizado para fornecer um cliente de instalação de pacote Python atualizado (por exemplo, executando python -m ensurepip).