Maneira padrão de incorporar versão no pacote python?

265

Existe uma maneira padrão de associar uma string de versão a um pacote python de forma que eu possa fazer o seguinte?

import foo
print foo.version

Eu imagino que há alguma maneira de recuperar esses dados sem nenhuma codificação extra, uma vez que as seqüências principais / secundárias já estão especificadas setup.py. Solução alternativa que eu encontrei era para ter import __version__no meu foo/__init__.pye depois ter __version__.pygerado por setup.py.

Dimitri Tcaciuc
fonte
7
FYI, há uma visão muito boa em: packaging.python.org/en/latest/...
ionelmc
1
A versão de um pacote instalado pode ser recuperada dos metadados com o setuptools , portanto, em muitos casos, colocar apenas a versão setup.pyé suficiente. Veja esta pergunta .
saaj 21/07
2
Para sua informação, existem basicamente cinco padrões comuns para manter a única fonte de verdade (tanto na configuração quanto no tempo de execução) do número da versão.
KF Lin
A documentação do @ionelmc Python lista 7 opções diferentes para fonte única . Isso não contradiz o conceito de uma " única fonte de verdade "?
Stevoisiak
@StevenVascellaro não sabe o que está perguntando. Existem muitas maneiras listadas lá porque o guia de embalagem não deseja ser opinativo.
ionelmc

Respostas:

136

Não é uma resposta direta à sua pergunta, mas considere nomeá-la __version__, não version.

Isso é quase um quase-padrão. Muitos módulos da biblioteca padrão usam __version__, e isso também é usado em muitos módulos de terceiros, por isso é quase-padrão.

Geralmente, __version__é uma string, mas às vezes também é uma flutuação ou tupla.

Edit: como mencionado por S.Lott (obrigado!), O PEP 8 diz explicitamente:

Nomes de Dunder no nível do módulo

Nível "dunders" do módulo (ou seja, com dois nomes líder e dois sublinhados no final), tais como __all__, __author__, __version__, etc, devem ser colocadas após o Docstring módulo, mas antes de quaisquer instruções de importação, com excepção de __future__importação.

Você também deve garantir que o número da versão esteja em conformidade com o formato descrito no PEP 440 ( PEP 386, uma versão anterior desta norma).

oefe
fonte
9
Deve ser uma string e ter um version_info para a versão da tupla.
James Antill 21/01
James: Por que __version_info__especificamente? (Que "inventa" a sua própria palavra com sublinhado duplo.) [Quando James comentou, os sublinhados não fizeram nada nos comentários, agora eles indicam ênfase, então James __version_info__também escreveu . --- ed.]
Você pode ver algo sobre o que a versão deve dizer em packages.python.org/distribute/… Essa página é sobre distribuição, mas o significado do número da versão está se tornando um padrão de fato.
precisa saber é o seguinte
2
Certo. Parece que esses PEPs se contradizem. Bem, o PEP 8 diz "if" e "crud", por isso realmente não endossa a expansão da palavra-chave VCS. Além disso, se você mudar para um VCS diferente, perderá as informações da revisão. Portanto, eu sugeriria o uso de uma informação de versão compatível com PEP 386/440 incorporada em um único arquivo de origem, pelo menos para projetos maiores.
oefe
2
Onde você colocaria essa versão . Considerando que esta é a versão aceita, eu adoraria ver essas informações adicionais aqui.
darkgaze
120

Eu uso um único _version.pyarquivo como o "local uma vez canônico" para armazenar as informações da versão:

  1. Ele fornece um __version__atributo.

  2. Ele fornece a versão padrão dos metadados. Portanto, ele será detectado por pkg_resourcesou outras ferramentas que analisam os metadados do pacote (EGG-INFO e / ou PKG-INFO, PEP 0345).

  3. Ele não importa o seu pacote (ou qualquer outra coisa) ao criar o seu pacote, o que pode causar problemas em algumas situações. (Veja os comentários abaixo sobre quais problemas isso pode causar.)

  4. Há apenas um local em que o número da versão é anotado; portanto, existe apenas um local para alterá-lo quando o número da versão é alterado e há menos chances de versões inconsistentes.

Eis como funciona: o "único local canônico" para armazenar o número da versão é um arquivo .py, chamado "_version.py", que está no seu pacote Python, por exemplo em myniftyapp/_version.py. Este arquivo é um módulo Python, mas o seu setup.py não o importa! (Isso anularia o recurso 3.) Em vez disso, o seu setup.py sabe que o conteúdo deste arquivo é muito simples, algo como:

__version__ = "3.6.5"

E assim o seu setup.py abre o arquivo e o analisa, com código como:

import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
    verstr = mo.group(1)
else:
    raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))

Em seguida, seu setup.py passa essa string como o valor do argumento "version" para setup(), satisfazendo o recurso 2.

Para satisfazer o recurso 1, você pode fazer com que seu pacote (no tempo de execução, não no tempo de configuração!) Importe o arquivo _version da myniftyapp/__init__.pyseguinte maneira:

from _version import __version__

Aqui está um exemplo dessa técnica que uso há anos.

O código nesse exemplo é um pouco mais complicado, mas o exemplo simplificado que escrevi neste comentário deve ser uma implementação completa.

Aqui está o código de exemplo para importar a versão .

Se você encontrar algo errado com essa abordagem, entre em contato.

Zooko
fonte
8
Você poderia descrever os problemas que motivam o nº 3? A Glyph disse ter algo a ver com "o setuptools gosta de fingir que seu código não está em nenhum lugar do sistema quando o setup.py é executado", mas os detalhes ajudariam a convencer a mim e a outras pessoas.
Ivan Kozik 23/09
2
@Iva Agora, em que ordem a ferramenta deve fazer isso? Ele não pode (no sistema setuptools / pip / virtualenv de hoje) nem saber quais são os deps até avaliar o seu setup.py. Além disso, se tentasse fazer profundidade total primeiro e executar todos os deps antes de executar este, ele ficaria preso se houvesse deps circulares. Mas se ele tentar compilar este pacote antes de instalar as dependências, se você importar o seu pacote setup.py, ele não poderá necessariamente importar seus deps ou as versões corretas dos deps.
Zooko 30/10/11
3
Você poderia escrever o arquivo "version.py" em "setup.py" em vez de analisá-lo? Isso parece mais simples.
22612 Jonathan Hartley
3
Jonathan Hartley: Concordo que seria um pouco mais simples para o seu "setup.py" escrever o arquivo "version.py" em vez de analisá-lo, mas abriria uma janela de inconsistência quando você editasse o seu setup.py para ter a nova versão, mas ainda não executou o setup.py para atualizar o arquivo version.py. Outro motivo para que a versão canônica esteja em um pequeno arquivo separado é que facilita a gravação de arquivos de versão por outras ferramentas, como as que lêem seu estado de controle de revisão.
Zooko
3
Uma abordagem semelhante é execfile("myniftyapp/_version.py")de dentro do arquivo setup.py, em vez de tentar analisar o código da versão manualmente. Sugerido em stackoverflow.com/a/2073599/647002 - a discussão também pode ser útil.
medmunds 03/03
97

Reescrito 2017-05

Após mais de dez anos escrevendo código Python e gerenciando vários pacotes, cheguei à conclusão de que o DIY talvez não seja a melhor abordagem.

Comecei a usar o pbrpacote para lidar com o controle de versão em meus pacotes. Se você estiver usando o git como seu SCM, isso caberá no seu fluxo de trabalho como mágica, economizando suas semanas de trabalho (você ficará surpreso com a complexidade do problema).

Atualmente, o pbr está classificado como o 11º pacote python mais usado e não atingiu nenhum truque sujo: era apenas um: corrigir um problema de empacotamento comum de uma maneira muito simples.

pbr pode fazer mais da carga de manutenção de pacote, não se limita à versão, mas não o força a adotar todos os seus benefícios.

Então, para lhe dar uma idéia de como parece adotar o pbr em um commit, dê uma olhada na embalagem do pbr

Provavelmente, você observaria que a versão não está armazenada no repositório. O PBR o detecta nas ramificações e tags do Git.

Não é necessário se preocupar com o que acontece quando você não possui um repositório git porque o pbr "compila" e armazena em cache a versão quando você empacota ou instala os aplicativos, para que não haja dependência de tempo de execução no git.

Solução antiga

Aqui está a melhor solução que eu já vi até agora e também explica o porquê:

Dentro yourpackage/version.py:

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'

Dentro yourpackage/__init__.py:

from .version import __version__

Dentro setup.py:

exec(open('yourpackage/version.py').read())
setup(
    ...
    version=__version__,
    ...

Se você conhece outra abordagem que parece melhor, me avise.

sorin
fonte
12
Err, não. execfile () não existe no Python 3, por isso é melhor usar exec (open (). read ()).
Christophe Vu-Brugier 19/10/2013
4
por que não from .version import __version__no setup.py também?
Aprillion
4
@Aprillion Como o pacote não é carregado quando setup.pyestá em execução - experimentá-lo, você obterá um erro (ou, pelo menos, eu fiz :-))
darthbith
3
O link para pbr resulta em um gateway incorreto.
MERose
4
pbr , sem dúvida, é uma ótima ferramenta, mas você não conseguiu resolver a questão. Como você pode acessar a versão atual ou o pacote instalado via bpr .
nad2000
29

De acordo com o PEP 396 diferido (números de versão do módulo) , existe uma maneira proposta de fazer isso. Descreve, com justificativa, um padrão (reconhecidamente opcional) para os módulos seguirem. Aqui está um trecho:

3) Quando um módulo (ou pacote) inclui um número de versão, a versão deve estar disponível no __version__atributo.

4) Para os módulos que vivem dentro de um pacote de namespace, o módulo DEVE incluir o __version__atributo. O próprio pacote de namespace NÃO DEVE incluir seu próprio __version__atributo.

5) O __version__valor do atributo DEVE ser uma string.

Oddthinking
fonte
13
Esse PEP não é aceito / padronizado, mas diferido (devido à falta de interesse). Portanto, é um pouco enganador afirmar que " existe uma maneira padrão " especificada por ele.
tecelão
@ tecelão: Oh meu! Eu aprendi algo novo. Eu não sabia que isso era algo que eu precisava verificar.
Oddthinking
4
Editado para observar que não é um padrão. Agora, sinto-me envergonhado, porque levantei solicitações de recursos em projetos pedindo-lhes que seguissem esse "padrão".
Oddthinking
1
Talvez você deve assumir o trabalho de normalização no que PEP, desde que você parece :) interessados
tecelão
Isso funcionaria para a versão de um módulo individual, mas não tenho certeza se isso se aplicaria à versão de um projeto completo.
Stevoisiak
21

Embora isso provavelmente seja tarde demais, há uma alternativa um pouco mais simples para a resposta anterior:

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(E seria bastante simples converter partes de números de versão com incremento automático em uma string usando str().)

Obviamente, pelo que vi, as pessoas tendem a usar algo como a versão mencionada anteriormente ao usá-la __version_info__e, como tal, armazenam-na como uma tupla de ints; no entanto, não entendo bem o motivo, pois duvido que existam situações nas quais você executaria operações matemáticas, como adição e subtração em partes de números de versão para qualquer finalidade, além de curiosidade ou auto-incremento (e mesmo assim, int()e str()pode ser usado com bastante facilidade). (Por outro lado, existe a possibilidade do código de outra pessoa esperar uma tupla numérica em vez de uma tupla de string e, portanto, falhar.)

Essa é, é claro, minha própria visão, e eu gostaria com prazer da opinião de outras pessoas sobre o uso de uma tupla numérica.


Como shezi me lembrou, comparações (lexicais) de cadeias de números não têm necessariamente o mesmo resultado que comparações numéricas diretas; zeros à esquerda seriam necessários para prover isso. Portanto, no final, o armazenamento __version_info__(ou o que seria chamado) como uma tupla de valores inteiros permitiria comparações de versões mais eficientes.

