Jawaban:
Untuk mengambil versi dari dalam paket Anda pada saat runtime (apa yang sebenarnya ditanyakan oleh pertanyaan Anda), Anda dapat menggunakan:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Jika Anda ingin melakukan hal sebaliknya (yang tampaknya merupakan jawaban yang oleh penulis lain tampaknya Anda tanyakan), letakkan string versi di file terpisah dan baca isi file itu setup.py
.
Anda bisa membuat version.py dalam paket Anda dengan sebuah __version__
baris, lalu membacanya dari setup.py menggunakan execfile('mypackage/version.py')
, sehingga ia menetapkan __version__
di namespace setup.py.
Jika Anda menginginkan cara yang lebih sederhana yang akan bekerja dengan semua versi Python dan bahkan bahasa non-Python yang mungkin memerlukan akses ke string versi:
Simpan string versi sebagai satu-satunya konten file teks biasa, beri nama eg VERSION
, dan baca file itu selama setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
File yang sama VERSION
kemudian akan berfungsi sama baiknya di program lain, bahkan yang non-Python, dan Anda hanya perlu mengubah string versi di satu tempat untuk semua program.
Ngomong-ngomong, JANGAN mengimpor paket Anda dari setup.py Anda seperti yang disarankan dalam jawaban lain di sini: tampaknya akan bekerja untuk Anda (karena Anda sudah memiliki dependensi paket Anda diinstal), tetapi itu akan mendatangkan malapetaka pada pengguna baru paket Anda. , karena mereka tidak akan dapat menginstal paket Anda tanpa menginstal dependensi secara manual terlebih dahulu.
execfile
berfungsi dengan sangat baik ... tetapi (sayangnya) tidak bekerja dengan Python 3.
with open('mypackage/version.py') as f: exec(f.read())
sebagai pengganti execfile('mypackage/version.py')
. (Dari stackoverflow.com/a/437857/647002 )
mymodule
Bayangkan konfigurasi ini:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Kemudian bayangkan beberapa skenario biasa di mana Anda memiliki dependensi dan setup.py
terlihat seperti:
setup(...
install_requires=['dep1','dep2', ...]
...)
Dan sebuah contoh __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
Dan misalnya myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
selama pengaturanJika setup.py
impor Anda mymodule
maka selama setup Anda kemungkinan besar akan mendapatkan ImportError
. Ini adalah kesalahan yang sangat umum ketika paket Anda memiliki dependensi. Jika paket Anda tidak memiliki dependensi lain selain bawaan, Anda mungkin aman; Namun ini bukan praktik yang baik. Alasan untuk itu adalah bahwa itu bukan bukti masa depan; katakan besok kode Anda perlu mengkonsumsi beberapa ketergantungan lainnya.
__version__
?Jika Anda memasukkan hardcode ke __version__
dalam, setup.py
maka itu mungkin tidak cocok dengan versi yang akan Anda kirimkan dalam modul Anda. Agar konsisten, Anda akan meletakkannya di satu tempat dan membacanya dari tempat yang sama saat Anda membutuhkannya. Menggunakan import
Anda mungkin mendapatkan masalah # 1.
setuptools
Anda akan menggunakan kombinasi dari open
, exec
dan memberikan perintah untuk exec
menambahkan variabel:
# 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__'],
...)
Dan dalam mymodule/version.py
mengekspos versi:
__version__ = 'some.semantic.version'
Dengan cara ini, versi dikirimkan bersama modul, dan Anda tidak memiliki masalah selama pengaturan mencoba mengimpor modul yang tidak memiliki dependensi (belum diinstal).
Teknik terbaik adalah mendefinisikan __version__
kode produk Anda, lalu mengimpornya ke setup.py dari sana. Ini memberi Anda nilai yang dapat Anda baca di modul yang sedang berjalan, dan hanya memiliki satu tempat untuk mendefinisikannya.
Nilai-nilai di setup.py tidak diinstal, dan setup.py tidak bertahan setelah instalasi.
Apa yang saya lakukan (misalnya) di coverage.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): coverage.py tidak lagi mengimpor sendiri untuk mendapatkan versi. Mengimpor kode Anda sendiri dapat membuatnya tidak bisa dihapus, karena kode produk Anda akan mencoba mengimpor dependensi, yang belum diinstal, karena setup.py adalah yang menginstalnya.
__version__
itu rusak dalam beberapa cara, maka impor Anda juga akan rusak. Python tidak tahu bagaimana hanya menafsirkan pernyataan yang Anda inginkan. @pjeby benar: jika modul Anda perlu mengimpor modul lain, mereka mungkin belum diinstal, dan itu akan kacau. Teknik ini berfungsi jika Anda berhati-hati bahwa impor tidak menyebabkan rantai panjang impor lainnya.
pip install <packagename>
, saya mendapatkan error berikut: ImportError: No module named <packagename>
. PERINGATAN pembaca Anda bahwa Anda tidak dapat menjalankan file setup.py seperti ini di lingkungan di mana paket belum diinstal!
Pertanyaan Anda agak kabur, tetapi saya pikir apa yang Anda tanyakan adalah bagaimana menentukannya.
Anda perlu mendefinisikan __version__
seperti:
__version__ = '1.4.4'
Dan kemudian Anda dapat mengonfirmasi bahwa setup.py tahu tentang versi yang baru saja Anda tentukan:
% ./setup.py --version
1.4.4
Saya tidak senang dengan jawaban ini ... tidak ingin memerlukan setuptools, atau membuat modul terpisah untuk variabel tunggal, jadi saya datang dengan ini.
Karena ketika Anda yakin modul utama dalam gaya pep8 dan akan tetap seperti itu:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Jika Anda ingin ekstra hati-hati dan menggunakan pengurai nyata:
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 agak modul sekali pakai jadi tidak masalah jika itu agak jelek.
Pembaruan: cukup lucu Saya sudah pindah dari ini dalam beberapa tahun terakhir dan mulai menggunakan file terpisah dalam paket yang disebut meta.py
. Saya memasukkan banyak data meta ke sana yang mungkin ingin saya ubah sesering mungkin. Jadi, bukan hanya untuk satu nilai.
ast.get_docstring()
beberapa .split('\n')[0].strip()
dll untuk secara otomatis mengisi description
dari sumbernya. Satu hal lagi yang perlu disinkronkan
Buat file di pohon sumber Anda, misalnya di yourbasedir / yourpackage / _version.py. Biarkan file itu hanya berisi satu baris kode, seperti ini:
__version__ = "1.1.0-r4704"
Kemudian di setup.py Anda, buka file itu dan uraikan nomor versi seperti ini:
verstr = "tidak diketahui" mencoba: verstrline = terbuka ('paket Anda / _version.py', "rt"). read () kecuali EnvironmentError: lulus # Oke, tidak ada file versi. lain: VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] " mo = penelitian ulang (VSRE, verstrline, re.M) jika mo: verstr = mo.group (1) lain: meningkatkan RuntimeError ("tidak dapat menemukan versi di paket Anda / _version.py")
Akhirnya, dalam yourbasedir/yourpackage/__init__.py
impor _versi seperti ini:
__version__ = "tidak diketahui" mencoba: dari _version import __version__ kecuali ImportError: # Kami berjalan di pohon yang tidak memiliki _version.py, jadi kami tidak tahu apa versi kami. lulus
Contoh kode yang melakukan ini adalah paket "pyutil" yang saya pelihara. (Lihat pencarian PyPI atau google - stackoverflow melarang saya untuk memasukkan hyperlink ke dalamnya dalam jawaban ini.)
@pjeby benar bahwa Anda tidak boleh mengimpor paket Anda dari setup.py sendiri. Itu akan bekerja ketika Anda mengujinya dengan membuat juru bahasa Python baru dan menjalankan setup.py di dalamnya hal pertama:, python setup.py
tetapi ada beberapa kasus ketika itu tidak akan berfungsi. Itu karena import youpackage
tidak berarti membaca direktori kerja saat ini untuk direktori bernama "paket Anda", artinya mencari arus sys.modules
untuk kunci "paket Anda" dan kemudian melakukan berbagai hal jika tidak ada di sana. Jadi selalu berfungsi ketika Anda melakukannya python setup.py
karena Anda memiliki yang baru, kosong sys.modules
, tetapi ini tidak bekerja secara umum.
Misalnya, bagaimana jika py2exe mengeksekusi setup.py Anda sebagai bagian dari proses mengemas aplikasi? Saya telah melihat kasus seperti ini di mana py2exe akan memasukkan nomor versi yang salah pada suatu paket karena paket tersebut mendapatkan nomor versinyaimport myownthing
di setup.py-nya, tetapi versi berbeda dari paket itu sebelumnya telah diimpor selama menjalankan py2exe. Demikian juga, bagaimana jika setuptools, easy_install, distribusikan, atau distutils2 sedang mencoba membangun paket Anda sebagai bagian dari proses menginstal paket berbeda yang tergantung pada Anda? Lalu apakah paket Anda dapat diimpor pada saat setup.py sedang dievaluasi, atau apakah sudah ada versi paket Anda yang telah diimpor selama masa juru bahasa Python ini, atau apakah mengimpor paket Anda memerlukan paket lain yang harus diinstal terlebih dahulu , atau memiliki efek samping, dapat mengubah hasil. Saya mengalami beberapa kesulitan dalam mencoba menggunakan kembali paket Python yang menyebabkan masalah untuk alat seperti py2exe dan setuptools karena setup.py mereka mengimpor paket itu sendiri untuk menemukan nomor versinya.
Omong-omong, teknik ini berfungsi baik dengan alat untuk secara otomatis membuat yourpackage/_version.py
file untuk Anda, misalnya dengan membaca riwayat kontrol revisi Anda dan menuliskan nomor versi berdasarkan tag terbaru dalam riwayat kontrol revisi. Berikut adalah alat yang melakukan itu untuk darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst dan di sini adalah potongan kode yang melakukan hal yang sama untuk git: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Ini juga harus berfungsi, menggunakan ekspresi reguler dan bergantung pada bidang metadata untuk memiliki format seperti ini:
__fieldname__ = 'value'
Gunakan yang berikut di awal setup.py Anda:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Setelah itu, Anda dapat menggunakan metadata dalam skrip Anda seperti ini:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Dengan struktur seperti ini:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
di mana version.py berisi:
__version__ = 'version_string'
Anda dapat melakukan ini di setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Ini tidak akan menimbulkan masalah dengan dependensi apa pun yang Anda miliki di mymodule / __ init__.py
Untuk menghindari mengimpor file (dan dengan demikian mengeksekusi kodenya) orang dapat menguraikannya dan memulihkan version
atribut dari pohon sintaks:
# 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
Kode ini mencoba untuk menemukan __version__
atau VERSION
penugasan di tingkat atas pengembalian modul adalah nilai string. Sisi kanan bisa berupa string atau tupel.
Ada seribu cara menguliti kucing - inilah milik saya:
# 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.")
Membersihkan https://stackoverflow.com/a/12413800 dari @ 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
Sekarang ini kotor dan perlu beberapa pemurnian (bahkan mungkin ada panggilan anggota yang tidak ditemukan di pkg_resources yang saya lewatkan), tetapi saya tidak melihat mengapa ini tidak berhasil, atau mengapa tidak ada yang menyarankan untuk berkencan (Googling sekitar memiliki tidak mengaktifkan ini) ... perhatikan bahwa ini adalah Python 2.x, dan akan membutuhkan pkg_resources (sigh):
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
Kami ingin menempatkan informasi meta tentang paket kami pypackagery
di __init__.py
, tapi tidak bisa karena memiliki dependensi pihak ketiga sebagai PJ Eby sudah menunjukkan (lihat jawaban dan peringatan mengenai kondisi lomba).
Kami menyelesaikannya dengan membuat modul terpisah pypackagery_meta.py
yang hanya berisi informasi 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'
kemudian mengimpor informasi meta di packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
dan akhirnya menggunakannya di 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'],
)
Anda harus memasukkan pypackagery_meta
ke dalam paket Anda dengan py_modules
argumen pengaturan. Kalau tidak, Anda tidak dapat mengimpornya saat instalasi karena distribusi paket akan kekurangannya.
Sederhana dan lurus, buat file yang disebut source/package_name/version.py
dengan konten berikut:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Kemudian, pada file source/package_name/__init__.py
Anda, Anda mengimpor versi untuk digunakan orang lain:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Sekarang, Anda bisa memakai ini 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()
Diuji ini dengan Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
dan 3.7
di Linux, Windows dan Mac OS. Saya menggunakan paket saya yang memiliki Tes Integrasi dan Unit untuk semua platform tesis. Anda dapat melihat hasil dari .travis.yml
dan di appveyor.yml
sini:
Versi alternatif menggunakan pengelola konteks:
#!/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 ) )
Anda juga dapat menggunakan codecs
modul untuk menangani kesalahan unicode baik pada Python 2.7
dan3.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 ) )
Jika Anda menulis modul Python 100% dalam C / C ++ menggunakan Python C Extensions, Anda dapat melakukan hal yang sama, tetapi menggunakan C / C ++ alih-alih Python.
Pada kasus ini, buat yang berikut ini 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'],
)
],
)
Yang membaca versi dari file version.h
:
const char* __version__ = "1.0.12";
Tapi, jangan lupa untuk membuat MANIFEST.in
menyertakan version.h
file:
include README.md
include LICENSE.txt
recursive-include source *.h
Dan itu terintegrasi ke dalam aplikasi utama dengan:
#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__ ) );
...
}
Referensi:
menyebarkan paket ke server dan konvensi penamaan file untuk paket indeks:
contoh untuk konversi versi dinamis pip:
menang:
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
Temukan contoh setup.py memanggil versi pip dinamis yang cocok dari git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Saya menggunakan variabel lingkungan seperti di bawah ini
VERSION = 0.0.0 python setup.py sdist bdist_wheel
Di setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Untuk pemeriksaan konsistensi dengan versi pengepak, saya menggunakan skrip di bawah ini.
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