importlib.metadata – Zugriff auf Paketmetadaten¶
Hinzugefügt in Version 3.8.
Geändert in Version 3.10: importlib.metadata ist nicht mehr provisorisch.
Quellcode: Lib/importlib/metadata/__init__.py
Das Modul importlib.metadata stellt Zugriff auf die Metadaten eines installierten Distribution Package bereit, wie z.B. dessen Einstiegspunkte oder dessen Top-Level-Namen (Import Packages, Module, falls vorhanden). Dieses Modul, das teilweise auf dem Import-System von Python aufbaut, soll die ähnliche Funktionalität in der Entry Point API und der Metadaten-API von pkg_resources ersetzen. Zusammen mit importlib.resources kann dieses Paket die Notwendigkeit der Verwendung des älteren und weniger effizienten Pakets pkg_resources eliminieren.
importlib.metadata arbeitet mit externen Distribution Packages, die in das site-packages Verzeichnis von Python über Werkzeuge wie pip installiert wurden. Speziell arbeitet es mit Distributionen mit auffindbaren dist-info oder egg-info Verzeichnissen und Metadaten, die durch die Core Metadata Spezifikationen definiert sind.
Wichtig
Dies ist nicht notwendigerweise äquivalent zu oder entspricht 1:1 den Top-Level-Namen von Import Packages, die innerhalb von Python-Code importiert werden können. Ein Distribution Package kann mehrere Import Packages (und einzelne Module) enthalten, und ein Top-Level-Import Package kann mehreren Distribution Packages zugeordnet sein, wenn es sich um ein Namespace-Paket handelt. Sie können packages_distributions() verwenden, um eine Zuordnung zwischen ihnen zu erhalten.
Standardmäßig können Distributionsmetadaten im Dateisystem oder in Zip-Archiven in sys.path liegen. Durch einen Erweiterungsmechanismus können die Metadaten fast überall liegen.
Siehe auch
- https://importlib-metadata.readthedocs.io/
Die Dokumentation für
importlib_metadata, das einen Backport vonimportlib.metadatabereitstellt. Dies beinhaltet eine API-Referenz für die Klassen und Funktionen dieses Moduls sowie eine Migrationsanleitung für bestehende Benutzer vonpkg_resources.
Übersicht¶
Nehmen wir an, Sie möchten die Versionszeichenkette eines Distribution Package abrufen, das Sie mit pip installiert haben. Wir beginnen mit der Erstellung einer virtuellen Umgebung und der Installation von etwas darin
$ python -m venv example
$ source example/bin/activate
(example) $ python -m pip install wheel
Sie können die Versionszeichenkette für wheel erhalten, indem Sie Folgendes ausführen:
(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'0.32.3'
Sie können auch eine Sammlung von Einstiegspunkten erhalten, die nach Eigenschaften von EntryPoint (typischerweise 'group' oder 'name') auswählbar sind, wie z.B. console_scripts, distutils.commands und andere. Jede Gruppe enthält eine Sammlung von EntryPoint Objekten.
Sie können die Metadaten für eine Distribution abrufen
>>> 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']
Sie können auch die Versionsnummer einer Distribution abrufen, ihre zugehörigen Dateien auflisten und eine Liste der Anforderungen der Distribution erhalten.
- exception importlib.metadata.PackageNotFoundError¶
Unterklasse von
ModuleNotFoundError, die von mehreren Funktionen in diesem Modul ausgelöst wird, wenn nach einem Distribution Package abgefragt wird, das in der aktuellen Python-Umgebung nicht installiert ist.
Funktionale API¶
Dieses Paket bietet die folgende Funktionalität über seine öffentliche API.
Einstiegspunkte¶
- importlib.metadata.entry_points(**select_params)¶
Gibt eine
EntryPoints-Instanz zurück, die Einstiegspunkte für die aktuelle Umgebung beschreibt. Alle angegebenen Schlüsselwortparameter werden an dieselect()-Methode übergeben, um sie mit den Attributen der einzelnen Einstiegspunktdefinitionen zu vergleichen.Hinweis: Es ist derzeit nicht möglich, nach Einstiegspunkten anhand ihres
EntryPoint.distAttributs abzufragen (da verschiedeneDistribution-Instanzen derzeit nicht als gleich verglichen werden, auch wenn sie dieselben Attribute haben).
- class importlib.metadata.EntryPoints¶
Details zu einer Sammlung von installierten Einstiegspunkten.
Bietet außerdem ein Attribut
.groups, das alle identifizierten Einstiegspunktgruppen meldet, und ein Attribut.names, das alle identifizierten Einstiegspunknamen meldet.
- class importlib.metadata.EntryPoint¶
Details zu einem installierten Einstiegspunkt.
Jede
EntryPoint-Instanz hat die Attribute.name,.groupund.valuesowie eine Methode.load(), um den Wert aufzulösen. Es gibt auch die Attribute.module,.attrund.extras, um die Komponenten des.valueAttributs zu erhalten, und.dist, um Informationen über das Distribution Package zu erhalten, das den Einstiegspunkt bereitstellt.
Alle Einstiegspunkte abfragen
>>> eps = entry_points()
Die Funktion entry_points() gibt ein EntryPoints-Objekt zurück, eine Sammlung aller EntryPoint-Objekte mit den Attributen names und groups zur Bequemlichkeit.
>>> sorted(eps.groups)
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
EntryPoints hat eine Methode select(), um Einstiegspunkte auszuwählen, die bestimmten Eigenschaften entsprechen. Wähle Einstiegspunkte in der Gruppe console_scripts aus.
>>> scripts = eps.select(group='console_scripts')
Äquivalent, da entry_points() Schlüsselwortargumente an select weitergibt:
>>> scripts = entry_points(group='console_scripts')
Wähle ein bestimmtes Skript namens "wheel" (gefunden im wheel-Projekt) aus.
>>> 'wheel' in scripts.names
True
>>> wheel = scripts['wheel']
Äquivalent, frage diesen Einstiegspunkt während der Auswahl ab:
>>> (wheel,) = entry_points(group='console_scripts', name='wheel')
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')
Inspiziere den aufgelösten Einstiegspunkt:
>>> 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>
Die Werte group und name sind beliebige Werte, die vom Paketautor definiert wurden, und normalerweise möchte ein Client alle Einstiegspunkte für eine bestimmte Gruppe auflösen. Lesen Sie die Dokumentation von setuptools für weitere Informationen über Einstiegspunkte, deren Definition und Verwendung.
Geändert in Version 3.12: Die "auswählbaren" Einstiegspunkte wurden in importlib_metadata 3.6 und Python 3.10 eingeführt. Vor diesen Änderungen akzeptierte entry_points keine Parameter und gab immer ein Wörterbuch von Einstiegspunkten zurück, das nach Gruppen indiziert war. Mit importlib_metadata 5.0 und Python 3.12 gibt entry_points immer ein EntryPoints-Objekt zurück. Siehe backports.entry_points_selectable für Kompatibilitätsoptionen.
Geändert in Version 3.13: EntryPoint-Objekte stellen keine tupelähnliche Schnittstelle mehr dar (__getitem__()).
Distributionsmetadaten¶
- importlib.metadata.metadata(distribution_name)¶
Gibt die Distributionsmetadaten für das benannte Distribution Package als
PackageMetadata-Instanz zurück.Löst
PackageNotFoundErroraus, wenn das benannte Distribution Package nicht in der aktuellen Python-Umgebung installiert ist.
- class importlib.metadata.PackageMetadata¶
Eine konkrete Implementierung des PackageMetadata-Protokolls.
Zusätzlich zur Bereitstellung der definierten Protokollmethoden und Attribute ist das Subscripting der Instanz äquivalent zum Aufruf der Methode
get().
Jedes Distribution Package enthält einige Metadaten, die Sie mit der Funktion metadata() extrahieren können.
>>> wheel_metadata = metadata('wheel')
Die Schlüssel der zurückgegebenen Datenstruktur benennen die Metadaten-Schlüsselwörter, und die Werte werden unverarbeitet aus den Distributionsmetadaten zurückgegeben.
>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
PackageMetadata stellt auch ein Attribut json zur Verfügung, das alle Metadaten in einem JSON-kompatiblen Format gemäß PEP 566 zurückgibt.
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
Der vollständige Satz verfügbarer Metadaten wird hier nicht beschrieben. Siehe die Core Metadata Spezifikation der PyPA für weitere Details.
Geändert in Version 3.10: Die Description ist nun in den Metadaten enthalten, wenn sie über die Payload präsentiert wird. Zeilenfortsetzungszeichen wurden entfernt.
Das Attribut json wurde hinzugefügt.
Distributionsversionen¶
- importlib.metadata.version(distribution_name)¶
Gibt die installierte Version des Distribution Package für das benannte Distribution Package zurück.
Löst
PackageNotFoundErroraus, wenn das benannte Distribution Package nicht in der aktuellen Python-Umgebung installiert ist.
Die Funktion version() ist der schnellste Weg, um die Versionsnummer eines Distribution Package als Zeichenkette zu erhalten.
>>> version('wheel')
'0.32.3'
Distributionsdateien¶
- importlib.metadata.files(distribution_name)¶
Gibt die vollständige Menge der Dateien zurück, die im benannten Distribution Package enthalten sind.
Löst
PackageNotFoundErroraus, wenn das benannte Distribution Package nicht in der aktuellen Python-Umgebung installiert ist.Gibt
Nonezurück, wenn die Distribution gefunden wird, aber die Installationsdatenbank, die die der Distribution zugeordneten Dateien meldet, fehlt.
- class importlib.metadata.PackagePath¶
Ein von
pathlib.PurePathabgeleitetes Objekt mit zusätzlichen Eigenschaftendist,sizeundhash, die den Installationsmetadaten des Distribution Package für diese Datei entsprechen.
Die Funktion files() nimmt einen Namen eines Distribution Package und gibt alle von dieser Distribution installierten Dateien zurück. Jede Datei wird als PackagePath-Instanz gemeldet. Zum Beispiel:
>>> 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>
Sobald Sie die Datei haben, können Sie auch deren Inhalt lesen:
>>> print(util.read_text())
import base64
import sys
...
def as_bytes(s):
if isinstance(s, text_type):
return s.encode('utf-8')
return s
Sie können auch die Methode locate() verwenden, um den absoluten Pfad zur Datei zu erhalten:
>>> util.locate()
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
Wenn die Metadatendatei, die die Dateien auflistet (RECORD oder SOURCES.txt), fehlt, gibt files() None zurück. Der Aufrufer möchte möglicherweise Aufrufe von files() in always_iterable einwickeln oder diese Bedingung anderweitig absichern, wenn das Ziel-Distribution nicht bekannt dafür ist, die Metadaten vorhanden zu haben.
Distributionsanforderungen¶
- importlib.metadata.requires(distribution_name)¶
Gibt die deklarierten Abhängigkeitsspezifizierer für das benannte Distribution Package zurück.
Löst
PackageNotFoundErroraus, wenn das benannte Distribution Package nicht in der aktuellen Python-Umgebung installiert ist.
Um die vollständige Menge der Anforderungen für ein Distribution Package zu erhalten, verwenden Sie die Funktion requires().
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
Zuordnung von Import zu Distribution Packages¶
- importlib.metadata.packages_distributions()¶
Gibt eine Zuordnung von den Top-Level-Modul- und Import-Package-Namen, die über
sys.meta_pathgefunden werden, zu den Namen der Distribution Packages (falls vorhanden) zurück, die die entsprechenden Dateien bereitstellen.Um Namespace-Pakete (die Mitglieder haben können, die von mehreren Distribution Packages bereitgestellt werden) zu berücksichtigen, wird jeder Top-Level-Importname einer Liste von Distributionsnamen zugeordnet, anstatt direkt einem einzelnen Namen zugeordnet zu werden.
Eine praktische Methode, um den Namen (oder Namen, im Fall eines Namespace-Pakets) des Distribution Package aufzulösen, das jedes importierbare Top-Level-Python-Modul oder Import Package bereitstellt.
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
Einige bearbeitbare Installationen liefern keine Top-Level-Namen und daher ist diese Funktion bei solchen Installationen nicht zuverlässig.
Hinzugefügt in Version 3.10.
Distributionen¶
- importlib.metadata.distribution(distribution_name)¶
Gibt eine
Distribution-Instanz zurück, die das benannte Distribution Package beschreibt.Löst
PackageNotFoundErroraus, wenn das benannte Distribution Package nicht in der aktuellen Python-Umgebung installiert ist.
- class importlib.metadata.Distribution¶
Details zu einem installierten Distribution Package.
Hinweis: Verschiedene
Distribution-Instanzen werden derzeit nicht als gleich verglichen, auch wenn sie sich auf dasselbe installierte Distribution beziehen und dementsprechend dieselben Attribute haben.
Obwohl die Modul-API, die oben beschrieben wurde, die gebräuchlichste und bequemste Verwendung ist, können Sie all diese Informationen von der Klasse Distribution erhalten. Distribution ist ein abstraktes Objekt, das die Metadaten für ein Python Distribution Package repräsentiert. Sie können die konkrete Distribution-Unterklasseninstanz für ein installiertes Distribution Package erhalten, indem Sie die Funktion distribution() aufrufen.
>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
Daher ist eine alternative Möglichkeit, die Versionsnummer zu erhalten, über die Distribution-Instanz:
>>> dist.version
'0.32.3'
Es gibt alle Arten von zusätzlichen Metadaten, die auf Distribution-Instanzen verfügbar sind:
>>> dist.metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> dist.metadata['License']
'MIT'
Für bearbeitbare Pakete kann eine origin-Eigenschaft PEP 610-Metadaten bereitstellen.
>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
Der vollständige Satz verfügbarer Metadaten wird hier nicht beschrieben. Siehe die Core Metadata Spezifikation der PyPA für weitere Details.
Hinzugefügt in Version 3.13: Die Eigenschaft .origin wurde hinzugefügt.
Discovery von Distributionen¶
Standardmäßig bietet dieses Paket integrierte Unterstützung für die Ermittlung von Metadaten für Distribution Packages im Dateisystem und in Zip-Dateien. Diese Metadatensuche durchsucht standardmäßig sys.path, weicht aber geringfügig davon ab, wie diese Werte interpretiert werden, verglichen mit anderen Import-Mechanismen. Insbesondere:
importlib.metadatabeachtet keinebytes-Objekte insys.path.importlib.metadatabeachtet zufälligpathlib.Path-Objekte insys.path, auch wenn solche Werte für Imports ignoriert werden.
Implementierung benutzerdefinierter Anbieter¶
importlib.metadata adressiert zwei API-Oberflächen, eine für Konsumenten und eine für Anbieter. Die meisten Benutzer sind Konsumenten, die von den Paketen bereitgestellte Metadaten konsumieren. Es gibt jedoch andere Anwendungsfälle, in denen Benutzer Metadaten über einen anderen Mechanismus bereitstellen möchten, z.B. neben einem benutzerdefinierten Importer. Ein solcher Anwendungsfall erfordert einen benutzerdefinierten Anbieter.
Da die Metadaten von Distribution Packages nicht über sys.path-Suchen oder direkt über Paketlader verfügbar sind, werden die Metadaten einer Distribution über Finder des Importsystems gefunden. Um die Metadaten eines Distribution Package zu finden, fragt importlib.metadata die Liste der Meta-Path-Finder in sys.meta_path ab.
Die Implementierung hat Hooks, die in den PathFinder integriert sind und Metadaten für Distribution Packages liefern, die im Dateisystem gefunden wurden.
Die abstrakte Klasse importlib.abc.MetaPathFinder definiert die Schnittstelle, die von Findern für das Python-Importsystem erwartet wird. importlib.metadata erweitert dieses Protokoll, indem es nach einem optionalen aufrufbaren Objekt find_distributions bei den Findern in sys.meta_path sucht und diese erweiterte Schnittstelle als abstrakte Basisklasse DistributionFinder präsentiert, die diese abstrakte Methode definiert.
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
"""Return an iterable of all Distribution instances capable of
loading the metadata for packages for the indicated ``context``.
"""
Das Objekt DistributionFinder.Context stellt die Eigenschaften .path und .name bereit, die den zu durchsuchenden Pfad und den abzugleichenden Namen angeben, und kann andere relevante Kontexte liefern, die vom Konsumenten gesucht werden.
In der Praxis müssen Sie, um die Ermittlung von Distribution Package-Metadaten an anderen Orten als dem Dateisystem zu unterstützen, von Distribution ableiten und die abstrakten Methoden implementieren. Von einem benutzerdefinierten Finder geben Sie dann Instanzen dieser abgeleiteten Distribution in der Methode find_distributions() zurück.
Beispiel¶
Stellen Sie sich einen benutzerdefinierten Finder vor, der Python-Module aus einer Datenbank lädt:
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(...)))
Dieser Importer stellt nun vermutlich importierbare Module aus einer Datenbank bereit, liefert aber keine Metadaten oder Einstiegspunkte. Damit dieser benutzerdefinierte Importer Metadaten bereitstellen kann, müsste er auch DistributionFinder implementieren:
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)
Auf diese Weise würde query_distributions Datensätze für jede von der Datenbank bereitgestellte Distribution zurückgeben, die der Abfrage entspricht. Wenn z.B. requests-1.0 in der Datenbank ist, würde find_distributions eine DatabaseDistribution für Context(name='requests') oder Context(name=None) liefern.
Der Einfachheit halber ignoriert dieses Beispiel context.path. Das Attribut path ist standardmäßig sys.path und ist die Menge der Importpfade, die bei der Suche berücksichtigt werden sollen. Ein DatabaseImporter könnte potenziell ohne Rücksicht auf einen Suchpfad funktionieren. Unter der Annahme, dass der Importer keine Partitionierung vornimmt, wäre der "Pfad" irrelevant. Um den Zweck von path zu veranschaulichen, müsste das Beispiel einen komplexeren DatabaseImporter veranschaulichen, dessen Verhalten je nach sys.path/PYTHONPATH variiert. In diesem Fall sollte find_distributions den context.path berücksichtigen und nur Distributions liefern, die für diesen Pfad relevant sind.
Die Klasse DatabaseDistribution würde dann etwa so aussehen:
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")
Diese grundlegende Implementierung sollte Metadaten und Einstiegspunkte für Pakete bereitstellen, die vom DatabaseImporter bedient werden, vorausgesetzt, der record liefert geeignete Attribute für .name, .version und .entry_points.
Die Klasse DatabaseDistribution kann auch andere Metadatendateien bereitstellen, wie z.B. RECORD (erforderlich für Distribution.files) oder die Implementierung von Distribution.files überschreiben. Sehen Sie sich die Quelle für weitere Inspiration an.