Каков правильный способ поделиться версией пакета setup.py а посылка?
С distutils
,setuptools
и т. д. версия пакета указана в setup.py
:
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
Я хотел бы иметь возможность получить доступ к тому же номеру версии из пакета:
>>> import foobar
>>> foobar.__version__
'1.0.0'
я мог бы добавить __version__ = '1.0.0'
к моему пакету __init__.py, но я также хотел бы включить дополнительный импорт в мой пакет, чтобы создать упрощенный интерфейс к пакету:
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = '1.0.0'
и
# file: setup.py
from foobar import __version__
...
setup(
name='foobar',
version=__version__,
# other attributes
)
однако, эти дополнительные ввозы могут причина установки foobar
сбой, если они импортируют другие пакеты, которые еще не установлены. Каков правильный способ поделиться версией пакета setup.py а посылка?
7 ответов:
установите версию в
setup.py
только, и читать свою собственную версию сpkg_resources
, фактически запрашиваяsetuptools
метаданных:file:
setup.py
setup( name='foobar', version='1.0.0', # other attributes )
file:
__init__.py
from pkg_resources import get_distribution __version__ = get_distribution('foobar').version
чтобы сделать эту работу во всех случаях, где вы могли бы в конечном итоге работает без установки, тест для
DistributionNotFound
и место распространения:from pkg_resources import get_distribution, DistributionNotFound import os.path try: _dist = get_distribution('foobar') # Normalize case for Windows systems dist_loc = os.path.normcase(_dist.location) here = os.path.normcase(__file__) if not here.startswith(os.path.join(dist_loc, 'foobar')): # not installed, but there is another version that *is* raise DistributionNotFound except DistributionNotFound: __version__ = 'Please install this project with setup.py' else: __version__ = _dist.version
Я не верю, что есть канонический ответ на это, но мой метод (либо непосредственно скопированный, либо слегка измененный из того, что я видел в разных других местах) выглядит следующим образом:
папка heirarchy (только соответствующие файлы):
package_root/ |- main_package/ | |- __init__.py | `- _version.py `- setup.py
main_package/_version.py
:"""Version information.""" # The following line *must* be the last in the module, exactly as formatted: __version__ = "1.0.0"
main_package/__init__.py
:"""Something nice and descriptive.""" from main_package.some_module import some_function_or_class # ... etc. from main_package._version import __version__ __all__ = ( some_function_or_class, # ... etc. )
setup.py
:from setuptools import setup setup( version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("\"'"), # ... etc. )
... что безобразно, как грех ... но это работает, и я видел это или что-то подобное в пакетах, распространяемых людьми, которые, как я ожидал, знали бы лучший способ, если бы он был.
Я согласен с @Стефано-м 'ы философии о:
С версия = "x. y. z" в источнике и разбор его внутри setup.py это определенно правильное решение, ИМХО. Гораздо лучше, чем (наоборот) полагаясь на магию времени выполнения.
и этот ответ получен из @zero-piraeus ' s ответ. Все дело в том, что " не используйте импорт в setup.py вместо этого прочитайте версию из a папка."
Я использую регулярное выражение для разбора
__version__
так что это не должно быть последней строкой выделенного файла вообще. На самом деле, я все еще ставлю единственный источник истины__version__
внутри .папка heirarchy (только соответствующие файлы):
package_root/ |- main_package/ | `- __init__.py `- setup.py
main_package/__init__.py
:# You can have other dependency if you really need to from main_package.some_module import some_function_or_class # Define your version number in the way you mother told you, # which is so straightforward that even your grandma will understand. __version__ = "1.2.3" __all__ = ( some_function_or_class, # ... etc. )
setup.py
:from setuptools import setup import re, io __version__ = re.search( r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too io.open('main_package/__init__.py', encoding='utf_8_sig').read() ).group(1) # The beautiful part is, I don't even need to check exceptions here. # If something messes up, let the build process fail noisy, BEFORE my release! setup( version=__version__, # ... etc. )
... который все еще не идеален ... но это завод.
и кстати, на этом этапе вы можете проверить свою новую игрушку следующим образом:
python setup.py --version 1.2.3
PS: Это официальный упаковочный документ Python (и зеркала) описывает несколько вариантов. Его первый вариант также использует регулярное выражение. (В зависимости от точного регулярного выражения, которое вы используете, оно может обрабатывать или не обрабатывать кавычки внутри строки версии. Вообще не большая проблема, хотя.)
ППС: о исправить в Adal Python теперь backported в этот ответ.
поставить
__version__
наyour_pkg/__init__.py
, и разобрать вsetup.py
используяast
:import ast import importlib.util from pkg_resources import safe_name PKG_DIR = 'my_pkg' def find_version(): """Return value of __version__. Reference: https://stackoverflow.com/a/42269185/ """ file_path = importlib.util.find_spec(PKG_DIR).origin with open(file_path) as file_obj: root_node = ast.parse(file_obj.read()) for node in ast.walk(root_node): if isinstance(node, ast.Assign): if len(node.targets) == 1 and node.targets[0].id == "__version__": return node.value.s raise RuntimeError("Unable to find version string.") setup(name=safe_name(PKG_DIR), version=find_version(), packages=[PKG_DIR], ... )
при использовании Python
importlib.util.find_spec
не имеется. Кроме того, каких-либо портировать изimportlib
конечно, нельзя полагаться на доступностьsetup.py
. В этом случае используйте:import os file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, '__init__.py')
есть несколько методов, предлагаемых в направляющие для упаковки on
python.org
.
на основе принято отвечать и комментарии, это то, что я закончил делать:
file:
setup.py
setup( name='foobar', version='1.0.0', # other attributes )
file:
__init__.py
from pkg_resources import get_distribution, DistributionNotFound __project__ = 'foobar' __version__ = None # required for initial installation try: __version__ = get_distribution(__project__).version except DistributionNotFound: VERSION = __project__ + '-' + '(local)' else: VERSION = __project__ + '-' + __version__ from foobar import foo from foobar.bar import Bar
объяснение:
__project__
это имя проекта для установки, который может быть отличается от имени пакета
VERSION
это то, что я показываю в интерфейсах командной строки, когда--version
is просилтолько дополнительный импорт (для упрощенного интерфейса пакета) произойдет, если проект действительно был установлен
принятый ответ требует, чтобы пакет был установлен. В моем случае мне нужно было извлечь параметры установки (в том числе
__version__
) из источникаsetup.py
. Я нашел прямое и простое решение, просматривая тесты пакета setuptools. В поисках дополнительной информации о_setup_stop_after
атрибут приведет меня к старый список рассылки пост котором упоминаетсяdistutils.core.run_setup
, что привело меня к фактические документы нужны. После всего этого, вот простое решение:file
setup.py
:from setuptools import setup setup(name='funniest', version='0.1', description='The funniest joke in the world', url='http://github.com/storborg/funniest', author='Flying Circus', author_email='flyingcircus@example.com', license='MIT', packages=['funniest'], zip_safe=False)
file
extract.py
:from distutils.core import run_setup dist = run_setup('./setup.py', stop_after='init') dist.get_version()