Pergunta

Existe uma maneira padrão de cadeia de versão associado com um pacote de python de tal maneira que eu poderia fazer o seguinte?

import foo
print foo.version

Eu imagino que há alguma maneira de recuperar os dados sem qualquer hardcode extra, desde pequenas / grandes cadeias são especificados no setup.py já. solução alternativa que eu encontrei foi ter import __version__ na minha foo/__init__.py e depois ter __version__.py gerado pelo setup.py.

Foi útil?

Solução

Não diretamente uma resposta à sua pergunta, mas você deve considerar nomeando-__version__, não version.

Este é quase um quasi-padrão. Muitos módulos no __version__ uso da biblioteca padrão, e isso também é usado em lotes de módulos de 3-parte, por isso é o quase-padrão.

Normalmente, __version__ é uma string, mas às vezes é também uma bóia ou tupla.

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

Versão Escrituração

Se você tem que ter Subversion, CVS, ou RCS crud em seu arquivo de origem, fazê-lo da seguinte forma.

    __version__ = "$Revision: 63990 $"
    # $Source$

Estas linhas devem ser incluídos após a docstring do módulo, antes de qualquer outro código, separadas por uma linha em branco acima e abaixo.

Você também deve se certificar de que os conforma número de versão para o formato descrito no PEP 440 ( PEP 386 uma versão anterior desta norma).

Outras dicas

Eu uso um único arquivo _version.py como o "uma vez lugar canônico" para armazenar informações de versão:

  1. Ele fornece um atributo __version__.

  2. Ele fornece a versão de metadados padrão. Por isso será detectado pelo pkg_resources ou outras ferramentas que analisar 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 construir o seu pacote, o que pode causar problemas em algumas situações. (Veja os comentários abaixo sobre os problemas que isso pode causar.)

  4. Há apenas um lugar que o número da versão é escrito para baixo, para que haja apenas um lugar para alterá-lo quando o número versão alterações, e há menos chance de versões inconsistentes.

Aqui está como funciona: a "um lugar canônico" para armazenar o número da versão é um arquivo .py, chamado "_version.py", que está em seu pacote Python, por exemplo, em myniftyapp/_version.py. Este arquivo é um módulo Python, mas o seu setup.py não importá-lo! (Isso seria derrotar 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 analisa-lo, com um 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 seqüência como o valor do argumento "versão" para setup(), satisfazendo assim recurso 2.

recurso para satisfazer a 1, você pode ter o seu pacote (em tempo de execução, e não no momento da instalação!) Importar o arquivo _VERSION de myniftyapp/__init__.py assim:

from _version import __version__

Aqui está um exemplo desta técnica que Eu tenho usado por anos.

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

Aqui está href="https://tahoe-lafs.org/trac/zfec/browser/trunk/zfec/zfec/__init__.py?rev=363" de importação a versão .

Se você ver nada de errado com esta abordagem, por favor me avise.

Rewritten 2017-05

Depois de mais de dez anos de escrever código Python e gerenciar vários pacotes cheguei à conclusão de que DIY não é talvez a melhor abordagem.

Eu comecei usando o pacote pbr para lidar com versões em meus pacotes. Se você estiver usando git como seu SCM, isso vai caber em seu fluxo de trabalho como mágica, salvando suas semanas de trabalho (você ficará surpreso sobre quão complexo o problema pode ser).

A partir de hoje PBR encontra-se classificado # 11 pacote python mais utilizado e chegar a este didn nível' t incluir quaisquer truques sujos: era apenas um:. fixação de um problema de embalagem comum de uma forma muito simples

pbr pode fazer mais do fardo de manutenção de pacotes, não se limita ao controle de versão, mas não forçá-lo a adotar todos os seus benefícios.

Assim, para lhe dar uma idéia sobre como ele olha para adotar PBR em um commit ter um olhar swiching embalagem para PBR

Provavelmente você observou que a versão não é armazenada em tudo no repositório. PBR faz detectá-lo a partir de ramos e tags Git.

Não há necessidade de se preocupar com o que acontece quando você não tem um repositório git porque pbr faz "compilação" e armazenar em cache a versão quando você pacotes ou instalar os aplicativos, então não há nenhuma dependência em tempo de execução git.

solução Old

Aqui é a melhor solução que eu vi até agora e isso também explica por que:

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 uma outra abordagem que parece ser melhor me avise.

Por PEP diferidos 396 (Números Versão de Módulo) , há uma forma proposta para fazer isso. Ele descreve, com lógica, um (reconhecidamente opcional) padrão para módulos a seguir. 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 atributo __version__.

4) Para os módulos que vivem dentro de um pacote namespace, o módulo deve incluir o atributo __version__. O pacote namespace si não deve incluir seu próprio atributo __version__.

5) O valor do atributo __version__ deve ser uma string.

Embora esta é provavelmente 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 para converter porções de autoincremento de números de versão para uma string usando str().)