JAB
fonte
12
legal (+1), mas você não prefere números em vez de strings? por exemplo,__version_info__ = (1,2,3)
oriP
3
A comparação de cadeias de caracteres pode se tornar perigosa quando os números de versão excederem 9, pois, por exemplo, '10' <'2'.
D Coetzee
13
Eu faço isso tão bem, mas ligeiramente ajustada para ints endereço .. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
rh0dium
2
Problema com __version__ = '.'.join(__version_info__)é que __version_info__ = ('1', '2', 'beta')se tornará 1.2.beta, não 1.2betaou1.2 beta
nagisa
4
Acho que o problema dessa abordagem é onde colocar as linhas de código que declaram a __version__. Se eles estiverem em setup.py, seu programa não poderá importá-los do seu pacote. Talvez isso não seja um problema para você; nesse caso, tudo bem. Se eles estiverem dentro do seu programa, o seu setup.py poderá importá-los, mas não deve, pois o setup.py é executado durante a instalação quando as dependências do programa ainda não estão instaladas (o setup.py é usado para determinar quais são as dependências. .) por isso a resposta de Zooko: analisar manualmente o valor a partir de um arquivo de origem do produto, sem importar o pacote do produto
Jonathan Hartley
11

Muitas dessas soluções aqui ignoram as gittags de versão, o que ainda significa que você precisa rastrear a versão em vários locais (ruim). Abordei isso com os seguintes objetivos:

  • Derivar todas as referências de versão python de uma tag no gitrepositório
  • Automatize git tag/ pushe setup.py uploadetapas com um único comando que não aceita entradas.

Como funciona:

  1. Em um make releasecomando, a última versão marcada no repositório git é encontrada e incrementada. A tag é enviada de volta para origin.

  2. Ele Makefilearmazena a versão em src/_version.pyque ela será lida setup.pye também incluída no release. Não verifique _version.pyno controle de origem!

  3. setup.pyO comando lê a nova string de versão de package.__version__.

Detalhes:

Makefile

# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver      = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))

.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
    echo '__version__ = "$(call git_describe_ver)"' > $@

.PHONY: release
release: test lint mypy
    git tag -a $(call next_patch_ver)
    $(MAKE) ${MODULE}/_version.py
    python setup.py check sdist upload # (legacy "upload" method)
    # twine upload dist/*  (preferred method)
    git push origin master --tags

O releasedestino sempre incrementa o dígito da terceira versão, mas você pode usar o next_minor_verounext_major_ver para incrementar os outros dígitos. Os comandos dependem do versionbump.pyscript verificado na raiz do repositório

versionbump.py

"""An auto-increment tool for version strings."""

import sys
import unittest

import click
from click.testing import CliRunner  # type: ignore

__version__ = '0.1'

MIN_DIGITS = 2
MAX_DIGITS = 3


@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
    """Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
    optional 'v' prefix is allowed and will be included in the output if found."""
    prefix = version[0] if version[0].isalpha() else ''
    digits = version.lower().lstrip('v').split('.')

    if len(digits) > MAX_DIGITS:
        click.secho('ERROR: Too many digits', fg='red', err=True)
        sys.exit(1)

    digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS]  # Extend total digits to max.
    digits[bump_idx] = str(int(digits[bump_idx]) + 1)  # Increment the desired digit.

    # Zero rightmost digits after bump position.
    for i in range(bump_idx + 1, MAX_DIGITS):
        digits[i] = '0'
    digits = digits[:max(MIN_DIGITS, bump_idx + 1)]  # Trim rightmost digits.
    click.echo(prefix + '.'.join(digits), nl=False)


if __name__ == '__main__':
    cli()  # pylint: disable=no-value-for-parameter

Isso faz o trabalho pesado de como processar e incrementar o número da versão de git .

__init__.py

O my_module/_version.pyarquivo é importado para my_module/__init__.py. Coloque qualquer configuração de instalação estática aqui que você deseja distribuir com seu módulo.

from ._version import __version__
__author__ = ''
__email__ = ''

setup.py

O último passo é ler as informações da versão no my_modulemódulo.

from setuptools import setup, find_packages

pkg_vars  = {}

with open("{MODULE}/_version.py") as fp:
    exec(fp.read(), pkg_vars)

setup(
    version=pkg_vars['__version__'],
    ...
    ...
)

Obviamente, para que tudo isso funcione, você precisará ter pelo menos uma tag de versão no seu repositório para começar.

