Escrevendo seu pyproject.toml#

pyproject.toml é um arquivo de configuração usado por ferramentas de empacotamento, bem como outras ferramentas como linters, verificadores de tipo, etc. Existem três tabelas TOML possíveis neste arquivo.

  • A tabela [build-system] é altamente recomendada. Ela permite que você declare qual backend de construção você usa e quais outras dependências são necessárias para construir seu projeto.

  • A tabela [project] é o formato que a maioria dos backends de construção usa para especificar os metadados básicos do seu projeto, como as dependências, seu nome, etc.

  • A tabela [tool] possui subtabelas específicas da ferramenta, por exemplo, [tool.hatch], [tool.black], [tool.mypy]. Tocamos aqui apenas nesta tabela porque seu conteúdo é definido por cada ferramenta. Consulte a documentação da ferramenta específica para saber o que ela pode conter.

Nota

Há uma diferença significativa entre as tabelas [build-system] e [project]. O primeiro deve estar sempre presente, independentemente de qual backend de construção você usa (já que define a ferramenta que você usa). Este último é entendido pela maioria dos backends de construção mas alguns backends de construção usam um formato diferente.

No momento em que este artigo foi escrito (novembro de 2023), Poetry era um backend de construção notável que não usava a tabela [project] (em vez disso, ele usava a tabela [tool.poetry]).

Além disso, o backend de construção do setuptools oferece suporte tanto à tabela [project], quanto o formato antigo em setup.cfg ou setup.py. Para novos projetos, é recomendado usar a tabela [project], e manter setup.py apenas se alguma configuração programática for necessária (como construir extensões C), mas os formatos setup.cfg e setup.py ainda são válidos. Veja setup.py foi descontinuado?.

Declarando o backend de construção#

A tabela [build-system] contém uma chave build-backend, que especifica o backend de construção a ser usado. Ele também contém uma chave requires, que é uma lista de dependências necessárias para construir o projeto – normalmente é apenas o pacote backend de construção, mas também pode conter dependências adicionais. Você também pode restringir as versões, por exemplo, requires = ["setuptools >= 61.0"].

Normalmente, você apenas copiará o que a documentação do seu backend de construção sugere (após escolher seu backend de construção). Aqui estão os valores para alguns backends de construção comuns:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[build-system]
requires = ["flit_core >= 3.4"]
build-backend = "flit_core.buildapi"
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

Metadados estáticos vs dinâmicos#

O resto deste guia é dedicado à tabela [project].

Na maioria das vezes, você escreverá diretamente o valor de um campo [project]. Por exemplo: requires-python = ">= 3.8", ou version = "1.0".

No entanto, em alguns casos, é útil permitir que o backend de construção calcule os metadados para você. Por exemplo: muitos backend sde construção podem ler a versão de um atributo __version__ em seu código, uma tag Git ou similar. Nesses casos, você deve marcar o campo como dinâmico usando, por exemplo,

[project]
dynamic = ["version"]

Quando um campo é dinâmico, é da responsabilidade do backend de construção preenchê-lo. Consulte a documentação do seu backend de construção para saber como ele faz.

Informações básicas#

name#

Coloque o nome do seu projeto em PyPI. Este campo é necessário e é o único campo que não pode ser marcado como dinâmico.

[project]
name = "spam-eggs"

O nome do projeto deve consistir em letras ASCII, dígitos, sublinhado “_”, hífenes “-” e pontos “.”. Não deve começar ou terminar com um sublinhado, hífen ou ponto.

A comparação de nomes de projetos não diferencia maiúsculas de minúsculas e trata sequências arbitrariamente longas de sublinhados, hífenes e/ou pontos como iguais. Por exemplo, se você registrar um projeto chamado cool-stuff, os usuários poderão baixá-lo ou declarar uma dependência dele usando qualquer uma das seguintes formas de escrever: Cool-Stuff, cool.stuff, COOL_STUFF, CoOl__-.-__sTuFF.

version#

Coloque a versão do seu projeto.

[project]
version = "2020.0.0"

Alguns especificadores de versão mais complicados como 2020.0.0a1 (para uma versão alfa) são possíveis; veja a especificação para detalhes completos.

Este campo é necessário, embora muitas vezes seja marcado como dinâmico usando

[project]
dynamic = ["version"]

Isso permite usar casos como preencher a versão de um atributo __version__ ou uma tag Git. Consulte Mantendo uma única fonte da versão do pacote para obter mais detalhes.

Dependências e requisitos#

dependencies/optional-dependencies#

Se o seu projeto tiver dependências, liste-as assim:

[project]
dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'",
]

Veja Especificadores de dependências para a sintaxe completa que você pode usar para restringir versões.

Você pode querer fazer algumas de suas dependências opcionais, se forem necessárias apenas para um recurso específico do seu pacote. Nesse caso, coloque-os em optional-dependencies.

[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
  "rich",
  "click",
]

Cada uma das chaves define um “extra de empacotamento”. No exemplo acima, pode-se usar, por exemplo, pip install seu-projeto-de-nome[gui] para instalar seu projeto com suporte GUI, adicionando a dependência PyQt5.

requires-python#