Claro que, pelo que tenho visto, as pessoas tendem a usar algo como a versão anteriormente mencionados quando se utiliza __version_info__, e como tal armazená-lo como uma tupla de inteiros; no entanto, eu não vejo o ponto em fazê-lo, como eu duvido que há situações em que você iria realizar operações matemáticas, como adição e subtração em porções 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, há a possibilidade de alguém código de outra pessoa esperando um tuple numérica ao invés de uma tupla corda e, portanto, não.)

Este é, naturalmente, o meu próprio ponto de vista, e eu de bom grado como entrada de outras pessoas sobre o uso de uma tupla numérica.


Como Shezi me lembrou, (lexicais) comparações de cadeias numéricas não têm necessariamente o mesmo resultado que comparações numéricas diretos; zeros à esquerda seriam obrigados a fornecer para isso. Então, no final, armazenar __version_info__ (ou o que seria chamado) como uma tupla de valores inteiros permitiria comparações versão mais eficientes.

Eu uso um arquivo JSON na dir pacote. Isso se encaixa requisitos de 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 colocar outras informações em pkg_info.json, como autor. Eu como usar JSON porque eu posso automatizar o gerenciamento de metadados.

Muitas dessas soluções aqui ignorar as tags versão git que ainda significa que você tem que controlar a versão em vários lugares (mau). Aproximei-me isso com os seguintes objetivos:

  • derivar todas as referências versão python de uma tag no git repo
  • Automatizar git tag / push e passos setup.py upload com um único comando que leva sem entradas.

Como funciona:

  1. A partir de um comando make release, a última versão marcado no repo git é encontrado e incrementado. O tag é adiado para origin.

  2. Os Makefile armazena a versão em src/_version.py onde será lido por setup.py e também incluído na versão. Não verificar _version.py no controle de origem!

  3. comando setup.py lê a nova cadeia 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 alvo release sempre incrementa a versão 3 dígitos, mas você pode usar o next_minor_ver ou next_major_ver para incrementar os outros dígitos. Os comandos contar com o roteiro versionbump.py que é verificada na raiz do repo

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

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

__ o init __. Py

O arquivo my_module/_version.py é importado para my_module/__init__.py. Coloque qualquer estática instalar config-aqui que você quer distribuído com seu módulo.

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

setup.py

O último passo é ler a informação da versão do módulo my_module.

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__'],
    ...
    ...
)

Claro que, para tudo isso funcionar, você terá que ter tag pelo menos uma versão em sua repo para começar.

git tag -a v0.0.1

Não parece ser uma maneira padrão para incorporar uma cadeia de versão em um pacote python. A maioria dos pacotes que eu vi uso alguma variante da sua solução, ou seja, Eitner

  1. Integrar a versão em setup.py e ter setup.py gerar um módulo (por exemplo version.py) contendo apenas informação de versão, que é importado por 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

Também digno de nota é que, assim como __version__ ser um semi-std. em python assim é __version_info__ que é uma tupla, nos casos simples que você pode apenas fazer algo como:

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

... e você pode obter a seqüência __version__ de um arquivo, ou o que quer.

Eu também vi um outro estilo:

>>> django.VERSION
(1, 1, 0, 'final', 0)

seta manipula-lo em uma maneira interessante.

Em arrow/__init__.py:

__version__ = '0.8.0'
VERSION = __version__

Em setup.py:

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

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

Por que vale a pena, se você estiver usando distutils Numpy, numpy.distutils.misc_util.Configuration tem um make_svn_version_py() método que incorpora o número de revisão dentro package.__svn_version__ no version variável.

  1. Use um arquivo version.py apenas com param __version__ = <VERSION> no arquivo. No arquivo setup.py importar o param __version__ e colocá-lo de valor no arquivo setup.py assim: version=__version__
  2. Outra maneira é usar apenas um arquivo setup.py com version=<CURRENT_VERSION> -. A CURRENT_VERSION é codificado

Uma vez que não deseja alterar manualmente a versão no arquivo cada vez que criar um novo tag (pronto para lançar uma nova versão do pacote), podemos usar o seguinte ..

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

começar adicionando version=<VERSION> ao seu arquivo setup.py se você não o tenha feito.

Você deve usar um pequeno script como este cada vez que você bater uma versão:

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

Em seguida, adicione um arquivo por repo 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 parâmetro __version__ sob arquivo version.py como foi sugerido em outros lugares e atualizar o arquivo bumpversion assim: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Você deve git commit ou tudo git reset em sua repo, caso contrário, você vai ter um erro sujo repo.
  • Certifique-se de que seu ambiente virtual inclui o pacote de bumpversion, sem ele não vai funcionar.

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

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

(Claro, o número de revisão será substituído por você, CVS).

Isto dá-lhe uma versão para impressão e uma informação da versão que você pode usar para verificar que o módulo que você está importando tem pelo menos a versão esperada:

import my_module
assert my_module.__version_info__ >= (1, 1)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top