Versionamento¶
Esta discussão cobre todos os aspectos do versionamento de pacotes Python.
Números de versão validos¶
Diferentes projetos Python podem usar diferentes esquemas de versionamento com base nas necessidades daquele projeto específico, mas para serem compatíveis com ferramentas como pip, todos eles são obrigados a cumprir um formato flexível para identificadores de versão, para o qual a referência oficial é a especificação dos especificadores de versão. Aqui estão alguns exemplos de números de versão [1]:
Uma versão simples (versão final):
1.2.0
Uma versão de desenvolvimento:
1.2.0.dev1
Uma versão alfa:
1.2.0a1
Uma versão beta:
1.2.0b1
Um candidato a lançamento:
1.2.0rc1
Um pós-lançamento:
1.2.0.post1
Um pós-lançamento de uma versão alfa (possível, mas desencorajado):
1.2.0a1.post1
Uma versão simples com apenas dois componentes:
23.12
Uma versão simples com apenas um componente:
42
Uma versão com uma época:
1!1.0
Os projetos podem usar um ciclo de pré-lançamentos para dar suporte aos testes de seus usuários antes do lançamento final. Em ordem, as etapas são: versões alfa, versões beta, candidatos a lançamento, versão final. Pip e outros instaladores modernos de pacotes Python ignoram pré-lançamentos por padrão ao decidir quais versões de dependências instalar, a menos que solicitado explicitamente (por exemplo, com `pip install pkg==1.1a3
ou pip install --pre pkg
).
O objetivo dos lançamentos de desenvolvimento é oferecer suporte aos lançamentos feitos no início de um ciclo de desenvolvimento, por exemplo, uma construção noturna ou uma construção a partir do código-fonte mais recente em uma distribuição Linux.
Os pós-lançamentos são usados para corrigir pequenos erros em uma versão final que não afetam o software distribuído, como corrigir um erro nas notas de versão. Eles não devem ser usados para correção de bugs; isso deve ser feito com uma nova versão final (por exemplo, incrementando o terceiro componente ao usar o versionamento semântico).
Por fim, as épocas, um recurso raramente usado, servem para corrigir a ordem de classificação ao alterar o esquema de versionamento. Por exemplo, se um projeto estiver usando versionamento de calendário, com versões como 23.12, e mudar para versionamento semântico, com versões como 1.0, a comparação entre 1.0 e 23.12 irá para o lado errado. Para corrigir isso, os novos números de versão devem ter uma época explícita, como em “1!1.0”, para serem tratados como mais recentes que os números de versão antigos.
Versionamento semântico vs. versionamento de calendário¶
Um esquema de versionamento é uma forma formalizada de interpretar os segmentos de um número de versão e de decidir qual deve ser o próximo número de versão para um novo lançamento de um pacote. Dois esquemas de versionamento são comumente usados para pacotes Python, versionamento semântico e versionamento de calendário.
Cuidado
A decisão de qual número de versão escolher cabe ao mantenedor do projeto. Isso efetivamente significa que os incrementos de versão refletem a visão do mantenedor. Essa visão pode diferir da percepção dos usuários finais sobre o que o referido esquema de versionamento formalizado lhes promete.
There are known exceptions for selecting the next version number. The maintainers may consciously choose to break the assumption that the last version segment only contains backwards-compatible changes. One such case is when a security vulnerability needs to be addressed. Security releases often come in patch versions but contain breaking changes inevitably.
Versionamento semântico¶
A ideia do versionamento semântico (ou SemVer) é usar números de versão de 3 partes, principal.menor.correção, onde o autor do projeto incrementa:
principal quando são feitas alterações incompatíveis à API,
menor quando é adicionada funcionalidade de maneira compatível com versões anteriores, e
correção, quando são feitas correções de bugs compatíveis com versões anteriores.
A maioria dos projetos Python usa um esquema que se assemelha ao versionamento semântico. No entanto, a maioria dos projetos, especialmente os maiores, não aderem estritamente ao versionamento semântico, uma vez que muitas alterações são tecnicamente prejudiciais, mas afetam apenas uma pequena fração dos usuários. Tais projetos tendem a aumentar o número maior quando a incompatibilidade é alta, ou para sinalizar uma mudança no projeto, ao invés de qualquer pequena incompatibilidade [2]. Por outro lado, um aumento no número da versão principal às vezes é usado para sinalizar novos recursos significativos, mas compatíveis com versões anteriores.
Para aqueles projetos que usam controle de versão semântico estrito, esta abordagem permite que os usuários façam uso de especificadores de versão de lançamento compatíveis, com o operador ~=
. Por exemplo, name ~= X.Y
é aproximadamente equivalente a name >= X.Y, == X.*
, ou seja, requer pelo menos a versão X.Y, e permite qualquer versão posterior com Y maior, desde que X é o mesmo. Da mesma forma, name ~= X.Y.Z
é aproximadamente equivalente a name >= X.Y.Z, == X.Y.*
, ou seja, requer pelo menos X.Y.Z e permite uma versão posterior com o mesmo X e Y, mas Z superior.
Projetos Python que adotam versões semânticas devem obedecer às cláusulas 1-8 da especificação de Versionamento Semântico 2.0.0.
O popular gerador de documentação Sphinx é um exemplo de projeto que usa controle de versionamento semântico estrito (Política de versionamento do Sphinx). O famoso pacote de computação científica NumPy usa explicitamente versionamento semântico “folgado”, onde lançamentos incrementando a versão menor podem conter alterações de API incompatíveis com versões anteriores (Política de versionamento do NumPy).
Versionamento de calendário¶
Semantic versioning is not a suitable choice for all projects, such as those with a regular time-based release cadence and a deprecation process that provides warnings for a number of releases prior to removal of a feature.
Uma vantagem principal do versionamento baseado em data, ou versionamento de calendário (CalVer), é que é simples dizer quantos anos o conjunto de recursos básicos de uma determinada versão recebe apenas o número da versão.
Os números de versão do calendário normalmente assumem o formato ano.mês (por exemplo, 23.12 para dezembro de 2023).
Pip, o instalador padrão do pacote Python, usa versionamento de calendário.
Outros esquemas¶
O versionamento serial refere-se ao esquema de versionamento mais simples possível, que consiste em um único número incrementado a cada versão. Embora o versionamento serial seja muito fácil de gerenciar como desenvolvedor, é o mais difícil de rastrear como usuário final, pois os números de versão serial transmitem pouca ou nenhuma informação sobre compatibilidade com versões anteriores da API.
Combinations of the above schemes are possible. For example, a project may combine date-based versioning with serial versioning to create a year.serial numbering scheme that readily conveys the approximate age of a release, but doesn’t otherwise commit to a particular release cadence within the year.
Identificadores de versão local¶
Identificadores de versão pública são projetados para suportar distribuição via PyPI. As ferramentas de empacotamento do Python também oferecem suporte à noção de um identificador de versão local, que pode ser usado para identificar compilações de desenvolvimento local não destinadas à publicação, ou variantes modificadas de uma versão mantida por um redistribuidor.
Um identificador de versão local assume a forma de um identificador de versão pública, seguido por “+” e um rótulo de versão local. Por exemplo, um pacote com patches específicos do Fedora aplicados poderia ter a versão “1.2.1+fedora.4”. Outro exemplo são as versões calculadas pelo setuptools-scm, um plugin do setuptools que lê a versão dos dados do Git. Em um repositório Git com alguns commits desde a versão mais recente, setuptools-scm gera uma versão como “0.5.dev1+gd00980f”, ou se o repositório tiver alterações não rastreadas, como “0.5.dev1+gd00980f.d20231217”.
Accessing version information at runtime¶
Version information for all distribution packages
that are locally available in the current environment can be obtained at runtime
using the standard library’s importlib.metadata.version()
function:
>>> importlib.metadata.version("cryptography")
'41.0.7'
Many projects also choose to version their top level
import packages by providing a package level
__version__
attribute:
>>> import cryptography
>>> cryptography.__version__
'41.0.7'
This technique can be particularly valuable for CLI applications which want
to ensure that version query invocations (such as pip -V
) run as quickly
as possible.
Package publishers wishing to ensure their reported distribution package and import package versions are consistent with each other can review the Mantendo uma única fonte da versão do pacote discussion for potential approaches to doing so.
As import packages and modules are not required to publish runtime
version information in this way (see the withdrawn proposal in
PEP 396), the __version__
attribute should either only be
queried with interfaces that are known to provide it (such as a project
querying its own version or the version of one of its direct dependencies),
or else the querying code should be designed to handle the case where the
attribute is missing [3].
Some projects may need to publish version information for external APIs
that aren’t the version of the module itself. Such projects should
define their own project-specific ways of obtaining the relevant information
at runtime. For example, the standard library’s ssl
module offers
multiple ways to access the underlying OpenSSL library version:
>>> ssl.OPENSSL_VERSION
'OpenSSL 3.2.2 4 Jun 2024'
>>> ssl.OPENSSL_VERSION_INFO
(3, 2, 0, 2, 0)
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x30200020'