ฉันจะรับรุ่นที่กำหนดใน setup.py (setuptools) ในแพ็คเกจของฉันได้อย่างไร


153

ฉันจะรับรุ่นที่กำหนดsetup.pyจากแพ็คเกจของฉัน (เพื่อ--versionหรือวัตถุประสงค์อื่น ๆ ) ได้อย่างไร


3
ผมคิดว่านี่จะอยู่ที่ประมาณคำถามเดียวกับstackoverflow.com/questions/458550/...
Zooko

คำตอบ:


246

สอบถามสตริงรุ่นของการกระจายที่ติดตั้งแล้ว

ในการเรียกคืนเวอร์ชันจากภายในแพ็คเกจของคุณเมื่อรันไทม์ (คำถามของคุณดูเหมือนคำถามที่ถามจริง) คุณสามารถใช้:

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version

สตริงเวอร์ชันร้านค้าสำหรับใช้ระหว่างการติดตั้ง

หากคุณต้องการไปในทางอื่น 'รอบ (ซึ่งดูเหมือนจะเป็นสิ่งที่ผู้เขียนคำตอบอื่น ๆ ที่นี่ดูเหมือนจะคิดว่าคุณถาม) ใส่สตริงรุ่นในไฟล์แยกต่างหากและอ่านเนื้อหาของไฟล์setup.pyนั้นมา

คุณสามารถสร้าง version.py ในแพ็คเกจด้วย__version__บรรทัดจากนั้นอ่านจาก setup.py โดยใช้execfile('mypackage/version.py')เพื่อให้มันตั้งค่า__version__ในเนมสเปซ setup.py

หากคุณต้องการวิธีที่ง่ายกว่ามากที่จะทำงานกับเวอร์ชัน Python ทั้งหมดและแม้แต่ภาษาที่ไม่ใช่ภาษาไพ ธ อนที่อาจต้องการการเข้าถึงสตริงเวอร์ชัน:

เก็บสตริงรุ่นเป็นเนื้อหาเพียงอย่างเดียวของไฟล์ข้อความธรรมดาชื่อเช่นและอ่านไฟล์ว่าในช่วงVERSIONsetup.py

version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()

VERSIONไฟล์เดียวกันนั้นจะทำงานได้ดีเหมือนกันในโปรแกรมอื่น ๆ แม้แต่ไฟล์ที่ไม่ใช่ Python และคุณจะต้องเปลี่ยนสตริงเวอร์ชันในที่เดียวสำหรับโปรแกรมทั้งหมด

คำเตือนเกี่ยวกับสภาพการแข่งขันระหว่างการติดตั้ง

อย่างไรก็ตามอย่านำเข้าแพคเกจของคุณจาก setup.py ตามคำแนะนำในคำตอบอื่นที่นี่: มันจะทำงานได้ดีสำหรับคุณ (เพราะคุณได้ติดตั้งแพ็กเกจของคุณไว้แล้ว) แต่มันจะสร้างความเสียหายให้กับผู้ใช้ใหม่ของแพ็คเกจของคุณ เนื่องจากพวกเขาจะไม่สามารถติดตั้งแพ็คเกจของคุณโดยไม่ต้องติดตั้งการพึ่งพาตนเองก่อน


1
นี่เป็นคำตอบที่ดีที่สุดที่นี่: วิธีที่ดีที่สุดคือใช้ execfile หากคุณต้องการ setup.py เพื่อรับเวอร์ชันจากแพ็คเกจของคุณ
trinth

2
execfileทำงานได้ดีจริงๆ ... แต่ (น่าเศร้า) ไม่สามารถใช้งานกับ Python 3 ได้
medmunds

9
... ตกลงสำหรับงูหลาม 2 และ 3 ใช้ในสถานที่ของwith open('mypackage/version.py') as f: exec(f.read()) execfile('mypackage/version.py')(จากstackoverflow.com/a/437857/647002 )
medmunds

5
ส่วน wreak havoc ไม่จำเป็นต้องเป็นจริง ... มันจะเกิดความเสียหายได้ก็ต่อเมื่อไฟล์init .py ของคุณในแพ็คเกจมีการพึ่งพาการนำเข้ารหัส (โดยตรงหรือโดยอ้อม)
Pykler

