setuptools: vị trí thư mục dữ liệu gói


94

Tôi sử dụng setuptools để phân phối gói python của mình. Bây giờ tôi cần phân phối các tệp dữ liệu bổ sung.

Từ những gì tôi thu thập được từ tài liệu setuptools, tôi cần có các tệp dữ liệu của mình bên trong thư mục gói. Tuy nhiên, tôi muốn có các tệp dữ liệu của mình bên trong một thư mục con trong thư mục gốc.

Những gì tôi muốn tránh:

/ #root
|- src/
|  |- mypackage/
|  |  |- data/
|  |  |  |- resource1
|  |  |  |- [...]
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Những gì tôi muốn có thay thế:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Tôi chỉ không cảm thấy thoải mái khi có quá nhiều thư mục con, nếu nó không cần thiết. Tôi không tìm thấy lý do, tại sao tôi / có / để đặt các tệp bên trong thư mục gói. Nó cũng cồng kềnh khi làm việc với quá nhiều thư mục con lồng nhau IMHO. Hoặc có lý do chính đáng nào biện minh cho hạn chế này không?


8
Tôi đã hỏi một câu hỏi tương tự về việc sử dụng 'data_files' để phân phối tài nguyên (tài liệu, hình ảnh, v.v.): stackoverflow.com/questions/5192386/… ... và (hai) câu trả lời đều cho biết sử dụng 'package_data' để thay thế. Bây giờ tôi đang sử dụng dữ liệu gói, nhưng điều đó có nghĩa là tôi phải đặt dữ liệu và tài liệu của mình bên trong gói của mình, tức là trộn lẫn giữa mã nguồn của tôi. Tôi không thích nó. Khi ghi mã nguồn của mình, tôi không chỉ tìm thấy định nghĩa lớp mà tôi đang tìm kiếm, mà còn hàng tá đề cập mà chúng nhận được trong các tệp RST, HTML và tệp trung gian của tôi. :-(
Jonathan Hartley

2
Tôi biết phản hồi này rất muộn, @JonathanHartley, nhưng bạn có thể đặt bất kỳ thư mục nào thành "gói" bằng cách thêm một __init__.pytệp, ngay cả khi tệp đó trống. Vì vậy, bạn có thể giữ một thư mục dữ liệu riêng biệt với một __init__.pytệp trống để làm cho nó giống như một gói. Điều đó sẽ giữ cho grep từ bên trong cây nguồn của bạn không nhặt chúng nhưng nó vẫn sẽ được python và các công cụ xây dựng của nó công nhận là một gói.
dhj

@dhj Một ý tưởng thú vị, cảm ơn.
Jonathan Hartley

4
@dhj vấn đề duy nhất với cách tiếp cận đó là python nghĩ rằng bạn đã cài đặt một gói có tên 'dữ liệu'. Nếu một gói khác bạn đã cài đặt cố gắng đóng gói dữ liệu theo cách tương tự, bạn sẽ có hai gói 'dữ liệu' xung đột được cài đặt.
ngón chân

Câu trả lời:


111

Tùy chọn 1: Cài đặt dưới dạng dữ liệu gói

Ưu điểm chính của việc đặt các tệp dữ liệu bên trong thư mục gốc của gói Python là nó cho phép bạn tránh lo lắng về vị trí các tệp sẽ sống trên hệ thống của người dùng, có thể là Windows, Mac, Linux, một số nền tảng di động hoặc bên trong Egg. Bạn luôn có thể tìm thấy thư mục dataliên quan đến thư mục gốc của gói Python, bất kể nó được cài đặt ở đâu hoặc như thế nào.

Ví dụ: nếu tôi có bố cục dự án như vậy:

project/
    foo/
        __init__.py
        data/
            resource1/
                foo.txt

Bạn có thể thêm một hàm để __init__.pyđịnh vị đường dẫn tuyệt đối đến tệp dữ liệu:

import os

_ROOT = os.path.abspath(os.path.dirname(__file__))
def get_data(path):
    return os.path.join(_ROOT, 'data', path)

print get_data('resource1/foo.txt')

Kết quả đầu ra:

/Users/pat/project/foo/data/resource1/foo.txt

Sau khi dự án được cài đặt dưới dạng Egg, đường dẫn đến datasẽ thay đổi, nhưng mã không cần thay đổi:

/Users/pat/virtenv/foo/lib/python2.6/site-packages/foo-0.0.0-py2.6.egg/foo/data/resource1/foo.txt

Tùy chọn 2: Cài đặt đến vị trí cố định

Giải pháp thay thế sẽ là đặt dữ liệu của bạn bên ngoài gói Python và sau đó:

  1. Có vị trí datađược chuyển vào qua tệp cấu hình, đối số dòng lệnh hoặc
  2. Nhúng vị trí vào mã Python của bạn.

Điều này ít được mong đợi hơn nếu bạn định phân phối dự án của mình. Nếu bạn thực sự muốn làm điều này, bạn có thể cài đặt databất cứ nơi nào bạn muốn trên hệ thống đích bằng cách chỉ định đích cho từng nhóm tệp bằng cách chuyển vào danh sách các bộ giá trị:

from setuptools import setup
setup(
    ...
    data_files=[
        ('/var/data1', ['data/foo.txt']),
        ('/var/data2', ['data/bar.txt'])
        ]
    )

Đã cập nhật : Ví dụ về hàm shell để ghi tệp Python một cách đệ quy:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}

