Mantendo uma única fonte da versão do pacote

Existem muitas técnicas para manter uma única fonte para o número de versão do seu projeto:

  1. Leia o arquivo em setup.py e obtenha a versão. Exemplo (de pip setup.py):

    import codecs
    import os.path
    
    def read(rel_path):
        here = os.path.abspath(os.path.dirname(__file__))
        with codecs.open(os.path.join(here, rel_path), 'r') as fp:
            return fp.read()
    
    def get_version(rel_path):
        for line in read(rel_path).splitlines():
            if line.startswith('__version__'):
                delim = '"' if '"' in line else "'"
                return line.split(delim)[1]
        else:
            raise RuntimeError("Unable to find version string.")
    
    setup(
       ...
       version=get_version("package/__init__.py")
       ...
    )
    

    Nota

    A partir do lançamento do setuptools 46.4.0, pode-se realizar a mesma coisa colocando o seguinte no arquivo setup.cfg do projeto (substituindo “pacote” pelo nome de importação do pacote):

    [metadata]
    version = attr: package.__version__
    

    Versões anteriores de setuptools implementavam a diretiva attr: importando o módulo, mas setuptools 46.4.0 adicionou uma análise de AST rudimentar para que attr: possa funcionar sem ter que importar nenhuma das dependências do pacote.

    Além disso, esteja ciente de que os indicadores de configuração declarativos, incluindo a diretiva attr: ``, não são suportados em parâmetros para ``setup.py.

  2. Use uma ferramenta de construção externa que gerencia a atualização de ambos os locais ou oferece uma API que ambos os locais podem usar.

    Poucas ferramentas que você poderia usar, sem nenhuma ordem específica, e não necessariamente completas: bump2version, changes, commitizen, zest.releaser.

  3. Defina o valor para uma variável global __version__ em um módulo dedicado em seu projeto (por exemplo, version.py), então faça com que setup.py leia e execute (exec) o valor em uma variável.

    version = {}
    with open("...sample/version.py") as fp:
        exec(fp.read(), version)
    # later on we use: version['__version__']
    

    Exemplo usando esta técnica: warehouse.

  4. Coloque o valor em um arquivo de texto simples VERSION e tenha ambos setup.py e o código do projeto lidos.

    with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file:
        version = version_file.read().strip()
    

    Uma vantagem dessa técnica é que ela não é específica do Python. Qualquer ferramenta pode ler a versão.

    Aviso

    Com esta abordagem, você deve se certificar de que o arquivo VERSION está incluído em todas as suas distribuições de fonte e binárias (por exemplo, adicione include VERSION ao seu MANIFEST.in).

  5. Defina o valor em setup.py e faça com que o código do projeto use a API importlib.metadata para buscar o valor em tempo de execução. (importlib.metadata foi introduzido no Python 3.8 e está disponível para versões anteriores como o projeto importlib-metadata.) A versão de um projeto instalado pode ser obtida com a API da seguinte forma:

    import sys
    
    if sys.version_info >= (3, 8):
        from importlib import metadata
    else:
        import importlib_metadata as metadata
    
    assert metadata.version('pip') == '1.2.0'
    

    Esteja ciente de que a API importlib.metadata só sabe sobre o que está nos metadados de instalação, o que não é necessariamente o código que está importado no momento.

    Se um projeto usa este método para buscar sua versão em tempo de execução, então seu valor install_requires precisa ser editado para instalar importlib-metadata em versões pré-3.8 do Python como:

    setup(
        ...
        install_requires=[
            ...
            'importlib-metadata >= 1.0 ; python_version < "3.8"',
            ...
        ],
        ...
    )
    

    ’Uma alternativa mais antiga (e menos eficiente) ao importlib.metadata é a API pkg_resources fornecida pelo setuptools:

    import pkg_resources
    assert pkg_resources.get_distribution('pip').version == '1.2.0'
    

    Se um projeto usa pkg_resources para buscar sua própria versão em tempo de execução, então setuptools deve ser adicionado à lista install_requires do projeto.

    Exemplo usando esta técnica: setuptools.

  6. Defina o valor para __version__ em sample/__init__.py e importe sample no setup.py.

    import sample
    setup(
        ...
        version=sample.__version__
        ...
    )
    

    Aviso

    Embora esta técnica seja comum, esteja ciente que ela vai falhar se sample/__init__.py importar pacotes de dependências install_requires, que muito provavelmente ainda não estarão instalados quando setup.py for executado.

  7. Mantenha o número da versão nas tags de um sistema de controle de versão (Git, Mercurial, etc), em vez de no código, e extraia-o automaticamente de lá usando setuptools_scm.