2
ดูเหมือนว่ามูลค่าการกล่าวขวัญว่าดำเนิน Django การโทร __import__ ใน setup.py '__import__' vs 'import' ทำให้ปลอดภัยหรือไม่ ดูเหมือนจะไม่ก่อให้เกิดปัญหาใด ๆ
user1978019

33

ตัวอย่างการศึกษา: mymodule

ลองนึกภาพการกำหนดค่านี้:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

จากนั้นให้จินตนาการถึงสถานการณ์ปกติที่คุณมีการพึ่งพาและsetup.pyดูเหมือนว่า:

setup(...
    install_requires=['dep1','dep2', ...]
    ...)

และตัวอย่าง__init__.py:

from mymodule.myclasses import *
from mymodule.version import __version__

และตัวอย่างเช่นmyclasses.py:

# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2

ปัญหา # 1: การนำเข้า mymoduleระหว่างการตั้งค่า

หากคุณsetup.pyนำเข้าmymoduleแล้วระหว่างการติดตั้งที่คุณจะมีโอกาสมากที่สุดจะได้รับImportErrorที่คุณจะมีโอกาสมากที่สุดจะได้รับนี่เป็นข้อผิดพลาดทั่วไปมากเมื่อแพ็คเกจของคุณมีการขึ้นต่อกัน หากแพ็คเกจของคุณไม่มีการขึ้นต่อกันอื่น ๆ กว่าบิวอินคุณอาจจะปลอดภัย อย่างไรก็ตามนี่ไม่ใช่วิธีปฏิบัติที่ดี เหตุผลสำหรับสิ่งนี้คือมันไม่ได้เป็นหลักฐานในอนาคต บอกว่าพรุ่งนี้รหัสของคุณจำเป็นต้องพึ่งพาคนอื่น

ปัญหา # 2: ของฉันอยู่ที่ไหน __version__ที่ไหน

หากคุณ hardcode __version__ในsetup.pyแล้วมันอาจไม่ตรงกับรุ่นที่คุณจะจัดส่งในโมดูลของคุณ เพื่อให้สอดคล้องคุณจะวางไว้ในที่เดียวและอ่านจากที่เดียวกันเมื่อคุณต้องการ การใช้importคุณอาจได้รับปัญหา # 1

ทางออก: à la setuptools

คุณจะใช้การรวมกันของopen, execและให้ Dict สำหรับexecตัวแปรเพิ่ม:

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

และในการmymodule/version.pyเปิดเผยรุ่น:

__version__ = 'some.semantic.version'

วิธีนี้รุ่นจะจัดส่งมาพร้อมกับโมดูลและคุณไม่มีปัญหาในระหว่างการตั้งค่าพยายามที่จะนำเข้าโมดูลที่ขาดการอ้างอิง (ยังไม่ได้ติดตั้ง)


2
ดูเหมือนว่านี่เป็นทางออกที่สมเหตุสมผลที่สุดเพราะอาศัยการอ้างอิงที่ได้รับอนุญาตเท่านั้น
Dogweather

แนวคิดของเวอร์ชันแพ็กเกจถูกกำหนดในไฟล์ setup.py แตกต่างจากเวอร์ชันหรือไม่?
ตัวแปร

16

เทคนิคที่ดีที่สุดคือการกำหนด__version__ในรหัสผลิตภัณฑ์ของคุณจากนั้นนำเข้าสู่ setup.py จากที่นั่น สิ่งนี้ให้คุณค่าที่คุณสามารถอ่านได้ในโมดูลการทำงานของคุณและมีเพียงที่เดียวเท่านั้นที่จะกำหนด

ไม่ได้ติดตั้งค่าใน setup.py และ setup.py จะไม่ติดหลังจากการติดตั้ง

สิ่งที่ฉันทำ (ตัวอย่าง) ใน coverage.py:

# coverage/__init__.py
__version__ = "3.2"


# setup.py
from coverage import __version__

setup(
    name = 'coverage',
    version = __version__,
    ...
    )