Isso permite que você declare a versão mínima de Python que você suporta [1].

[project]
requires-python = ">= 3.8"

Criando scripts executáveis#

Para instalar um comando como parte do seu pacote, declare-o na tabela [project.scripts].

[project.scripts]
spam-cli = "spam:main_cli"

Neste exemplo, depois de instalar seu projeto, um comando spam-cli estará disponível. Executar este comando fará o equivalente a from spam import main_cli; main_cli().

No Windows, scripts empacotados desta forma precisam de um terminal, então se você lançá-los de dentro de uma aplicação gráfica, eles vão fazer um pop up de terminal. Para evitar que isso aconteça, use o [project.gui-scripts] tabela em vez de [project.scripts].

[project.gui-scripts]
spam-gui = "spam:main_gui"

Nesse caso, a inicialização do seu script a partir da linha de comando devolve o controle imediatamente, deixando o script a ser executado em segundo plano.

A diferença entre [project.scripts] e [project.gui-scripts] é apenas relevante no Windows.

Sobre o seu projeto#

authors/maintainers#

Ambos os campos contêm listas de pessoas identificadas por um nome e/ou um endereço de e-mail.

[project]
authors = [
  {name = "Pradyun Gedam", email = "pradyun@example.com"},
  {name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
  {name = "Another person"},
  {email = "different.person@example.com"},
]
maintainers = [
  {name = "Brett Cannon", email = "brett@example.com"}
]

description#

Esta deve ser uma descrição de uma linha do seu projeto, para mostrar como o “título” da sua página do projeto no PyPI (exemplo), e outros lugares como listas de resultados de pesquisa (exemplo).

[project]
description = "Lovely Spam! Wonderful Spam!"

readme#

Esta é uma descrição mais longa do seu projeto, para exibir em sua página do projeto em PyPI. Tipicamente, seu projeto terá um arquivo README.md ou README.rst e você acabou de colocar seu nome de arquivo aqui.

[project]
readme = "README.md"

O formato do README é detectado automaticamente a partir da extensão:

Você também pode especificar o formato explicitamente assim:

[project]
readme = {file = "README.txt", content-type = "text/markdown"}
# or
readme = {file = "README.txt", content-type = "text/x-rst"}

license#

Isto pode tomar duas formas. Você pode colocar sua licença em um arquivo, tipicamente LICENSE ou LICENSE.txt, e vincular esse arquivo aqui:

[project]
license = {file = "LICENSE"}

ou você pode escrever o nome da licença:

[project]
license = {text = "MIT License"}

If you are using a standard, well-known license, it is not necessary to use this field. Instead, you should one of the classifiers starting with License ::. (As a general rule, it is a good idea to use a standard, well-known license, both to avoid confusion and because some organizations avoid software whose license is unapproved.)

keywords#

Isso ajudará a caixa de pesquisa do PyPI a sugerir o seu projeto quando as pessoas procuram essas palavras-chave.

[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]

classifiers#

Uma lista de classificadores do PyPI que se aplicam ao seu projeto. Verifique a lista completa de possibilidades.

classifiers = [
  # How mature is this project? Common values are
  #   3 - Alpha
  #   4 - Beta
  #   5 - Production/Stable
  "Development Status :: 4 - Beta",

  # Indicate who your project is intended for
  "Intended Audience :: Developers",
  "Topic :: Software Development :: Build Tools",

  # Pick your license as you wish (see also "license" above)
  "License :: OSI Approved :: MIT License",

  # Specify the Python versions you support here.
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.6",
  "Programming Language :: Python :: 3.7",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
]

Although the list of classifiers is often used to declare what Python versions a project supports, this information is only used for searching and browsing projects on PyPI, not for installing projects. To actually restrict what Python versions a project can be installed on, use the requires-python argument.

To prevent a package from being uploaded to PyPI, use the special Private :: Do Not Upload classifier. PyPI will always reject packages with classifiers beginning with Private ::.

urls#

Uma lista de URLs associadas ao seu projeto, exibida na barra lateral esquerda da página do projeto PyPI.

[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
Issues = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"

Note que se a chave contém espaços, ela precisa ser citada, por exemplo, Website = "https://example.com", mas "Official Website" = "https://example.com".

Plugins avançados#

Alguns pacotes podem ser estendidos através de plugins. Exemplos incluem Pytest e Pygments. Para criar tal plugin, você precisa declará-lo em uma subtabela de [project.entry-points] assim:

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

Veja o guia Plugin para mais informações.

Um exemplo completo#

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "spam-eggs"
version = "2020.0.0"
dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'",
]
requires-python = ">=3.8"
authors = [
  {name = "Pradyun Gedam", email = "pradyun@example.com"},
  {name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
  {name = "Another person"},
  {email = "different.person@example.com"},
]
maintainers = [
  {name = "Brett Cannon", email = "brett@example.com"}
]
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
license = {file = "LICENSE.txt"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python"
]

[project.optional-dependencies]
gui = ["PyQt5"]
cli = [
  "rich",
  "click",
]

[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
"Bug Tracker" = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"

[project.scripts]
spam-cli = "spam:main_cli"

[project.gui-scripts]
spam-gui = "spam:main_gui"

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"