distutils
、setuptools
などを使用すると、setup.py
でパッケージバージョンが指定されます。
# file: setup.py
...
setup(
name='foobar',
version='1.0.0',
# other attributes
)
パッケージ内から同じバージョン番号にアクセスできるようにしたい:
>>> import foobar
>>> foobar.__version__
'1.0.0'
パッケージの__init__.pyに__version__ = '1.0.0'
を追加できましたが、パッケージに追加のインポートを含めて、パッケージへの単純化されたインターフェースを作成したいと思います。
# 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とパッケージでパッケージバージョンを共有する正しい方法は何ですか?
バージョンをsetup.py
のみに設定し、独自のバージョンを pkg_resources
で読み取り、setuptools
メタデータを効率的に照会します。
ファイル:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
ファイル:__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
私はこれに正統的な答えがあるとは思わないが、私の方法(直接コピーするか、他のさまざまな場所で見たものから少し調整した)は次のとおりです:
フォルダー階層(関連ファイルのみ):
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.
)
...これは罪のようにいです...しかしそれは機能しますし、もしあればもっと良い方法を知りたいと思っている人々によって配布されたパッケージでそれやそのようなものを見てきました。
@ stefano-mの哲学 について同意します:
ソースにversion= "x.y.z"を設定し、setup.py内で解析することは間違いなく正しい解決策です。ランタイムマジックに依存するよりもはるかに優れています。
そして、この答えは@ zero-piraeusの answer から派生しています。要点は、「setup.pyでインポートを使用せず、代わりにファイルからバージョンを読み取る」ことです。
正規表現を使用して__version__
を解析し、専用ファイルの最後の行にする必要がないようにします。実際、プロジェクトの__version__
の中に、単一の真実のソース__init__.py
を配置しています。
フォルダ階層(関連ファイルのみ):
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パッケージングドキュメント (およびその ミラー )には、より多くのオプションが記載されています。最初のオプションも正規表現を使用しています。使用する正確な正規表現。バージョン文字列内の引用符を処理する場合もしない場合もあります。一般的には大きな問題ではありません。)
PPS: ADAL Pythonの修正 は、この回答にバックポートされました。
__version__
in 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 <3.4を使用する場合、 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
。
受け入れられた答え とコメントに基づいて、これは私がやったことです:
ファイル:setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
ファイル:__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
が要求されます
プロジェクトが実際にインストールされている場合にのみ、追加のインポート(簡易パッケージインターフェイス用)が発生します。
受け入れられた回答では、パッケージがインストールされている必要があります。私の場合、ソース__version__
からインストールパラメーター(setup.py
を含む)を抽出する必要がありました。 setuptoolsパッケージのテスト を見ていると、直接かつ簡単な解決策が見つかりました。 _setup_stop_after
属性に関する詳細情報を探すと、 古いメーリングリストの投稿 につながり、distutils.core.run_setup
に言及しました 実際に必要なドキュメント 。結局のところ、ここに簡単な解決策があります。
ファイル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='[email protected]',
license='MIT',
packages=['funniest'],
Zip_safe=False)
ファイルextract.py
:
from distutils.core import run_setup
dist = run_setup('./setup.py', stop_after='init')
dist.get_version()