7
Cảm ơn rất nhiều vì đã giúp tôi giải quyết tình hình. Vì vậy, tôi rất vui khi sử dụng package_data như bạn (và mọi người khác) đề xuất. Tuy nhiên: Có phải chỉ tôi thấy việc đặt dữ liệu và tài liệu của họ bên trong thư mục nguồn gói của họ là lộn xộn một cách bất tiện không? (ví dụ: grep mã nguồn của tôi trả về hàng tá lần truy cập không mong muốn từ tài liệu của tôi. Tôi có thể thêm tham số '--exclude-dir' vào grep mỗi khi tôi sử dụng nó, điều này sẽ khác nhau giữa các dự án này với dự án tiếp theo, nhưng điều đó có vẻ khó) có thể một cái gì đó giống như bao gồm một 'src' subdir bên dir tôi gói mà không cần nhập khẩu vi phạm, vv
Jonathan Hartley

Tôi thường chỉ đặt các tệp dữ liệu mà gói yêu cầu dưới gói dir. Tôi sẽ cài đặt các tài liệu dưới dạng data_files. Ngoài ra, bạn có thể nghĩ ra một bí danh shell cho grep để bỏ qua các tệp không phải Python, đại loại như grep_py.
samplebias

Này samplebias. Cảm ơn vì các bản cập nhật. Tuy nhiên, nó không chỉ là grep, mà là tất cả mọi thứ , từ tìm kiếm trong tệp của trình soạn thảo văn bản đến thẻ ctags đến awk. Tôi sẽ thử sắp xếp lại dự án của mình để đưa tài liệu vào data_files như bạn đề xuất, xem cách đó hoạt động như thế nào. Trở lại sớm ... :-)
Jonathan Hartley.

... điều đó có vẻ ổn. Cảm ơn vì đã đưa tôi đi đúng hướng. +50 điểm danh tiếng có ngon không?
Jonathan Hartley,

Cảm ơn! Thật tuyệt khi nghe, rất vui vì nó đã thành công và bạn đang tiến bộ!
samplebias

13

Tôi nghĩ rằng tôi đã tìm thấy một thỏa hiệp tốt sẽ cho phép bạn nắm được cấu trúc sau:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Bạn nên cài đặt dữ liệu dưới dạng package_data, để tránh các vấn đề được mô tả trong câu trả lời samplebias, nhưng để duy trì cấu trúc tệp, bạn nên thêm vào setup.py:

try:
    os.symlink('../../data', 'src/mypackage/data')
    setup(
        ...
        package_data = {'mypackage': ['data/*']}
        ...
    )
finally:
    os.unlink('src/mypackage/data')

Bằng cách này, chúng tôi tạo ra cấu trúc thích hợp "đúng lúc" và đảm bảo cây nguồn của chúng tôi có tổ chức.

Để truy cập các tệp dữ liệu như vậy trong mã của bạn, bạn 'chỉ cần' sử dụng:

data = resource_filename(Requirement.parse("main_package"), 'mypackage/data')

Tôi vẫn không thích phải chỉ định 'mypackage' trong mã, vì dữ liệu có thể không liên quan gì đến mô-đun này một cách cần thiết, nhưng tôi đoán đó là một thỏa hiệp tốt.


-4

Tôi nghĩ rằng về cơ bản bạn có thể cung cấp bất kỳ thứ gì dưới dạng đối số * data_files * cho setup () .


Hmm ... Tôi có thể thấy nó có trong tài liệu distutils, không thấy nó trong tài liệu setuptools. Dù sao thì, cuối cùng thì làm cách nào để tôi có thể truy cập được?
phant0m

Tôi nghĩ rằng data_files chỉ nên được sử dụng cho dữ liệu được chia sẻ giữa một số gói. ví dụ: nếu bạn pip cài đặt từ PyPI, thì các tệp được liệt kê trong data_files được cài đặt vào các thư mục trực tiếp trong trình cài đặt Python chính của bạn. (tức là. không phải trong Python27 / Lib / site-pack / mypackage, nhưng song song với 'Python27 / Lib')
Jonathan Hartley
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.