การรวมไฟล์ที่ไม่ใช่ไพ ธ อนด้วย setup.py


200

ฉันจะทำอย่างไร setup.pyรวมไฟล์ที่ไม่ได้เป็นส่วนหนึ่งของรหัสได้อย่างไร (โดยเฉพาะมันเป็นไฟล์ลิขสิทธิ์ แต่อาจเป็นอย่างอื่นก็ได้)

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


คุณจะทำอย่างนั้นได้อย่างไรในเวลานี้? คำถามก่อนหน้าของคุณระบุว่าคุณคุ้นเคยกับวิธีเพิ่มไฟล์ลิขสิทธิ์ดังนั้นรหัสของคุณที่ "ไม่ทำงาน" คืออะไร
— SilentGhost

2
data_files = [('', ['lgpl2.1_license.txt',]),]วางไว้ในโฟลเดอร์ Python26
— Ram Rachum

หลังจากข้อเสนอแนะเชิงลบฉันอ่านคำถามของคุณอีกครั้งและตระหนักว่าสิ่งที่ฉันหายไป ฉันได้อัปเดตคำตอบของฉันเพื่อให้โซลูชันที่ไม่แฮ็คกับคำถามของคุณที่ไม่ต้องการโมดูลเพิ่มเติมใด ๆ (เช่น setuptools หรือแจกจ่าย)
— Evan Plaice

ขอบคุณอีวาน อย่างไรก็ตามฉันก็โอเคอย่างสมบูรณ์เมื่อใช้ setuptools เพราะมันแพร่หลายมาก
— Ram Rachum

คำตอบ:


224

อาจเป็นวิธีที่ดีที่สุดในการทำเช่นนี้คือการใช้setuptools package_dataคำสั่ง นี่หมายถึงการใช้setuptools(หรือdistribute) แทนdistutilsแต่นี่เป็น "การอัพเกรด" ที่ราบรื่นมาก

นี่คือตัวอย่างเต็มรูปแบบ (แต่ยังไม่ทดลอง):

from setuptools import setup, find_packages

setup(
    name='your_project_name',
    version='0.1',
    description='A description.',
    packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']),
    package_data={'': ['license.txt']},
    include_package_data=True,
    install_requires=[],
)

สังเกตบรรทัดเฉพาะที่สำคัญตรงนี้:

package_data={'': ['license.txt']},
include_package_data=True,

package_dataเป็นdictชื่อแพ็คเกจ (ว่าง = แพ็คเกจทั้งหมด) ไปยังรายการรูปแบบ (สามารถรวม globs) ตัวอย่างเช่นหากคุณต้องการระบุเฉพาะไฟล์ภายในแพ็คเกจของคุณคุณสามารถทำได้เช่นกัน:

package_data={'yourpackage': ['*.txt', 'path/to/resources/*.txt']}

วิธีแก้ปัญหาที่นี่คือไม่เปลี่ยนชื่อpyไฟล์ที่ไม่ใช่ของคุณด้วย.pyนามสกุล

ดูการนำเสนอของ Ian Bickingสำหรับข้อมูลเพิ่มเติม

อัปเดต: วิธีการ [ดีกว่า] อีกวิธีหนึ่ง

วิธีการอื่นที่ใช้งานได้ดีหากคุณต้องการควบคุมเนื้อหาของการแจกจ่ายซอร์ส ( sdist) และมีไฟล์นอกแพ็คเกจ (เช่นไดเรกทอรีระดับบนสุด) คือการเพิ่มMANIFEST.inไฟล์ ดูเอกสาร Pythonสำหรับรูปแบบของไฟล์นี้

ตั้งแต่เขียนคำตอบนี้ฉันพบว่าการใช้โดยMANIFEST.inทั่วไปแล้วเป็นวิธีที่น่าผิดหวังน้อยกว่าเพื่อให้แน่ใจว่าการกระจายแหล่งที่มาของคุณ (tar.gz ) มีไฟล์ที่คุณต้องการ

ตัวอย่างเช่นหากคุณต้องการรวมrequirements.txtจากระดับบนสุดให้รวมไดเรกทอรี "data" ระดับบนสุดซ้ำ:

