importlib.metadata -- دسترسی به فرادادهی بسته¶
اضافه شده در نسخهی 3.8.
تغییر یافته در نسخهی 3.10: importlib.metadata دیگر موقتی نیست.
کد منبع: Lib/importlib/metadata/__init__.py
importlib.metadata کتابخانهای است که دسترسی به فرادادهی یک بسته توزیعی نصبشده را فراهم میکند، مانند نقاط ورود آن یا نامهای سطح بالای آن (بستههای ایمپورت، ماژولها، در صورت وجود). این کتابخانه که تا حدی بر پایهی سیستم ایمپورت پایتون ساخته شده است، APIهای نقطهی ورود و فراداده را فراهم میکند که پیشتر توسط بستهی اکنون حذفشدهی pkg_resources ارائه میشدند. این کتابخانه به همراه importlib.resources، جایگزین pkg_resources شده است.
importlib.metadata روی بستههای توزیع شخص ثالث نصبشده از طریق ابزارهایی مانند pip در پوشهی site-packages پایتون عمل میکند. بهطور مشخص، با توزیعهایی کار میکند که دارای پوشههای dist-info یا egg-info قابلکشف هستند، و نیز با فرادادهای که در مشخصات فرادادهی اصلی تعریفشده است.
مهم
این موارد لزوماً معادل یا متناظر یکبهیک با نامهای بسته ایمپورت سطح بالا که میتوان آنها را در کد پایتون ایمپورت کرد، نیستند. یک بسته توزیع میتواند شامل چندین بسته ایمپورت (و ماژولهای منفرد) باشد، و یک بسته ایمپورت سطح بالا ممکن است در صورتی که یک بسته فضای نام باشد، به چندین بسته توزیع نگاشت شود. میتوانید از packages_distributions() برای دریافت نگاشت بین آنها استفاده کنید.
بهطور پیشفرض، فرادادهی توزیع میتواند در سامانه فایلبندی یا در آرشیوهای zip موجود در sys.path قرار داشته باشد. از طریق یک سازوکار توسعه، فراداده میتواند تقریباً در هر جایی قرار داشته باشد.
همچنین ملاحظه نمائید
- https://importlib-metadata.readthedocs.io/
مستندات
importlib_metadata، که یک بکپورت ازimportlib.metadataرا فراهم میکند. این مستندات شامل یک مرجع API برای کلاسها و توابع این ماژول، و همچنین یک راهنمای مهاجرت برای کاربران فعلیpkg_resourcesاست.
نمای کلی¶
فرض کنید میخواهید رشته نسخه را برای یک بسته توزیع (Distribution Package) که با استفاده از pip نصب کردهاید، دریافت کنید. ما با ایجاد یک محیط مجازی و نصب چیزی در آن شروع میکنیم:
$ python -m venv example
$ source example/bin/activate
(example) $ python -m pip install wheel
میتوانید رشتهی نسخهی wheel را با اجرای دستور زیر به دست آورید:
(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'0.32.3'
همچنین میتوانید مجموعهای از نقطههای ورودی را دریافت کنید که بر اساس ویژگیهای EntryPoint (معمولاً 'group' یا 'name') قابل انتخاب هستند، مانند console_scripts، distutils.commands و دیگر موارد. هر گروه شامل مجموعهای از اشیای EntryPoint است.
میتوانید فراداده یک توزیع را دریافت کنید:
>>> from importlib.metadata import metadata
>>> 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']
همچنین میتوانید شماره نسخه توزیع را دریافت کنید، پروندههای تشکیلدهنده آن را فهرست کنید و فهرستی از نیازمندیهای توزیع توزیع را به دست آورید.
- exception importlib.metadata.PackageNotFoundError¶
زیرکلاسی از
ModuleNotFoundErrorکه توسط چندین تابع در این ماژول، هنگام پرسوجو برای بسته توزیعی که در محیط پایتون جاری نصب نیست، پرتاب میشود.
API تابعی¶
این بسته، قابلیتهای زیر را از طریق API عمومی خود فراهم میکند.
نقاط ورود¶
- importlib.metadata.entry_points(**select_params)¶
نمونهای از
EntryPointsرا برمیگرداند که نقاط ورود محیط جاری را توصیف میکند. هر پارامتر کلیدواژهای که داده شود، به متدselect()ارسال میشود تا با ویژگیهای تعریفهای جداگانهی نقاط ورود مقایسه شود.نکته: در حال حاضر نمیتوان نقاط ورود را بر اساس ویژگی
EntryPoint.distآنها جستجو کرد (زیرا نمونههای مختلفDistributionدر حال حاضر هنگام مقایسه برابر نیستند، حتی اگر ویژگیهای یکسانی داشته باشند)
- class importlib.metadata.EntryPoints¶
جزئیات مجموعهای از نقاط ورودی نصبشده (entry points).
همچنین یک ویژگی
.groupsارائه میدهد که تمام گروههای شناساییشدهی نقطه ورود (entry point) را گزارش میدهد، و یک ویژگی.namesکه تمام نامهای شناساییشدهی نقطه ورود را گزارش میدهد.
- class importlib.metadata.EntryPoint¶
جزئیات یک نقطه ورود نصبشده.
هر نمونهی
EntryPointدارای ویژگیهای.name،.groupو.valueو یک متد.load()برای حل کردن مقدار است. همچنین ویژگیهای.module،.attrو.extrasبرای دریافت کامپوننتهای ویژگی.valueو نیز.distبرای به دست آوردن اطلاعات مربوط به بستهی توزیعی که نقطه ورود را فراهم میکند، وجود دارند.
پرسوجوی همهی نقطههای ورود:
>>> eps = entry_points()
تابع entry_points() یک شیء EntryPoints را برمیگرداند، مجموعهای از تمام اشیای EntryPoint که برای سهولت دارای ویژگیهای names و groups است:
>>> sorted(eps.groups)
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
EntryPoints یک متد select() برای انتخاب نقاط ورود منطبق بر ویژگیهای مشخص دارد. نقاط ورود را در گروه console_scripts انتخاب کنید:
>>> scripts = eps.select(group='console_scripts')
بهطور معادل، از آنجا که entry_points() آرگومانهای کلیدواژهای را به select منتقل میکند:
>>> scripts = entry_points(group='console_scripts')
یک اسکریپت مشخص به نام "wheel" (موجود در پروژه wheel) را انتخاب کنید:
>>> 'wheel' in scripts.names
True
>>> wheel = scripts['wheel']
معادل آن، در هنگام انتخاب، آن نقطه ورود را پرسوجو کنید:
>>> (wheel,) = entry_points(group='console_scripts', name='wheel')
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')
نقطه ورود تعیینشده را بررسی کنید:
>>> 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>
group و name مقادیر دلخواهی هستند که نویسنده بسته آنها را تعریف کرده است و معمولاً یک کلاینت مایل است تمام نقطههای ورود یک گروه خاص را حل کند. برای اطلاعات بیشتر درباره نقطههای ورود، تعریف و کاربرد آنها، مستندات setuptools را بخوانید.
تغییر یافته در نسخهی 3.12: نقاط ورودی «قابلانتخاب» در importlib_metadata 3.6 و پایتون 3.10 معرفی شدند. پیش از آن تغییرات، entry_points هیچ پارامتری را نمیپذیرفت و همیشه یک دیکشنری از نقاط ورودی را برمیگرداند که بر اساس گروه کلیدگذاری شده بود. با importlib_metadata 5.0 و پایتون 3.12، entry_points همیشه یک شیء EntryPoints را برمیگرداند. برای گزینههای سازگاری، backports.entry_points_selectable را ببینید.
تغییر یافته در نسخهی 3.13: اشیای EntryPoint دیگر رابطی شبیه به تاپل ارائه نمیدهند (__getitem__()).
فرادادهی توزیع¶
- importlib.metadata.metadata(distribution_name)¶
فراداده توزیع مربوط به بسته توزیع نامبردهشده را بهعنوان یک نمونه از
PackageMetadataبرمیگرداند.اگر بسته توزیع نامبرده در محیط پایتون جاری نصب نشده باشد،
PackageNotFoundErrorرا پرتاب میکند.
- class importlib.metadata.PackageMetadata¶
یک پیادهسازی عینی از پروتکل PackageMetadata.
علاوه بر ارائه متدها و ویژگیهای تعریفشدهی پروتکل، اندیسگذاری نمونه معادل فراخوانی متد
get()است.
هر بسته توزیع شامل فرادادهای است که میتوانید آن را با استفاده از تابع metadata() استخراج کنید:
>>> wheel_metadata = metadata('wheel')
کلیدهای ساختار دادهی بازگرداندهشده، نام کلیدواژههای فراداده هستند و مقادیر بهصورت تجزیهنشده از فرادادهی توزیع بازگردانده میشوند:
>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
PackageMetadata همچنین ویژگی json را ارائه میدهد که تمام فرادادهها را در قالبی سازگار با JSON مطابق PEP 566 برمیگرداند:
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
مجموعهی کامل فرادادههای موجود در اینجا توصیف نشده است. برای جزئیات بیشتر، Core metadata specification در PyPA را ببینید.
تغییر یافته در نسخهی 3.10: Description اکنون هنگام ارائه از طریق بار در فراداده گنجانده شده است. نویسههای ادامهی خط حذف شدهاند.
ویژگی json افزوده شد.
نسخههای توزیع¶
- importlib.metadata.version(distribution_name)¶
نسخه بسته توزیع نصبشده را برای بسته توزیع نامبرده برمیگرداند.
اگر بسته توزیع نامبرده در محیط پایتون جاری نصب نشده باشد،
PackageNotFoundErrorرا پرتاب میکند.
تابع version() سریعترین راه برای دریافت شمارهی نسخهی یک بسته توزیع بهصورت یک رشته است:
>>> version('wheel')
'0.32.3'
پروندههای توزیع¶
- importlib.metadata.files(distribution_name)¶
مجموعه کامل پروندههای موجود در بسته توزیع نامبرده را برمیگرداند.
اگر بسته توزیع نامبرده در محیط پایتون جاری نصب نشده باشد،
PackageNotFoundErrorرا پرتاب میکند.اگر توزیع یافت شود اما رکوردهای پایگاهدادهی نصب که پروندههای مرتبط با بسته توزیع را گزارش میدهند موجود نباشند، مقدار
Noneرا برمیگرداند.
- class importlib.metadata.PackagePath¶
یک شیء مشتقشده از
pathlib.PurePathبا ویژگیهای اضافیdist،sizeوhashکه با فراداده نصب بسته توزیع برای آن پرونده متناظر هستند.
تابع files() نام یک Distribution Package را میگیرد و تمام پروندههای نصبشده توسط این توزیع را برمیگرداند. هر پرونده بهعنوان نمونهای از PackagePath گزارش میشود. برای مثال:
>>> 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>
پس از اینکه پرونده را داشتید، میتوانید محتوای آن را نیز بخوانید:
>>> print(util.read_text())
import base64
import sys
...
def as_bytes(s):
if isinstance(s, text_type):
return s.encode('utf-8')
return s
همچنین میتوانید از متد locate() برای دریافت مسیر مطلق پرونده استفاده کنید:
>>> util.locate()
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
در صورتی که پرونده فرادادهای که پروندهها را فهرست میکند (RECORD یا SOURCES.txt) موجود نباشد، files() مقدار None را برمیگرداند. اگر معلوم نباشد که توزیع هدف دارای فراداده است، فراخواننده ممکن است بخواهد فراخوانیهای files() را در always_iterable قرار دهد یا به روش دیگری در برابر این وضعیت محافظت کند.
نیازمندیهای توزیع¶
- importlib.metadata.requires(distribution_name)¶
مشخصکنندههای وابستگی اعلامشده برای بسته توزیع نامبرده را برمیگرداند.
اگر بسته توزیع نامبرده در محیط پایتون جاری نصب نشده باشد،
PackageNotFoundErrorرا پرتاب میکند.
برای دریافت مجموعه کامل نیازمندیهای یک بسته توزیع، از تابع requires() استفاده کنید:
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
نگاشت ایمپورت به بستههای توزیع¶
- importlib.metadata.packages_distributions()¶
نگاشتی از نام ماژولهای سطح بالا و بستههای ایمپورت که از طریق
sys.meta_pathیافت میشوند، به نام بستههای توزیع (در صورت وجود) که پروندههای متناظر را فراهم میکنند، برمیگرداند.برای پشتیبانی از بستههای فضای نام (که ممکن است اعضای آنها توسط چندین بسته توزیع فراهم شده باشند)، هر نام ایمپورت سطح بالا به فهرستی از نامهای توزیع نگاشت میشود، نه اینکه بهطور مستقیم به یک نام واحد نگاشت شود.
متدی آسانکننده برای تعیین نام بسته توزیع (یا نامها، در مورد یک بسته فضای نام) که هر ماژول پایتون سطحبالای قابل ایمپورت یا بسته ایمپورت را فراهم میکنند:
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
برخی نصبهای قابلویرایش، نامهای سطح بالا را ارائه نمیدهند، و بنابراین این تابع با چنین نصبهایی قابلاتکا نیست.
اضافه شده در نسخهی 3.10.
توزیعها¶
- importlib.metadata.distribution(distribution_name)¶
نمونهای از
Distributionبرمیگرداند که بسته توزیع نامبردهشده را توصیف میکند.اگر بسته توزیع نامبرده در محیط پایتون جاری نصب نشده باشد،
PackageNotFoundErrorرا پرتاب میکند.
- class importlib.metadata.Distribution¶
جزئیات یک بسته توزیع نصبشده.
توجه: نمونههای متفاوت
Distributionدر حال حاضر هنگام مقایسه برابر در نظر گرفته نمیشوند، حتی اگر به همان توزیع نصبشده مربوط باشند و بنابراین ویژگیهای یکسانی داشته باشند.
اگرچه API سطح ماژول توصیفشده در بالا رایجترین و مناسبترین کاربرد است، میتوانید تمام آن اطلاعات را از کلاس Distribution دریافت کنید. Distribution یک شیء انتزاعی است که فرادادهی یک بسته توزیع پایتون را نشان میدهد. میتوانید با فراخوانی تابع distribution()، نمونهای از زیرکلاس عینی Distribution برای یک بسته توزیع نصبشده را دریافت کنید:
>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
بنابراین، روشی جایگزین برای دریافت شماره نسخه از طریق نمونهی Distribution است:
>>> dist.version
'0.32.3'
انواع مختلفی از فرادادههای اضافی روی نمونههای Distribution در دسترس است:
>>> dist.metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> dist.metadata['License']
'MIT'
برای بستههای قابلویرایش، ممکن است ویژگی origin فرادادهی PEP 610 را ارائه دهد:
>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
مجموعهی کامل فرادادههای موجود در اینجا توصیف نشده است. برای جزئیات بیشتر، Core metadata specification در PyPA را ببینید.
اضافه شده در نسخهی 3.13: ویژگی .origin افزوده شد.
کشف توزیع¶
بهطور پیشفرض، این بسته پشتیبانی توکاری برای کشف فرادادهی بستههای توزیع در سامانه فایلبندی و پروندههای zip فراهم میکند. جستوجوی این یابندهی فراداده بهطور پیشفرض از sys.path استفاده میکند، اما تفسیر آن از این مقادیر، تفاوت اندکی با تفسیر سایر سازوکارهای ایمپورت دارد. بهویژه:
importlib.metadataاشیایbytesموجود درsys.pathرا به رسمیت نمیشناسد.importlib.metadataبهطور اتفاقی اشیایpathlib.Pathموجود درsys.pathرا معتبر میشمارد، حتی اگر چنین مقادیری برای ایمپورتها نادیده گرفته شوند.
پیادهسازی فراهمکنندگان سفارشی¶
importlib.metadata دو سطح API را پوشش میدهد: یکی برای مصرفکنندگان و دیگری برای ارائهدهندگان. بیشتر کاربران مصرفکننده هستند و فرادادهی ارائهشده توسط بستهها را مصرف میکنند. با این حال، موارد استفاده دیگری نیز وجود دارند که در آنها کاربران میخواهند فراداده را از طریق سازوکار دیگری، مثلاً در کنار یک واردکننده سفارشی، در معرض قرار دهند. چنین مورد استفادهای به یک ارائهدهنده سفارشی نیاز دارد.
از آنجا که فرادادهی بسته توزیعی از طریق جستوجوهای sys.path یا بهطور مستقیم از طریق بارگذارهای بسته در دسترس نیست، فرادادهی یک توزیع از طریق یابندهها در سیستم ایمپورت یافته میشود. برای یافتن فرادادهی یک بسته توزیعی، importlib.metadata فهرست یابندههای فرامسیر در sys.meta_path را جستوجو میکند.
این پیادهسازی دارای قلابهایی (hooks) است که در PathFinder ادغام شدهاند و فراداده بستههای توزیع یافتشده در سامانه پرونده را ارائه میکنند.
کلاس انتزاعی importlib.abc.MetaPathFinder رابطی را تعریف میکند که سیستم ایمپورت پایتون از یابندهها انتظار دارد. importlib.metadata این پروتکل را با جستوجوی یک شیء فراخوانیپذیر اختیاری find_distributions در یابندههای sys.meta_path گسترش میدهد و این رابط گسترشیافته را بهعنوان کلاس پایه انتزاعی DistributionFinder ارائه میکند، که این متد انتزاعی را تعریف میکند:
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
"""پیمایشپذیری از همه نمونههای Distribution برمیگرداند که توانایی بارگذاری فراداده بستهها برای ``context`` مشخصشده را دارند.
"""
شیء DistributionFinder.Context ویژگیهای .path و .name را ارائه میدهد که مسیر جستجو و نام برای تطبیق را نشان میدهند و ممکن است سایر زمینههای مرتبط مورد نیاز مصرفکننده را نیز فراهم کند.
در عمل، برای پشتیبانی از یافتن فرادادهی بستهی توزیع در مکانهایی غیر از سامانه فایلبندی، زیرکلاسی از Distribution بسازید و متدهای انتزاعی را پیادهسازی کنید. سپس از یک یابندهی سفارشی، نمونههایی از این Distribution مشتقشده را در متد find_distributions() برگردانید.
مثال¶
یک یابنده سفارشی را تصور کنید که ماژولهای پایتون را از یک پایگاه داده بارگذاری میکند:
class DatabaseImporter(importlib.abc.MetaPathFinder):
def __init__(self, db):
self.db = db
def find_spec(self, fullname, target=None) -> ModuleSpec:
return self.db.spec_from_name(fullname)
sys.meta_path.append(DatabaseImporter(connect_db(...)))
آن ایمپورتکننده اکنون احتمالاً ماژولهای قابل ایمپورت را از یک پایگاه داده فراهم میکند، اما هیچ فراداده یا نقطه ورودی ارائه نمیدهد. برای اینکه این ایمپورتکننده سفارشی فراداده ارائه دهد، همچنین باید DistributionFinder را پیادهسازی کند:
from importlib.metadata import DistributionFinder
class DatabaseImporter(DistributionFinder):
...
def find_distributions(self, context=DistributionFinder.Context()):
query = dict(name=context.name) if context.name else {}
for dist_record in self.db.query_distributions(query):
yield DatabaseDistribution(dist_record)
به این ترتیب، query_distributions رکوردهایی را برای هر توزیع ارائهشده توسط پایگاه داده که با پرسوجو مطابقت دارد، بازمیگرداند. برای مثال، اگر requests-1.0 در پایگاه داده باشد، find_distributions یک DatabaseDistribution برای Context(name='requests') یا Context(name=None) تولید میکند.
برای سادگی، این مثال context.path را نادیده میگیرد. ویژگی path بهطور پیشفرض برابر با sys.path است و مجموعهای از مسیرهای ایمپورت است که در جستجو در نظر گرفته میشوند. یک DatabaseImporter بهطور بالقوه میتواند بدون هیچ توجهی به مسیر جستجو کار کند. با فرض اینکه ایمپورتکننده هیچ بخشبندی انجام نمیدهد، «path» بیارتباط خواهد بود. برای نشان دادن هدف از path، مثال باید یک DatabaseImporter پیچیدهتر را نشان دهد که رفتار آن بسته به sys.path/PYTHONPATH تغییر میکند. در آن صورت، find_distributions باید context.path را رعایت کند و فقط نمونههای Distribution مرتبط با آن مسیر را برگرداند.
در این صورت، DatabaseDistribution چیزی شبیه به این خواهد بود:
class DatabaseDistribution(importlib.metadata.Distribution):
def __init__(self, record):
self.record = record
def read_text(self, filename):
"""
Read a file like "METADATA" for the current distribution.
"""
if filename == "METADATA":
return f"""Name: {self.record.name}
Version: {self.record.version}
"""
if filename == "entry_points.txt":
return "\n".join(
f"""[{ep.group}]\n{ep.name}={ep.value}"""
for ep in self.record.entry_points)
def locate_file(self, path):
raise RuntimeError("This distribution has no file system")
این پیادهسازی اولیه باید فراداده و نقاط ورود را برای بستههای ارائهشده توسط DatabaseImporter فراهم کند، با فرض اینکه record ویژگیهای مناسب .name، .version و .entry_points را فراهم کند.
DatabaseDistribution ممکن است پروندههای فراداده دیگری را نیز ارائه کند، مانند RECORD (که برای Distribution.files لازم است) یا پیادهسازی Distribution.files را بازنویسی کند. برای ایدههای بیشتر، منبع را ببینید.