importlib.metadata -- Truy cập siêu dữ liệu gói

Added in version 3.8.

Thay đổi trong phiên bản 3.10: importlib.metadata không còn là tạm thời nữa.

Source code: Lib/importlib/metadata/__init__.py

importlib.metadata là thư viện cung cấp quyền truy cập vào siêu dữ liệu của Distribution Package đã cài đặt, chẳng hạn như điểm vào hoặc tên cấp cao nhất của nó (Import Packages, mô-đun, nếu có). Được xây dựng một phần trên hệ thống nhập của Python, thư viện này dự định thay thế chức năng tương tự trong entry point APImetadata API của pkg_resources. Cùng với importlib.resources, gói này có thể loại bỏ nhu cầu sử dụng gói pkg_resources cũ hơn và kém hiệu quả hơn.

importlib.metadata hoạt động trên distribution packages của bên thứ ba được cài đặt vào thư mục site-packages của Python thông qua các công cụ như pip. Cụ thể, nó hoạt động với các bản phân phối có thư mục dist-info hoặc egg-info có thể phát hiện được và siêu dữ liệu được xác định bởi Core metadata specifications.

Quan trọng

Đây là các not nhất thiết phải tương đương hoặc tương ứng với tỷ lệ 1:1 với các tên import package cấp cao nhất có thể được nhập bên trong mã Python. Một distribution package có thể chứa nhiều import packages (và các mô-đun đơn) và một import package cấp cao nhất có thể ánh xạ tới nhiều distribution packages nếu đó là gói không gian tên. Bạn có thể sử dụng packages_distributions() để có được ánh xạ giữa chúng.

Theo mặc định, siêu dữ liệu phân phối có thể tồn tại trên hệ thống tệp hoặc trong kho lưu trữ zip trên sys.path. Thông qua cơ chế mở rộng, siêu dữ liệu có thể tồn tại ở hầu hết mọi nơi.

Xem thêm

https://importlib-metadata.readthedocs.io/

Tài liệu dành cho importlib_metadata, cung cấp cổng sau của importlib.metadata. Điều này bao gồm một API reference cho các lớp và chức năng của mô-đun này, cũng như một migration guide cho những người dùng hiện tại của pkg_resources.

Tổng quan

Giả sử bạn muốn lấy chuỗi phiên bản cho Distribution Package mà bạn đã cài đặt bằng pip. Chúng tôi bắt đầu bằng cách tạo một môi trường ảo và cài đặt một cái gì đó vào đó:

$ python -m  dụ về venv
$  dụ nguồn/bin/kích hoạt
(ví dụ) $ python -m pip cài đặt bánh xe

Bạn có thể lấy chuỗi phiên bản cho wheel bằng cách chạy như sau:

(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'0.32.3'

Bạn cũng có thể nhận được một tập hợp các điểm vào có thể được chọn theo thuộc tính của EntryPoint (thường là 'nhóm' hoặc 'tên'), chẳng hạn như console_scripts, distutils.commands và các điểm khác. Mỗi nhóm chứa một tập hợp các đối tượng EntryPoint.

Bạn có thể nhận được metadata for a distribution:

>>> list(metadata('wheel'))
['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist']

Bạn cũng có thể lấy distribution's version number, liệt kê constituent files của nó và lấy danh sách Yêu cầu phân phối của bản phân phối.

exception importlib.metadata.PackageNotFoundError

Lớp con của ModuleNotFoundError được tạo ra bởi một số hàm trong mô-đun này khi được truy vấn gói phân phối chưa được cài đặt trong môi trường Python hiện tại.

Chức năng API

Gói này cung cấp chức năng sau thông qua API công khai.

Điểm vào

importlib.metadata.entry_points(**select_params)

Trả về một phiên bản EntryPoints mô tả các điểm vào cho môi trường hiện tại. Mọi tham số từ khóa nhất định đều được chuyển đến phương thức select() để so sánh với các thuộc tính của định nghĩa điểm nhập riêng lẻ.

Lưu ý: hiện không thể truy vấn các điểm vào dựa trên thuộc tính EntryPoint.dist của chúng (vì các phiên bản Distribution khác nhau hiện không so sánh bằng nhau, ngay cả khi chúng có cùng thuộc tính)

class importlib.metadata.EntryPoints

Chi tiết về tập hợp các điểm vào được cài đặt.

Đồng thời cung cấp thuộc tính .groups báo cáo tất cả các nhóm điểm vào đã xác định và thuộc tính .names báo cáo tất cả các tên điểm vào đã xác định.

class importlib.metadata.EntryPoint

Chi tiết về một điểm vào được cài đặt.

Mỗi phiên bản EntryPoint có các thuộc tính .name, .group.value và một phương thức .load() để phân giải giá trị. Ngoài ra còn có các thuộc tính .module, .attr.extras để lấy các thành phần của thuộc tính .value.dist để lấy thông tin về gói phân phối cung cấp điểm vào.

Truy vấn tất cả các điểm vào:

>>> eps = entry_points()

Hàm entry_points() trả về một đối tượng EntryPoints, một tập hợp tất cả các đối tượng EntryPoint có thuộc tính namesgroups để thuận tiện:

>>> sorted(eps.groups)
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']

EntryPoints có phương thức select() để chọn điểm vào phù hợp với các thuộc tính cụ thể. Chọn điểm vào trong nhóm console_scripts:

>>> scripts = eps.select(group='console_scripts')

Tương tự, vì entry_points() chuyển các đối số từ khóa để chọn:

>>> scripts = entry_points(group='console_scripts')

Chọn một tập lệnh cụ thể có tên "bánh xe" (được tìm thấy trong dự án bánh xe):

>>> 'wheel' in scripts.names
True
>>> wheel = scripts['wheel']

Tương tự, truy vấn điểm vào đó trong quá trình lựa chọn:

>>> (wheel,) = entry_points(group='console_scripts', name='wheel')
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')

Kiểm tra điểm vào đã giải quyết:

>>> wheel
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
>>> wheel.module
'wheel.cli'
>>> wheel.attr
'main'
>>> wheel.extras
[]
>>> main = wheel.load()
>>> main
<function main at 0x103528488>

groupname là các giá trị tùy ý do tác giả gói xác định và thông thường khách hàng sẽ muốn giải quyết tất cả các điểm vào cho một nhóm cụ thể. Đọc the setuptools docs để biết thêm thông tin về điểm vào, định nghĩa và cách sử dụng của chúng.

Thay đổi trong phiên bản 3.12: Điểm vào "có thể chọn" đã được giới thiệu trong importlib_metadata 3.6 và Python 3.10. Trước những thay đổi đó, entry_points không chấp nhận tham số nào và luôn trả về một từ điển các điểm nhập, được khóa theo nhóm. Với importlib_metadata 5.0 và Python 3.12, entry_points luôn trả về một đối tượng EntryPoints. Xem backports.entry_points_selectable để biết các tùy chọn tương thích.

Thay đổi trong phiên bản 3.13: Các đối tượng EntryPoint không còn có giao diện giống tuple (__getitem__()).

Siêu dữ liệu phân phối

importlib.metadata.metadata(distribution_name)

Trả về siêu dữ liệu phân phối tương ứng với gói phân phối được đặt tên dưới dạng phiên bản PackageMetadata.

Tăng PackageNotFoundError nếu gói phân phối có tên không được cài đặt trong môi trường Python hiện tại.

class importlib.metadata.PackageMetadata

Triển khai cụ thể của PackageMetadata protocol.

Ngoài việc cung cấp các thuộc tính và phương thức giao thức đã xác định, việc đăng ký phiên bản tương đương với việc gọi phương thức get().

Mỗi Distribution Package bao gồm một số siêu dữ liệu mà bạn có thể trích xuất bằng chức năng metadata()

>>> wheel_metadata = metadata('wheel')

Các khóa của cấu trúc dữ liệu được trả về đặt tên cho các từ khóa siêu dữ liệu và các giá trị được trả về chưa được phân tích cú pháp từ siêu dữ liệu phân phối:

>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

PackageMetadata cũng trình bày thuộc tính json trả về tất cả siêu dữ liệu ở dạng tương thích với JSON cho mỗi PEP 566:

>>> Wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

Toàn bộ siêu dữ liệu có sẵn không được mô tả ở đây. Xem PyPA Core metadata specification để biết thêm chi tiết.

Thay đổi trong phiên bản 3.10: Zz000zz hiện được bao gồm trong siêu dữ liệu khi được hiển thị thông qua tải trọng. Các ký tự tiếp tục dòng đã bị loại bỏ.

Thuộc tính json đã được thêm vào.

Phiên bản phân phối

importlib.metadata.version(distribution_name)

Trả lại gói phân phối đã cài đặt version cho gói phân phối được đặt tên.

Tăng PackageNotFoundError nếu gói phân phối có tên không được cài đặt trong môi trường Python hiện tại.

Hàm version() là cách nhanh nhất để lấy số phiên bản của Distribution Package, dưới dạng chuỗi

>>> version('wheel')
'0.32.3'

Tệp phân phối

importlib.metadata.files(distribution_name)

Trả về toàn bộ tập tin có trong gói phân phối được đặt tên.

Tăng PackageNotFoundError nếu gói phân phối có tên không được cài đặt trong môi trường Python hiện tại.

Trả về None nếu tìm thấy bản phân phối nhưng bản ghi cơ sở dữ liệu cài đặt báo cáo các tệp liên quan đến gói phân phối bị thiếu.

class importlib.metadata.PackagePath

Đối tượng dẫn xuất pathlib.PurePath có các thuộc tính dist, sizehash bổ sung tương ứng với siêu dữ liệu cài đặt của gói phân phối cho tệp đó.

Hàm files() lấy tên Distribution Package và trả về tất cả các tệp được cài đặt bởi bản phân phối này. Mỗi tệp được báo cáo là một phiên bản PackagePath. Ví dụ:

>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]
>>> util
PackagePath('wheel/util.py')
>>> util.size
859
>>> util.dist
<importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
>>> util.hash
<FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>

Sau khi có tệp, bạn cũng có thể đọc nội dung của nó:

>>> print(util.read_text())
import base64
import sys
...
def as_bytes(s):
    if isinstance(s, text_type):
        return s.encode('utf-8')
    return s

Bạn cũng có thể sử dụng phương thức locate() để lấy đường dẫn tuyệt đối đến tệp

>>> util.locate()
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')

Trong trường hợp thiếu tệp liệt kê tệp siêu dữ liệu (RECORD hoặc SOURCES.txt), files() sẽ trả về None. Người gọi có thể muốn kết thúc các cuộc gọi đến files() trong always_iterable hoặc đề phòng tình trạng này nếu phân phối mục tiêu không được biết là có siêu dữ liệu.

Yêu cầu phân phối

importlib.metadata.requires(distribution_name)

Trả về các thông số xác định phụ thuộc đã khai báo cho gói phân phối được đặt tên.

Tăng PackageNotFoundError nếu gói phân phối có tên không được cài đặt trong môi trường Python hiện tại.

Để có được bộ yêu cầu đầy đủ cho Distribution Package, hãy sử dụng hàm requires()

>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]

Ánh xạ nhập vào các gói phân phối

importlib.metadata.packages_distributions()

Trả về ánh xạ từ mô-đun cấp cao nhất và nhập tên gói được tìm thấy qua sys.meta_path vào tên của gói phân phối (nếu có) cung cấp các tệp tương ứng.

Để cho phép các gói không gian tên (có thể có các thành viên được cung cấp bởi nhiều gói phân phối), mỗi tên nhập cấp cao nhất sẽ ánh xạ tới một danh sách các tên phân phối thay vì ánh xạ trực tiếp tới một tên duy nhất.

Một phương pháp thuận tiện để phân giải tên Distribution Package (hoặc các tên, trong trường hợp gói không gian tên) cung cấp từng mô-đun Python cấp cao nhất có thể nhập hoặc Import Package

>>> gói_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}

Một số bản cài đặt có thể chỉnh sửa, do not supply top-level names, và do đó chức năng này không đáng tin cậy với những bản cài đặt như vậy.

Added in version 3.10.

Phân phối

importlib.metadata.distribution(distribution_name)

Trả về một phiên bản Distribution mô tả gói phân phối được đặt tên.

Tăng PackageNotFoundError nếu gói phân phối có tên không được cài đặt trong môi trường Python hiện tại.

class importlib.metadata.Distribution

Chi tiết về gói phân phối đã cài đặt.

Lưu ý: các phiên bản Distribution khác nhau hiện không so sánh bằng nhau, ngay cả khi chúng liên quan đến cùng một bản phân phối được cài đặt và do đó có cùng thuộc tính.

Mặc dù cấp độ mô-đun API được mô tả ở trên là cách sử dụng phổ biến và thuận tiện nhất, nhưng bạn có thể lấy tất cả thông tin đó từ lớp Distribution. Distribution là một đối tượng trừu tượng đại diện cho siêu dữ liệu cho Distribution Package Python. Bạn có thể lấy phiên bản lớp con Distribution cụ thể cho gói phân phối đã cài đặt bằng cách gọi hàm distribution()

>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>

Do đó, một cách khác để lấy số phiên bản là thông qua phiên bản Distribution:

>>> dist.version
'0.32.3'

Có tất cả các loại siêu dữ liệu bổ sung có sẵn trên các phiên bản Distribution:

>>> dist.metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> dist.metadata['License']
'MIT'

Đối với các gói có thể chỉnh sửa, thuộc tính origin có thể hiển thị siêu dữ liệu PEP 610:

>>> dist.origin.url
'tệp:///path/to/wheel-0.32.3.editable-py3-none-any.whl'

Toàn bộ siêu dữ liệu có sẵn không được mô tả ở đây. Xem PyPA Core metadata specification để biết thêm chi tiết.

Added in version 3.13: Thuộc tính .origin đã được thêm vào.

Khám phá phân phối

Theo mặc định, gói này cung cấp hỗ trợ tích hợp để khám phá siêu dữ liệu cho hệ thống tệp và tệp zip Distribution Packages. Tìm kiếm của công cụ tìm siêu dữ liệu này mặc định là sys.path, nhưng thay đổi đôi chút về cách diễn giải các giá trị đó so với cách thực hiện của các công cụ nhập khác. Đặc biệt:

  • importlib.metadata không tôn vinh các đối tượng bytes trên sys.path.

  • importlib.metadata sẽ ngẫu nhiên vinh danh các đối tượng pathlib.Path trên sys.path mặc dù các giá trị đó sẽ bị bỏ qua khi nhập.

Triển khai nhà cung cấp tùy chỉnh

importlib.metadata xử lý hai bề mặt API, một bề mặt dành cho consumers và một bề mặt khác dành cho providers. Hầu hết người dùng là người tiêu dùng, sử dụng siêu dữ liệu do các gói cung cấp. Tuy nhiên, có những trường hợp sử dụng khác trong đó người dùng muốn hiển thị siêu dữ liệu thông qua một số cơ chế khác, chẳng hạn như cùng với trình nhập tùy chỉnh. Trường hợp sử dụng như vậy cần có custom provider.

Vì siêu dữ liệu Distribution Package không có sẵn thông qua tìm kiếm sys.path hoặc trình tải gói trực tiếp nên siêu dữ liệu cho bản phân phối được tìm thấy thông qua hệ thống nhập finders. Để tìm siêu dữ liệu của gói phân phối, importlib.metadata truy vấn danh sách meta path finders trên sys.meta_path.

Việc triển khai có các hook được tích hợp vào PathFinder, cung cấp siêu dữ liệu cho các gói phân phối được tìm thấy trên hệ thống tệp.

Lớp trừu tượng importlib.abc.MetaPathFinder xác định giao diện mà hệ thống nhập của Python mong đợi cho các công cụ tìm kiếm. importlib.metadata mở rộng giao thức này bằng cách tìm kiếm một find_distributions tùy chọn có thể gọi được trên các công cụ tìm từ sys.meta_path và trình bày giao diện mở rộng này dưới dạng lớp cơ sở trừu tượng DistributionFinder, định nghĩa phương thức trừu tượng này:

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
    """Trả về một bản lặp của tất cả các phiên bản Phân phối có khả năng
    đang tải siêu dữ liệu cho các gói dành cho ``context`` được chỉ định.
    """