include requirements.txt
recursive-include data *

อย่างไรก็ตามในการที่จะคัดลอกไฟล์เหล่านี้ในเวลาติดตั้งไปยังโฟลเดอร์ของแพ็คเกจภายในไซต์แพ็คเกจคุณจะต้องส่งinclude_package_data=Trueไปยังsetup()ฟังก์ชัน ดูการเพิ่มไฟล์ที่ไม่ใช่รหัสสำหรับข้อมูลเพิ่มเติม


5
package_data มีให้สำหรับสคริปต์การติดตั้ง distutils ล้วนตั้งแต่ Python 2.3
— Éric Araujo

15
คำตอบนี้ดูสมเหตุสมผล แต่ไม่ได้ผลสำหรับฉัน เนื่องจาก package_data ไม่น่าเชื่อถืออย่างมาก (ต้องมีการประสานงานของ MANIFEST.in และ setup.py เพื่อเพิ่มไฟล์ไปยัง sdist และติดตั้งพวกเขาเป็นขั้นตอนที่แยกต่างหาก) และผู้เขียนคำตอบนี้บันทึกว่า "ไม่ผ่านการทดสอบ" ทุกคนสามารถทำได้ อื่นยืนยันว่ามันใช้งานได้สำหรับพวกเขา? ไฟล์ LICENSE ของฉันรวมอยู่ใน sdist แต่ไม่ได้ติดตั้งเมื่อฉันเรียกใช้ "python setup.py install" หรือ "pip install Package"
— Jonathan Hartley

11
การนำเสนอของ Ian Bicking แสดงเฉพาะวิธีการติดตั้งข้อมูลแพ็คเกจสำหรับไฟล์ที่อยู่ในแพ็คเกจ ไฟล์ใบอนุญาตของฉันอยู่ที่ระดับบนสุดของโปรเจคคือไม่อยู่ในแพ็คเกจใด ๆ ฉันยังสามารถใช้ package_data ได้หรือไม่ การใช้ data_files เป็น non-starter เนื่องจากมันทำให้ไฟล์อยู่ในตำแหน่งที่ทั้งระบบ ไม่เกี่ยวข้องกับโครงการของฉันและทำให้แย่ลงตำแหน่งเปลี่ยนไปขึ้นอยู่กับว่าฉันเรียกใช้ "setup.py install" หรือ "pip install" จาก sdist เดียวกัน
— Jonathan Hartley

8
ฉันเดาว่าเหตุผลที่มันใช้งานไม่ได้สำหรับฉันก็คือไฟล์ไม่ได้อยู่ในแพ็คเกจใด ๆ - เป็นไฟล์ LICENSE ในระดับบนสุดของที่เก็บและดังนั้นจึงไม่สามารถติดตั้งได้โดยใช้ 'package_data'
— Jonathan Hartley

7
คำตอบนี้ไม่ได้ผลสำหรับฉัน แฟ้มเพิ่มเติมจะไม่ได้รับการใส่ลงไปใน tarball และ ...
— lpapp

44

ในการบรรลุสิ่งที่คุณอธิบายจะใช้สองขั้นตอน ...

  • ไฟล์จะต้องเพิ่มไปยังแหล่ง tarball
  • จำเป็นต้องได้รับการแก้ไข setup.py เพื่อติดตั้งไฟล์ข้อมูลไปยังเส้นทางต้นทาง

ขั้นตอนที่ 1: ในการเพิ่มไฟล์ลงใน tarball ต้นทางให้รวมไว้ใน MANIFEST

สร้างMANIFESTเทมเพลตในโฟลเดอร์ที่มี setup.py

รายการนั้นเป็นไฟล์ข้อความที่มีรายชื่อของไฟล์ทั้งหมดที่จะรวมอยู่ในแหล่ง tarball

นี่คือลักษณะที่ปรากฏสำหรับโครงการของฉัน:

  • CHANGELOG.txt
  • INSTALL.txt
  • LICENSE.txt
  • pypreprocessor.py
  • README.txt
  • setup.py
  • test.py
  • todo.txt

