Câu trả lời:
Để truy xuất phiên bản từ bên trong gói của bạn khi chạy (câu hỏi của bạn thực sự đang hỏi), bạn có thể sử dụng:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Nếu bạn muốn đi theo hướng khác (dường như là những gì các tác giả câu trả lời khác ở đây dường như nghĩ rằng bạn đang hỏi), hãy đặt chuỗi phiên bản vào một tệp riêng và đọc nội dung của tệp đó setup.py
.
Bạn có thể tạo một phiên bản gói trong gói của mình bằng một __version__
dòng, sau đó đọc nó từ setup.py bằng cách sử dụng execfile('mypackage/version.py')
nó để đặt __version__
trong không gian tên setup.py.
Nếu bạn muốn một cách đơn giản hơn nhiều sẽ hoạt động với tất cả các phiên bản Python và ngay cả các ngôn ngữ không phải Python có thể cần truy cập vào chuỗi phiên bản:
Lưu trữ chuỗi phiên bản dưới dạng nội dung duy nhất của tệp văn bản thuần, có tên ví dụ VERSION
và đọc tệp đó trong thời gian setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Sau đó, cùng một VERSION
tệp sẽ hoạt động chính xác trong bất kỳ chương trình nào khác, ngay cả những người không phải là Python và bạn chỉ cần thay đổi chuỗi phiên bản ở một nơi cho tất cả các chương trình.
Nhân tiện, KHÔNG nhập gói của bạn từ setup.py như được đề xuất trong câu trả lời khác ở đây: có vẻ như nó sẽ hiệu quả với bạn (vì bạn đã cài đặt các phụ thuộc của gói), nhưng nó sẽ phá hỏng người dùng mới của gói của bạn , vì họ sẽ không thể cài đặt gói của bạn mà không cài đặt thủ công các phụ thuộc trước.
execfile
hoạt động rất tốt ... nhưng (đáng buồn thay) không hoạt động với Python 3.
with open('mypackage/version.py') as f: exec(f.read())
thay cho execfile('mypackage/version.py')
. (Từ stackoverflow.com/a/437857/647002 )
mymodule
Hãy tưởng tượng cấu hình này:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Sau đó tưởng tượng một số kịch bản thông thường nơi bạn có phụ thuộc và setup.py
trông giống như:
setup(...
install_requires=['dep1','dep2', ...]
...)
Và một ví dụ __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
Và ví dụ myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
trong khi thiết lậpNếu bạn setup.py
nhập mymodule
sau đó trong quá trình thiết lập, rất có thể bạn sẽ nhận được ImportError
. Đây là một lỗi rất phổ biến khi gói của bạn có phụ thuộc. Nếu gói của bạn không có các phụ thuộc khác ngoài nội dung, bạn có thể an toàn; tuy nhiên đây không phải là một thực hành tốt. Lý do cho điều đó là nó không phải là bằng chứng trong tương lai; nói ngày mai mã của bạn cần tiêu thụ một số phụ thuộc khác.
__version__
đâu?Nếu bạn hardcode __version__
trong setup.py
sau đó nó có thể không phù hợp với phiên bản mà bạn sẽ xuất xưởng vào mô-đun của bạn. Để thống nhất, bạn sẽ đặt nó ở một nơi và đọc nó từ cùng một nơi khi bạn cần. Sử dụng import
bạn có thể nhận được vấn đề # 1.
setuptools
Bạn sẽ sử dụng một sự kết hợp của open
, exec
và cung cấp một dict cho exec
đến các biến add:
# 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__'],
...)
Và trong mymodule/version.py
phiên bản phơi bày:
__version__ = 'some.semantic.version'
Bằng cách này, phiên bản được cung cấp cùng với mô-đun và bạn không gặp sự cố trong quá trình thiết lập khi cố gắng nhập mô-đun thiếu phụ thuộc (chưa được cài đặt).
Kỹ thuật tốt nhất là xác định __version__
mã sản phẩm của bạn, sau đó nhập mã vào setup.py từ đó. Điều này cung cấp cho bạn một giá trị bạn có thể đọc trong mô-đun đang chạy của mình và chỉ có một nơi để xác định nó.
Các giá trị trong setup.py không được cài đặt và setup.py không dính xung quanh sau khi cài đặt.
Những gì tôi đã làm (ví dụ) trong vùng phủ sóng:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
CẬP NHẬT (2017): cover.txt không còn nhập chính nó để có phiên bản. Nhập mã của riêng bạn có thể làm cho nó có thể gỡ cài đặt được, bởi vì mã sản phẩm của bạn sẽ cố gắng nhập các phụ thuộc chưa được cài đặt, bởi vì setup.py là thứ cài đặt chúng.
__version__
trong đó bị hỏng theo một cách nào đó, thì quá trình nhập của bạn cũng sẽ bị hỏng. Python không biết làm thế nào để chỉ diễn giải các câu bạn muốn. @pjither là đúng: nếu mô-đun của bạn cần nhập các mô-đun khác, chúng có thể chưa được cài đặt và nó sẽ hỗn loạn. Kỹ thuật này hoạt động nếu bạn cẩn thận rằng việc nhập không tạo ra một chuỗi dài các lần nhập khác.
pip install <packagename>
, tôi gặp lỗi sau : ImportError: No module named <packagename>
. Vui lòng CẢNH BÁO cho độc giả của bạn rằng bạn không thể chạy các tệp setup.py như thế này trên các môi trường nơi gói chưa được cài đặt!
Tôi không hài lòng với những câu trả lời này ... không muốn yêu cầu setuptools, cũng không tạo một mô-đun riêng cho một biến duy nhất, vì vậy tôi đã đưa ra những điều này.
Vì khi bạn chắc chắn mô-đun chính theo kiểu pep8 và sẽ giữ nguyên như vậy:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Nếu bạn muốn cẩn thận hơn và sử dụng trình phân tích cú pháp thực sự:
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 là một phần của mô-đun bỏ đi nên không thành vấn đề nếu nó hơi xấu.
Cập nhật: đủ buồn cười Tôi đã chuyển đi từ này trong những năm gần đây và bắt đầu sử dụng một tệp riêng trong gói được gọi meta.py
. Tôi đặt rất nhiều dữ liệu meta vào đó mà tôi có thể muốn thay đổi thường xuyên. Vì vậy, không chỉ cho một giá trị.
ast.get_docstring()
với một số .split('\n')[0].strip()
vv để tự động điền description
từ nguồn. Một điều ít để giữ đồng bộ
Tạo một tệp trong cây nguồn của bạn, ví dụ như trong yourbasingir / yourpackage / _version.py. Để tập tin đó chỉ chứa một dòng mã, như thế này:
__version__ = "1.1.0-r4704"
Sau đó, trong setup.py của bạn, mở tệp đó và phân tích số phiên bản như thế này:
verstr = "không xác định" thử: verstrline = open ('yourpackage / _version.py', "rt"). read () ngoại trừ môi trườngError: vượt qua # Được rồi, không có tệp phiên bản. khác: VSRE = r "^ __ phiên bản__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) nếu mo: verstr = mo.group (1) khác: nâng cao RuntimeError ("không thể tìm thấy phiên bản trong gói của bạn / _version.py")
Cuối cùng, trong quá trình yourbasedir/yourpackage/__init__.py
nhập _version như thế này:
__version__ = "không xác định" thử: từ nhập _version __version__ ngoại trừ ImportError: # Chúng tôi đang chạy trong một cái cây không có _version.py, vì vậy chúng tôi không biết phiên bản của chúng tôi là gì. vượt qua
Một ví dụ về mã thực hiện điều này là gói "pyutil" mà tôi duy trì. (Xem PyPI hoặc tìm kiếm google - stackoverflow không cho phép tôi bao gồm một siêu liên kết đến nó trong câu trả lời này.)
@pjither đúng là bạn không nên nhập gói của mình từ setup.py của chính nó. Điều đó sẽ hoạt động khi bạn kiểm tra nó bằng cách tạo một trình thông dịch Python mới và thực thi setup.py trong điều đầu tiên: python setup.py
nhưng có những trường hợp khi nó không hoạt động. Đó là bởi vì import youpackage
không có nghĩa là đọc thư mục làm việc hiện tại cho một thư mục có tên là "yourpackage", nó có nghĩa là tìm trong hiện tại sys.modules
một khóa "yourpackage" và sau đó làm nhiều việc khác nếu nó không có ở đó. Vì vậy, nó luôn hoạt động khi bạn làm python setup.py
bởi vì bạn có một sản phẩm mới, trống rỗng sys.modules
, nhưng điều này không hoạt động nói chung.
Ví dụ: điều gì sẽ xảy ra nếu py2exe đang thực thi setup.py của bạn như là một phần của quy trình đóng gói ứng dụng? Tôi đã thấy một trường hợp như thế này trong đó py2exe sẽ đặt sai số phiên bản trên một gói vì gói đó lấy số phiên bản từimport myownthing
trong setup.py, nhưng một phiên bản khác của gói đó trước đây đã được nhập trong quá trình chạy py2exe. Tương tự, điều gì sẽ xảy ra nếu setuptools, easy_install, phân phối hoặc distutils2 đang cố gắng xây dựng gói của bạn như là một phần của quá trình cài đặt một gói khác phụ thuộc vào bạn? Sau đó, liệu gói của bạn có thể được nhập vào thời điểm mà cài đặt của nó được đánh giá hay không, hoặc đã có phiên bản của gói đã được nhập trong vòng đời của trình thông dịch Python này hay chưa, hoặc việc nhập gói của bạn yêu cầu các gói khác phải được cài đặt trước hoặc có tác dụng phụ, có thể thay đổi kết quả. Tôi đã gặp nhiều khó khăn khi cố gắng sử dụng lại các gói Python gây ra sự cố cho các công cụ như py2exe và setuptools vì setup.py của họ nhập chính gói đó để tìm số phiên bản của nó.
Nhân tiện, kỹ thuật này chơi độc đáo với các công cụ để tự động tạo yourpackage/_version.py
tệp cho bạn, ví dụ bằng cách đọc lịch sử kiểm soát sửa đổi của bạn và viết ra một số phiên bản dựa trên thẻ gần đây nhất trong lịch sử kiểm soát sửa đổi. Đây là một công cụ thực hiện điều đó cho các darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst và đây là một đoạn mã thực hiện điều tương tự cho git: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Điều này cũng sẽ hoạt động, sử dụng các biểu thức thông thường và tùy thuộc vào các trường siêu dữ liệu để có định dạng như thế này:
__fieldname__ = 'value'
Sử dụng như sau khi bắt đầu thiết lập của bạn:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Sau đó, bạn có thể sử dụng siêu dữ liệu trong tập lệnh của mình như thế này:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Với cấu trúc như thế này:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
nơi phiên bản có chứa:
__version__ = 'version_string'
Bạn có thể làm điều này trong setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Điều này sẽ không gây ra bất kỳ vấn đề nào với bất kỳ sự phụ thuộc nào bạn có trong mô hình của bạn / __ init__.py
Để tránh nhập tệp (và do đó thực thi mã của nó), người ta có thể phân tích cú pháp và khôi phục version
thuộc tính từ cây cú pháp:
# 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
Mã này cố gắng tìm __version__
hoặc VERSION
gán ở mức cao nhất của trả về mô-đun là giá trị chuỗi. Phía bên phải có thể là một chuỗi hoặc một tuple.
Có một ngàn cách để lột da một con mèo - đây là của tôi:
# 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.")
Dọn dẹp https://stackoverflow.com/a/12413800 từ @ 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
Bây giờ điều này là thô thiển và cần một số tinh chỉnh (thậm chí có thể có một cuộc gọi thành viên chưa được phát hiện trong pkg_resource mà tôi đã bỏ lỡ), nhưng tôi đơn giản không hiểu tại sao điều này không hoạt động, và tại sao không ai đề xuất nó cho đến nay (Googling xung quanh có không bật cái này lên) ... lưu ý rằng đây là Python 2.x và sẽ yêu cầu pkg_resource (thở dài):
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
Chúng tôi muốn đưa thông tin meta về gói của chúng tôi pypackagery
vào __init__.py
, nhưng không thể vì nó có sự phụ thuộc của bên thứ ba như PJ Eby đã chỉ ra (xem câu trả lời của anh ấy và cảnh báo về tình trạng cuộc đua).
Chúng tôi đã giải quyết nó bằng cách tạo một mô-đun riêng chỉpypackagery_meta.py
chứa thông tin meta:
"""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'
sau đó nhập thông tin meta vào packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
và cuối cùng đã sử dụng nó trong 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'],
)
Bạn phải bao gồm pypackagery_meta
trong gói của bạn với py_modules
đối số thiết lập. Mặt khác, bạn không thể nhập nó khi cài đặt vì bản phân phối đóng gói sẽ thiếu nó.
Đơn giản và dễ dàng, tạo một tệp được gọi source/package_name/version.py
với nội dung sau:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Sau đó, trên tệp của bạn source/package_name/__init__.py
, bạn nhập phiên bản cho người khác sử dụng:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Bây giờ, bạn có thể đặt cái này lên 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()
Thử nghiệm điều này với Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
và 3.7
trên Linux, Windows và Mac OS. Tôi đã sử dụng trên gói của mình có Kiểm tra tích hợp và đơn vị cho tất cả các nền tảng luận văn. Bạn có thể xem kết quả từ .travis.yml
và appveyor.yml
tại đây:
Một phiên bản thay thế đang sử dụng trình quản lý bối cảnh:
#!/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 ) )
Bạn cũng có thể sử dụng codecs
mô-đun để xử lý các lỗi unicode cả trên Python 2.7
và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 ) )
Nếu bạn đang viết mô-đun Python 100% bằng C / C ++ bằng phần mở rộng Python C, bạn có thể làm điều tương tự, nhưng sử dụng C / C ++ thay vì Python.
Trong trường hợp này, tạo như sau 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'],
)
],
)
Mà đọc phiên bản từ tệp version.h
:
const char* __version__ = "1.0.12";
Nhưng, đừng quên tạo tệp MANIFEST.in
để bao gồm version.h
tệp:
include README.md
include LICENSE.txt
recursive-include source *.h
Và nó được tích hợp vào ứng dụng chính với:
#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__ ) );
...
}
Người giới thiệu:
triển khai gói đến máy chủ và quy ước đặt tên tệp cho các gói chỉ mục:
ví dụ cho chuyển đổi phiên bản động của pip:
thắng lợi:
mac:
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
Tìm thiết lập mẫu gọi các phiên bản pip động khớp từ git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Tôi đang sử dụng một biến môi trường như dưới đây
VERSION = 0.0.0 python setup.py sdist bdist_wheel
Trong setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Để kiểm tra tính nhất quán với phiên bản đóng gói, tôi đang sử dụng tập lệnh bên dưới.
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