web-dev-qa-db-ja.com

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

パッケージからsetup.pyで定義されたバージョンを(--versionまたはその他の目的で)取得するにはどうすればよいですか?

135
elmarco

既にインストールされているディストリビューションのバージョン文字列を調べる

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

_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からインポートしないでください:パッケージの依存関係が既にインストールされているため、それはあなたにとってはうまくいくようですが、パッケージの新しいユーザーに大混乱をもたらします、最初に依存関係を手動でインストールしないとパッケージをインストールできないためです。

226
PJ Eby

調査例: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.pymymoduleをインポートする場合、セットアップ中おそらくImportErrorを取得します。これは、パッケージに依存関係がある場合の非常に一般的なエラーです。パッケージに組み込み以外の依存関係がない場合は、安全かもしれません。ただし、これは良い方法ではありません。その理由は、将来にわたって保証されないためです。明日あなたのコードは他の依存関係を消費する必要があると言ってください。

問題#2:私の__version__はどこですか?

__version__setup.pyをハードコーディングすると、モジュールに同梱されるバージョンと一致しない場合があります。一貫性を保つために、1つの場所に配置し、必要なときに同じ場所から読み取ります。 importを使用すると、問題#1が発生する場合があります。

解決策:àla setuptools

openexecの組み合わせを使用し、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'

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

30
dnozay

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

14
Ned Batchelder

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

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

__version__ = '1.4.4'

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

% ./setup.py --version
1.4.4
13
jathanism

私はこれらの答えに満足していませんでした... 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い場合は問題になりません。

7
Gringo Suave

ソースツリーにファイルを作成します。 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

4
Zooko

このような構造では:

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にある依存関係に問題は発生しません

3
Jahid

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

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

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

2
Mikhail Edoshin

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

# 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
Marc Abramowitz

クリーンアップ 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
2
A T

パッケージ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をパッケージに含める必要があります。そうしないと、パッケージ化されたディストリビューションに不足するため、インストール時にインポートできません。

2
marko.ristin

これは大雑把で洗練が必要です(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
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.7でテストしました、WindowsおよびMac OS。すべてのこれらのプラットフォームの統合および単体テストが含まれるパッケージで使用しました。.travis.ymlおよびappveyor.ymlの結果はこちらで確認できます。

  1. https://travis-ci.org/evandrocoan/debugtools/builds/5271108
  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 ) )

Python 2.73.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__ ) );

    ...
}

参照:

  1. pythonファイルオープンエラー
  2. 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)を使用してサブディレクトリを含めることは可能ですか?
0
user