อัปเดต (2017): coverage.py ไม่นำเข้าตัวเองเพื่อรับเวอร์ชันอีกต่อไป การนำเข้ารหัสของคุณสามารถถอนการติดตั้งได้เพราะรหัสผลิตภัณฑ์ของคุณจะพยายามนำเข้าการอ้างอิงซึ่งยังไม่ได้ติดตั้งเนื่องจาก setup.py เป็นสิ่งที่ติดตั้ง


4
@Evan: ฉันไม่แน่ใจว่าสิ่งที่คุณได้รับเกี่ยวกับ "เพียงดึงค่าที่มาจากแหล่งที่มา" หากไฟล์ที่__version__อยู่ในนั้นถูกทำลายอย่างใดการนำเข้าของคุณก็จะถูกทำลายเช่นกัน Python ไม่ทราบวิธีตีความข้อความที่คุณต้องการเท่านั้น @pjeby ถูกต้อง: หากโมดูลของคุณต้องการนำเข้าโมดูลอื่น ๆ พวกเขาอาจยังไม่ได้ติดตั้งและมันจะวุ่นวาย เทคนิคนี้ใช้งานได้หากคุณระมัดระวังว่าการนำเข้าไม่ได้เป็นการ จำกัด การนำเข้าอื่น ๆ
Ned Batchelder

1
@Ned Batchelder ฉันค่อนข้างมั่นใจว่าถ้าคุณใส่รุ่นก่อนที่จะนำเข้าใด ๆ ในไฟล์ต้นฉบับและเฉพาะ 'จากโมดูลการนำเข้ารุ่น' มันจะไม่เพิ่มไฟล์ต้นฉบับมากกว่าที่มันต้องการ นอกจากนี้ใครที่จะปล่อยรหัสที่ใช้งานไม่ได้? หากแพ็กเกจต้องการการพึ่งพาให้ใช้ setuptools หรือรอจนกว่าจะมีการปล่อย distutils2 ในภายหลังในปีนี้
Evan Plaice

15
@Evan ฉันขอโทษ แต่คุณผิดเกี่ยวกับการนำเข้าไฟล์บางส่วน ลองวางคำสั่งการพิมพ์ที่ส่วนท้ายของโมดูลแบบยาวและนำเข้าตัวแปรแรกที่กำหนดไว้ในไฟล์ คำสั่งพิมพ์จะดำเนินการ
Ned Batchelder

23
ฉันตกหลุมรักสิ่งนี้ แต่ @pjeby นั้นถูกต้องทั้งหมด: "อย่างไรก็ตามอย่านำเข้าแพคเกจของคุณจาก setup.py ตามที่แนะนำในคำตอบอื่นที่นี่: ดูเหมือนว่ามันจะใช้ได้ผลสำหรับคุณ (เพราะคุณได้ติดตั้งการพึ่งพาแพคเกจแล้ว ) แต่มันจะสร้างความเสียหายให้กับผู้ใช้ใหม่ของแพ็คเกจของคุณเนื่องจากพวกเขาจะไม่สามารถติดตั้งแพ็คเกจของคุณโดยไม่ต้องติดตั้งการพึ่งพาตนเองก่อน " คุณคิดจะเพิ่มคำเตือนลงในคำตอบของคุณหรือไม่?
ดร. Jan-Philip Gehrcke

1
ชอบ @ Jan-PhilipGehrcke เมื่อฉันอัปโหลดไฟล์ setup.py เช่นนี้ไปยัง PyPI จากนั้นpip install <packagename>ฉันได้รับข้อผิดพลาดต่อไปนี้: ImportError: No module named <packagename>. โปรดเตือนผู้อ่านของคุณว่าคุณไม่สามารถเรียกใช้ไฟล์ setup.py เช่นนี้ในสภาพแวดล้อมที่ไม่ได้ติดตั้งแพ็คเกจ
เตาแก๊ส

14

คำถามของคุณค่อนข้างคลุมเครือ แต่ฉันคิดว่าสิ่งที่คุณถามคือวิธีระบุ

คุณจำเป็นต้องนิยาม__version__ดังนี้:

__version__ = '1.4.4'

จากนั้นคุณสามารถยืนยันได้ว่า setup.py รู้เกี่ยวกับเวอร์ชันที่คุณเพิ่งระบุ:

% ./setup.py --version
1.4.4

9

ฉันไม่พอใจกับคำตอบเหล่านี้ ... ไม่ต้องการ setuptools หรือสร้างโมดูลแยกต่างหากสำหรับตัวแปรเดี่ยวดังนั้นฉันจึงได้สิ่งเหล่านี้

เมื่อคุณแน่ใจว่าโมดูลหลักอยู่ในรูปแบบ pep8 และจะยังคงอยู่:

version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            _, _, version = line.replace("'", '').split()
            break

หากคุณต้องการระวังเป็นพิเศษและใช้ parser จริง:

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 นั้นเป็นโมดูลของการโยนทิ้งดังนั้นจึงไม่เป็นปัญหาหากเป็นบิตที่น่าเกลียด


ปรับปรุง: meta.pyตลกพอฉันได้ย้ายออกไปจากนี้ในปีที่ผ่านมาและเริ่มใช้เป็นไฟล์แยกต่างหากในแพคเกจที่เรียกว่า ฉันใส่ข้อมูลเมตาจำนวนมากลงไปที่นั่นซึ่งฉันอาจต้องการเปลี่ยนบ่อยๆ ดังนั้นไม่ใช่เพียงแค่ค่าเดียว


+1 มันค่อนข้างง่ายเก็บหมายเลขรุ่นไว้ในที่เดียวไม่จำเป็นต้องมีไฟล์แยกต่างหากเพื่อเก็บไว้และไม่กำหนดการพึ่งพาการนำเข้าของโมดูลไพ ธ อนใน setup.py ฉันจะไม่รำคาญกับตัวจัดการบริบทในโปรแกรมสั้น ๆ เหมือน setup.py
ʇsәɹoɈ

มาก nicer กว่าใช้ regex ขอบคุณ คุณยังสามารถใช้ast.get_docstring()กับบางอย่าง.split('\n')[0].strip()เพื่อกรอกข้อมูลdescriptionจากแหล่งที่มาโดยอัตโนมัติ สิ่งหนึ่งที่น้อยกว่าในการซิงค์
แพ้

4

สร้างไฟล์ในแผนผังต้นทางของคุณเช่นใน yourbasedir / yourpackage / _version.py ปล่อยให้ไฟล์นั้นมีโค้ดเพียงบรรทัดเดียวเช่นนี้

__version__ = "1.1.0-r4704"

จากนั้นใน setup.py ของคุณให้เปิดไฟล์นั้นแล้วแยกวิเคราะห์หมายเลขเวอร์ชันดังนี้:

verstr = "ไม่ทราบ"
ลอง:
    verstrline = open ('yourpackage / _version.py', "rt"). read ()
ยกเว้น EnvironmentError:
    ผ่าน # โอเคไม่มีไฟล์เวอร์ชั่น
อื่น:
    VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] "
    mo = re.search (VSRE, verstrline, re.M)
    ถ้า mo:
        verstr = mo.group (1)
    อื่น:
        เพิ่ม RuntimeError ("ไม่สามารถหารุ่นใน yourpackage / _version.py")

ในที่สุดค่ะ yourbasedir/yourpackage/__init__.pyการนำเข้า _version เช่นนี้:

__version__ = "ไม่ทราบ"
ลอง:
    จาก _version นำเข้า __version__
ยกเว้น ImportError:
    # เรากำลังทำงานในแผนผังที่ไม่มี _version.py ดังนั้นเราจึงไม่ทราบว่าเวอร์ชันของเราคืออะไร
    ผ่านไป

ตัวอย่างของรหัสที่ทำนี่คือแพ็คเกจ "pyutil" ที่ฉันดูแล (ดู PyPI หรือ google search - stackoverflow ไม่อนุญาตให้ฉันใส่ไฮเปอร์ลิงก์ไปยังคำตอบนี้)