หมายเหตุ: แม้ว่าsdist จะเพิ่มไฟล์บางไฟล์โดยอัตโนมัติแต่ฉันต้องการระบุให้ชัดเจนเพื่อให้แน่ใจว่าจะไม่คาดเดาว่ามันทำอะไรและไม่ได้

ขั้นตอนที่ 2: ในการติดตั้งไฟล์ข้อมูลไปยังโฟลเดอร์แหล่งข้อมูลให้แก้ไข setup.py

เนื่องจากคุณต้องการเพิ่มไฟล์ข้อมูล (LICENSE.txt) ในโฟลเดอร์การติดตั้งซอร์สคุณต้องแก้ไขพา ธ การติดตั้งข้อมูลเพื่อให้ตรงกับพา ธ การติดตั้งต้นทาง สิ่งนี้จำเป็นเพราะโดยปกติแล้วไฟล์ข้อมูลจะถูกติดตั้งในตำแหน่งที่แตกต่างจากไฟล์ต้นฉบับ

หากต้องการแก้ไขข้อมูลการติดตั้ง dir ให้ตรงกับแหล่งข้อมูลการติดตั้ง ...

ดึงข้อมูลการติดตั้งจาก distutils ด้วย:

from distutils.command.install import INSTALL_SCHEMES

ปรับเปลี่ยน dir การติดตั้งข้อมูลเพื่อให้ตรงกับ dir ติดตั้งแหล่งที่มา:

for scheme in INSTALL_SCHEMES.values():
    scheme['data'] = scheme['purelib']

และเพิ่มไฟล์ข้อมูลและตำแหน่งเพื่อตั้งค่า ():

data_files=[('', ['LICENSE.txt'])]

หมายเหตุ: ขั้นตอนข้างต้นควรบรรลุสิ่งที่คุณอธิบายในลักษณะมาตรฐานโดยไม่ต้องใช้ไลบรารีส่วนขยายใด ๆ


10
รายการควบคุมเฉพาะไฟล์ที่รวมอยู่ในแหล่ง tarball (ผลิตโดย sdist) ไฟล์ที่อยู่ในรายการจะไม่ถูกติดตั้ง
— David Cournapeau

@ David ฉันไม่ทราบว่าฉันอยู่ไกลแค่ไหนในแนวทางแรกของฉัน ฉันได้อัปเดตคำตอบให้ถูกต้องเพื่อให้บรรลุคำถามที่ถามโดยไม่ต้องมีห้องสมุดบุคคลที่สามเพิ่มเติม
— Evan Plaice

3
@ Éricเหตุผลพิเศษทำไม และคุณมีตัวเลือกการติดตั้งที่ใช้งานได้ซึ่งไม่ต้องการแพ็คเกจของบุคคลที่สาม (เช่น setup_tools) ในการทำงาน ฉันเลือก distutils มากกว่า setuptools เพราะมันมาพร้อมกับการติดตั้ง vanilla ของ python และฉันก็สร้างโมดูลสำหรับ PYPI ควรมีวิธีที่ดีกว่าในการทำเช่นนี้โดยใช้ distutils2 แต่ฉันไม่ได้สัมผัสกับไพ ธ อนสักพักแล้วฉันก็ไม่รู้เหมือนกัน เนื่องจากคุณดูเหมือนจะมีความรู้เกี่ยวกับ distutils2 ฉันคิดว่ามันจะเป็นประโยชน์ต่อพวกเราที่เหลือในการมีทางเลือก distutils2 ที่เหมาะสม
— Evan Plaice

6
ดังที่ได้กล่าวไว้ในเธรดอื่นpackage_dataไม่ทำงานหากไฟล์ไม่ได้อยู่ในแพ็คเกจ
— Gringo Suave

2
@ ÉricAraujo: มันไม่ใช่ความคิดที่ดีที่จะใช้วิธีนี้เพราะไม่มีวิธีอื่น มันคือการออกแบบ distutils ที่ไม่ดี - จริง แต่มันเป็น API สาธารณะแบบไม่ใช้ความจริงซึ่งจะไม่มีวันเปลี่ยนแปลงเพราะมันจะแตกหลายอย่าง หวังว่า distutils2 จะให้วิธีที่แนะนำที่ดีกว่า
— Anatoly techtonik