git tag -a v0.0.1
cmcginty
fonte
1
de fato - todo esse tópico esquece que um VCS é muito importante nessa discussão. apenas um obs: o incremento da versão deve permanecer um processo manual, então a maneira preferida seria 1. criar e enviar manualmente uma tag 2. deixar as ferramentas do VCS descobrir essa tag e armazená-la onde necessário (uau - essa interface de edição do SO está realmente me prejudicando - Eu tive que editar esta uma dúzia de vezes apenas para lidar com novas linhas e ainda não funciona @ # $% ^ & *)
AXD
Não é necessário usar versionbump.pyquando temos um pacote impressionante de bumpversion para python.
Oran
@ Oran, olhei para o versionbump. Os documentos não são muito claros e não lidam muito bem com a marcação. Nos meus testes, parece entrar em estados que causam uma falha: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folders / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'retornou o status de saída diferente de zero 1.
cmcginty 29/01
1
Por que não deve _version.pyser rastreado com controle de versão?
precisa saber é o seguinte
1
@StevenVascellaro Isso complica o processo de lançamento. Agora você precisa garantir que suas tags e confirmações correspondam ao valor em _version.py. Usar uma tag de versão única é um IMO mais limpo e significa que você pode criar uma versão diretamente da interface do usuário do github.
precisa
10

Eu uso um arquivo JSON no diretório do pacote. Isso se encaixa nos requisitos da Zooko.

Dentro pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Dentro setup.py:

from distutils.core import setup
import json

with open('pkg_dir/pkg_info.json') as fp:
    _info = json.load(fp)

setup(
    version=_info['version'],
    ...
    )

Dentro pkg_dir/__init__.py:

import json
from os.path import dirname

with open(dirname(__file__) + '/pkg_info.json') as fp:
    _info = json.load(fp)

__version__ = _info['version']

Eu também coloquei outras informações pkg_info.json, como autor. Eu gosto de usar JSON porque posso automatizar o gerenciamento de metadados.

Andy Lee
fonte
Você poderia elaborar como usar o json para automatizar o gerenciamento de metadados? Obrigado!
Ryanjdillon
6

Também digno de nota é que, além de __version__ser um semi-std. no python, então é __version_info__uma tupla, nos casos simples, você pode fazer algo como:

__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])

... e você pode obter a __version__string de um arquivo, ou qualquer outra coisa.

James Antill
fonte
1
Você tem referências / links sobre a origem desse uso __version_info__?
22711 Craig McQueen
3
Bem, é o mesmo mapeamento que sys.version para sys.version_info. Então: docs.python.org/library/sys.html#sys.version_info
James Antill
7
É mais fácil fazer o mapeamento na outra direção ( __version_info__ = (1, 2, 3)e __version__ = '.'.join(map(str, __version_info__))).
Eric O Lebigot
2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)- um pouco mais longo, mas mais pitônico.
ArtOfWarfare
2
Não tenho certeza de que o que você propõe seja claramente mais pitônico, pois contém uma variável fictícia que não é realmente necessária e cujo significado é um pouco difícil de expressar ( inão tem significado, version_numé um pouco longo e ambíguo ...). Eu até considero a existência do map()Python como uma forte dica de que ele deve ser usado aqui, pois o que precisamos fazer aqui é o caso de uso típico de map()(uso com uma função existente) - não vejo muitos outros usos razoáveis.
Eric O Lebigot
5

Não parece haver uma maneira padrão de incorporar uma string de versão em um pacote python. A maioria dos pacotes que vi usam alguma variante da sua solução, ou seja, eitner

  1. Incorpore a versão setup.pye setup.pygere um módulo (por exemplo version.py) contendo apenas informações da versão, importadas pelo seu pacote ou

  2. O inverso: colocar a informação de versão em sua própria embalagem, e importação que para definir a versão em setup.py