@pjeby ถูกต้องที่คุณไม่ควรนำเข้าแพ็คเกจจาก setup.py ของตัวเอง สิ่งนี้จะใช้งานได้เมื่อคุณทดสอบโดยการสร้าง Python interpreter ใหม่และเรียกใช้งาน setup.py ในสิ่งแรก: python setup.pyแต่มีบางกรณีที่มันไม่ทำงาน นั่นเป็นเพราะimport youpackageไม่ได้หมายถึงการอ่านไดเรกทอรีการทำงานปัจจุบันสำหรับไดเรกทอรีที่ชื่อ "yourpackage" มันหมายถึงการค้นหาในปัจจุบันsys.modulesสำหรับคีย์ "yourpackage" และจากนั้นทำสิ่งต่าง ๆ ถ้ามันไม่ได้อยู่ที่นั่น ดังนั้นจึงใช้ได้เสมอเมื่อคุณทำpython setup.pyเพราะคุณมีความสดใหม่ที่ว่างเปล่าsys.modulesแต่สิ่งนี้ไม่ได้ผล

ตัวอย่างเช่นถ้า py2exe กำลังเรียกใช้งาน setup.py ของคุณเป็นส่วนหนึ่งของกระบวนการบรรจุแอปพลิเคชัน ฉันเห็นกรณีเช่นนี้ซึ่ง py2exe จะใส่หมายเลขเวอร์ชันผิดบนแพ็คเกจเนื่องจากแพ็คเกจได้รับหมายเลขเวอร์ชันมาimport myownthingใน setup.py แต่แพคเกจนั้นมีการนำเข้าเวอร์ชันที่แตกต่างกันก่อนหน้านี้ในระหว่างการเรียกใช้ py2exe ในทำนองเดียวกันถ้า setuptools, easy_install, กระจายหรือ distutils2 กำลังพยายามสร้างแพ็คเกจของคุณเป็นส่วนหนึ่งของกระบวนการติดตั้งแพ็คเกจอื่นที่ขึ้นอยู่กับคุณ จากนั้นแพคเกจของคุณจะสามารถนำเข้าได้ในเวลาที่มีการประเมินค่า setup.py หรือว่ามีแพ็กเกจเวอร์ชันของคุณที่นำเข้ามาในช่วงชีวิตของล่าม Python หรือไม่หรือการนำเข้าแพ็คเกจของคุณต้องติดตั้งแพ็กเกจอื่นก่อน หรือมีผลข้างเคียงสามารถเปลี่ยนผลลัพธ์ได้ ฉันมีปัญหาหลายครั้งที่พยายามใช้แพ็กเกจ Python ซ้ำซึ่งทำให้เกิดปัญหากับเครื่องมือเช่น py2exe และ setuptools เนื่องจาก setup.py พวกเขานำเข้าแพ็คเกจตัวเองเพื่อค้นหาหมายเลขเวอร์ชัน

เทคนิคนี้เล่นกับเครื่องมือในการสร้างyourpackage/_version.pyไฟล์ให้คุณโดยอัตโนมัติตัวอย่างเช่นการอ่านประวัติการควบคุมการแก้ไขของคุณและการเขียนหมายเลขเวอร์ชันตามแท็กล่าสุดในประวัติการควบคุมการแก้ไข นี่คือเครื่องมือที่ใช้สำหรับ darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rstและนี่คือข้อมูลโค้ดที่ทำสิ่งเดียวกันสำหรับ git: http: // github .com / วอร์เนอร์ / หลาม ECDSA / หยด / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34


3

สิ่งนี้ควรใช้งานได้โดยใช้นิพจน์ทั่วไปและขึ้นอยู่กับเขตข้อมูลเมทาดาทาที่จะมีรูปแบบดังนี้:

__fieldname__ = 'value'

ใช้สิ่งต่อไปนี้ที่จุดเริ่มต้นของ setup.py:

import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))

หลังจากนั้นคุณสามารถใช้ข้อมูลเมตาในสคริปต์ของคุณดังนี้:

print 'Author is:', metadata['author']
print 'Version is:', metadata['version']

ดังที่ฉันได้กล่าวไว้ข้างต้นเพียงแค่ใช้ str.split :)
Éric Araujo