Đối tượng DistributionFinder.Context cung cấp các thuộc tính .path.name cho biết đường dẫn tìm kiếm và đặt tên phù hợp, đồng thời có thể cung cấp ngữ cảnh liên quan khác mà người tiêu dùng đang tìm kiếm.

Trong thực tế, để hỗ trợ tìm siêu dữ liệu gói phân phối ở các vị trí khác ngoài hệ thống tệp, hãy phân lớp Distribution và triển khai các phương thức trừu tượng. Sau đó, từ một công cụ tìm tùy chỉnh, trả về các phiên bản của Distribution dẫn xuất này trong phương thức find_distributions().

Ví dụ

Hãy tưởng tượng một công cụ tìm tùy chỉnh tải các mô-đun Python từ cơ sở dữ liệu

lớp  sở dữ liệuImporter (importlib.abc.MetaPathFinder):
    định nghĩa __init__(self, db):
        self.db = db

    def find_spec(self, fullname, target=None) -> ModuleSpec:
        trả về self.db.spec_from_name(fullname)

sys.meta_path.append(DatabaseImporter(connect_db(...)))

Trình nhập đó hiện có lẽ cung cấp các mô-đun có thể nhập được từ cơ sở dữ liệu, nhưng nó không cung cấp siêu dữ liệu hoặc điểm truy nhập. Để nhà nhập khẩu tùy chỉnh này cung cấp siêu dữ liệu, nó cũng cần phải triển khai DistributionFinder:

từ importlib.metadata nhập DistributionFinder

lớp  sở dữ liệu nhập khẩu (DistributionFinder):
    ...

    def find_distributions(self, context=DistributionFinder.Context()):
        truy vấn = dict(name=context.name) if context.name else {}
        cho dist_record trong self.db.query_distributions(query):
            mang lại Phân phối  sở dữ liệu (dist_record)

Bằng cách này, query_distributions sẽ trả về các bản ghi cho mỗi phân phối được cơ sở dữ liệu cung cấp phù hợp với truy vấn. Ví dụ: nếu requests-1.0 có trong cơ sở dữ liệu, find_distributions sẽ mang lại DatabaseDistribution cho Context(name='requests') hoặc Context(name=None).

Để đơn giản, ví dụ này bỏ qua context.path. Thuộc tính path mặc định là sys.path và là tập hợp các đường dẫn nhập sẽ được xem xét trong tìm kiếm. Zz003zz có thể hoạt động mà không cần quan tâm đến đường dẫn tìm kiếm. Giả sử nhà nhập khẩu không phân vùng thì "đường dẫn" sẽ không liên quan. Để minh họa mục đích của path, ví dụ này cần minh họa một DatabaseImporter phức tạp hơn có hành vi thay đổi tùy thuộc vào sys.path/PYTHONPATH. Trong trường hợp đó, find_distributions phải tôn trọng context.path và chỉ mang lại Distribution phù hợp với đường dẫn đó.

DatabaseDistribution thì sẽ trông giống như:

Phân phối  sở dữ liệu lớp (importlib.metadata.Distribution):
    def __init__(tự, ghi):
        self.record = bản ghi

    def read_text(tự, tên tệp):
        """
        Đọc tệp như "METADATA" cho bản phân phối hiện tại.
        """
        nếu tên tệp == "METADATA":
            return f"""Tên: {self.record.name}
Phiên bản: {self.record.version}
"""
        nếu tên tệp == "entry_points.txt":
            trả về "\n".join(
              f"""[{ep.group}]\n{ep.name}={ep.value}"""
              cho ep trong self.record.entry_point)

    def định vị_file(tự, đường dẫn):
        raise RuntimeError("Bản phân phối này không có hệ thống tập tin")

Việc triển khai cơ bản này sẽ cung cấp siêu dữ liệu và điểm truy cập cho các gói do DatabaseImporter phân phối, giả sử rằng record cung cấp các thuộc tính .name, .version.entry_points phù hợp.

DatabaseDistribution cũng có thể cung cấp các tệp siêu dữ liệu khác, như RECORD (bắt buộc đối với Distribution.files) hoặc ghi đè việc triển khai Distribution.files. Xem nguồn để có thêm cảm hứng.