dF.
fonte
Gosto da sua recomendação, mas como gerar este módulo a partir do setup.py?
sorin
1
Gosto da ideia da opção (1), parece mais simples que a resposta de Zooko de analisar o número da versão de um arquivo. Mas você não pode garantir que version.py seja criado quando um desenvolvedor apenas clona seu repositório. A menos que você faça o check-in do version.py, que abre as pequenas rugas, você pode alterar o número da versão em setup.py, confirmar, liberar e, em seguida, é necessário (barra esquecer) confirmar a alteração no version.py.
22612 Jonathan Hartley
Presumivelmente, você pode gerar o módulo usando algo como "" "com open (" mypackage / version.py "," w ") como fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Jonathan Hartley
1
Acho que a opção 2. está suscetível a falhar durante a instalação, conforme observado nos comentários da resposta do JAB.
Jonathan Hartley
Que tal? Você coloca __version__ = '0.0.1' "(onde a versão é uma string, é claro) no __init__.py" do pacote principal do seu software. Em seguida, vá para o ponto 2: Na instalação que você faz do pacote .__ init__ importa __version__ como ve depois define a variável v como a versão do seu setup.py. Em seguida, importe mypack como my, imprima minha versão .__ version__ imprimirá a versão. A versão será armazenada em apenas um local, acessível a partir de todo o código, em um arquivo que não importa mais nada relacionado ao software.
SeF 29/07
5

A seta lida com isso de uma maneira interessante.

Agora (desde 2e5031b )

Em arrow/__init__.py:

__version__ = 'x.y.z'

Em setup.py:

from arrow import __version__

setup(
    name='arrow',
    version=__version__,
    # [...]
)

Antes

Em arrow/__init__.py:

__version__ = 'x.y.z'
VERSION = __version__

Em setup.py:

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

file_text = read(fpath('arrow/__init__.py'))

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)
Anto
fonte
o que é file_text?
Ely
2
a solução atualizada é realmente prejudicial. Quando o setup.py estiver em execução, ele não verá necessariamente a versão do pacote no caminho do arquivo local. Pode reverter para uma versão instalada anteriormente, por exemplo, executando pip install -e .em uma ramificação de desenvolvimento ou algo durante o teste. O setup.py não deve absolutamente importar a importação do pacote que está em processo de instalação para determinar os parâmetros para a implantação. Caramba.
Ely
Sim, não sei por que a Arrow decidiu regredir para esta solução. Além disso, a mensagem de confirmação diz "Setup.py atualizado com os padrões modernos do Python " ... #
Anto
4

Eu também vi outro estilo:

>>> django.VERSION
(1, 1, 0, 'final', 0)
L1ker
fonte
1
Sim, eu também vi. BTW cada resposta leva outro estilo, então agora eu não sei o que é um estilo "padrão". Procurando PEPs mencionados ...
kbec
Outro caminho visto; O cliente Python do Mongo usa a versão simples, sem os sublinhados. Então isso funciona; $ python >>> import pymongo >>> pymongo.version '2.7'
AnneTheAgile
Implementar .VERSIONnão significa que você não precisa implementá-lo __version__.
Acumenus
Isso requer implementação djangono projeto?
Stevoisiak
3

Usando setuptoolsepbr

Não existe uma maneira padrão de gerenciar versão, mas a maneira padrão de gerenciar seus pacotes é setuptools.

A melhor solução que encontrei no geral para gerenciar a versão é usar setuptoolscom a pbrextensão. Esta é agora a minha maneira padrão de gerenciar versão.

A configuração do seu projeto para o pacote completo pode ser um exagero para projetos simples, mas se você precisar gerenciar a versão, provavelmente estará no nível certo para definir tudo. Fazer isso também torna seu pacote liberável no PyPi para que todos possam fazer o download e usá-lo com o Pip.

O PBR move a maioria dos metadados das setup.pyferramentas e para um setup.cfgarquivo que é usado como fonte para a maioria dos metadados, que pode incluir a versão. Isso permite que os metadados sejam empacotados em um executável usando algo como pyinstallerse necessário (nesse caso, você provavelmente precisará dessas informações ) e separa os metadados dos outros scripts de gerenciamento / configuração de pacotes. Você pode atualizar diretamente a string de versão setup.cfgmanualmente, e ela será puxada para a *.egg-infopasta ao criar as versões do pacote. Seus scripts podem acessar a versão a partir dos metadados usando vários métodos (esses processos são descritos nas seções abaixo).

Ao usar o Git para VCS / SCM, essa configuração é ainda melhor, pois extrai muitos metadados do Git para que seu repo possa ser sua principal fonte de verdade para alguns dos metadados, incluindo versão, autores, changelogs, etc. Para a versão especificamente, ele criará uma string de versão para o commit atual com base nas tags git no repositório.