7

ฉันต้องการโพสต์ความคิดเห็นในคำถามใดคำถามหนึ่ง แต่ฉันไม่มีชื่อเสียงพอที่จะทำเช่นนั้น>.>

นี่คือสิ่งที่ใช้งานได้สำหรับฉัน (เกิดขึ้นหลังจากอ้างอิงเอกสาร):

package_data={
    'mypkg': ['../*.txt']
},

include_package_data: False

บรรทัดสุดท้ายนั้นแปลกมากและสำคัญสำหรับฉันเช่นกัน (คุณสามารถละเว้นอาร์กิวเมนต์คำหลักนี้ - มันใช้งานได้เหมือนกัน)

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

หวังว่านี่จะช่วยได้!


ฉันกำลังมองหาวิธีที่จะไม่ต้องสร้างสิ่งMANIFEST.inนี้ได้ผลสำหรับฉัน บรรทัดสุดท้ายก็สำคัญสำหรับฉันเช่นกัน สายของฉันถูกinclude_package_data=False, package_data={ "": ["../CHANGELOG.md"] },
— Mendhak

7

ขั้นตอนที่ 1:สร้างMANIFEST.inไฟล์ในโฟลเดอร์เดียวกันด้วย setup.py

ขั้นตอนที่ 2:รวมเส้นทางสัมพันธ์ไปยังไฟล์ที่คุณต้องการเพิ่มMANIFEST.in

