Cara standar untuk menanamkan versi ke dalam paket python?

265

Apakah ada cara standar untuk mengaitkan string versi dengan paket python sedemikian rupa sehingga saya bisa melakukan hal berikut?

import foo
print foo.version

Saya akan membayangkan ada beberapa cara untuk mengambil data tanpa hardcoding tambahan, karena string minor / major sudah ditentukan di dalam setup.py. Solusi alternatif yang saya temukan adalah memiliki import __version__di saya foo/__init__.pydan kemudian __version__.pydihasilkan oleh setup.py.

Dimitri Tcaciuc
sumber
7
FYI, ada gambaran yang sangat bagus di: packaging.python.org/en/latest/…
ionelmc
1
Versi dari paket yang diinstal dapat diambil dari metadata dengan setuptools , jadi dalam banyak kasus memasukkan versi saja setup.pysudah cukup. Lihat pertanyaan ini .
Saaj
2
FYI, pada dasarnya ada 5 pola umum untuk mempertahankan satu sumber kebenaran (pada saat setup dan run time) untuk nomor versi.
KF Lin
Dokumentasi @ionelmc Python mencantumkan 7 opsi berbeda untuk satu-asam . Bukankah itu bertentangan dengan konsep " satu sumber kebenaran "?
Stevoisiak
@StevenVascellaro tidak yakin dengan apa yang Anda minta. Ada begitu banyak cara yang terdaftar di sana karena panduan pengemasan tidak ingin dinilai.
ionelmc

Jawaban:

136

Bukan jawaban langsung untuk pertanyaan Anda, tetapi Anda sebaiknya mempertimbangkan untuk menamainya __version__, bukan version.

Ini hampir merupakan standar kuasi. Banyak modul dalam pustaka standar yang digunakan __version__, dan ini juga digunakan dalam banyak modul pihak ke-3, jadi ini adalah standar-semu.

Biasanya, __version__adalah string, tetapi kadang-kadang juga float atau tuple.

Sunting: seperti yang disebutkan oleh S.Lott (Terima kasih!), PEP 8 mengatakannya secara eksplisit:

Modul Nama Dunder Level

Tingkat modul "Dunders" (yaitu nama dengan dua terkemuka dan dua garis bawah tambahan) seperti __all__, __author__, __version__, dll harus ditempatkan setelah docstring modul tapi sebelum pernyataan impor kecuali dari __future__impor.

Anda juga harus memastikan bahwa nomor versi sesuai dengan format yang dijelaskan dalam PEP 440 ( PEP 386 versi sebelumnya dari standar ini).

Oefe
sumber
9
Ini harus berupa string, dan memiliki versi_info untuk versi tuple.
James Antill
James: Kenapa __version_info__khusus? (Yang "menciptakan" kata dua garis bawah Anda sendiri.) [Ketika James berkomentar, garis bawah tidak melakukan apa pun dalam komentar, sekarang mereka menunjukkan penekanan, sehingga James benar-benar __version_info__juga menulis . --- ed.]
Anda dapat melihat sesuatu tentang apa yang harus dikatakan versi di packages.python.org/distribute/... Halaman itu adalah tentang mendistribusikan, tetapi arti dari nomor versi menjadi standar de-facto.
sienkiew
2
Baik. Tampaknya PEP ini saling bertentangan. Nah, PEP 8 mengatakan "jika" dan "kasar", jadi itu tidak benar-benar mendukung menggunakan ekspansi kata kunci VCS. Juga, jika Anda pernah beralih ke VCS yang berbeda, Anda akan kehilangan informasi revisi. Oleh karena itu saya akan menyarankan menggunakan informasi versi PEP 386/440-compliant tertanam dalam file sumber tunggal, setidaknya untuk proyek yang lebih besar.
oefe
2
Di mana Anda akan meletakkan versi itu . Mengingat ini adalah versi yang diterima, saya ingin melihat info tambahan di sini.
darkgaze
120

Saya menggunakan satu _version.pyfile sebagai "tempat kanonis sekali" untuk menyimpan informasi versi:

  1. Ini menyediakan __version__atribut.

  2. Ini menyediakan versi metadata standar. Oleh karena itu akan terdeteksi oleh pkg_resourcesatau alat lain yang mengurai paket metadata (EGG-INFO dan / atau PKG-INFO, PEP 0345).

  3. Itu tidak mengimpor paket Anda (atau apa pun) ketika membangun paket Anda, yang dapat menyebabkan masalah dalam beberapa situasi. (Lihat komentar di bawah tentang masalah apa yang dapat menyebabkan ini.)

  4. Hanya ada satu tempat nomor versi ditulis, jadi hanya ada satu tempat untuk mengubahnya ketika nomor versi berubah, dan ada sedikit peluang versi tidak konsisten.

Inilah cara kerjanya: "satu tempat kanonik" untuk menyimpan nomor versi adalah file .py, bernama "_version.py" yang ada dalam paket Python Anda, misalnya di myniftyapp/_version.py. File ini adalah modul Python, tetapi setup.py Anda tidak mengimpornya! (Itu akan mengalahkan fitur 3.) Sebaliknya setup.py Anda tahu bahwa isi file ini sangat sederhana, seperti:

__version__ = "3.6.5"

Dan setup.py Anda membuka file dan mem-parsingnya, dengan kode seperti:

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,))

Kemudian setup.py Anda meneruskan string itu sebagai nilai argumen "versi" setup(), sehingga fitur 2 memuaskan.

Untuk memenuhi fitur 1, Anda dapat meminta paket Anda (pada saat run-time, bukan pada waktu setup!) Mengimpor file _version dari myniftyapp/__init__.pyseperti ini:

from _version import __version__

Berikut adalah contoh teknik ini yang telah saya gunakan selama bertahun-tahun.

Kode dalam contoh itu sedikit lebih rumit, tetapi contoh sederhana yang saya tulis dalam komentar ini harus merupakan implementasi yang lengkap.

Berikut ini contoh kode untuk mengimpor versi .

Jika Anda melihat ada yang salah dengan pendekatan ini, beri tahu saya.

Zooko
sumber
8
Bisakah Anda jelaskan masalah yang memotivasi # 3? Glyph mengatakan itu ada hubungannya dengan "setuptools suka berpura-pura bahwa kode Anda tidak ada di sistem ketika setup.py Anda berjalan", tetapi detailnya akan membantu meyakinkan saya dan orang lain.
Ivan Kozik
2
@Iva Sekarang, bagaimana urutan alat ini melakukan ini? Hal ini dapat tidak (dalam setuptools / pip / virtualenv sistem hari ini) bahkan tahu apa deps yang sampai mengevaluasi Anda setup.py. Juga, jika mencoba melakukan kedalaman penuh-pertama dan melakukan semua deps sebelum melakukan ini, itu akan macet jika ada deps melingkar. Tetapi jika ia mencoba untuk membangun paket ini sebelum menginstal dependensi, maka jika Anda mengimpor paket Anda dari Anda setup.py, ia tidak akan selalu dapat mengimpor depsnya, atau versi deps yang tepat.
Zooko
3
Bisakah Anda menulis file "version.py" dari "setup.py" alih-alih menguraikannya? Itu tampak lebih sederhana.
Jonathan Hartley
3
Jonathan Hartley: Saya setuju akan sedikit lebih mudah untuk "setup.py" Anda untuk menulis file "version.py" daripada menguraikannya, tetapi itu akan membuka jendela untuk inkonsistensi, ketika Anda mengedit setup.py Anda untuk memiliki versi baru tetapi belum menjalankan setup.py untuk memperbarui file version.py. Alasan lain untuk membuat versi kanonik berada dalam file kecil yang terpisah adalah karena hal itu memudahkan alat lain , seperti alat yang membaca status kontrol revisi Anda, untuk menulis file versi.
Zooko
3
Pendekatan serupa adalah execfile("myniftyapp/_version.py")dari dalam setup.py, daripada mencoba mengurai kode versi secara manual. Disarankan di stackoverflow.com/a/2073599/647002 - diskusi mungkin juga bermanfaat.
medmunds
97

Ditulis ulang 2017-05

Setelah lebih dari sepuluh tahun menulis kode Python dan mengelola berbagai paket saya sampai pada kesimpulan bahwa DIY mungkin bukan pendekatan terbaik.

Saya mulai menggunakan pbrpaket untuk berurusan dengan versi dalam paket saya. Jika Anda menggunakan git sebagai SCM Anda, ini akan cocok dengan alur kerja Anda seperti sulap, menghemat minggu kerja Anda (Anda akan terkejut dengan betapa kompleksnya masalah ini).

Sampai hari ini pbr berada di peringkat # 11 paket python yang paling sering digunakan dan mencapai level ini tidak termasuk trik kotor: hanya satu: memperbaiki masalah pengemasan umum dengan cara yang sangat sederhana.

pbr dapat melakukan lebih banyak dari beban pemeliharaan paket, tidak terbatas pada versi tetapi tidak memaksa Anda untuk mengadopsi semua manfaatnya.

Jadi untuk memberi Anda gambaran tentang bagaimana mengadopsi pbr dalam satu komit, lihatlah kemasan swiching untuk pbr

Mungkin Anda akan mengamati bahwa versi tersebut tidak disimpan sama sekali di repositori. PBR mendeteksi itu dari cabang dan tag Git.

Tidak perlu khawatir tentang apa yang terjadi ketika Anda tidak memiliki repositori git karena pbr melakukan "kompilasi" dan men-cache versi ketika Anda mengemas atau menginstal aplikasi, sehingga tidak ada ketergantungan runtime pada git.

Solusi lama

Inilah solusi terbaik yang pernah saya lihat sejauh ini dan itu juga menjelaskan mengapa:

Di dalam 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'

Di dalam yourpackage/__init__.py:

from .version import __version__

Di dalam setup.py:

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

Jika Anda tahu pendekatan lain yang tampaknya lebih baik beri tahu saya.

Sorin
sumber
12
Err, tidak. execfile () tidak ada di Python 3, jadi lebih baik menggunakan exec (open (). read ()).
Christophe Vu-Brugier
4
mengapa tidak from .version import __version__di setup.py juga?
April
4
@Arillion Karena paket tidak dimuat ketika setup.pysedang berjalan - coba, Anda akan mendapatkan kesalahan (atau setidaknya, saya lakukan :-))
darthbith
3
Tautan ke pbr menghasilkan gateway yang buruk.
MERose
4
pbr , tidak diragukan lagi, adalah alat yang hebat, tetapi Anda gagal menjawab pertanyaan itu. Bagaimana Anda dapat mengakses versi saat ini atau paket yang diinstal melalui bpr .
nad2000
29

Per PEP 396 yang ditangguhkan (Nomor Versi Modul) , ada cara yang diusulkan untuk melakukan ini. Ini menjelaskan, dengan alasan, standar (diakui opsional) untuk mengikuti modul. Berikut cuplikan:

3) Ketika modul (atau paket) menyertakan nomor versi, versi HARUS tersedia dalam __version__atribut.

4) Untuk modul yang hidup di dalam paket namespace, modul HARUS menyertakan __version__atribut. Paket namespace itu sendiri TIDAK HARUS menyertakan __version__atributnya sendiri .

5) Nilai __version__atribut HARUS berupa string.

Berpikir aneh
sumber
13
PEP itu tidak diterima / standar, tetapi ditangguhkan (karena kurangnya minat). Oleh karena itu agak menyesatkan untuk menyatakan bahwa " ada cara standar " yang ditentukan olehnya.
penenun
@weaver: Ya ampun! Saya belajar sesuatu yang baru. Saya tidak tahu itu adalah sesuatu yang perlu saya periksa.
Penghinaan
4
Diedit untuk dicatat itu bukan standar. Sekarang saya merasa malu, karena saya telah mengajukan permintaan fitur pada proyek meminta mereka untuk mengikuti "standar" ini.
Penghinaan
1
Mungkin Anda harus mengambil alih pekerjaan standardisasi pada PEP itu, karena Anda tampaknya tertarik :)
weaver
Ini akan berfungsi untuk membuat versi modul individual, tapi saya tidak yakin itu akan berlaku untuk versi proyek penuh.
Stevoisiak
21

Meskipun ini mungkin terlalu terlambat, ada alternatif yang sedikit lebih sederhana dari jawaban sebelumnya:

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

(Dan itu akan cukup sederhana untuk mengkonversi bagian nomor versi yang ditambahkan secara otomatis ke string menggunakan str().)

Tentu saja, dari apa yang saya lihat, orang cenderung menggunakan sesuatu seperti versi yang disebutkan sebelumnya ketika menggunakan __version_info__, dan menyimpannya sebagai tuple int; namun, saya tidak mengerti maksudnya, karena saya ragu ada situasi di mana Anda akan melakukan operasi matematika seperti penambahan dan pengurangan pada bagian-bagian nomor versi untuk tujuan apa pun selain keingintahuan atau peningkatan otomatis (dan bahkan kemudian, int()danstr() dapat digunakan dengan cukup mudah). (Di sisi lain, ada kemungkinan kode orang lain mengharapkan tupel numerik daripada tupel string dan dengan demikian gagal.)

Ini, tentu saja, pandangan saya sendiri, dan saya dengan senang hati ingin masukan dari orang lain tentang menggunakan tuple numerik.


Seperti yang diingatkan oleh shezi kepada saya, perbandingan string nomor (leksikal) tidak harus memiliki hasil yang sama dengan perbandingan numerik langsung; nol terkemuka akan diminta untuk menyediakan untuk itu. Jadi pada akhirnya, menyimpan __version_info__(atau apa pun namanya) sebagai tuple nilai integer akan memungkinkan perbandingan versi yang lebih efisien.

TUSUKAN
sumber
12
bagus (+1), tetapi bukankah Anda lebih suka angka daripada string? misalnya__version_info__ = (1,2,3)
orip
3
Perbandingan string dapat menjadi berbahaya ketika nomor versi melebihi 9, karena misalnya '10' <'2'.
D Coetzee
13
Saya melakukan ini juga tetapi sedikit tweak untuk mengatasi int .. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
rh0dium
2
Masalahnya __version__ = '.'.join(__version_info__)adalah apakah itu __version_info__ = ('1', '2', 'beta')akan menjadi 1.2.beta, bukan 1.2betaatau1.2 beta
nagisa
4
Saya pikir masalah dengan pendekatan ini adalah di mana harus meletakkan baris kode yang menyatakan versi__. Jika mereka ada di setup.py maka program Anda tidak dapat mengimpornya dari dalam paketnya. Mungkin ini bukan masalah bagi Anda, dalam hal ini, baik-baik saja. Jika mereka masuk dalam program Anda, maka setup.py Anda dapat mengimpornya, tetapi seharusnya tidak, karena setup.py dijalankan saat instalasi ketika dependensi program Anda belum diinstal (setup.py digunakan untuk menentukan apa dependensi itu .) Maka jawaban Zooko: parsing nilai secara manual dari file sumber produk, tanpa mengimpor paket produk
Jonathan Hartley
11

Banyak dari solusi ini di sini mengabaikan gittag versi yang masih berarti Anda harus melacak versi di banyak tempat (buruk). Saya mendekati ini dengan tujuan-tujuan berikut:

  • Turunkan semua referensi versi python dari tag di git repo
  • Otomatis git tag/ pushdan setup.py uploadlangkah-langkah dengan satu perintah yang tidak membutuhkan input.

Bagaimana itu bekerja:

  1. Dari sebuah make releaseperintah, versi yang di-tag terakhir di git repo ditemukan dan ditambahkan. Tag didorong kembali ke origin.

  2. The Makefiletoko versi di src/_version.pymana ia akan dibaca oleh setup.pydan juga termasuk dalam rilis. Jangan memeriksa _version.pykontrol sumber!

  3. setup.pyperintah membaca string versi baru dari package.__version__.

Detail:

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

The releaseTarget selalu increment ke-3 versi digit, tetapi Anda dapat menggunakan next_minor_veratau next_major_veruntuk kenaikan lain digit. Perintah-perintah bergantung pada versionbump.pyskrip yang diperiksa ke root dari repo

versibump.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

Ini tidak mengangkat berat bagaimana memproses dan menambah nomor versi dari git.

__init__.py

The my_module/_version.pyfile diimpor ke my_module/__init__.py. Letakkan konfigurasi pemasangan statis di sini yang ingin Anda bagikan dengan modul Anda.

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

setup.py

Langkah terakhir adalah membaca info versi dari my_modulemodul.

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__'],
    ...
    ...
)

Tentu saja, agar semua ini berfungsi, Anda harus memiliki setidaknya satu tag versi dalam repo Anda untuk memulai.

git tag -a v0.0.1
cmcginty
sumber
1
memang - seluruh utas ini lupa bahwa VCS sangat penting dalam diskusi ini. hanya sebuah obs: penambahan versi harus tetap menjadi proses manual, jadi cara yang disukai adalah 1. secara manual membuat dan mendorong tag 2. biarkan alat VCS menemukan tag itu dan menyimpannya di tempat yang diperlukan (wow - antarmuka pengeditan SO ini benar-benar melumpuhkan saya - Saya harus mengedit ini selusin kali hanya untuk berurusan dengan baris baru DAN MASIH TIDAK BEKERJA! @ # $% ^ & *)
axd
Tidak perlu digunakan versionbump.pysaat kami memiliki paket bumpversion yang luar biasa untuk python.
Oran
@ Oran Saya melihat versibump. Dokumen tidak begitu jelas, dan tidak menangani penandaan dengan sangat baik. Dalam pengujian saya tampaknya masuk ke keadaan yang menyebabkannya crash: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folder / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'mengembalikan status keluar non-nol 1.
cmcginty
1
Mengapa tidak _version.pydilacak dengan kontrol versi?
Stevoisiak
1
@StevenVascellaro Ini mempersulit proses rilis. Sekarang Anda harus memastikan bahwa Anda menandai dan melakukan kecocokan dengan nilai di _version.py. Menggunakan tag versi tunggal adalah IMO yang lebih bersih dan berarti Anda dapat membuat rilis langsung dari UI github.
cmcginty
10

Saya menggunakan file JSON di dir paket. Ini sesuai dengan persyaratan Zooko.

Di dalam pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Di dalam 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'],
    ...
    )

Di dalam 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']

Saya juga memasukkan informasi lain pkg_info.json, seperti penulis. Saya suka menggunakan JSON karena saya dapat mengotomatisasi manajemen metadata.

Andy Lee
sumber
Bisakah Anda menguraikan bagaimana menggunakan json untuk mengotomatisasi manajemen metadata? Terima kasih!
ryanjdillon
6

Juga perlu dicatat adalah bahwa juga __version__semi-std. dalam python begitu juga __version_info__yang merupakan tuple, dalam kasus-kasus sederhana Anda bisa melakukan sesuatu seperti:

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

... dan Anda bisa mendapatkan __version__string dari file, atau apa pun.

James Antill
sumber
1
Apakah Anda memiliki referensi / tautan mengenai asal dari penggunaan ini __version_info__?
Craig McQueen
3
Yah itu pemetaan yang sama dengan sys.version ke sys.version_info. Jadi: docs.python.org/library/sys.html#sys.version_info
James Antill
7
Lebih mudah melakukan pemetaan ke arah lain ( __version_info__ = (1, 2, 3)dan __version__ = '.'.join(map(str, __version_info__))).
Eric O Lebigot
2
@ EOL - __version__ = '.'.join(str(i) for i in __version_info__)- sedikit lebih panjang tetapi lebih pythonic.
ArtOfWarfare
2
Saya tidak yakin apa yang Anda usulkan jelas lebih pythonic, karena mengandung variabel dummy yang tidak benar-benar diperlukan dan yang maknanya agak sulit untuk diungkapkan ( itidak memiliki makna, version_numagak panjang dan membingungkan ...). Saya bahkan menganggap keberadaan map()dalam Python sebagai petunjuk kuat bahwa itu harus digunakan di sini, karena apa yang perlu kita lakukan di sini adalah kasus penggunaan khas map()(gunakan dengan fungsi yang ada) —Aku tidak melihat banyak kegunaan lain yang masuk akal.
Eric O Lebigot
5

Sepertinya tidak ada cara standar untuk menanamkan string versi dalam paket python. Sebagian besar paket yang saya lihat menggunakan beberapa varian dari solusi Anda, yaitu eitner

  1. Cantumkan versi setup.pydan telah setup.pymenghasilkan modul (mis. version.py) Yang hanya berisi info versi, yang diimpor oleh paket Anda, atau

  2. Sebaliknya: menaruh info versi dalam paket Anda sendiri, dan impor yang mengatur versi di setup.py

dF.
sumber
Saya suka rekomendasi Anda, tetapi bagaimana membuat modul ini dari setup.py?
sorin
1
Saya suka gagasan opsi (1), sepertinya lebih sederhana daripada jawaban Zooko untuk menguraikan nomor versi dari suatu file. Tetapi Anda tidak dapat memastikan bahwa version.py dibuat ketika dev hanya mengkloning repo Anda. Kecuali jika Anda memeriksa di version.py, yang membuka kerutan kecil yang Anda mungkin mengubah nomor versi di setup.py, komit, lepaskan, dan kemudian harus (garis miring lupa) komit perubahan ke version.py.
Jonathan Hartley
Agaknya Anda dapat membuat modul menggunakan sesuatu seperti "" "dengan open (" mypackage / version.py "," w ") sebagai fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Jonathan Hartley
1
Saya pikir opsi 2. rentan terhadap kegagalan saat menginstal seperti yang tercantum dalam komentar untuk jawaban JAB.
Jonathan Hartley
Bagaimana dengan itu? Anda memasukkan __versi__ = '0.0.1' "(di mana versinya adalah string, tentu saja) di __init__.py" dari paket utama perangkat lunak Anda. Kemudian pergi untuk poin 2: Dalam pengaturan yang Anda lakukan dari paket .__ init__ import __version__ sebagai v, dan kemudian Anda mengatur variabel v sebagai versi setup.py Anda. Kemudian impor mypack sebagai saya, cetak versi .__ saya akan mencetak versi. Versi ini akan disimpan hanya di satu tempat, dapat diakses dari seluruh kode, dalam file yang tidak mengimpor apa pun yang terkait dengan perangkat lunak.
SeF
5

panah menanganinya dengan cara yang menarik.

Sekarang (sejak 2e5031b )

Dalam arrow/__init__.py:

__version__ = 'x.y.z'

Dalam setup.py:

from arrow import __version__

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

Sebelum

Dalam arrow/__init__.py:

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

Dalam 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__'),
    # [...]
)
Anto
sumber
apa file_text?
ely
2
solusi yang diperbarui sebenarnya berbahaya. Saat setup.py berjalan, ia tidak perlu melihat versi paket dari jalur file lokal. Itu mungkin kembali ke versi yang sebelumnya diinstal, misalnya dari berjalan pip install -e .di cabang pengembangan atau sesuatu saat pengujian. setup.py benar-benar tidak harus bergantung pada impor paket itu dalam proses pemasangan untuk menentukan parameter untuk penyebaran. Astaga.
Ely
Ya saya tidak tahu mengapa panah memutuskan untuk mundur ke solusi ini. Selain itu pesan komit mengatakan "Diperbarui setup.py dengan standar Python modern " ... 🤷
Anto
4

Saya juga melihat gaya lain:

>>> django.VERSION
(1, 1, 0, 'final', 0)
L1ker
sumber
1
Ya, saya juga melihat. Setiap jawaban BTW mengambil gaya lain jadi sekarang saya tidak tahu apa gaya adalah "standar". Mencari PEP yang disebutkan ...
kbec
Cara lain terlihat; Klien Python Mongo menggunakan versi biasa, tanpa garis bawah. Jadi ini berhasil; $ python >>> import pymongo >>> pymongo.version '2.7'
AnneTheAgile
Menerapkan .VERSIONbukan berarti Anda tidak harus menerapkan __version__.
Acumenus
Apakah ini memerlukan implementasi djangodalam proyek?
Stevoisiak
3

Menggunakan setuptools danpbr

Tidak ada cara standar untuk mengelola versi, tetapi cara standar untuk mengelola paket Anda adalah setuptools .

Solusi terbaik yang saya temukan secara keseluruhan untuk mengelola versi adalah menggunakan setuptoolsdenganpbr ekstensi. Ini sekarang cara standar saya mengelola versi.

Menyiapkan proyek Anda untuk pengemasan penuh mungkin berlebihan untuk proyek-proyek sederhana, tetapi jika Anda perlu mengelola versi, Anda mungkin berada pada level yang tepat untuk hanya mengatur semuanya. Melakukan hal itu juga membuat paket Anda dapat dirilis di PyPi sehingga semua orang dapat mengunduh dan menggunakannya dengan Pip.

PBR memindahkan sebagian besar metadata dari setup.pyalat dan ke setup.cfgfile yang kemudian digunakan sebagai sumber untuk sebagian besar metadata, yang dapat menyertakan versi. Ini memungkinkan metadata untuk dikemas menjadi yang dapat dieksekusi menggunakan sesuatu seperti pyinstallerjika diperlukan (jika demikian, Anda mungkin akan memerlukan info ini ), dan memisahkan metadata dari skrip pengaturan / pengaturan paket lainnya. Anda dapat langsung memperbarui string versi setup.cfgsecara manual, dan itu akan ditarik ke dalam*.egg-info folder saat membangun rilis paket Anda. Skrip Anda kemudian dapat mengakses versi dari metadata menggunakan berbagai metode (proses ini diuraikan dalam bagian di bawah).

Saat menggunakan Git untuk VCS / SCM, pengaturan ini bahkan lebih baik, karena akan menarik banyak metadata dari Git sehingga repo Anda dapat menjadi sumber kebenaran utama Anda untuk beberapa metadata, termasuk versi, penulis, changelogs, dll. Untuk versi khusus, itu akan membuat string versi untuk komit saat ini berdasarkan pada tag git di repo.

Karena PBR akan menarik versi, penulis, changelog, dan info lainnya langsung dari repo git Anda, sehingga beberapa metadata di setup.cfgdapat ditinggalkan dan dibuat secara otomatis setiap kali distribusi dibuat untuk paket Anda (menggunakan setup.py)

Versi aktual saat ini

setuptoolsakan menarik info terbaru secara real-time menggunakan setup.py:

python setup.py --version

Ini akan menarik versi terbaru dari setup.cfg file, atau dari repo git, berdasarkan komit terbaru yang dibuat dan tag yang ada di repo. Perintah ini tidak memperbarui versi dalam distribusi.

Memperbarui versi

Ketika Anda membuat distribusi dengan setup.py(misalnya py setup.py sdist, misalnya), maka semua info saat ini akan diekstraksi dan disimpan dalam distribusi. Ini pada dasarnya menjalankan setup.py --versionperintah dan kemudian menyimpan info versi itu ke package.egg-infofolder dalam satu set file yang menyimpan metadata distribusi.

Catatan tentang proses untuk memperbarui meta-data versi:

Jika Anda tidak menggunakan pbr untuk menarik data versi dari git, maka perbarui saja setup.cfg Anda langsung dengan info versi baru (cukup mudah, tetapi pastikan ini adalah bagian standar dari proses rilis Anda).

Jika Anda menggunakan git, dan Anda tidak perlu membuat sumber atau distribusi biner (menggunakan python setup.py sdistatau salah satu python setup.py bdist_xxxperintah) cara termudah untuk memperbarui info repo git ke <mypackage>.egg-infofolder metadata Anda adalah dengan menjalankan python setup.py installperintah. Ini akan menjalankan semua fungsi PBR yang terkait dengan menarik metadata dari git repo dan memperbarui .egg-infofolder lokal Anda , menginstal skrip yang dapat dieksekusi untuk setiap titik masuk yang telah Anda tentukan, dan fungsi lain yang dapat Anda lihat dari output ketika Anda menjalankan perintah ini.

Perhatikan bahwa .egg-infofolder tersebut secara umum dikecualikan dari yang disimpan dalam git repo itu sendiri dalam .gitignorefile Python standar (seperti dari Gitignore.IO ), karena dapat dihasilkan dari sumber Anda. Jika dikecualikan, pastikan Anda memiliki "proses rilis" standar untuk memperbarui metadata secara lokal sebelum rilis, dan paket apa pun yang Anda unggah ke PyPi.org atau didistribusikan harus menyertakan data ini untuk memiliki versi yang benar. Jika Anda ingin repo Git berisi info ini, Anda dapat mengecualikan file tertentu agar tidak diabaikan (yaitu menambahkan !*.egg-info/PKG_INFOke .gitignore)

Mengakses versi dari skrip

Anda bisa mengakses metadata dari build saat ini di dalam skrip Python dalam paket itu sendiri. Untuk versi, misalnya, ada beberapa cara untuk melakukan ini yang saya temukan sejauh ini:

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

Anda dapat memasukkan salah satu dari ini secara langsung __init__.pyke dalam paket Anda untuk mengekstrak info versi sebagai berikut, mirip dengan beberapa jawaban lain:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
LightCC
sumber
Dipformat ulang per suara untuk langsung menjawab pertanyaan sekarang.
LightCC
1

Setelah beberapa jam mencoba menemukan solusi andal yang paling sederhana, berikut adalah bagian-bagiannya:

buat file version.py DI DALAM folder "/ mypackage" paket Anda:

# 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'

di setup.py:

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

di folder utama init .py:

from .version import __version__

The exec()Fungsi menjalankan luar naskah impor apapun, karena setup.py dijalankan sebelum modul dapat diimpor. Anda masih hanya perlu mengelola nomor versi dalam satu file di satu tempat, tetapi sayangnya itu tidak ada di setup.py. (itulah sisi buruknya, tetapi tidak memiliki bug impor adalah sisi baiknya)

Marc Maxmeister
sumber
1

Banyak pekerjaan menuju versi seragam dan mendukung konvensi telah selesai sejak pertanyaan ini pertama kali diajukan . Opsi Palatable sekarang dirinci dalam Panduan Pengguna Kemasan Python . Juga patut dicatat adalah bahwa skema nomor versi relatif ketat dalam Python per PEP 440 , dan menjaga hal-hal yang waras sangat penting jika paket Anda akan dirilis ke Toko Keju .

Berikut uraian singkat opsi versi:

  1. Baca file di setup.py( setuptools ) dan dapatkan versinya.
  2. Gunakan alat membangun eksternal (untuk memperbarui keduanya __init__.pyserta kontrol sumber), misalnya bump2version , perubahan atau zest.releaser .
  3. Tetapkan nilai ke __version__variabel global dalam modul tertentu.
  4. Tempatkan nilai dalam file teks VERSION sederhana untuk setup.py dan kode untuk dibaca.
  5. Tetapkan nilai melalui setup.pyrilis, dan gunakan importlib.metadata untuk mengambilnya saat runtime. (Peringatan, ada versi pra-3.8 dan pasca-3.8.)
  6. Tetapkan nilai ke __version__dalam sample/__init__.pydan impor sampel masuksetup.py .
  7. Gunakan setuptools_scm untuk mengekstrak versi dari kontrol sumber sehingga referensi kanonik, bukan kode.

Perhatikan bahwa (7) mungkin pendekatan yang paling modern (metadata build tidak tergantung pada kode, diterbitkan oleh otomatisasi). Juga PERHATIKAN bahwa jika pengaturan digunakan untuk rilis paket yang sederhana python3 setup.py --versionakan melaporkan versi secara langsung.

di sana
sumber
-1

Untuk apa nilainya, jika Anda menggunakan distutils NumPy, numpy.distutils.misc_util.Configurationmemiliki make_svn_version_py()metode yang menyematkan nomor revisi di package.__svn_version__dalam variabel version.

Mat
sumber
Bisakah Anda memberikan lebih banyak detail atau contoh bagaimana ini akan bekerja?
Stevoisiak
Hmm. Pada tahun 2020, ini (apakah selalu?) Dimaksudkan untuk FORTRAN . Package "numpy.distutils adalah bagian dari NumPy yang memperluas distutil Python standar untuk menangani sumber-sumber Fortran."
ingyhere
-1
  1. Gunakan version.pyfile hanya dengan __version__ = <VERSION>param di file. Dalam setup.pyfile impor __version__param dan masukkan nilainya dalam setup.pyfile seperti ini: version=__version__
  2. Cara lain adalah dengan hanya menggunakan setup.pyfile dengan version=<CURRENT_VERSION>- CURRENT_VERSION adalah hardcoded.

Karena kami tidak ingin secara manual mengubah versi dalam file setiap kali kami membuat tag baru (siap merilis versi paket baru), kami dapat menggunakan yang berikut ..

Saya sangat merekomendasikan paket bumpversion . Saya telah menggunakannya selama bertahun-tahun untuk menabrak versi.

mulai dengan menambahkan version=<VERSION>ke setup.pyfile Anda jika Anda belum memilikinya.

Anda harus menggunakan skrip pendek seperti ini setiap kali Anda membuat versi:

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

Kemudian tambahkan satu file per repo bernama .bumpversion.cfg::

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

catatan:

  • Anda dapat menggunakan __version__parameter di bawah version.pyfile seperti yang disarankan di posting lain dan memperbarui file bumpversion seperti ini: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Anda harus git commit atau git resetsemua yang ada dalam repo Anda, jika tidak, Anda akan mendapatkan kesalahan repo kotor.
  • Pastikan bahwa lingkungan virtual Anda termasuk paket bumpversion, tanpanya tidak akan berfungsi.
Oran
sumber
@cmcginty Maaf atas keterlambatan ini, silakan periksa jawaban saya ^^^ - perhatikan bahwa Anda harus git komit atau git setel ulang semua yang ada di repo Anda dan pastikan bahwa lingkungan virtual Anda menyertakan paket bumpversion, tanpa itu tidak akan berfungsi. Gunakan versi terbaru.
Oran
Saya agak tidak jelas tentang solusi apa yang disarankan di sini. Apakah Anda merekomendasikan untuk melacak versi secara manual version.py, atau melacaknya dengan bumpversion?
Stevoisiak
@StevenVascellaro Saya menyarankan menggunakan bumpversion, tidak pernah menggunakan versi manual. Apa yang saya coba jelaskan adalah Anda dapat mengarahkan bumpversion untuk memperbarui versi dalam file setup.py atau lebih baik menggunakannya untuk memperbarui file version.py. Ini adalah praktik yang lebih umum untuk memperbarui file version.py dan mengambil nilai __version__param ke dalam file setup.py. Solusi saya digunakan dalam produksi dan ini adalah praktik umum. Catatan: hanya untuk menjadi jelas, menggunakan bumpversion sebagai bagian dari skrip adalah solusi terbaik, taruh di CI Anda dan itu akan menjadi operasi otomatis.
Oran
-3

Jika Anda menggunakan CVS (atau RCS) dan menginginkan solusi cepat, Anda dapat menggunakan:

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

(Tentu saja, nomor revisi akan diganti untuk Anda oleh CVS.)

Ini memberi Anda versi ramah-cetak dan info versi yang dapat Anda gunakan untuk memeriksa apakah modul yang Anda impor memiliki setidaknya versi yang diharapkan:

import my_module
assert my_module.__version_info__ >= (1, 1)
Martin Ibert
sumber
File apa yang Anda rekomendasikan __version__untuk disimpan ? Bagaimana seseorang menambah nomor versi dengan solusi ini?
Stevoisiak