โอ้พระเจ้าไม่ คุณกำลังแยกวิเคราะห์ Python ใน Python หรือไม่ อย่างน้อยที่สุดให้ใช้ eval (), child
slacy

3
คำแนะนำที่ไม่ดีควรหลีกเลี่ยงการใช้ eval เมื่อทำได้ง่าย
Gringo Suave

3

ด้วยโครงสร้างเช่นนี้

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

โดยที่version.pyมี:

__version__ = 'version_string'

คุณสามารถทำได้ในsetup.py :

import sys

sys.path[0:0] = ['mymodule']

from version import __version__

สิ่งนี้จะไม่ทำให้เกิดปัญหากับการพึ่งพาใด ๆ ที่คุณมีใน mymodule / __ init__.py


2

เพื่อหลีกเลี่ยงการนำเข้าไฟล์ (และดำเนินการรหัส) หนึ่งสามารถแยกมันและกู้คืนversionแอตทริบิวต์จากต้นไม้ไวยากรณ์:

# 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

รหัสนี้พยายามค้นหา__version__หรือการVERSIONกำหนดที่ระดับบนสุดของการส่งคืนโมดูลคือค่าสตริง ด้านขวาอาจเป็นสตริงหรือทูเปิลก็ได้


3
มันดูฉลาดและซับซ้อน :) มันง่ายกว่าที่จะมีไฟล์ _version.py ที่มีหนึ่งการมอบหมายและแยกวิเคราะห์ด้วย open-read-str.split
Éric Araujo

ขอขอบคุณที่โพสต์สิ่งนี้ ฉันคิดว่ามันซับซ้อนเกินไปสำหรับสถานการณ์นี้ แต่มีประโยชน์มากสำหรับการทำความเข้าใจว่าคน ๆ หนึ่งอาจเข้าถึงปัญหาได้อย่างไร ฉันชอบที่มันเก็บหมายเลขเวอร์ชันไว้ในที่เดียวไม่จำเป็นต้องมีไฟล์แยกต่างหากเพื่อเก็บไว้และไม่กำหนดการพึ่งพาการนำเข้าโมดูลของหลามในสคริปต์การตั้งค่า
ʇsәɹoɈ

2

มีหลายพันวิธีในการปรนนิบัติแมว - นี่คือของฉัน:

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


2

ตอนนี้เป็นขั้นต้นและต้องมีการปรับปรุงบางอย่าง (อาจมีการโทรหาสมาชิกแบบไม่เปิดเผยใน pkg_resources ที่ฉันพลาด) แต่ฉันไม่เห็นว่าทำไมสิ่งนี้ถึงใช้งานไม่ได้และทำไมไม่มีใครแนะนำเลยถึงวันที่ ไม่เปิดใช้งานนี้) ... โปรดทราบว่านี่คือ Python 2.x และจะต้องมี pkg_resources (ถอนหายใจ):

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

2

เราต้องการที่จะนำข้อมูลเมตาเกี่ยวกับแพคเกจของเราpypackageryใน__init__.pyแต่ไม่สามารถเพราะมันมีการพึ่งพาบุคคลที่สามเป็น PJ Eby ชี้แล้วออก (ดูคำตอบและคำเตือนเกี่ยวกับสภาพการแข่งขันของเขา)

เราแก้ไขได้โดยการสร้างโมดูลแยกpypackagery_meta.pyที่มีเฉพาะข้อมูลเมตา:

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

จากนั้นนำเข้าข้อมูลเมตาในpackagery/__init__.py:

# ...

from pypackagery_meta import __title__, __description__, __url__, \
    __version__, __author__, __author_email__, \
    __license__, __copyright__

# ...

และในที่สุดก็ใช้มันใน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'],
 )

คุณต้องรวมpypackagery_metaไว้ในแพ็คเกจของคุณด้วยpy_modulesอาร์กิวเมนต์การตั้งค่า มิฉะนั้นคุณไม่สามารถนำเข้ามันเมื่อทำการติดตั้งเนื่องจากการกระจายแพ็คเกจจะขาดหายไป


1

