الطريقة القياسية لتضمين الإصدار في حزمة بايثون؟

StackOverflow https://stackoverflow.com/questions/458550

  •  19-08-2019
  •  | 
  •  

سؤال

هل هناك طريقة قياسية لربط سلسلة الإصدار بحزمة بايثون بطريقة تمكنني من القيام بما يلي؟

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 الملف باعتباره "المكان الأساسي" لتخزين معلومات الإصدار:

  1. يوفر أ __version__ يصف.

  2. وهو يوفر إصدار بيانات التعريف القياسية.ولذلك سيتم الكشف عن طريق pkg_resources أو الأدوات الأخرى التي تحلل بيانات تعريف الحزمة (EGG-INFO و/أو PKG-INFO, PEP 0345).

  3. فهو لا يستورد الحزمة الخاصة بك (أو أي شيء آخر) عند إنشاء الحزمة الخاصة بك، مما قد يسبب مشاكل في بعض المواقف.(راجع التعليقات أدناه حول المشكلات التي يمكن أن يسببها ذلك.)

  4. يوجد مكان واحد فقط يتم فيه كتابة رقم الإصدار، لذلك هناك مكان واحد فقط لتغييره عندما يتغير رقم الإصدار، وتكون هناك فرصة أقل للإصدارات غير المتسقة.

وهنا كيف يعمل:"المكان الأساسي الوحيد" لتخزين رقم الإصدار هو ملف .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 خطوات باستخدام أمر واحد لا يأخذ أي مدخلات.

كيف تعمل:

  1. من make release الأمر، تم العثور على الإصدار الأخير الموسوم في git repo وزيادته.يتم دفع العلامة مرة أخرى إلى origin.

  2. ال Makefile يخزن الإصدار في src/_version.py حيث سيتم قراءتها setup.py وأيضا المدرجة في الإصدار. لا تتحقق _version.py في التحكم بالمصادر!

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

لا يبدو أن هناك طريقة قياسية لتضمين سلسلة إصدار في حزمة بايثون.معظم الحزم التي رأيتها تستخدم بعض أشكال الحل الخاص بك، على سبيل المثال.ايتنر

  1. تضمين الإصدار في setup.py و لدي setup.py إنشاء وحدة نمطية (على سبيل المثال version.py) تحتوي فقط على معلومات الإصدار التي تم استيرادها بواسطة الحزمة الخاصة بك، أو

  2. العكس:ضع معلومات الإصدار في الحزمة الخاصة بك نفسها، وقم باستيرادها الذي - التي لتعيين الإصدار في 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 متغير.

  1. إستخدم version.py الملف فقط مع __version__ = <VERSION> المعلمة في الملف.في ال setup.py استيراد ملف __version__ param ووضع قيمتها في setup.py ملف مثل هذا:version=__version__
  2. طريقة أخرى هي استخدام مجرد 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)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top