パッケージ内の setup.py (setuptools) で定義されているバージョンを取得するにはどうすればよいですか?
-
20-09-2019 - |
質問
で定義されているバージョンを取得するにはどうすればよいですか setup.py
私のパッケージから( --version
, 、または他の目的)?
解決
実行時にパッケージ内部からのバージョンを取得するには(あなたの質問は、実際に尋ねると思われるもの)は、使用することができます:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
使用のためのストアバージョン文字列は、インストール中に
あなたは(他の回答著者はここにあなたが求めていたと思っているように見える何のように見える)他の方法」ラウンドに行きたい場合は、別のファイルにバージョン文字列を入れて、setup.py
で、そのファイルの内容を読み取ります。
__version__
を設定するようにあなたは、その後、execfile('mypackage/version.py')
を使用してsetup.pyからそれを読んで、__version__
ラインを使ってパッケージにversion.pyを作ることができます。
あなたがバージョン文字列へのアクセスを必要とする可能性のあるすべてのPythonのバージョン、さらには非Pythonの言語で動作しますはるかに簡単な方法をしたい場合:
などという名前のプレーンテキストファイルの唯一の内容として、バージョン文字列を保存VERSION
、およびsetup.py
中にそのファイルを読み込みます。
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
同じVERSION
ファイルは、その後も、非Pythonのもの、他のプログラムでも同様に正確に動作します、そしてあなただけのすべてのプログラムのために一つの場所にバージョン文字列を変更する必要があります。
警告 (あなたはすでにあなたのパッケージの依存関係がインストールされているので)あなたのために働くように見えますが、それは新しいユーザー時に大混乱をもたらすでしょう。
ここでは別の答えで提案されているようにところで、あなたのsetup.pyからあなたのパッケージをインポートしないでくださいあなたのパッケージの、彼らは手動で最初の依存関係をインストールすることなく、あなたのパッケージをインストールすることはできませんと。
他のヒント
例の研究:mymodule
この設定を想像します:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
そして、あなたが依存関係を持っているとsetup.py
がどのように見えるいくつかの一般的なシナリオを想像してみて
setup(...
install_requires=['dep1','dep2', ...]
...)
そして例の__init__.py
ます:
from mymodule.myclasses import *
from mymodule.version import __version__
そして、例えばmyclasses.py
ます:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
問題#1:セットアップ中にmymodule
をインポートする
あなたのsetup.py
の輸入は、その後mymodule
場合の設定の中にあなたが最も可能性の高いImportError
になるだろう。これはあなたのパッケージが依存関係を持っている非常に一般的なエラーです。あなたのパッケージが組み込みコマンド以外の依存関係を持っていない場合は、安全であってもよく、しかし、これは良い方法ではありません。その理由は、それが将来性はないということです。あなたのコードは、いくつかの他の依存関係を消費する必要が明日と言っています。
問題#2:?私の__version__
だ
あなたは__version__
でsetup.py
をハードコードした場合、それはあなたがあなたのモジュールに出荷うバージョンと一致しない場合があります。一貫性を保つためには、一つの場所にそれを入れて、あなたがそれを必要なときに同じ場所からそれを読んでいました。あなたは問題#1を得ることができimport
使用します。
ソリューション:アラカルトsetuptools
あなたは、open
の組み合わせを使用exec
と変数を追加するexec
のための辞書を提供します。
# 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__'],
...)
そしてmymodule/version.py
でバージョンを公開します:
__version__ = 'some.semantic.version'
この方法では、バージョンがモジュールに同梱されている、とあなたは依存関係が欠落したモジュールをインポートしようとしてセットアップ中に問題を持っていない(まだインストールされている)。
最高の技術がそこからsetup.pyにインポートした後、お使いの製品コードで__version__
を定義することです。これは、あなたが実行中のモジュールを読み込み、それを定義するための唯一の場所を持つことができる値が得られます。
setup.pyの値がインストールされていない、とsetup.pyは、インストール後に固執しません。
私はcoverage.pyに(例えば)でします:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE(2017):バージョンを取得するために、輸入自体はもはやcoverage.pyません。あなたが製品コードがsetup.pyがそれらをインストールするものですので、まだインストールされていない依存関係を、インポートしようとするため、独自のコードをインポートすると、それをアンインストールすることができます。
あなたの質問は少しあいまいですが、私は何を求めていることは、それを指定する方法だと思います。
あなたがそうのような__version__
を定義する必要があります:
__version__ = '1.4.4'
そして、あなたはそのsetup.pyは、あなただけの指定されたバージョンを知っているを確認することができます:
% ./setup.py --version
1.4.4
私はsetuptoolsのを必要とし、また、単一の変数のための全体の別のモジュールを作りたくなかった...これらの答えに満足していなかったので、私はこれらを思い付います。
あなたは、メインモジュールがpep8スタイルであり、そのように滞在することを確認しているときのために:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
あなたは余分な注意が必要と本当のパーサーを使用したい場合:
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
それは少し醜いである場合、setup.pyので幾分使い捨てモジュールの問題ではない。
ソースツリーにファイルを作成します。yourbasedir/yourpackage/_version.py にあります。このファイルには、次のように 1 行のコードのみが含まれるようにします。
__version__ = "1.1.0-r4704"
次に、setup.py でそのファイルを開いて、次のようにバージョン番号を解析します。
verstr = "unknown" try: verstrline = open('yourpackage/_version.py', "rt").read() except EnvironmentError: pass # Okay, there is no version file. else: VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) else: raise RuntimeError("unable to find version in yourpackage/_version.py")
最後に、 yourbasedir/yourpackage/__init__.py
このように_versionをインポートします:
__version__ = "unknown" try: from _version import __version__ except ImportError: # We're running in a tree that doesn't have a _version.py, so we don't know what our version is. pass
これを行うコードの例は、私が管理している「pyutil」パッケージです。(PyPI または Google 検索を参照してください -- stackoverflow により、この回答にハイパーリンクを含めることができなくなりました。)
@pjeby は、パッケージを独自の setup.py からインポートすべきではないという意見は正しいです。これは、新しい Python インタープリターを作成し、最初にその中で setup.py を実行してテストすると機能します。 python setup.py
, しかし、うまくいかないケースもあります。それは、 import youpackage
これは、「yourpackage」という名前のディレクトリの現在の作業ディレクトリを読み取るという意味ではなく、現在のパッケージを調べることを意味します。 sys.modules
キー「yourpackage」の場合、それが存在しない場合はさまざまな処理を行います。したがって、実行すると常に機能します python setup.py
なぜならあなたには新鮮で空っぽのものがいるからです sys.modules
, しかし、これは一般的には機能しません。
たとえば、アプリケーションをパッケージ化するプロセスの一部として py2exe が setup.py を実行している場合はどうなるでしょうか?パッケージのバージョン番号が import myownthing
しかし、そのパッケージの別のバージョンが py2exe の実行中に以前にインポートされていました。同様に、setuptools、easy_install、distribute、または distutils2 が、あなたのパッケージに依存する別のパッケージをインストールするプロセスの一部としてあなたのパッケージをビルドしようとした場合はどうなるでしょうか?次に、setup.py の評価時にパッケージがインポート可能かどうか、この Python インタープリターの有効期間中にインポートされたパッケージのバージョンがすでに存在するかどうか、またはパッケージをインポートするには最初に他のパッケージをインストールする必要があるかどうかを確認します。 、または副作用がある場合、結果が変わる可能性があります。Python パッケージを再利用しようとすると、setup.py がバージョン番号を見つけるためにパッケージ自体をインポートするため、py2exe や setuptools などのツールで問題が発生するという問題が何度かありました。
ちなみに、このテクニックは、 yourpackage/_version.py
たとえば、リビジョン管理履歴を読み取り、リビジョン管理履歴内の最新のタグに基づいてバージョン番号を書き出すことによって、ファイルを作成します。これは darcs に対してそれを行うツールです。 http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst これは、git に対して同じことを行うコード スニペットです。 http://github.com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34
このも、正規表現を使用して、仕事とこのような形式を持っているメタデータフィールドに依存する必要があります:
__fieldname__ = 'value'
あなたのsetup.pyの冒頭で次のように使用します:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
その後、あなたは次のようにスクリプトのメタデータを使用することができます:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
このような構成によると:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
ここでの version.py の含まれています:
__version__ = 'version_string'
あなたが行うことができ、この中の setup.py の
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
のこれは、あなたのmymoduleに持っているものは何でも依存関係に問題が発生することはありません/ __ __ INIT。PY の
ファイルをインポートする(したがって、そのコードを実行する)を回避するために1がそれを解析し、構文木からversion
属性を回復することができます:
# 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
このコードは、モジュールリターンの最上位レベルで__version__
又はVERSION
割り当てが文字列値で見つけようとします。右側には、文字列またはタプルのいずれかになります。
猫の皮を剥ぐために千個の方法があります - ここに私のもの:
# 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.")
のクリーンアップ https://stackoverflow.com/a/12413800 のグリンゴ、上品@から:
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
私たちは、pypackagery
で私たちのパッケージ__init__.py
に関するメタ情報を入れたかったが、PJイービがすでに(彼の答えと競合状態に関する警告を参照してください)。
私たちは、の含まれている別のモジュールのpypackagery_meta.py
を作成することによってのみのメタ情報を、それを解決しました
"""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'
は、packagery/__init__.py
にメタ情報をインポートします:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
、最後に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'],
)
あなたはpypackagery_meta
セットアップ引数を使用してパッケージにpy_modules
を含める必要があります。パッケージ配布はそれが欠けてしまうので、そうでない場合は、インストール時に、それをインポートすることはできません。
さて、これは総額であり、いくつかの精製を必要とします(でも、私は逃したpkg_resourcesで覆われていないメンバーの呼び出しがあるかもしれない)が、これは動作しませんなぜ私は単に表示されていない、また、なぜ誰も(これまでにそれを示唆していません周りグーグルでは(これは、Python 2.xのあることに注意してください...)このアップになっていない、と要求するpkg_resourcesが必要になりため息):
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
シンプルかつストレートに、というファイルを作成します。 source/package_name/version.py
以下の内容で:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
次に、ファイル上で source/package_name/__init__.py
, 、他の人が使用できるようにバージョンをインポートします。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
これで、これを履くことができます 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()
これをPythonでテストしました 2.7
, 3.3
, 3.4
, 3.5
, 3.6
そして 3.7
Linux、Windows、Mac OS 上で。私は、これらすべてのプラットフォームの統合テストと単体テストを含むパッケージを使用しました。結果はから見ることができます .travis.yml
そして appveyor.yml
ここ:
- https://travis-ci.org/evandrocoan/debugtools/builds/527110800
- https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446
代替バージョンではコンテキスト マネージャーを使用します。
#!/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 ) )
を使用することもできます。 codecs
Python の両方で Unicode エラーを処理するモジュール 2.7
そして 3.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 ) )
Python C 拡張機能を使用して Python モジュールを 100% C/C++ で作成している場合は、Python の代わりに C/C++ を使用して同じことを行うことができます。
この場合、以下を作成します 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'],
)
],
)
ファイルからバージョンを読み取ります version.h
:
const char* __version__ = "1.0.12";
ただし、作成することを忘れないでください。 MANIFEST.in
を含める version.h
ファイル:
include README.md
include LICENSE.txt
recursive-include source *.h
そして、それは以下を備えたメインアプリケーションに統合されています。
#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__ ) );
...
}
参考文献: