Cách chuẩn để nhúng phiên bản vào gói python?


264

Có cách nào chuẩn để liên kết chuỗi phiên bản với gói python theo cách mà tôi có thể làm như sau không?

import foo
print foo.version

Tôi sẽ tưởng tượng có một số cách để lấy dữ liệu đó mà không cần thêm mã hóa cứng, vì các chuỗi nhỏ / chính đã được chỉ định trong setup.py. Giải pháp thay thế mà tôi tìm thấy là có import __version__trong tôi foo/__init__.pyvà sau đó đã __version__.pyđược tạo ra bởi setup.py.


7
FYI, có một cái nhìn tổng quan rất tốt tại địa chỉ: packaging.python.org/en/latest/...
ionelmc

1
Phiên bản của gói đã cài đặt có thể được truy xuất từ ​​siêu dữ liệu với setuptools , vì vậy trong nhiều trường hợp chỉ đưa phiên bản vào setup.pylà đủ. Xem câu hỏi này .
saaj

2
FYI, về cơ bản có 5 mẫu phổ biến để duy trì nguồn sự thật duy nhất (ở cả thời gian thiết lập và thời gian chạy) cho số phiên bản.
KF Lin

Tài liệu của @ionelmc Python liệt kê 7 tùy chọn khác nhau cho việc đơn lẻ . Điều đó có mâu thuẫn với khái niệm " một nguồn chân lý " không?
Stevoisiak

@StevenVascellaro không chắc chắn những gì bạn đang hỏi. Có rất nhiều cách được liệt kê ở đó bởi vì hướng dẫn đóng gói không muốn bị tranh luận.
ionelmc

Câu trả lời:


136

Không trực tiếp trả lời cho câu hỏi của bạn, nhưng bạn nên xem xét việc đặt tên cho nó __version__, không version.

Đây gần như là một tiêu chuẩn gần đúng. Nhiều mô-đun trong thư viện tiêu chuẩn sử dụng __version__và điều này cũng được sử dụng trong rất nhiều mô-đun của bên thứ 3, do đó, đây là tiêu chuẩn gần đúng.

Thông thường, __version__là một chuỗi, nhưng đôi khi nó cũng là một float hoặc tuple.

Chỉnh sửa: như được đề cập bởi S.Lott (Cảm ơn bạn!), PEP 8 nói rõ ràng:

Tên Dunder cấp mô-đun

Cấp module "dunders" (tức là tên với hai hàng đầu và hai dấu gạch dấu) như __all__, __author__, __version__, vv nên được đặt sau docstring mô-đun nhưng trước bất kỳ câu lệnh import trừ từ __future__nhập khẩu.

Bạn cũng nên đảm bảo rằng số phiên bản phù hợp với định dạng được mô tả trong PEP 440 ( PEP 386 phiên bản trước của tiêu chuẩn này).


9
Nó phải là một chuỗi và có phiên bản_info cho phiên bản tuple.
James Antill

James: Tại sao __version_info__cụ thể? (Mà "phát minh" từ hai dấu gạch dưới của riêng bạn.) [Khi James nhận xét, dấu gạch dưới không làm gì trong các bình luận, bây giờ họ chỉ ra sự nhấn mạnh, vì vậy James thực sự đã viết __version_info__. --- ed.]

Bạn có thể thấy một cái gì đó về phiên bản nên nói tại gói.txtthon.org/ distribution/ trộm Trang đó là về phân phối, nhưng ý nghĩa của số phiên bản đang trở thành một tiêu chuẩn thực tế.
sienkiew

2
Đúng. Có vẻ như các PEP này mâu thuẫn với nhau. Chà, PEP 8 nói "nếu" và "crud", vì vậy nó không thực sự xác nhận khi sử dụng mở rộng từ khóa VCS. Ngoài ra, nếu bạn từng chuyển sang một VCS khác, bạn sẽ mất thông tin sửa đổi. Do đó, tôi sẽ đề xuất sử dụng thông tin phiên bản tuân thủ PEP 386/440 được nhúng trong một tệp nguồn duy nhất, ít nhất là cho các dự án lớn hơn.
oefe

2
Bạn sẽ đặt phiên bản đó ở đâu . Xem xét đây là phiên bản được chấp nhận, tôi rất muốn xem thông tin bổ sung ở đây.
bóng tối

120

Tôi sử dụng một _version.pytệp duy nhất là "nơi từng là pháo" để lưu trữ thông tin phiên bản:

  1. Nó cung cấp một __version__thuộc tính.

  2. Nó cung cấp phiên bản siêu dữ liệu tiêu chuẩn. Do đó, nó sẽ được phát hiện bởi pkg_resourceshoặc các công cụ khác phân tích siêu dữ liệu gói (EGG-INFO và / hoặc PKG-INFO, PEP 0345).

  3. Nó không nhập gói của bạn (hoặc bất cứ thứ gì khác) khi xây dựng gói của bạn, điều này có thể gây ra sự cố trong một số trường hợp. (Xem các bình luận bên dưới về những vấn đề này có thể gây ra.)

  4. Chỉ có một nơi ghi số phiên bản, vì vậy chỉ có một nơi để thay đổi khi số phiên bản thay đổi và ít có cơ hội phiên bản không nhất quán.

Đây là cách nó hoạt động: "một vị trí chính tắc" để lưu trữ số phiên bản là một tệp .py, được đặt tên là "_version.py" trong gói Python của bạn, ví dụ như trong myniftyapp/_version.py. Tệp này là một mô-đun Python, nhưng setup.py của bạn không nhập nó! (Điều đó sẽ đánh bại tính năng 3.) Thay vào đó, setup.py của bạn biết rằng nội dung của tệp này rất đơn giản, đại loại như:

__version__ = "3.6.5"

Và vì vậy, setup.py của bạn mở tệp và phân tích cú pháp, với mã như:

import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
    verstr = mo.group(1)
else:
    raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))

Sau đó, setup.py của bạn chuyển chuỗi đó thành giá trị của đối số "phiên bản" setup(), do đó thỏa mãn tính năng 2.

Để đáp ứng tính năng 1, bạn có thể có gói của mình (trong thời gian chạy, không phải lúc thiết lập!) Nhập tệp _version từ myniftyapp/__init__.pynhư thế này:

from _version import __version__

Đây là một ví dụ về kỹ thuật này mà tôi đã sử dụng trong nhiều năm.

Mã trong ví dụ đó phức tạp hơn một chút, nhưng ví dụ đơn giản mà tôi đã viết vào bình luận này phải là một triển khai hoàn chỉnh.

Dưới đây là ví dụ mã nhập phiên bản .

Nếu bạn thấy bất cứ điều gì sai với phương pháp này, xin vui lòng cho tôi biết.


8
Bạn có thể vui lòng mô tả các vấn đề thúc đẩy # 3? Glyph nói rằng nó có liên quan đến "setuptools thích giả vờ rằng mã của bạn không ở bất cứ đâu trên hệ thống khi setup.py chạy", nhưng các chi tiết sẽ giúp thuyết phục tôi và những người khác.
Ivan Kozik

2
@Iva Bây giờ, công cụ nên làm theo thứ tự nào? Nó không thể (trong hệ thống setuptools / pip / virtualenv ngày nay) thậm chí còn biết các deps là gì cho đến khi nó đánh giá của bạn setup.py. Ngoài ra, nếu nó cố gắng thực hiện đầy đủ độ sâu trước và thực hiện tất cả các dep trước khi thực hiện thao tác này, nó sẽ bị kẹt nếu có các deps tròn. Nhưng nếu nó cố gắng xây dựng gói này trước khi cài đặt các phụ thuộc, thì nếu bạn nhập gói của bạn từ gói của mình setup.py, nó sẽ không nhất thiết có thể nhập deps của nó, hoặc các phiên bản đúng của deps.
Zooko

3
Bạn có thể viết tập tin "version.py" từ "setup.py" thay vì phân tích cú pháp không? Điều đó có vẻ đơn giản hơn.
Jonathan Hartley

3
Jonathan Hartley: Tôi đồng ý rằng việc "setup.py" của bạn sẽ đơn giản hơn một chút khi viết tệp "version.py" thay vì phân tích cú pháp, nhưng nó sẽ mở ra một cửa sổ cho sự không nhất quán, khi bạn đã chỉnh sửa setup.py để có phiên bản mới nhưng chưa thực hiện setup.py để cập nhật tệp version.py. Một lý do khác để có phiên bản chính tắc nằm trong một tệp nhỏ riêng biệt là vì nó giúp các công cụ khác dễ dàng , chẳng hạn như các công cụ đọc trạng thái kiểm soát sửa đổi của bạn, để ghi tệp phiên bản.
Zooko

3
Cách tiếp cận tương tự là execfile("myniftyapp/_version.py")từ bên trong setup.py, thay vì cố phân tích mã phiên bản theo cách thủ công. Được đề xuất trong stackoverflow.com/a/2073599/647002 - thảo luận cũng có thể hữu ích.
medmunds

97

Viết lại 2017-05

Sau hơn mười năm viết mã Python và quản lý các gói khác nhau, tôi đã đi đến kết luận rằng DIY có thể không phải là cách tiếp cận tốt nhất.

Tôi bắt đầu sử dụng pbrgói để xử lý phiên bản trong các gói của mình. Nếu bạn đang sử dụng git làm SCM của mình, điều này sẽ phù hợp với quy trình làm việc của bạn như ma thuật, tiết kiệm hàng tuần làm việc của bạn (bạn sẽ ngạc nhiên về mức độ phức tạp của vấn đề).

Cho đến ngày hôm nay, pbr được xếp hạng # 11 gói python được sử dụng nhiều nhất và đạt đến cấp độ này không bao gồm bất kỳ thủ thuật bẩn thỉu nào: chỉ có một: khắc phục sự cố đóng gói phổ biến theo cách rất đơn giản.

pbr có thể làm nhiều hơn gánh nặng bảo trì gói, không giới hạn ở phiên bản nhưng nó không buộc bạn phải áp dụng tất cả các lợi ích của nó.

Vì vậy, để cung cấp cho bạn một ý tưởng về việc trông như thế nào khi áp dụng pbr trong một lần cam kết, hãy nhìn vào bao bì của pbr

Có lẽ bạn sẽ quan sát thấy rằng phiên bản hoàn toàn không được lưu trữ trong kho lưu trữ. PBR không phát hiện ra nó từ các nhánh và thẻ Git.

Không cần phải lo lắng về những gì xảy ra khi bạn không có kho lưu trữ git vì pbr không "biên dịch" và lưu trữ phiên bản khi bạn đóng gói hoặc cài đặt các ứng dụng, do đó không có phụ thuộc thời gian chạy vào git.

Giải pháp cũ

Đây là giải pháp tốt nhất tôi từng thấy cho đến nay và nó cũng giải thích tại sao:

Bên trong yourpackage/version.py:

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'

Bên trong yourpackage/__init__.py:

from .version import __version__

Bên trong setup.py:

exec(open('yourpackage/version.py').read())
setup(
    ...
    version=__version__,
    ...

Nếu bạn biết một cách tiếp cận khác có vẻ tốt hơn hãy cho tôi biết.


12
Ơ, không. execfile () không tồn tại trong Python 3, vì vậy tốt hơn là sử dụng exec (open (). read ()).
Barshe Vu-Brugier

4
Tại sao không có from .version import __version__trong setup.py?
Aprillion

4
@Aprillion Vì gói không được tải khi setup.pyđang chạy - hãy thử, bạn sẽ gặp lỗi (hoặc ít nhất, tôi đã làm :-))
darthbith

3
Các liên kết đến pbr dẫn đến một cổng xấu.
MERose

4
pbr , không nghi ngờ gì, là một công cụ tuyệt vời, nhưng bạn đã thất bại trong việc giải quyết câu hỏi. Làm thế nào bạn có thể truy cập phiên bản hiện tại hoặc gói đã cài đặt qua bpr .
nad2000

29

Theo PEP 396 hoãn lại (Số phiên bản mô-đun) , có một cách được đề xuất để làm điều này. Nó mô tả, với lý do, một tiêu chuẩn (được thừa nhận tùy chọn) cho các mô-đun tuân theo. Đây là một đoạn:

3) Khi một mô-đun (hoặc gói) bao gồm số phiên bản, phiên bản NÊN có sẵn trong __version__thuộc tính.

4) Đối với các mô-đun sống trong gói không gian tên, mô-đun NÊN bao gồm __version__thuộc tính. Bản thân gói không gian tên KHÔNG NÊN bao gồm __version__thuộc tính của chính nó .

5) __version__Giá trị của thuộc tính NÊN là một chuỗi.


13
PEP đó không được chấp nhận / tiêu chuẩn hóa, nhưng bị hoãn lại (do thiếu sự quan tâm). Do đó, có một chút sai lầm khi nói rằng " có một cách tiêu chuẩn " được chỉ định bởi nó.
thợ dệt

@weaver: Ôi trời! Tôi đã học được một cái gì đó mới. Tôi không biết đó là thứ tôi cần kiểm tra.
Oddthinking ngày

4
Chỉnh sửa để lưu ý nó không phải là một tiêu chuẩn. Bây giờ tôi cảm thấy xấu hổ, vì tôi đã đưa ra các yêu cầu tính năng cho các dự án yêu cầu họ tuân theo "tiêu chuẩn" này.
Oddthinking ngày

1
Có lẽ bạn nên đảm nhận công việc tiêu chuẩn hóa trên PEP đó, vì bạn có vẻ thích :)
thợ dệt

Điều này sẽ hoạt động để phiên bản một mô-đun riêng lẻ, nhưng tôi không chắc nó sẽ áp dụng cho việc tạo phiên bản cho một dự án đầy đủ.
Stevois

21

Mặc dù điều này có lẽ đã quá muộn, nhưng có một cách thay thế đơn giản hơn cho câu trả lời trước:

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(Và sẽ khá đơn giản để chuyển đổi các phần của số phiên bản tự động thành chuỗi bằng cách sử dụng str().)

Tất nhiên, từ những gì tôi đã thấy, mọi người có xu hướng sử dụng một cái gì đó giống như phiên bản được đề cập trước đó khi sử dụng __version_info__, và như vậy lưu trữ nó như một bộ dữ liệu ints; tuy nhiên, tôi hoàn toàn không thấy được điểm nào khi làm như vậy, vì tôi nghi ngờ có những tình huống bạn sẽ thực hiện các phép toán như cộng và trừ trên các phần của số phiên bản cho bất kỳ mục đích nào ngoài sự tò mò hoặc tự động tăng (và thậm chí sau đó, int()str()có thể được sử dụng khá dễ dàng). (Mặt khác, có khả năng mã của người khác đang mong đợi một bộ dữ liệu số thay vì một bộ dữ liệu chuỗi và do đó không thành công.)

Tất nhiên, đây là quan điểm của riêng tôi và tôi rất sẵn lòng thích đầu vào của người khác về việc sử dụng một bộ số.


Như shezi đã nhắc nhở tôi, so sánh (từ vựng) của chuỗi số không nhất thiết phải có kết quả giống như so sánh số trực tiếp; số không hàng đầu sẽ được yêu cầu để cung cấp cho điều đó. Vì vậy, cuối cùng, lưu trữ__version_info__ (hoặc bất cứ thứ gì nó sẽ được gọi) như một bộ các giá trị nguyên sẽ cho phép so sánh phiên bản hiệu quả hơn.


12
đẹp (+1), nhưng bạn không thích số thay vì chuỗi? ví dụ__version_info__ = (1,2,3)
orip

3
So sánh các chuỗi có thể trở nên nguy hiểm khi số phiên bản vượt quá 9, ví dụ như '10' <'2'.
D Coetzee

13
Tôi cũng làm điều này nhưng hơi tinh chỉnh để xử lý ints .. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
rh0dium

2
Vấn đề __version__ = '.'.join(__version_info__)là nó __version_info__ = ('1', '2', 'beta')sẽ trở thành 1.2.beta, không 1.2betahay1.2 beta
nagisa

4
Tôi nghĩ vấn đề với cách tiếp cận này là nơi đặt các dòng mã khai báo __version__. Nếu chúng nằm trong setup.py thì chương trình của bạn không thể nhập chúng từ trong gói. Có lẽ đây không phải là vấn đề với bạn, trong trường hợp đó, tốt thôi. Nếu chúng nằm trong chương trình của bạn, thì setup.txt của bạn có thể nhập chúng, nhưng không nên, vì setup.py sẽ chạy trong khi cài đặt khi phần phụ thuộc của chương trình chưa được cài đặt (setup.py được sử dụng để xác định phần phụ thuộc là gì .) Do đó, câu trả lời của Zooko: phân tích thủ công giá trị ra khỏi tệp nguồn sản phẩm, mà không cần nhập gói sản phẩm
Jonathan Hartley

11

Nhiều giải pháp trong số này ở đây bỏ qua gitcác thẻ phiên bản, điều đó vẫn có nghĩa là bạn phải theo dõi phiên bản ở nhiều nơi (xấu). Tôi đã tiếp cận điều này với các mục tiêu sau:

  • Lấy tất cả các tham chiếu phiên bản python từ một thẻ trong gitrepo
  • Tự động hóa git tag/ pushsetup.py uploadcác bước với một lệnh không có đầu vào.

Làm thế nào nó hoạt động:

  1. Từ một make releaselệnh, phiên bản được gắn thẻ cuối cùng trong repo git được tìm thấy và tăng lên. Thẻ được đẩy trở lại origin.

  2. Các Makefilecửa hàng phiên bản src/_version.pynơi nó sẽ được đọc setup.pyvà cũng được bao gồm trong bản phát hành. Đừng kiểm tra _version.pynguồn!

  3. setup.pylệnh đọc chuỗi phiên bản mới từ package.__version__.

Chi tiết:

Makefile

# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver      = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))

.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
    echo '__version__ = "$(call git_describe_ver)"' > $@

.PHONY: release
release: test lint mypy
    git tag -a $(call next_patch_ver)
    $(MAKE) ${MODULE}/_version.py
    python setup.py check sdist upload # (legacy "upload" method)
    # twine upload dist/*  (preferred method)
    git push origin master --tags

Các releasemục tiêu luôn increments phiên bản chữ số thứ 3, nhưng bạn có thể sử dụng next_minor_verhoặc next_major_verđể tăng các chữ số khác. Các lệnh dựa trên versionbump.pytập lệnh được kiểm tra vào thư mục gốc của repo

phiên bảnbump.py

"""An auto-increment tool for version strings."""

import sys
import unittest

import click
from click.testing import CliRunner  # type: ignore

__version__ = '0.1'

MIN_DIGITS = 2
MAX_DIGITS = 3


@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
    """Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
    optional 'v' prefix is allowed and will be included in the output if found."""
    prefix = version[0] if version[0].isalpha() else ''
    digits = version.lower().lstrip('v').split('.')

    if len(digits) > MAX_DIGITS:
        click.secho('ERROR: Too many digits', fg='red', err=True)
        sys.exit(1)

    digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS]  # Extend total digits to max.
    digits[bump_idx] = str(int(digits[bump_idx]) + 1)  # Increment the desired digit.

    # Zero rightmost digits after bump position.
    for i in range(bump_idx + 1, MAX_DIGITS):
        digits[i] = '0'
    digits = digits[:max(MIN_DIGITS, bump_idx + 1)]  # Trim rightmost digits.
    click.echo(prefix + '.'.join(digits), nl=False)


if __name__ == '__main__':
    cli()  # pylint: disable=no-value-for-parameter

Điều này thực hiện rất nhiều cách xử lý và tăng số phiên bản từ git.

__init__.py

Các my_module/_version.pytập tin được nhập vào my_module/__init__.py. Đặt bất kỳ cấu hình cài đặt tĩnh nào ở đây mà bạn muốn phân phối với mô-đun của mình.

from ._version import __version__
__author__ = ''
__email__ = ''

thiết lập

Bước cuối cùng là đọc thông tin phiên bản từ my_modulemô-đun.

from setuptools import setup, find_packages

pkg_vars  = {}

with open("{MODULE}/_version.py") as fp:
    exec(fp.read(), pkg_vars)

setup(
    version=pkg_vars['__version__'],
    ...
    ...
)

Tất nhiên, để tất cả những thứ này hoạt động, bạn sẽ phải có ít nhất một thẻ phiên bản trong repo của mình để bắt đầu.

git tag -a v0.0.1

1
thật vậy - toàn bộ chủ đề này quên rằng một VCS rất quan trọng trong cuộc thảo luận này. chỉ là một sự ám ảnh: việc tăng phiên bản sẽ vẫn là một quy trình thủ công, vì vậy cách ưa thích sẽ là 1. tạo và đẩy một cách thủ công 2. hãy để các công cụ VCS khám phá thẻ đó và lưu trữ nó ở nơi cần thiết (wow - giao diện chỉnh sửa SO này thực sự làm tôi tê liệt - Tôi đã phải chỉnh sửa điều này hàng chục lần chỉ để xử lý các dòng mới VÀ NÓ VẪN KHÔNG LÀM VIỆC! @ # $% ^ & *)
axd

Không cần sử dụng versionbump.pykhi chúng ta có một gói chuyển đổi tuyệt vời cho python.
Oran

@Oran Tôi đã xem phiên bản. Các tài liệu không rõ ràng lắm và nó không xử lý việc gắn thẻ rất tốt. Trong thử nghiệm của tôi, nó dường như đi vào các trạng thái khiến nó bị sập: sub process.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / thư mục / rl / tjyk4hns7kndnx035p26wg692g_7 'trả về trạng thái thoát khác không 1.
cmcginty

1
Tại sao không _version.pynên theo dõi với kiểm soát phiên bản?
Stevoisiak

1
@StevenVascellaro Điều này làm phức tạp quá trình phát hành. Bây giờ bạn phải đảm bảo rằng các thẻ và cam kết của bạn khớp với giá trị trong _version.py. Sử dụng một thẻ phiên bản duy nhất là IMO sạch hơn và có nghĩa là bạn có thể tạo một bản phát hành trực tiếp từ github UI.
cmcginty

10

Tôi sử dụng một tệp JSON trong thư mục gói. Điều này phù hợp với yêu cầu của Zooko.

Bên trong pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Bên trong setup.py:

from distutils.core import setup
import json

with open('pkg_dir/pkg_info.json') as fp:
    _info = json.load(fp)

setup(
    version=_info['version'],
    ...
    )

Bên trong pkg_dir/__init__.py:

import json
from os.path import dirname

with open(dirname(__file__) + '/pkg_info.json') as fp:
    _info = json.load(fp)

__version__ = _info['version']

Tôi cũng đưa thông tin khác vào pkg_info.json, như tác giả. Tôi thích sử dụng JSON vì tôi có thể tự động hóa việc quản lý siêu dữ liệu.


Bạn có thể giải thích cách sử dụng json để tự động hóa quản lý siêu dữ liệu không? Cảm ơn!
ryanjdillon

6

Cũng đáng chú ý là cũng như __version__là một bán std. trong python __version_info__, đó là một tuple, trong các trường hợp đơn giản, bạn có thể làm một cái gì đó như:

__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])

... Và bạn có thể lấy __version__chuỗi từ một tập tin, hoặc bất cứ điều gì.


1
Bạn có bất kỳ tài liệu tham khảo / liên kết liên quan đến nguồn gốc của việc sử dụng này __version_info__?
Craig McQueen

3
Vâng, đó là ánh xạ tương tự như sys.version đến sys.version_info. Vì vậy: docs.python.org/l Library / sys.html
James Antill

7
Việc lập bản đồ theo hướng khác ( __version_info__ = (1, 2, 3)__version__ = '.'.join(map(str, __version_info__))) sẽ dễ dàng hơn .
Eric O Lebigot

2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)- hơi dài nhưng nhiều pythonic.
ArtOfWarfare

2
Tôi không chắc chắn rằng những gì bạn đề xuất rõ ràng là nhiều pythonic hơn, vì nó chứa một biến giả không thực sự cần thiết và ý nghĩa của nó hơi khó diễn đạt ( ikhông có ý nghĩa gì, version_numhơi dài và mơ hồ). Tôi thậm chí còn lấy sự tồn tại của map()Python như một gợi ý mạnh mẽ rằng nó nên được sử dụng ở đây, vì điều chúng ta cần làm ở đây là trường hợp sử dụng điển hình của map()(sử dụng với một chức năng hiện có), tôi không thấy nhiều cách sử dụng hợp lý khác.
Eric O Lebigot

5

Dường như không có một cách tiêu chuẩn nào để nhúng chuỗi phiên bản trong gói python. Hầu hết các gói tôi đã thấy sử dụng một số biến thể của giải pháp của bạn, tức là eitner

  1. Nhúng phiên bản vào setup.pyvà đã setup.pytạo một mô-đun (ví dụ version.py) chỉ chứa thông tin phiên bản, được nhập bởi gói của bạn, hoặc

  2. Ngược lại: đặt thông tin phiên bản trong gói của bạn và nhập thông tin đó để đặt phiên bản trong setup.py


Tôi thích lời giới thiệu của bạn, nhưng làm thế nào để tạo mô-đun này từ setup.py?
sorin

1
Tôi thích ý tưởng về tùy chọn (1), có vẻ đơn giản hơn câu trả lời của Zook về phân tích số phiên bản từ một tệp. Nhưng bạn không thể đảm bảo rằng phiên bản được tạo khi một nhà phát triển chỉ nhân bản repo của bạn. Trừ khi bạn kiểm tra phiên bản, điều này sẽ mở ra một nếp nhăn nhỏ mà bạn có thể thay đổi số phiên bản trong setup.py, cam kết, phát hành và sau đó phải (chém quên) cam kết thay đổi thành phiên bản.
Jonathan Hartley

Có lẽ bạn có thể tạo mô-đun bằng cách sử dụng một cái gì đó như "" "với open (" mypackage / version.py "," w ") là fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Jonathan Hartley

1
Tôi nghĩ rằng tùy chọn 2. dễ bị lỗi trong khi cài đặt như đã lưu ý trong các nhận xét cho câu trả lời của JAB.
Jonathan Hartley

Thế còn cái đó? Bạn đặt __version__ = '0.0.1' "(tất nhiên phiên bản là một chuỗi) trong __init__.py" của gói chính của phần mềm của bạn. Sau đó, đi đến điểm 2: Trong phần thiết lập bạn thực hiện từ gói .__ init__ nhập __version__ là v, và sau đó bạn đặt biến v là phiên bản của setup.py. Sau đó nhập mypack dưới dạng của tôi, in .__ phiên bản _ của tôi sẽ in phiên bản. Phiên bản sẽ được lưu trữ ở một nơi duy nhất, có thể truy cập từ toàn bộ mã, trong một tệp không nhập bất kỳ thứ gì khác liên quan đến phần mềm.
SeF

5

mũi tên xử lý nó một cách thú vị.

Bây giờ (kể từ 2e5031b )

Trong arrow/__init__.py:

__version__ = 'x.y.z'

Trong setup.py:

from arrow import __version__

setup(
    name='arrow',
    version=__version__,
    # [...]
)

Trước

Trong arrow/__init__.py:

__version__ = 'x.y.z'
VERSION = __version__

Trong setup.py:

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

file_text = read(fpath('arrow/__init__.py'))

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)

file_text
ely

2
các giải pháp cập nhật là thực sự có hại. Khi setup.py đang chạy, nó sẽ không nhất thiết phải xem phiên bản của gói từ đường dẫn tệp cục bộ. Nó có thể trở lại phiên bản đã cài đặt trước đó, ví dụ như chạy pip install -e .trên nhánh phát triển hoặc thứ gì đó khi thử nghiệm. setup.py tuyệt đối không nên dựa vào việc nhập gói đang trong quá trình cài đặt để xác định tham số cho việc triển khai. Rất tiếc.
ely

Có, tôi không biết tại sao mũi tên quyết định thụt lùi cho giải pháp này. Ngoài ra, thông báo cam kết cho biết "Đã cập nhật setup.py với các tiêu chuẩn Python hiện đại " ...
Anto

4

Tôi cũng thấy một phong cách khác:

>>> django.VERSION
(1, 1, 0, 'final', 0)

1
Vâng, tôi đã thấy quá. BTW mỗi câu trả lời có phong cách khác vì vậy bây giờ tôi không biết phong cách nào là "tiêu chuẩn". Tìm kiếm các PEP được đề cập ...
kbec

Một cách khác nhìn thấy; Máy khách Python của Mongo sử dụng phiên bản đơn giản, không có dấu gạch dưới. Vì vậy, điều này hoạt động; $ python >>> nhập pymongo >>> pymongo.version '2.7'
AnneTheAgile

Triển khai .VERSIONkhông có nghĩa là bạn không phải thực hiện __version__.
Acumenus

Điều này có yêu cầu thực hiện djangotrong dự án?
Stevoisiak

3

Sử dụng setuptoolspbr

Không có cách tiêu chuẩn để quản lý phiên bản, nhưng cách tiêu chuẩn để quản lý các gói của bạn là setuptools.

Giải pháp tốt nhất tôi tìm thấy tổng thể để quản lý phiên bản là sử dụng setuptoolsvới pbrtiện ích mở rộng. Đây là cách quản lý phiên bản tiêu chuẩn của tôi.

Thiết lập dự án của bạn để đóng gói đầy đủ có thể là quá mức cần thiết cho các dự án đơn giản, nhưng nếu bạn cần quản lý phiên bản, có lẽ bạn đang ở cấp độ phù hợp để chỉ thiết lập mọi thứ. Làm như vậy cũng làm cho gói của bạn đáng tin cậy tại PyPi để mọi người có thể tải xuống và sử dụng nó với Pip.

PBR chuyển hầu hết siêu dữ liệu ra khỏi các setup.pycông cụ và vào một setup.cfgtệp sau đó được sử dụng làm nguồn cho hầu hết siêu dữ liệu, có thể bao gồm phiên bản. Điều này cho phép siêu dữ liệu được đóng gói thành một tệp thực thi bằng cách sử dụng một cái gì đó như pyinstallernếu cần (nếu vậy, bạn có thể sẽ cần thông tin này ) và tách siêu dữ liệu khỏi các tập lệnh thiết lập / quản lý gói khác. Bạn có thể trực tiếp cập nhật chuỗi phiên bản setup.cfgtheo cách thủ công và nó sẽ được kéo vào *.egg-infothư mục khi xây dựng các bản phát hành gói của bạn. Tập lệnh của bạn sau đó có thể truy cập phiên bản từ siêu dữ liệu bằng nhiều phương pháp khác nhau (các quy trình này được nêu trong các phần bên dưới).

Khi sử dụng Git cho VCS / SCM, thiết lập này thậm chí còn tốt hơn, vì nó sẽ thu được rất nhiều siêu dữ liệu từ Git để repo của bạn có thể là nguồn sự thật chính của bạn cho một số siêu dữ liệu, bao gồm phiên bản, tác giả, thay đổi, v.v ... Đối với phiên bản cụ thể, nó sẽ tạo một chuỗi phiên bản cho cam kết hiện tại dựa trên các thẻ git trong repo.

Vì PBR sẽ lấy phiên bản, tác giả, thay đổi và thông tin khác trực tiếp từ repo git của bạn, do đó, một số siêu dữ liệu setup.cfgcó thể bị bỏ qua và tự động tạo bất cứ khi nào phân phối được tạo cho gói của bạn (sử dụng setup.py)

Phiên bản hiện tại thời gian thực

setuptoolssẽ lấy thông tin mới nhất trong thời gian thực bằng cách sử dụng setup.py:

python setup.py --version

Điều này sẽ lấy phiên bản mới nhất từ setup.cfgtệp hoặc từ repo git, dựa trên cam kết mới nhất đã được thực hiện và các thẻ tồn tại trong repo. Lệnh này không cập nhật phiên bản trong một bản phân phối.

Cập nhật phiên bản

Khi bạn tạo phân phối với setup.py(ví py setup.py sdistdụ: chẳng hạn), thì tất cả thông tin hiện tại sẽ được trích xuất và lưu trữ trong phân phối. Điều này về cơ bản chạy setup.py --versionlệnh và sau đó lưu thông tin phiên bản đó vào package.egg-infothư mục trong một tập hợp các tệp lưu trữ siêu dữ liệu phân phối.

Lưu ý về quy trình cập nhật dữ liệu meta phiên bản:

Nếu bạn không sử dụng pbr để lấy dữ liệu phiên bản từ git, thì chỉ cần cập nhật trực tiếp setup.cfg của bạn với thông tin phiên bản mới (đủ dễ, nhưng đảm bảo đây là một phần tiêu chuẩn của quy trình phát hành của bạn).

Nếu bạn đang sử dụng git và bạn không cần tạo phân phối nguồn hoặc nhị phân (sử dụng python setup.py sdisthoặc một trong các python setup.py bdist_xxxlệnh), cách đơn giản nhất để cập nhật thông tin git repo vào <mypackage>.egg-infothư mục siêu dữ liệu của bạn là chỉ cần chạy python setup.py installlệnh. Điều này sẽ chạy tất cả các hàm PBR liên quan đến việc lấy siêu dữ liệu từ git repo và cập nhật .egg-infothư mục cục bộ của bạn , cài đặt các tệp thực thi tập lệnh cho bất kỳ điểm nhập nào bạn đã xác định và các chức năng khác bạn có thể thấy từ đầu ra khi bạn chạy lệnh này.

Lưu ý rằng .egg-infothư mục thường được loại trừ khỏi việc được lưu trữ trong chính git repo trong các .gitignoretệp Python tiêu chuẩn (chẳng hạn như từ Gitignore.IO ), vì nó có thể được tạo từ nguồn của bạn. Nếu nó bị loại trừ, hãy đảm bảo bạn có "quy trình phát hành" tiêu chuẩn để cập nhật siêu dữ liệu cục bộ trước khi phát hành và bất kỳ gói nào bạn tải lên PyPi.org hoặc phân phối khác phải bao gồm dữ liệu này để có phiên bản chính xác. Nếu bạn muốn repo Git chứa thông tin này, bạn có thể loại trừ các tệp cụ thể khỏi bị bỏ qua (tức là thêm !*.egg-info/PKG_INFOvào .gitignore)

Truy cập phiên bản từ tập lệnh

Bạn có thể truy cập siêu dữ liệu từ bản dựng hiện tại trong các tập lệnh Python trong chính gói đó. Ví dụ, đối với phiên bản, có một số cách để làm điều này tôi đã tìm thấy cho đến nay:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib-metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

Bạn có thể đặt một trong những thứ này trực tiếp trong __init__.pygói của bạn để trích xuất thông tin phiên bản như sau, tương tự như một số câu trả lời khác:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version

Định dạng lại mỗi phiếu bầu xuống để trả lời trực tiếp câu hỏi ngay bây giờ.
LightCC

1

Sau vài giờ cố gắng tìm giải pháp đáng tin cậy đơn giản nhất, đây là các phần:

tạo một tập tin phiên bản ẩn bên trong thư mục gói "/ mypackage" của bạn:

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '1.2.7'

trong setup.py:

exec(open('mypackage/version.py').read())
setup(
    name='mypackage',
    version=__version__,

trong thư mục chính init .py:

from .version import __version__

Các exec()chức năng chạy bên ngoài kịch bản của bất kỳ nhập khẩu, vì setup.py được thực hiện trước các mô-đun có thể được nhập khẩu. Bạn vẫn chỉ cần quản lý số phiên bản trong một tệp ở một nơi, nhưng tiếc là nó không có trong setup.py. (đó là nhược điểm, nhưng không có lỗi nhập khẩu là nhược điểm)


1

Rất nhiều công việc hướng tới phiên bản thống nhất và hỗ trợ các công ước đã được hoàn thành kể từ khi câu hỏi này lần đầu tiên được hỏi . Các tùy chọn có thể đọc được hiện chi tiết trong Hướng dẫn sử dụng bao bì Python . Ngoài ra, đáng chú ý là các lược đồ số phiên bản tương đối nghiêm ngặt trong Python trên PEP 440 , và vì vậy việc giữ mọi thứ lành mạnh là rất quan trọng nếu gói của bạn sẽ được phát hành cho Cheese Shop .

Dưới đây là phân tích rút gọn của các tùy chọn phiên bản:

  1. Đọc tệp trong setup.py( setuptools ) và lấy phiên bản.
  2. Sử dụng một công cụ xây dựng bên ngoài (để cập nhật cả __init__.pykiểm soát nguồn cũng như kiểm soát nguồn), ví dụ: bẻ khóa , thay đổi hoặc zest.releaser .
  3. Đặt giá trị thành một __version__biến toàn cục trong một mô-đun cụ thể.
  4. Đặt giá trị trong tệp văn bản VERSION đơn giản cho cả setup.py và mã để đọc.
  5. Đặt giá trị thông qua một setup.pybản phát hành và sử dụng importlib.metadata để chọn nó khi chạy. (Cảnh báo, có các phiên bản trước 3,8 và sau 3,8.)
  6. Đặt giá trị thành __version__trong sample/__init__.pyvà nhập mẫu vào setup.py.
  7. Sử dụng setuptools_scm để trích xuất phiên bản từ kiểm soát nguồn sao cho đó là tham chiếu chính tắc, không phải mã.

LƯU Ý rằng (7) có thể là cách tiếp cận hiện đại nhất (siêu dữ liệu xây dựng độc lập với mã, được xuất bản bởi tự động hóa). Ngoài ra, LƯU Ý rằng nếu thiết lập được sử dụng để phát hành gói, đơn giản python3 setup.py --versionsẽ báo cáo phiên bản trực tiếp.


-1

Để biết giá trị của nó, nếu bạn đang sử dụng các bản phân phối NumPy, numpy.distutils.misc_util.Configurationcó một make_svn_version_py()phương pháp nhúng số sửa đổi bên package.__svn_version__trong biến version.


Bạn có thể cung cấp thêm chi tiết hoặc một ví dụ về cách thức này sẽ hoạt động?
Stevoisiak

Hừm. Vào năm 2020, đây là (luôn luôn là vậy?) Có nghĩa là cho FORTRAN . Gói "numpy.distutils là một phần của NumPy mở rộng các bản phân phối Python tiêu chuẩn để đối phó với các nguồn Fortran."
ingyhere

-1
  1. Sử dụng một version.pytệp chỉ với __version__ = <VERSION>param trong tệp. Trong setup.pytệp nhập __version__tham số và đặt giá trị của nó vào setup.pytệp như sau: version=__version__
  2. Một cách khác là chỉ sử dụng một setup.pytệp với version=<CURRENT_VERSION>- CURRENT_VERSION được mã hóa cứng.

Vì chúng tôi không muốn thay đổi thủ công phiên bản trong tệp mỗi khi chúng tôi tạo thẻ mới (sẵn sàng phát hành phiên bản gói mới), chúng tôi có thể sử dụng cách sau ..

Tôi khuyên bạn nên bumpversion gói. Tôi đã sử dụng nó trong nhiều năm để tạo ra một phiên bản.

bắt đầu bằng cách thêm version=<VERSION>vào setup.pytập tin của bạn nếu bạn chưa có nó.

Bạn nên sử dụng một đoạn script ngắn như thế này mỗi khi bạn tạo một phiên bản:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

Sau đó thêm một tệp cho mỗi repo được gọi là .bumpversion.cfg::

[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]

Ghi chú:

  • Bạn có thể sử dụng __version__tham số trong version.pytệp như được đề xuất trong các bài đăng khác và cập nhật tệp chuyển đổi như thế này: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Bạn phải git commit hoặc git resetmọi thứ trong repo của mình, nếu không bạn sẽ gặp lỗi repo bẩn.
  • Hãy chắc chắn rằng môi trường ảo của bạn bao gồm gói chuyển đổi, nếu không nó sẽ không hoạt động.

@cmcginty Xin lỗi vì sự chậm trễ, xin vui lòng kiểm tra câu trả lời của tôi ^^^ - lưu ý rằng bạn phải git commit hoặc tất cả mọi thứ reset git trong repo của bạn và chắc chắn rằng môi trường ảo của bạn bao gồm gói bumpversion, mà không có nó, nó sẽ không làm việc. Sử dụng phiên bản mới nhất.
Oran

Tôi hơi không rõ về giải pháp nào đang được đề xuất ở đây. Bạn có đề xuất phiên bản theo dõi thủ công với version.pyhoặc theo dõi phiên bản đó bumpversionkhông?
Stevoisiak

@StevenVascellaro Tôi khuyên bạn nên sử dụng tính năng đảo ngược, không bao giờ sử dụng phiên bản thủ công. Những gì tôi đã cố gắng giải thích là bạn có thể trực tiếp chuyển đổi để cập nhật phiên bản trong tệp setup.py hoặc tốt hơn là sử dụng nó để cập nhật tệp version.py. Đó là cách phổ biến hơn để cập nhật tệp version.py và lấy __version__giá trị param vào tệp setup.py. Giải pháp của tôi được sử dụng trong sản xuất và đó là một thông lệ. Lưu ý: chỉ cần rõ ràng, sử dụng chuyển đổi như một phần của tập lệnh là giải pháp tốt nhất, hãy đặt nó vào CI của bạn và nó sẽ được vận hành tự động.
Oran

-3

Nếu bạn sử dụng CVS (hoặc RCS) và muốn có giải pháp nhanh chóng, bạn có thể sử dụng:

__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])

(Tất nhiên, số sửa đổi sẽ được thay thế cho bạn bằng CVS.)

Điều này cung cấp cho bạn phiên bản thân thiện với bản in và thông tin phiên bản mà bạn có thể sử dụng để kiểm tra xem mô-đun bạn đang nhập có ít nhất là phiên bản dự kiến ​​không:

import my_module
assert my_module.__version_info__ >= (1, 1)

Tập tin nào bạn khuyên bạn nên tiết kiệm __version__? Làm thế nào một tăng số phiên bản với giải pháp này?
Stevoisiak
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.