パッケージからsetup.py
で定義されたバージョンを(--version
またはその他の目的で)取得するにはどうすればよいですか?
実行時にパッケージ内からバージョン(実際に質問しているように見えるもの)を取得するには、次を使用できます。
_import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
_
別の方法でやりたい場合は(ここで他の回答の作成者が尋ねていると思われるものと思われる)、バージョン文字列を別のファイルに入れ、そのファイルの内容を_setup.py
_で読み取ります。
パッケージ内で___version__
_行を使用してversion.pyを作成し、execfile('mypackage/version.py')
を使用してsetup.pyから読み取って、setup.py名前空間に___version__
_を設定できます。 。
すべてのPythonバージョンと、バージョン文字列へのアクセスが必要な非Python言語でさえも動作するはるかに簡単な方法が必要な場合:
バージョン文字列を、プレーンテキストファイルの唯一のコンテンツとして保存します。 VERSION
、および_setup.py
_の間にそのファイルを読み取ります。
_version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
_
同じVERSION
ファイルは、他のプログラム(Python以外のプログラムでも)とまったく同じように機能し、すべてのプログラムの1つの場所でバージョン文字列を変更するだけで済みます。
ところで、ここの別の回答で示唆されているように、パッケージを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
mymodule
をインポートするsetup.py
がmymodule
をインポートする場合、セットアップ中おそらくImportError
を取得します。これは、パッケージに依存関係がある場合の非常に一般的なエラーです。パッケージに組み込み以外の依存関係がない場合は、安全かもしれません。ただし、これは良い方法ではありません。その理由は、将来にわたって保証されないためです。明日あなたのコードは他の依存関係を消費する必要があると言ってください。
__version__
はどこですか?__version__
にsetup.py
をハードコーディングすると、モジュールに同梱されるバージョンと一致しない場合があります。一貫性を保つために、1つの場所に配置し、必要なときに同じ場所から読み取ります。 import
を使用すると、問題#1が発生する場合があります。
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'
このように、バージョンはモジュールに付属しており、セットアップ中に依存関係が欠落している(まだインストールされていない)モジュールをインポートしようとしても問題はありません。
最良の方法は、製品コードで__version__
を定義し、そこからsetup.pyにインポートすることです。これにより、実行中のモジュールで読み取ることができ、定義する場所は1つだけになります。
Setup.pyの値はインストールされず、setup.pyはインストール後に保持されません。
Coverage.pyで(たとえば)私がしたこと:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
更新(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はやや使い捨てのモジュールなので、少しsoい場合は問題になりません。
ソースツリーにファイルを作成します。 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#さて、バージョンファイルはありません。 else: VSRE = r "^ __ version__ = ['\"]([^'\"] *)['\"]" mo = re.search(VSRE、verstrline、re.M) if mo: verstr = mo.group(1) else : raiseRuntimeError( "yourpackage/_version.pyでバージョンが見つかりません")
最後に、yourbasedir/yourpackage/__init__.py
import _versionで次のようにします。
__ version__ = "unknown" try: from _version import __version __ except ImportError: #実行していないツリーで_version.pyがあるため、バージョンがわかりません。 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
からsetup.pyでバージョン番号を取得していたのに、py2exeがパッケージに間違ったバージョン番号を付けるようなケースを見てきましたが、そのパッケージの異なるバージョンが以前にインポートされていましたpy2exeの実行中。同様に、setuptools、easy_install、distribute、またはdistutils2が、パッケージに依存する別のパッケージをインストールするプロセスの一部としてパッケージをビルドしようとした場合はどうなりますか?その後、パッケージがsetup.pyの評価時にインポート可能か、このPythonインタープリターの存続中にインポートされたバージョンのパッケージが既に存在するか、またはインポートするかパッケージには、他のパッケージを最初にインストールする必要があるか、副作用があり、結果が変わる可能性があります。Pythonパッケージを再利用しようとすると、 py2exeおよびsetuptoolsは、setup.pyがバージョン番号を見つけるためにパッケージ自体をインポートするためです。
ちなみに、この手法は、たとえば、リビジョン管理履歴を読み取り、リビジョン管理履歴の最新のタグに基づいてバージョン番号を書き出すことで、自動的に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
このような構造では:
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にある依存関係に問題は発生しません
これは、正規表現を使用し、メタデータフィールドに応じて次のような形式にすることでも機能します。
__field= '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']
ファイルのインポート(およびコードの実行)を回避するには、ファイルを解析し、構文ツリーから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/124138 @ 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
パッケージpypackagery
のメタ情報を__init__.py
に入れたかったのですが、PJ Ebyが既に指摘したようにサードパーティの依存関係があるため、できませんでした(彼の答えと競合状態に関する警告を参照してください) )。
onlyメタ情報を含む別のモジュール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__ = '[email protected]'
__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で公開されていないメンバー呼び出しでさえ見逃したこともあります)が、なぜこれが機能しないのか、誰も今日まで示唆していないのかわかりません(Googling aroundはこれは判明していません)...これは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
でテストしました、WindowsおよびMac OS。すべてのこれらのプラットフォームの統合および単体テストが含まれるパッケージで使用しました。.travis.yml
およびappveyor.yml
の結果はこちらで確認できます。
別のバージョンがコンテキストマネージャーを使用しています。
#!/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 ) )
Python 2.7
と3.6
]の両方でcodecs
モジュールを使用してUnicodeエラーを処理することもできます。
#!/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モジュール100%を使用してC/C++でPython C拡張機能を使用している場合、同じことを行うことができますが、C/C++ Python。
この場合、次の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__ ) );
...
}
参照: