パッケージのsetup.py(setuptools)で定義されているバージョンを取得するにはどうすればよいですか?


回答:


246

インストール済みのディストリビューションのバージョン文字列を調べる

実行時にパッケージ内からバージョンを取得するには(質問が実際に尋ねているように見えます)、次を使用できます。

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version

インストール中に使用するバージョン文字列を保存します

別の方法でやりたい場合(これは、他の回答者がここであなたが尋ねていたと思っていたように思われるものです)、バージョン文字列を別のファイルに入れ、そのファイルの内容をで読み取りますsetup.py

パッケージでversion.pyを1 __version__行で作成し、を使用してsetup.pyから読み取って、setup.py名前空間にexecfile('mypackage/version.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からパッケージをインポートしないでください。これは、(パッケージの依存関係がすでにインストールされているため)機能すると思われますが、パッケージの新しいユーザーに大混乱をもたらします。 、依存関係を最初に手動でインストールしないとパッケージをインストールできないためです。


1
これは単にここでの最良の答えです。パッケージからバージョンを取得するためにsetup.pyが必要な場合は、execfileを使用するのが最良の方法です。
2012

2
execfile本当にうまく動作します...しかし、(悲しげに)はPython 3では動作しません
medmunds

9
... OK、Python 2 および 3 with open('mypackage/version.py') as f: exec(f.read())ではの代わりに使用しexecfile('mypackage/version.py')ます。(stackoverflow.com/a/437857/647002から)
medmunds

5
wreak havocの部分は必ずしも真実ではありません... パッケージ内のinit .pyファイルにコードインポートの依存関係(直接または間接)がある場合にのみ、大混乱を引き起こします。
Pykler 2013

2
djangoがsetup.pyで __import__呼び出し実行することは言及する価値があるようです。「__import__」対「インポート」はこれを安全にしますか?問題を引き起こしているようには見えません。
user1978019 '13

33

調査例: 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あなたを使用すると、問題#1が発生する可能性があります。

解決策:アラ setuptools

あなたは、の組み合わせを使用するopenexecとのためのdictの提供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'

このようにして、バージョンはモジュールに同梱され、依存関係のない(まだインストールされていない)モジュールをインポートしようとしてセットアップ中に問題が発生することはありません。


2
許可された依存関係のみに依存するため、これは最も合理的な解決策のようです。
Dogweather、2015

setup.pyファイルで定義されたパッケージのバージョンの概念が異なっているバージョン
変数

16

最良の方法は__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がそれらをインストールするためです。


4
@エヴァン:「ソースからその値を引き出すだけ」について何が得られているのかわかりません。その中のファイル__version__が何らかの方法で壊れている場合、インポートも壊れます。Pythonは、必要なステートメントのみを解釈する方法を知りません。@pjebyは正しい:モジュールが他のモジュールをインポートする必要がある場合、それらはまだインストールされていない可能性があり、混乱します。この手法は、インポートが他のインポートの長いチェーンを引き起こさないように注意する場合に機能します。
Ned Batchelder 2010年

1
@Ned Batchelderソースファイルのインポートの前にバージョンを配置し、 'from module import version'のみにした場合、必要以上にソースファイルをレキシングしないと確信しています。その上、壊れたコードをリリースするのは誰ですか?パッケージに依存関係が必要な場合は、setuptoolsを使用するか、年内のdistutils2のリリースを待ちます。
Evan Plaice

15
@エヴァン、申し訳ありませんが、部分ファイルのインポートについてはあなたが間違っています。長いモジュールの最後に印刷ステートメントを置き、ファイルで定義されている最初の変数をインポートしてみてください。印刷ステートメントが実行されます。
Ned Batchelder

23
私はこれに失敗しましたが、@ pjebyは完全に正しいです。「ところで、ここの別の回答で提案されているように、setup.pyからパッケージをインポートしないでください:うまくいくようです(すでにパッケージの依存関係がインストールされているためです。 )、ただし、最初に依存関係を手動でインストールしないとパッケージをインストールできないため、パッケージの新しいユーザーに大混乱をもたらします。」ネッド、あなたの答えに警告を追加していただけませんか?
Jan-Philip Gehrcke博士2013

1
@ Jan-PhilipGehrckeのように、このようなsetup.pyファイルをPyPIにアップロードするとpip install <packagename>、次のエラーが発生しますImportError: No module named <packagename>。パッケージがまだインストールされていない環境では、このようなsetup.pyファイルを実行できないことを読者に警告してください!
ホブ2014年

14

あなたの質問は少し曖昧ですが、私はあなたが尋ねていることはそれをどのように指定するかだと思います。

次の__version__ように定義する必要があります:

__version__ = '1.4.4'

そして、setup.pyが指定したバージョンを知っていることを確認できます。

% ./setup.py --version
1.4.4

9

私はこれらの答えに満足していませんでした... 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は使い捨てのモジュールなので、少し醜い場合は問題ありません。


更新:面白いことに、私はここ数年、この場所から離れて、というパッケージで別のファイルを使用し始めましたmeta.py。私は頻繁に変更したいかもしれない多くのメタデータをそこに入れました。つまり、1つの値だけではありません。


+1。これは比較的単純で、バージョン番号を1か所に保持し、それを含めるために別のファイルを必要とせず、Pythonモジュールのインポート依存関係をsetup.pyに課しません。setup.pyのように短命なプログラムでは、コンテキストマネージャーに煩わされることもありません。
ʇsәɹoɈ

正規表現を使用するよりもはるかに優れています。ありがとうございます。また、ソースなどから自動的に入力するためにast.get_docstring()、いくつかを使用することもできます。同期を維持するためのもう1つのこと.split('\n')[0].strip()description
失わ

4

たとえばyourbasedir / yourpackage / _version.pyのように、ソースツリーにファイルを作成します。次のように、そのファイルにコードを1行だけ含めます。

__version__ = "1.1.0-r4704"

次に、setup.pyでそのファイルを開き、次のようにバージョン番号を解析します。

verstr = "不明"
試してください:
    verstrline = open( 'yourpackage / _version.py'、 "rt")。read()
EnvironmentErrorを除く:
    pass#さて、バージョンファイルはありません。
そうしないと:
    VSRE = r "^ __ version__ = ['\"]([^' \ "] *)['\"] "
    mo = re.search(VSRE、バーストライン、re.M)
    もしも:
        verstr = mo.group(1)
    そうしないと:
        RuntimeError( "yourpackage / _version.pyでバージョンが見つかりません")を発生させます

最後に、yourbasedir/yourpackage/__init__.pyimport _versionでは次のようにします。

__version__ = "不明"
試してください:
    from _version import __version__
ImportErrorを除く:
    #_version.pyがないツリーで実行しているため、バージョンがわかりません。
    パス

これを行うコードの例は、私が保守している「pyutil」パッケージです。(PyPIまたはgoogle searchを参照-stackoverflowは、この回答へのハイパーリンクを含めることを許可していません。)

@pjebyは、独自のsetup.pyからパッケージをインポートしてはいけないのは正しいことです。これは、新しいPythonインタープリターを作成し、最初にsetup.pyを実行してテストすると機能しますpython setup.pyが、機能しない場合もあります。これimport youpackageは、「yourpackage」という名前のディレクトリの現在の作業ディレクトリを読み取ることを意味するのではなく、現在sys.modulesのキー「yourpackage」を探して、そこにない場合はさまざまなことを行うことを意味します。つまりpython setup.py、新鮮で空のがあるため、いつでも機能しますsys.modulesが、これは一般的に機能しません。

たとえば、アプリケーションのパッケージ化プロセスの一部としてpy2exeがsetup.pyを実行している場合はどうなりますか?パッケージがそのバージョン番号を取得していたため、py2exeがパッケージに間違ったバージョン番号を配置するこのようなケースを見ましたimport myownthingsetup.pyにありますが、py2exeの実行中に、そのパッケージの異なるバージョンが以前にインポートされていました。同様に、setuptools、easy_install、distribute、またはdistutils2が、パッケージに依存する別のパッケージをインストールするプロセスの一部としてパッケージをビルドしようとしている場合はどうなりますか?次に、setup.pyの評価時にパッケージがインポート可能かどうか、このPythonインタープリターの存続期間中にインポートされたパッケージのバージョンがすでに存在するかどうか、またはパッケージをインポートするには他のパッケージを最初にインストールする必要があるかどうか、または副作用があり、結果が変わる可能性があります。バージョン番号を見つけるためにsetup.pyがパッケージ自体をインポートするため、py2exeやsetuptoolsなどのツールに問題を引き起こすPythonパッケージを再利用しようとするのにいくつかの苦労がありました。

ちなみに、この手法は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


3

これも機能し、正規表現を使用し、メタデータフィールドに応じて次のような形式になります。

__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']

私は上記に述べたように、単に使用str.split :)
エリックアラウージョ

ああ、神様。あなたはPythonでPythonを解析していますか?少なくとも、eval()の子を使用してください。
slacy

3
貧弱なアドバイスは怠惰です。とても簡単なときはevalを避けてください。
Gringo Suave

3

このような構造で:

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にある依存関係に問題を引き起こしません


2

ファイルのインポート(およびそのコードの実行)を回避するには、ファイルを解析して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割り当てを文字列値で見つけようとします。右側は文字列またはタプルです。


3
それは巧妙で複雑に見えます:) 1つの割り当てを含む_version.pyファイルを用意し、それをopen-read-str.splitで解析する方が簡単です。
エリックアラウージョ

これを投稿してくれてありがとう。この状況では複雑すぎると思いますが、問題への取り組み方を理解するのに非常に役立ちます。バージョン番号を1か所に保持し、それを含めるために別のファイルを必要とせず、Pythonモジュールのインポート依存関係をセットアップスクリプトに課さないことが好きです。
ʇsәɹoɈ

2

猫の皮を剥く方法は千通りあります-これが私のものです:

# 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.")

2

@ gringo-suaveから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

2

これは全体的であり、いくつかの調整が必要です(私が見逃した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

2

私たちは、パッケージに関するメタ情報を入れたかった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_metapy_modules使用してパッケージに含める必要があります。そうしないと、パッケージ化されたディストリビューションにはないため、インストール時にインポートできません。


1

シンプルでわかりやすい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.73.33.43.53.6および3.7Linuxでは、WindowsとMac OS上。私は、これらすべてのプラットフォームの統合テストと単体テストが含まれているパッケージを使用しました。あなたはその結果から見ることができます.travis.ymlし、appveyor.ymlここに:

  1. https://travis-ci.org/evandrocoan/debugtools/builds/527110800
  2. 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 2.7とUnicodeの両方でUnicodeエラーを処理することもできます。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 Extensionsを使用してC / C ++で100%Pythonモジュールを作成している場合、同じことを実行できますが、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__ ) );

    ...
}

参照:

  1. pythonオープンファイルエラー
  2. C APIからPythonモジュールでグローバルを定義する
  3. setuptools / distributeにパッケージデータを含める方法は?
  4. https://github.com/lark-parser/lark/blob/master/setup.py#L4
  5. 同じ名前のsetuptoolsパッケージとext_modulesを使用するにはどうすればよいですか?
  6. パッケージデータの一部としてdist utils(setup.py)を使用してサブディレクトリを含めることはできますか?

1

パッケージをサーバーに展開し、インデックスパッケージのファイル命名規則:

pip動的バージョン変換の例:

  • 勝つ:

    • test_pkg-1.0.0-cp36-cp36m-win_amd64.whl
    • test_pkg-1.0.0-py3.6-win-amd64.egg
  • マック:

    • test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.egg
    • test_pkg-1.0.0-py3.7-macosx-10.12-x86_64.whl
  • Linux:
    • test_pkg-1.0.0-cp36-cp36m-linux_x86_64.whl
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

setup.pyがgit commitから一致する動的pipバージョンを呼び出すサンプルを見つけます

setup(
    version=_get_version(),
    name=NAME,
    description=DESCRIPTION,
    long_description=LONG_DESCRIPTION,
    classifiers=CLASSIFIERS,

# add few more for wheel wheel package ...conversion

)

0

以下のように環境変数を使用しています

VERSION = 0.0.0 python setup.py sdist bdist_wheel

setup.py


import os

setup(
    version=os.environ['VERSION'],
    ...
)

パッカー版との整合性チェックのため、以下のスクリプトを使用しています。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.