الطريقة القياسية لتضمين الإصدار في حزمة بايثون؟
سؤال
هل هناك طريقة قياسية لربط سلسلة الإصدار بحزمة بايثون بطريقة تمكنني من القيام بما يلي؟
import foo
print foo.version
أتخيل أن هناك طريقة ما لاسترداد تلك البيانات دون أي تشفير إضافي، حيث يتم تحديد السلاسل الثانوية/الرئيسية في setup.py
بالفعل.الحل البديل الذي وجدته هو أن يكون import __version__
في foo/__init__.py
ومن ثم يكون __version__.py
تم إنشاؤها بواسطة setup.py
.
المحلول
ليست إجابة مباشرة على سؤالك، ولكن يجب أن تفكر في تسميتها __version__
, ، لا version
.
وهذا يكاد يكون شبه قياسي.العديد من الوحدات في استخدام المكتبة القياسية __version__
, ، وهذا يستخدم أيضا في الكثير من وحدات الطرف الثالث، لذلك فهي شبه القياسية.
عادة، __version__
عبارة عن سلسلة، ولكنها في بعض الأحيان تكون أيضًا عائمة أو صفًا.
يحرر:كما ذكر س. لوت (شكرًا لك!)، بيب 8 يقول ذلك صراحة:
مسك الدفاتر الإصدار
إذا كان عليك أن يكون لديك تخريب أو CVS أو RCS Crud في ملف المصدر الخاص بك ، فقم بذلك على النحو التالي.
__version__ = "$Revision: 63990 $" # $Source$
يجب تضمين هذه الأسطر بعد سلسلة docstring الخاصة بالوحدة ، قبل أي رمز آخر ، مفصول بسطر فارغ أعلى وأسفل.
يجب عليك أيضًا التأكد من أن رقم الإصدار يتوافق مع التنسيق الموضح فيه بيب 440 (بيب 386 نسخة سابقة من هذا المعيار).
نصائح أخرى
أنا استخدم واحد _version.py
الملف باعتباره "المكان الأساسي" لتخزين معلومات الإصدار:
يوفر أ
__version__
يصف.وهو يوفر إصدار بيانات التعريف القياسية.ولذلك سيتم الكشف عن طريق
pkg_resources
أو الأدوات الأخرى التي تحلل بيانات تعريف الحزمة (EGG-INFO و/أو PKG-INFO, PEP 0345).فهو لا يستورد الحزمة الخاصة بك (أو أي شيء آخر) عند إنشاء الحزمة الخاصة بك، مما قد يسبب مشاكل في بعض المواقف.(راجع التعليقات أدناه حول المشكلات التي يمكن أن يسببها ذلك.)
يوجد مكان واحد فقط يتم فيه كتابة رقم الإصدار، لذلك هناك مكان واحد فقط لتغييره عندما يتغير رقم الإصدار، وتكون هناك فرصة أقل للإصدارات غير المتسقة.
وهنا كيف يعمل:"المكان الأساسي الوحيد" لتخزين رقم الإصدار هو ملف .py، المسمى "_version.py" الموجود في حزمة Python الخاصة بك، على سبيل المثال في myniftyapp/_version.py
.هذا الملف عبارة عن وحدة Python، لكن ملف setup.py الخاص بك لا يستورده!(وهذا من شأنه أن يهزم الميزة 3.) وبدلاً من ذلك، يعرف ملف setup.py الخاص بك أن محتويات هذا الملف بسيطة جدًا، مثل:
__version__ = "3.6.5"
وهكذا يفتح ملف setup.py الخاص بك الملف ويوزعه باستخدام رمز مثل:
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,))
بعد ذلك، يقوم setup.py بتمرير تلك السلسلة كقيمة وسيطة "الإصدار" إليها setup()
, ، وبالتالي ميزة مرضية 2.
لتلبية الميزة 1، يمكنك جعل الحزمة الخاصة بك (في وقت التشغيل، وليس في وقت الإعداد!) تستورد ملف _version من myniftyapp/__init__.py
مثله:
from _version import __version__
هنا مثال على هذه التقنية الذي كنت أستخدمه لسنوات.
الكود الموجود في هذا المثال أكثر تعقيدًا بعض الشيء، لكن المثال المبسط الذي كتبته في هذا التعليق يجب أن يكون تطبيقًا كاملاً.
هنا رمز المثال لاستيراد الإصدار.
إذا رأيت أي خطأ في هذا النهج، واسمحوا لي أن أعرف.
إعادة كتابة 2017-05
وبعد أكثر من عشر سنوات من كتابة التعليمات البرمجية بايثون وإدارة مختلف حزم جئت إلى استنتاج مفاده أن DIY هو ربما لا يكون أفضل نهج.
ولقد بدأت باستخدام حزمة pbr
للتعامل مع الإصدارات في حزم بلدي. إذا كنت تستخدم بوابة كما SCM الخاص، وهذا سوف تنسجم مع العمل الخاص بك مثل السحر، وتوفير أسابيع عملك (سوف يفاجأ حول كيفية مجمع يمكن أن تكون قضية).
واعتبارا من اليوم PBR في المرتبة رقم 11 الأكثر استخداما حزمة الثعبان والوصول ديدن هذا المستوى " ر تتضمن أي الحيل القذرة: كان واحد فقط: تحديد مشكلة التعبئة والتغليف مشتركة في طريقة بسيطة للغاية
وpbr
يمكن أن تفعل أكثر من عبء صيانة الحزمة، لا يقتصر على إصدارات لكنه لا يجبرك على اتخاذ جميع فوائدها.
وذلك لإعطائك فكرة عن كيف يبدو اعتماد PBR في واحدة ارتكاب ديك swiching التعبئة والتغليف لPBR
وربما كنت لاحظت أن النسخة لا تخزن على الإطلاق في المخزون. PBR يفعل الكشف عن ذلك من فروع الجهاز الهضمي والعلامات.
لا داعي للقلق حول ما يحدث عندما لم يكن لديك بوابة مستودع لاستعراض البرامج والميزانية لا "تجميع" وذاكرة التخزين المؤقت النسخة عند حزم أو تثبيت التطبيقات، لذلك ليس هناك تبعية وقت التشغيل على بوابة.
قديم الحل
وهنا هو الحل الأفضل رأيت حتى الآن، وهذا ما يفسر أيضا لماذا:
وداخل 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'
وداخل yourpackage/__init__.py
:
from .version import __version__
وداخل setup.py
:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
إذا كنت تعرف نهج آخر يبدو أن من الافضل ترك لي أن أعرف.
PEP 396 (أرقام إصدار الوحدة النمطية) و هناك طريقة المقترحة للقيام بذلك. فهو يصف، مع المنطق، و(اختياري المسلم) معيارا لوحدات لمتابعة. وهنا مقتطف:
<اقتباس فقرة> و3) عندما تتضمن وحدة نمطية (أو مجموعة) ورقم الإصدار، النسخة ينبغي أن تكون متاحة في سمة __version__
.
و4)
لوحدات التي تعيش داخل حزمة مساحة الاسم، وحدة وينبغي أن تتضمن السمة __version__
. حزمة مساحة الاسم نفسه يجب أن لا تتضمن السمة __version__
الخاصة بها.
و5)
يجب أن تكون قيمة السمة __version__
في سلسلة.
وعلى الرغم من هذا، ربما تكون بعيدة في وقت متأخر جدا، وهناك بديل أبسط قليلا إلى الإجابة السابقة:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(وسيكون بسيطة الى حد كبير لتحويل أجزاء-تزايد السيارات من أرقام إصدار لسلسلة باستخدام str()
).
وبطبيعة الحال، من ما رأيت، ويميل الناس إلى استخدام شيء مثل النسخة المذكورة سابقا عند استخدام __version_info__
، وعلى هذا النحو مخزن بأنها الصفوف (tuple) من [إينتس]. ومع ذلك، لا أرى تماما نقطة في القيام بذلك، وأنا أشك في أن هناك الحالات التي من شأنها أن تؤدي العمليات الحسابية مثل الجمع والطرح على أجزاء من أرقام إصدار لأي غرض من الأغراض إلى جانب الفضول أو لصناعة السيارات في incrementation (وحتى ذلك الحين، int()
وstr()
يمكن استخدامها بسهولة إلى حد ما). (من ناحية أخرى، هناك إمكانية متاحة لشخص آخر تتوقع الصفوف (tuple) العددية بدلا من الصفوف (tuple) سلسلة، وبالتالي فشلها.)
وهذا هو، بالطبع، وجهة نظري الخاصة، وأود بكل سرور مثل مدخلات الآخرين على استخدام الصفوف (tuple) العددية.
وكما shezi ذكرني، (المعجمية) مقارنات بين سلاسل عدد ليس بالضرورة نفس النتيجة مثل المقارنات العددية المباشرة؛ ستكون هناك حاجة أصفار مما أدى إلى توفير ذلك. حتى في نهاية المطاف، وتخزين __version_info__
(أو ما سوف يطلق عليه) باعتباره الصفوف (tuple) من قيم الأعداد الصحيحة من شأنه أن يسمح للمقارنات نسخة أكثر كفاءة.
وأنا استخدم ملف JSON في حزمة دير. هذا يناسب متطلبات Zooko ل.
وداخل pkg_dir/pkg_info.json
:
{"version": "0.1.0"}
وداخل 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'],
...
)
وداخل 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']
وأنا أيضا وضع المعلومات الأخرى في pkg_info.json
، مثل المؤلف. أنا
ترغب في استخدام JSON لأنني لا يمكن أتمتة إدارة البيانات الوصفية.
يتم تجاهل العديد من هذه الحلول هنا git
علامات الإصدار والتي لا تزال تعني أنه يتعين عليك تتبع الإصدار في أماكن متعددة (سيء).لقد اقتربت من هذا مع الأهداف التالية:
- اشتق جميع مراجع إصدار بايثون من علامة في ملف
git
الريبو - أتمتة
git tag
/push
وsetup.py upload
خطوات باستخدام أمر واحد لا يأخذ أي مدخلات.
كيف تعمل:
من
make release
الأمر، تم العثور على الإصدار الأخير الموسوم في git repo وزيادته.يتم دفع العلامة مرة أخرى إلىorigin
.ال
Makefile
يخزن الإصدار فيsrc/_version.py
حيث سيتم قراءتهاsetup.py
وأيضا المدرجة في الإصدار. لا تتحقق_version.py
في التحكم بالمصادر!setup.py
يقرأ الأمر سلسلة الإصدار الجديد منpackage.__version__
.
تفاصيل:
ملف تعريف
# 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
ال release
الهدف يزيد دائمًا رقم الإصدار الثالث، ولكن يمكنك استخدام next_minor_ver
أو next_major_ver
لزيادة الأرقام الأخرى.الأوامر تعتمد على versionbump.py
البرنامج النصي الذي تم فحصه في جذر الريبو
versionbump.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
يؤدي هذا إلى الرفع الثقيل لكيفية معالجة رقم الإصدار وزيادته git
.
__init__.py
ال my_module/_version.py
يتم استيراد الملف إلى my_module/__init__.py
.ضع أي تكوين تثبيت ثابت هنا تريد توزيعه مع الوحدة النمطية الخاصة بك.
from ._version import __version__
__author__ = ''
__email__ = ''
setup.py
الخطوة الأخيرة هي قراءة معلومات الإصدار من ملف my_module
وحدة.
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__'],
...
...
)
بالطبع، لكي ينجح كل هذا، يجب أن يكون لديك علامة إصدار واحدة على الأقل في الريبو الخاص بك للبدء.
git tag -a v0.0.1
لا يبدو أن هناك طريقة قياسية لتضمين سلسلة إصدار في حزمة بايثون.معظم الحزم التي رأيتها تستخدم بعض أشكال الحل الخاص بك، على سبيل المثال.ايتنر
تضمين الإصدار في
setup.py
و لديsetup.py
إنشاء وحدة نمطية (على سبيل المثالversion.py
) تحتوي فقط على معلومات الإصدار التي تم استيرادها بواسطة الحزمة الخاصة بك، أوالعكس:ضع معلومات الإصدار في الحزمة الخاصة بك نفسها، وقم باستيرادها الذي - التي لتعيين الإصدار في
setup.py
والجدير بالذكر أيضا هو أن وكذلك __version__
كونه شبه الأمراض المنقولة جنسيا. في بيثون ذلك هو __version_info__
وهو الصفوف (tuple)، في الحالات البسيطة التي يمكن أن تفعل شيئا مثل:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
... ويمكنك الحصول على سلسلة __version__
من ملف، أو أيا كان.
ورأيت أيضا نمط آخر:
>>> django.VERSION
(1, 1, 0, 'final', 0)
السهم يعالج ذلك بطريقة مشوقة.
في arrow/__init__.py
:
__version__ = '0.8.0'
VERSION = __version__
في setup.py
:
def grep(attrname):
pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
strval, = re.findall(pattern, file_text)
return strval
setup(
name='arrow',
version=grep('__version__'),
# [...]
)
لما يستحق، إذا كنت تستخدم distutils نمباي، numpy.distutils.misc_util.Configuration
لها <لأ href = "http://docs.scipy.org/doc/numpy/reference/distutils.html#numpy.distutils.misc_util. Configuration.make_svn_version_py "يختلط =" نوفولو noreferrer "> make_svn_version_py()
طريقة التي يضمن رقم المراجعة داخل package.__svn_version__
في version
متغير.
- إستخدم
version.py
الملف فقط مع__version__ = <VERSION>
المعلمة في الملف.في الsetup.py
استيراد ملف__version__
param ووضع قيمتها فيsetup.py
ملف مثل هذا:version=__version__
- طريقة أخرى هي استخدام مجرد
setup.py
ملف معversion=<CURRENT_VERSION>
- تم ترميز CURRENT_VERSION بشكل ثابت.
نظرًا لأننا لا نريد تغيير الإصدار الموجود في الملف يدويًا في كل مرة نقوم فيها بإنشاء علامة جديدة (جاهزة لإصدار إصدار حزمة جديد)، فيمكننا استخدام ما يلي..
أنصح بشدة com.bumpversion طَرد.لقد كنت أستخدمه لسنوات لإصدار نسخة.
ابدأ بإضافة version=<VERSION>
لك setup.py
الملف إذا لم يكن لديك بالفعل.
يجب عليك استخدام برنامج نصي قصير مثل هذا في كل مرة تقوم فيها بتحديث إصدار:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
ثم قم بإضافة ملف واحد لكل الريبو يسمى: .bumpversion.cfg
:
[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]
ملحوظة:
- يمكنك استخدام
__version__
المعلمة تحتversion.py
الملف كما تم اقتراحه في منشورات أخرى وقم بتحديث ملفumpversion مثل هذا:[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- أنت يجب
git commit
أوgit reset
كل شيء في الريبو الخاص بك، وإلا فسوف تحصل على خطأ الريبو القذر. - تأكد من أن بيئتك الافتراضية تتضمن حزمة Bumpversion، فبدونها لن تعمل.
إذا كنت تستخدم CVS (أو RCS) وتريد حل سريع، يمكنك استخدام:
__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])
(وبطبيعة الحال، سوف تكون بديلا للرقم المراجعة لك من قبل CVS).
وهذا يوفر لك نسخة للطباعة ومعلومات النسخة التي يمكنك استخدامها للتأكد من أن وحدة تقوم باستيراده لديها ما لا يقل عن النسخة المتوقع:
import my_module
assert my_module.__version_info__ >= (1, 1)