ง่ายและตรงไปตรงมาสร้างไฟล์ที่เรียกว่าsource/package_name/version.pyมีเนื้อหาดังต่อไปนี้:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"

จากนั้นในไฟล์ของsource/package_name/__init__.pyคุณคุณนำเข้าเวอร์ชันเพื่อให้ผู้อื่นใช้:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__

ตอนนี้คุณสามารถใส่มัน 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()

การทดสอบนี้กับงูหลาม2.7, 3.3, 3.4, 3.5, 3.6และ3.7บน Linux, Windows และ Mac OS ฉันใช้แพคเกจของฉันที่มีการรวมและการทดสอบหน่วยสำหรับแพลตฟอร์มเหล่านี้ทั้งหมด คุณสามารถดูผลลัพธ์จาก.travis.ymlและappveyor.ymlที่นี่:

  1. https://travis-ci.org/evandrocoan/debugtools/builds/527110800
  2. https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446

เวอร์ชันสำรองกำลังใช้ตัวจัดการบริบท:

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

คุณสามารถใช้codecsโมดูลเพื่อจัดการข้อผิดพลาด unicode ได้ทั้งบน Python 2.7และ3.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 ) )

หากคุณกำลังเขียนโมดูล Python 100% ใน C / C ++ โดยใช้ Python C Extensions คุณสามารถทำสิ่งเดียวกันได้ แต่ใช้ C / C ++ แทน Python

ในกรณีนี้ให้สร้างสิ่งต่อไปนี้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'],
            )
        ],
    )

ซึ่งอ่านรุ่นจากไฟล์version.h:

const char* __version__ = "1.0.12";

แต่อย่าลืมที่จะสร้างMANIFEST.inเพื่อรวมversion.hไฟล์:

include README.md
include LICENSE.txt

recursive-include source *.h

และมันถูกรวมเข้ากับแอปพลิเคชันหลักด้วย:

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

    ...
}

อ้างอิง:

  1. ข้อผิดพลาดเปิดไฟล์หลาม
  2. กำหนดโกลบอลในโมดูล Python จาก C API
  3. จะรวมข้อมูลแพ็คเกจด้วย setuptools / แจกจ่ายอย่างไร
  4. https://github.com/lark-parser/lark/blob/master/setup.py#L4
  5. จะใช้แพ็คเกจ setuptools และ ext_modules ด้วยชื่อเดียวกันได้อย่างไร?
  6. เป็นไปได้หรือไม่ที่จะรวมไดเรกทอรีย่อยโดยใช้ dist utils (setup.py) เป็นส่วนหนึ่งของข้อมูลแพ็คเกจ

1

ปรับใช้แพคเกจกับเซิร์ฟเวอร์และแบบแผนการตั้งชื่อไฟล์สำหรับแพ็คเกจดัชนี:

ตัวอย่างสำหรับการแปลงเวอร์ชั่น pip แบบไดนามิก:

  • ชนะ:

    • test_pkg-1.0.0-cp36-cp36m-win_amd64.whl
    • test_pkg-1.0.0-py3.6-win-amd64.egg
  • Mac:

    • test_pkg-1.0.0-py3.7-MacOSX-10.12-x86_64.egg
    • test_pkg-1.0.0-py3.7-MacOSX-10.12-x86_64.whl
  • ลินุกซ์:
    • test_pkg-1.0.0-cp36-cp36m-linux_x86_64.whl
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

ค้นหาตัวอย่าง setup.py เรียกการจับคู่เวอร์ชั่น pip แบบไดนามิกจากคอมมิทคอม

setup(
    version=_get_version(),
    name=NAME,
    description=DESCRIPTION,
    long_description=LONG_DESCRIPTION,
    classifiers=CLASSIFIERS,

# add few more for wheel wheel package ...conversion

)

0

ฉันใช้ตัวแปรสภาพแวดล้อมด้านล่าง

VERSION = 0.0.0 python setup.py sdist bdist_wheel

ใน setup.py


import os

setup(
    version=os.environ['VERSION'],
    ...
)

สำหรับการตรวจสอบความสอดคล้องกับเวอร์ชั่นของแพคเกอร์ฉันใช้สคริปต์ด้านล่าง

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.