include README.rst
include docs/*.txt
include funniest/data.json

ขั้นตอนที่ 3:ตั้งค่าinclude_package_data=Trueในsetup()ฟังก์ชันเพื่อคัดลอกไฟล์เหล่านี้ไปยังไซต์แพ็คเกจ

การอ้างอิงอยู่ที่นี่


6

มันคือ 2019 และนี่คือสิ่งที่ทำงาน - แม้จะมีคำแนะนำที่นี่และที่นั่นสิ่งที่ฉันพบบนอินเทอร์เน็ตมีการใช้เอกสารครึ่งทางsetuptools_scmผ่านเป็นตัวเลือกsetuptools.setupจ่ายบอลสำเร็จเป็นตัวเลือกในการ ซึ่งจะรวมถึงไฟล์ข้อมูลใด ๆ ที่เป็นเวอร์ชันใน VCS ของคุณไม่ว่าจะเป็นคอมไพล์หรืออื่น ๆ ไปยังแพ็คเกจล้อและจะทำการ "ติดตั้ง pip" จากที่เก็บ git เพื่อนำไฟล์เหล่านั้นไปด้วย

ดังนั้นฉันเพิ่งเพิ่มสองบรรทัดนี้ในการเรียกใช้การตั้งค่าที่ "setup.py" ไม่จำเป็นต้องติดตั้งหรือนำเข้าเพิ่มเติม:

    setup_requires=['setuptools_scm'],
    include_package_data=True,

ไม่จำเป็นต้องแสดงรายการ package_data ด้วยตนเองหรือในไฟล์ MANIFEST.in - หากเป็นเวอร์ชั่นจะรวมอยู่ในแพ็คเกจ เอกสารใน "setuptools_scm" ให้ความสำคัญกับการสร้างหมายเลขเวอร์ชันจากตำแหน่งการส่งมอบและไม่สนใจส่วนที่สำคัญจริงๆของการเพิ่มไฟล์ข้อมูล (ฉันไม่สนหรอกถ้าไฟล์วงล้อกลางของฉันชื่อ "* 0.2.2.dev45 + g3495a1f" หรือจะใช้หมายเลขเวอร์ชัน hardcoded "0.3.0dev0" ที่ฉันได้พิมพ์ - แต่ปล่อยให้ไฟล์สำคัญสำหรับโปรแกรม การทำงานเบื้องหลังเป็นสิ่งสำคัญ)


5

ใน setup.py ภายใต้การตั้งค่า (:

setup(
   name = 'foo library'
   ...
  package_data={
   'foolibrary.folderA': ['*'],     # All files from folder A
   'foolibrary.folderB': ['*.txt']  #All text files from folder B
   },

1
สิ่งนี้ไม่ได้ทำอะไรเพื่อบรรลุเป้าหมายของ OP สิ่งที่คุณเขียนpackage_dataจะไม่มีผลกับสิ่งsetup.py installใดนอกจากคุณจะแก้ไขคำสั่งติดตั้งเอง ยกเว้นว่าไฟล์เหล่านั้นอยู่ภายใต้ไดเรกทอรีแพ็คเกจซึ่งมักเป็นสิ่งที่คุณต้องการหลีกเลี่ยง
— wvxvw

3

นี่เป็นคำตอบที่ง่ายกว่าสำหรับฉัน

ก่อนอื่นตามความเห็นของ Python Dev ข้างต้นไม่จำเป็นต้องใช้ setuptools:

package_data is also available to pure distutils setup scripts 
since 2.3. – Éric Araujo

เยี่ยมมากเพราะการวางข้อกำหนด setuptools ลงบนแพ็คเกจของคุณหมายความว่าคุณจะต้องติดตั้งด้วย ในระยะสั้น:

from distutils.core import setup

setup(
    # ...snip...
    packages          = ['pkgname'],
    package_data      = {'pkgname': ['license.txt']},
)

1
มันจะบ่นไดเรกทอรีpkgameไม่มีอยู่
— Anthony Kong

1

ฉันแค่อยากติดตามบางสิ่งที่ฉันพบว่าทำงานกับ Python 2.7 บน Centos 6 การเพิ่ม package_data หรือ data_files ตามที่กล่าวไว้ข้างต้นไม่ได้ผลสำหรับฉัน ฉันเพิ่ม MANIFEST.IN พร้อมไฟล์ที่ฉันต้องการซึ่งวางไฟล์ที่ไม่ใช่ไพ ธ อนลงใน tarball แต่ไม่ได้ติดตั้งไว้ในเครื่องเป้าหมายผ่าน RPM

ในที่สุดฉันก็สามารถนำไฟล์เข้าสู่โซลูชันของฉันโดยใช้ "ตัวเลือก" ในการตั้งค่า / setuptools ไฟล์ตัวเลือกให้คุณแก้ไขส่วนต่าง ๆ ของไฟล์ข้อมูลจำเพาะจาก setup.py ดังนี้

from setuptools import setup


setup(
    name='theProjectName',
    version='1',
    packages=['thePackage'],
    url='',
    license='',
    author='me',
    author_email='me@email.com',
    description='',
    options={'bdist_rpm': {'install_script': 'filewithinstallcommands'}},
)

ไฟล์ - MANIFEST.in:

include license.txt

ไฟล์ - คำสั่ง filewithinstall:

mkdir -p $RPM_BUILD_ROOT/pathtoinstall/
#this line installs your python files
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
#install license.txt into /pathtoinstall folder
install -m 700 license.txt $RPM_BUILD_ROOT/pathtoinstall/
echo /pathtoinstall/license.txt >> INSTALLED_FILES

-12

คิดวิธีแก้ปัญหา: ฉันเปลี่ยนชื่อฉันlgpl2.1_license.txtเป็นlgpl2.1_license.txt.pyและใส่เครื่องหมายคำพูดสามรอบข้อความ ตอนนี้ฉันไม่จำเป็นต้องใช้data_filesตัวเลือกหรือเพื่อระบุเส้นทางที่แน่นอน ทำให้เป็นโมดูล Python น่าเกลียดฉันรู้ แต่ฉันคิดว่ามันน่าเกลียดน้อยกว่าการระบุพา ธ สัมบูรณ์


7
ดูโพสต์ของฉัน มันไม่จำเป็นต้องน่าเกลียด มันยากที่จะหาตัวอย่างที่ดีในเน็ตเพราะเอกสารที่ดีสำหรับแพ็คเกจการติดตั้งหายาก
— Evan Plaice
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.