Como o PBR puxa a versão, o autor, o changelog e outras informações diretamente do seu repositório git, então alguns dos metadados setup.cfgpodem ser deixados de fora e gerados automaticamente sempre que uma distribuição é criada para o seu pacote (usando setup.py)

Versão atual em tempo real

setuptoolsextrairá as informações mais recentes em tempo real usando setup.py:

python setup.py --version

Isso puxará a versão mais recente do setup.cfgarquivo ou do repositório git, com base na confirmação mais recente que foi feita e nas tags existentes no repositório. Este comando não atualiza a versão em uma distribuição.

Atualizando a versão

Quando você cria uma distribuição com setup.py( py setup.py sdistpor exemplo, por exemplo), todas as informações atuais serão extraídas e armazenadas na distribuição. Essencialmente, ele executa o setup.py --versioncomando e armazena as informações da versão na package.egg-infopasta em um conjunto de arquivos que armazenam metadados de distribuição.

Nota sobre o processo para atualizar os metadados da versão:

Se você não estiver usando o pbr para extrair dados da versão do git, basta atualizar o setup.cfg diretamente com as novas informações da versão (fácil o suficiente, mas verifique se essa é uma parte padrão do seu processo de lançamento).

Se você estiver usando o git e não precisar criar uma fonte ou distribuição binária (usando python setup.py sdistou um dos python setup.py bdist_xxxcomandos), a maneira mais simples de atualizar as informações do repositório git na <mypackage>.egg-infopasta de metadados é apenas executar o python setup.py installcomando. Isso executará todas as funções do PBR relacionadas à extração de metadados do repositório git e atualizará a .egg-infopasta local , instalará executáveis ​​de scripts para quaisquer pontos de entrada que você definiu e outras funções que você poderá ver na saída ao executar este comando.

Observe que a .egg-infopasta geralmente é excluída do armazenamento no repositório git em .gitignorearquivos Python padrão (como o Gitignore.IO ), pois pode ser gerada a partir da sua fonte. Se for excluído, verifique se você possui um "processo de liberação" padrão para atualizar os metadados localmente antes da liberação e se qualquer pacote carregado no PyPi.org ou de outra forma distribuído deve incluir esses dados para ter a versão correta. Se você deseja que o repositório Git contenha essas informações, você pode excluir arquivos específicos de serem ignorados (por exemplo, adicionar !*.egg-info/PKG_INFOa .gitignore)

Acessando a versão de um script

Você pode acessar os metadados da compilação atual nos scripts Python no próprio pacote. Por versão, por exemplo, existem várias maneiras de fazer isso que encontrei até agora:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib-metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

Você pode colocar um desses diretamente no seu __init__.pypara o pacote extrair as informações da versão da seguinte maneira, semelhante a algumas outras respostas:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
LightCC
fonte
Reformado por voto negativo para responder diretamente à pergunta agora.
LightCC
1

Depois de várias horas tentando encontrar a solução mais simples e confiável, aqui estão as partes:

crie um arquivo version.py DENTRO da pasta do seu pacote "/ mypackage":

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '1.2.7'

em setup.py:

exec(open('mypackage/version.py').read())
setup(
    name='mypackage',
    version=__version__,

na pasta principal init .py:

from .version import __version__

A exec()função executa o script fora de qualquer importação, pois o setup.py é executado antes que o módulo possa ser importado. Você ainda precisa gerenciar o número da versão em um arquivo em um só lugar, mas infelizmente ele não está no setup.py. (essa é a desvantagem, mas não ter bugs de importação é a vantagem)

Marc Maxmeister
fonte
1

Muito trabalho para a versão uniforme e em apoio às convenções foi concluído desde que essa pergunta foi feita pela primeira vez . Opções palatáveis ​​agora estão detalhadas no Guia do Usuário de Empacotamento do Python . Também digno de nota é que os esquemas de número de versão são relativamente rigorosos no Python, de acordo com o PEP 440 , e, portanto, manter as coisas sãs é fundamental se o seu pacote for lançado na Cheese Shop .

Aqui está uma descrição resumida das opções de versão:

  1. Leia o arquivo em setup.py( setuptools ) e obtenha a versão.
  2. Use uma ferramenta de construção externa (para atualizar tanto o __init__.pycontrole quanto o de origem), por exemplo , bump2version , changes ou zest.releaser .
  3. Defina o valor para uma __version__variável global em um módulo específico.
  4. Coloque o valor em um arquivo de texto VERSION simples para que setup.py e código sejam lidos.
  5. Defina o valor por meio de uma setup.pyliberação e use importlib.metadata para buscá-lo em tempo de execução. (Aviso, existem versões pré-3.8 e pós-3.8.)
  6. Defina o valor para __version__in sample/__init__.pye importe a amostra em setup.py.
  7. Use setuptools_scm para extrair controle de versão do controle de origem, para que seja a referência canônica, não o código.

Observe que (7) pode ser a abordagem mais moderna (os metadados da compilação são independentes do código, publicado pela automação). Observe também que, se a instalação for usada para a liberação do pacote, um simples python3 setup.py --versionrelatará a versão diretamente.

aqui
fonte
-1

Para o que vale a pena, se você estiver usando distutils NumPy, numpy.distutils.misc_util.Configurationtem um make_svn_version_py()método que incorpora o número de revisão dentro package.__svn_version__da variável version.

Matt
fonte
Você pode fornecer mais detalhes ou um exemplo de como isso funcionaria?
Stevoisiak
Hmm. Em 2020, isso é (sempre foi?) Destinado ao FORTRAN . O pacote "numpy.distutils faz parte do NumPy que estende os distutils padrão do Python para lidar com fontes do Fortran."
ingyhere
-1
  1. Use um version.pyarquivo apenas com __version__ = <VERSION>param no arquivo. No setup.pyarquivo, importe o __version__parâmetro e coloque seu valor no setup.pyarquivo assim: version=__version__
  2. Outra maneira é usar apenas um setup.pyarquivo com version=<CURRENT_VERSION>- o CURRENT_VERSION é codificado.

Como não queremos alterar manualmente a versão do arquivo toda vez que criamos uma nova tag (pronta para lançar uma nova versão do pacote), podemos usar o seguinte ..

Eu recomendo o pacote bumpversion . Eu tenho usado por anos para encontrar uma versão.

comece adicionando version=<VERSION>ao seu setup.pyarquivo, se ainda não o tiver.

Você deve usar um script curto como este toda vez que encontrar uma versão:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

Em seguida, adicione um arquivo por repositório chamado .bumpversion.cfg::

[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]

Nota:

  • Você pode usar o __version__parâmetro no version.pyarquivo como sugerido em outras postagens e atualizar o arquivo bumpversion assim: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Você deve git commit ou git resettudo no seu repo, caso contrário, você receberá um erro de repo sujo.
  • Certifique-se de que seu ambiente virtual inclua o pacote de bumpversion, sem ele não funcionará.
Oran
fonte
@cmcginty Desculpe pelo atraso, verifique minha resposta ^^^ - observe que você deve git commit ou git reset tudo em seu repositório e certifique-se de que seu ambiente virtual inclua o pacote bumpversion, sem ele não funcionará. Use a versão mais recente.
Oran
Não sei ao certo qual solução está sendo sugerida aqui. Você está recomendando a versão com rastreamento manual version.pyou com ela bumpversion?
Stevoisiak
@StevenVascellaro Estou sugerindo o uso de bumpversion, nunca use versionamento manual. O que tentei explicar é que você pode direcionar o bumpversion para atualizar a versão no arquivo setup.py ou, melhor ainda, usá-lo para atualizar o arquivo version.py. É uma prática mais comum atualizar o arquivo version.py e levar o __version__valor do parâmetro para o arquivo setup.py. Minha solução é usada na produção e é uma prática comum. Nota: para deixar claro, usar o bumpversion como parte de um script é a melhor solução, coloque-o no seu IC e será uma operação automática.
Oran
-3

Se você usa o CVS (ou RCS) e deseja uma solução rápida, pode usar:

__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])

(Obviamente, o número da revisão será substituído por você pelo CVS.)

Isso fornece uma versão para impressão e informações sobre a versão que você pode usar para verificar se o módulo que você está importando possui pelo menos a versão esperada:

import my_module
assert my_module.__version_info__ >= (1, 1)
Martin Ibert
fonte
Em qual arquivo você recomenda salvar o salvamento __version__? Como incrementar o número da versão com esta solução?
Stevoisiak