Respostas:
Para recuperar a versão de dentro do seu pacote em tempo de execução (o que sua pergunta parece realmente estar perguntando), você pode usar:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Se você quiser seguir o caminho inverso (que parece ser o que outros autores de respostas aqui parecem ter pensado que você estava perguntando), coloque a string da versão em um arquivo separado e leia o conteúdo desse arquivo setup.py
.
Você pode criar um version.py no seu pacote com uma __version__
linha e, em seguida, lê-lo em setup.py usando execfile('mypackage/version.py')
, para que seja definido __version__
no namespace setup.py.
Se você deseja uma maneira muito mais simples que funcione com todas as versões do Python e até mesmo com as linguagens que não sejam do Python, que podem precisar de acesso à string da versão:
Armazene a string da versão como o único conteúdo de um arquivo de texto sem formatação, nomeado por exemplo VERSION
, e leia esse arquivo durante setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
O mesmo VERSION
arquivo funcionará exatamente como em qualquer outro programa, mesmo que não seja em Python, e você só precisará alterar a string da versão em um local para todos os programas.
A propósito, NÃO importe seu pacote do setup.py, como sugerido em outra resposta aqui: ele parecerá funcionar para você (porque você já possui as dependências do pacote instaladas), mas causará estragos nos novos usuários do seu pacote. , pois eles não poderão instalar seu pacote sem instalar manualmente as dependências primeiro.
execfile
funciona muito bem ... mas (infelizmente) não funciona com o Python 3.
with open('mypackage/version.py') as f: exec(f.read())
no lugar de execfile('mypackage/version.py')
. (From stackoverflow.com/a/437857/647002 )
mymodule
Imagine esta configuração:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Imagine um cenário comum em que você tenha dependências e se setup.py
pareça com:
setup(...
install_requires=['dep1','dep2', ...]
...)
E um exemplo __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
E por exemplo myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
durante a instalaçãoSe suas setup.py
importações mymodule
, durante a instalação, você provavelmente obterá um ImportError
. Este é um erro muito comum quando o seu pacote tem dependências. Se o seu pacote não tiver outras dependências além das internas, você pode estar seguro; no entanto, isso não é uma boa prática. A razão para isso é que não é à prova de futuro; digamos amanhã que seu código precisa consumir alguma outra dependência.
__version__
?Se você codificar __version__
em setup.py
seguida, ele pode não coincidir com a versão que seria lançado em seu módulo. Para ser consistente, você o colocaria em um só lugar e o leria do mesmo lugar quando necessário. Usando import
você pode obter o problema nº 1.
setuptools
Você usaria uma combinação de open
, exec
e fornecer um dicionário para exec
as variáveis add:
# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path
main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
exec(ver_file.read(), main_ns)
setup(...,
version=main_ns['__version__'],
...)
E em mymodule/version.py
expor a versão:
__version__ = 'some.semantic.version'
Dessa forma, a versão é fornecida com o módulo e você não apresenta problemas durante a instalação ao tentar importar um módulo que possui dependências ausentes (ainda a ser instalado).
A melhor técnica é definir __version__
no código do produto e importá-lo para o setup.py a partir daí. Isso fornece um valor que você pode ler no seu módulo em execução e tem apenas um lugar para defini-lo.
Os valores em setup.py não estão instalados e o setup.py não permanece após a instalação.
O que eu fiz (por exemplo) em composition.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): o cover.py não se importa mais para obter a versão. A importação do seu próprio código pode torná-lo desinstalável, porque o código do produto tentará importar dependências, que ainda não estão instaladas, porque o setup.py é o que as instala.
__version__
nele estiver quebrado de alguma forma, sua importação também será interrompida. Python não sabe como interpretar apenas as declarações que você deseja. @pjeby está certo: se o seu módulo precisar importar outros módulos, eles ainda não podem estar instalados e serão caóticos. Essa técnica funciona se você for cuidadoso para que a importação não cause uma longa cadeia de outras importações.
pip install <packagename>
, eu recebo o seguinte erro: ImportError: No module named <packagename>
. AVISO aos seus leitores que você não pode executar arquivos setup.py como este em ambientes onde o pacote ainda não está instalado!
Eu não estava feliz com essas respostas ... não queria exigir ferramentas de configuração, nem criar um módulo inteiro para uma única variável, então eu vim com elas.
Para quando você tiver certeza de que o módulo principal está no estilo pep8 e permanecerá assim:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Se você deseja ser mais cuidadoso e usar um analisador real:
import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
version = ast.parse(line).body[0].value.s
break
O setup.py é um módulo descartável, portanto não é um problema se for um pouco feio.
Atualização: engraçado o bastante, eu me afastei disso nos últimos anos e comecei a usar um arquivo separado no pacote chamado meta.py
. Coloquei muitos metadados lá que talvez eu queira mudar com frequência. Portanto, não apenas por um valor.
ast.get_docstring()
alguns .split('\n')[0].strip()
etc para preencher automaticamente a description
partir da fonte. Menos uma coisa para manter em sincronia
Crie um arquivo na sua árvore de fontes, por exemplo, em yourbasedir / yourpackage / _version.py. Deixe esse arquivo conter apenas uma única linha de código, como esta:
__version__ = "1.1.0-r4704"
Em seguida, em seu setup.py, abra esse arquivo e analise o número da versão assim:
verstr = "desconhecido" experimentar: verstrline = open ('yourpackage / _version.py', "rt"). read () exceto EnvironmentError: passe # Ok, não há arquivo de versão. outro: VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) se mo: verstr = mo.group (1) outro: raise RuntimeError ("incapaz de encontrar a versão em yourpackage / _version.py")
Finalmente, na yourbasedir/yourpackage/__init__.py
importação _version assim:
__version__ = "desconhecido" experimentar: de _version import __version__ exceto ImportError: # Estamos rodando em uma árvore que não possui _version.py, por isso não sabemos qual é a nossa versão. passar
Um exemplo de código que faz isso é o pacote "pyutil" que eu mantenho. (Consulte PyPI ou pesquisa no google - o stackoverflow está me impedindo de incluir um hiperlink para ele nesta resposta.)
@pjeby está certo em não importar seu pacote de seu próprio setup.py. Isso funcionará quando você testá-lo, criando um novo interpretador Python e executando o setup.py nele: primeiro python setup.py
, mas há casos em que ele não funciona. Isso porque import youpackage
não significa ler o diretório de trabalho atual para um diretório chamado "yourpackage", significa procurar no atual sys.modules
por uma chave "yourpackage" e, em seguida, fazer várias coisas se não estiver lá. Por isso, sempre funciona quando você faz, python setup.py
porque você tem um novo, vazio sys.modules
, mas isso não funciona em geral.
Por exemplo, e se o py2exe estiver executando o setup.py como parte do processo de empacotamento de um aplicativo? Eu já vi um caso como este em que py2exe colocaria o número da versão incorreta em um pacote porque o pacote estava obtendo o número da versão emimport myownthing
em seu setup.py, mas uma versão diferente desse pacote havia sido importada anteriormente durante a execução do py2exe. Da mesma forma, e se setuptools, easy_install, distribuir ou distutils2 estiver tentando criar seu pacote como parte de um processo de instalação de um pacote diferente que depende do seu? Então, se o seu pacote é importável no momento em que seu setup.py está sendo avaliado, ou se já existe uma versão do seu pacote que foi importada durante a vida deste intérprete Python, ou se a importação do seu pacote exige que outros pacotes sejam instalados primeiro , ou tem efeitos colaterais, pode alterar os resultados. Eu tive várias dificuldades ao tentar reutilizar pacotes Python, o que causou problemas para ferramentas como py2exe e setuptools, porque o setup.py importa o próprio pacote para encontrar o número da versão.
A propósito, essa técnica funciona bem com ferramentas para criar automaticamente o yourpackage/_version.py
arquivo para você, por exemplo, lendo seu histórico de controle de revisões e escrevendo um número de versão com base na tag mais recente do histórico de controle de revisões. Aqui está uma ferramenta que faz isso para darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst e aqui está um trecho de código que faz a mesma coisa para o git: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Isso também deve funcionar, usando expressões regulares e dependendo dos campos de metadados para ter um formato como este:
__fieldname__ = 'value'
Use o seguinte no início de seu setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Depois disso, você pode usar os metadados no seu script assim:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Com uma estrutura como esta:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
onde version.py contém:
__version__ = 'version_string'
Você pode fazer isso em setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Isso não causará nenhum problema com quaisquer dependências que você tenha no seu mymodule / __ init__.py
Para evitar importar um arquivo (e, portanto, executar seu código), é possível analisá-lo e recuperar o version
atributo da árvore de sintaxe:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
Este código tenta encontrar a __version__
ou a VERSION
atribuição no nível superior do retorno do módulo é o valor da string. O lado direito pode ser uma string ou uma tupla.
Existem mil maneiras de esfolar um gato - eis as minhas:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
Limpando https://stackoverflow.com/a/12413800 do @ gringo-suave:
from itertools import ifilter
from os import path
from ast import parse
with open(path.join('package_name', '__init__.py')) as f:
__version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
f))).body[0].value.s
Agora, isso é grosseiro e precisa ser aprimorado (pode até haver uma chamada de membro descoberta em pkg_resources que eu perdi), mas eu simplesmente não vejo por que isso não funciona, nem por que ninguém sugeriu isso até agora (pesquisar no Google não aumentou isso) ... observe que este é o Python 2.xe requereria o pkg_resources (sigh):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
Queríamos colocar as informações meta sobre o nosso pacote pypackagery
no __init__.py
, mas não podia, uma vez que tem dependências de terceiros como PJ Eby já apontado (ver sua resposta e a advertência sobre a condição de corrida).
Resolvemos isso criando um módulo separado pypackagery_meta.py
que contém apenas as meta informações:
"""Define meta information about pypackagery package."""
__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
depois importou as meta informações em packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
e finalmente o usou em setup.py
:
import pypackagery_meta
setup(
name=pypackagery_meta.__title__,
version=pypackagery_meta.__version__,
description=pypackagery_meta.__description__,
long_description=long_description,
url=pypackagery_meta.__url__,
author=pypackagery_meta.__author__,
author_email=pypackagery_meta.__author_email__,
# ...
py_modules=['packagery', 'pypackagery_meta'],
)
Você deve incluir pypackagery_meta
no seu pacote o py_modules
argumento de instalação. Caso contrário, você não poderá importá-lo na instalação, pois a distribuição empacotada não possui.
Simples e direto, crie um arquivo chamado source/package_name/version.py
com o seguinte conteúdo:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Em seguida, no seu arquivo source/package_name/__init__.py
, você importa a versão para outras pessoas usarem:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Agora, você pode colocar isso setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
Testado isso com Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
e 3.7
no Linux, Windows e Mac OS. Eu usei no meu pacote que possui testes de integração e unidade para todas as plataformas de teses. Você pode ver os resultados de .travis.yml
e appveyor.yml
aqui:
Uma versão alternativa está usando o gerenciador de contexto:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Você também pode usar o codecs
módulo para lidar com erros unicode no Python 2.7
e no3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Se você estiver escrevendo um módulo Python 100% em C / C ++ usando extensões Python C, poderá fazer a mesma coisa, mas usando C / C ++ em vez de Python.
Nesse caso, crie o seguinte setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
Que lê a versão do arquivo version.h
:
const char* __version__ = "1.0.12";
Mas, não esqueça de criar MANIFEST.in
o version.h
arquivo para incluir :
include README.md
include LICENSE.txt
recursive-include source *.h
E é integrado ao aplicativo principal com:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
Referências:
implante pacote no servidor e convenção de nomenclatura de arquivos para pacotes de índices:
exemplo para conversão de versão dinâmica pip:
ganhar:
Mac:
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
Encontre o exemplo setup.py chama a versão do pip dinâmico correspondente ao git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Eu estou usando uma variável de ambiente como abaixo
VERSION = 0.0.0 python setup.py sdist bdist_wheel
Em setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Para verificação de consistência com a versão do empacotador, estou usando o script abaixo.
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi