importlib.metadata – Tillgång till metadata för paket

Tillagd i version 3.8.

Ändrad i version 3.10: importlib.metadata är inte längre provisorisk.

Källkod: Lib/importlib/metadata/__init__.py

importlib.metadata är ett bibliotek som ger tillgång till metadata för ett installerat Distribution Package, såsom dess ingångspunkter eller dess toppnivånamn (Import Packages, moduler, om sådana finns). Det här biblioteket bygger delvis på Pythons importsystem och avser att ersätta liknande funktionalitet i entry point API och metadata API i pkg_resources. Tillsammans med importlib.resources kan detta paket eliminera behovet av att använda det äldre och mindre effektiva paketet pkg_resources.

importlib.metadata fungerar på tredjeparts distributionspaket som installeras i Pythons site-packages-katalog via verktyg som pip. Specifikt fungerar det med distributioner med upptäckbara dist-info eller egg-info-kataloger och metadata som definieras av Core metadata specifications.

Viktigt

Dessa är inte nödvändigtvis likvärdiga med eller motsvarar 1:1 de importpaket-namn på toppnivå som kan importeras i Python-kod. Ett distributionspaket kan innehålla flera importpaket (och enstaka moduler), och ett importpaket på toppnivå kan mappa till flera distributionspaket om det är ett namnrymdspaket. Du kan använda packages_distributions() för att få en mappning mellan dem.

Som standard kan distributionsmetadata finnas i filsystemet eller i zip-arkiv på sys.path. Genom en tilläggsmekanism kan metadata lagras nästan var som helst.

Se även

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

Dokumentationen för importlib_metadata, som tillhandahåller en backport av importlib.metadata. Detta inkluderar en API-referens för den här modulens klasser och funktioner, samt en migreringsguide för befintliga användare av pkg_resources.

Översikt

Låt oss säga att du vill få fram versionssträngen för ett Distribution Package som du har installerat med hjälp av pip. Vi börjar med att skapa en virtuell miljö och installerar något i den:

$ python -m venv example
$ source example/bin/activate
(exempel) $ python -m pip install wheel

Du kan få fram versionssträngen för wheel genom att köra följande:

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

Du kan också få en samling ingångspunkter som kan väljas genom egenskaper hos EntryPoint (vanligtvis ’group’ eller ’name’), t.ex. console_scripts, distutils.commands och andra. Varje grupp innehåller en samling av EntryPoint-objekt.

Du kan få metadata för en 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']

Du kan också få en distributions versionsnummer, lista dess beståndsdelar och få en lista över distributionens Krav på distribution.

exception importlib.metadata.PackageNotFoundError

Underklass till ModuleNotFoundError som orsakas av flera funktioner i denna modul när de frågar efter ett distributionspaket som inte är installerat i den aktuella Python-miljön.

Funktionellt API

Detta paket tillhandahåller följande funktionalitet via sitt publika API.

Ingångspunkter

importlib.metadata.entry_points(**select_params)

Returnerar en EntryPoints-instans som beskriver entry points för den aktuella miljön. Eventuella nyckelordsparametrar skickas till select()-metoden för jämförelse med attributen i de enskilda entry point-definitionerna.

Obs: det är för närvarande inte möjligt att fråga efter entry points baserat på deras EntryPoint.dist-attribut (eftersom olika Distribution-instanser för närvarande inte jämförs lika, även om de har samma attribut)

class importlib.metadata.EntryPoints

Detaljer om en samling installerade inmatningspunkter.

Innehåller även attributet .groups som rapporterar alla identifierade grupper av entrépunkter och attributet .names som rapporterar alla identifierade namn på entrépunkter.

class importlib.metadata.EntryPoint

Detaljer om en installerad ingångspunkt.

Varje EntryPoint-instans har attributen .name, .group och .value och en .load()-metod för att lösa upp värdet. Det finns också attributen .module, .attr och .extras för att hämta komponenterna i attributet .value och .dist för att hämta information om det distributionspaket som tillhandahåller entrypointen.

Fråga alla inmatningspunkter:

>>> eps = entry_points()

Funktionen entry_points() returnerar ett EntryPoints-objekt, en samling av alla EntryPoint-objekt med attributen names och groups för enkelhetens skull:

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

EntryPoints har en select()-metod för att välja entrypunkter som matchar specifika egenskaper. Välj ingångspunkter i gruppen console_scripts:

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

På motsvarande sätt, eftersom entry_points() skickar nyckelordsargument vidare till select:

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

Välj ut ett specifikt skript som heter ”wheel” (finns i wheel-projektet):

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

På motsvarande sätt, fråga efter den ingångspunkten under urvalet:

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

Inspektera den lösta ingångspunkten:

>>> 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 och name är godtyckliga värden som definieras av paketets författare och vanligtvis kommer en klient att vilja lösa alla ingångspunkter för en viss grupp. Läs the setuptools docs för mer information om ingångspunkter, deras definition och användning.

Ändrad i version 3.12: De ”valbara” ingångspunkterna introducerades i importlib_metadata 3.6 och Python 3.10. Före dessa ändringar accepterade entry_points inga parametrar och returnerade alltid en ordbok med ingångspunkter, med grupp som nyckel. Med importlib_metadata 5.0 och Python 3.12 returnerar entry_points alltid ett EntryPoints-objekt. Se backports.entry_points_selectable för kompatibilitetsalternativ.

Ändrad i version 3.13: EntryPoint-objekt har inte längre ett tuple-liknande gränssnitt (__getitem__()).

Metadata för distribution

importlib.metadata.metadata(distribution_name)

Returnerar distributionsmetadata som motsvarar det angivna distributionspaketet som en instans av PackageMetadata.

Utlöser PackageNotFoundError om det angivna distributionspaketet inte är installerat i den aktuella Python-miljön.

class importlib.metadata.PackageMetadata

En konkret implementering av PackageMetadata-protokollet.

Förutom att tillhandahålla de definierade protokollmetoderna och -attributen, motsvarar subskription av instansen anrop av metoden get().

Varje Distributionspaket innehåller en del metadata, som du kan extrahera med hjälp av funktionen metadata():

>>> wheel_metadata = metadata('wheel')

Nycklarna i den returnerade datastrukturen namnger nyckelorden i metadata och värdena returneras oanalyserade från distributionsmetadata:

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

PackageMetadata har också ett json-attribut som returnerar alla metadata i JSON-kompatibel form enligt PEP 566:

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

Den fullständiga uppsättningen tillgängliga metadata beskrivs inte här. Se PyPA:s Core metadata specification för ytterligare detaljer.

Ändrad i version 3.10: Description ingår nu i metadata när den presenteras genom nyttolasten. Radernas fortsättningstecken har tagits bort.

Attributet json har lagts till.

Distribution av versioner

importlib.metadata.version(distribution_name)

Returnerar det installerade distributionspaketet version för det angivna distributionspaketet.

Utlöser PackageNotFoundError om det angivna distributionspaketet inte är installerat i den aktuella Python-miljön.

Funktionen version() är det snabbaste sättet att få ett Distribution Package:s versionsnummer, som en sträng:

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

Distributionsfiler

importlib.metadata.files(distribution_name)

Returnerar den fullständiga uppsättningen filer som ingår i det namngivna distributionspaketet.

Utlöser PackageNotFoundError om det angivna distributionspaketet inte är installerat i den aktuella Python-miljön.

Returnerar None om distributionen hittas men installationsdatabasen rapporterar att de filer som är associerade med distributionspaketet saknas.

class importlib.metadata.PackagePath

Ett pathlib.PurePath-deriverat objekt med ytterligare dist-, size- och hash-egenskaper som motsvarar distributionspaketets installationsmetadata för den filen.

Funktionen files() tar ett namn på Distribution Package och returnerar alla filer som installerats av denna distribution. Varje fil rapporteras som en instans av PackagePath. Till exempel:

>>> 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>

När du har filen kan du också läsa dess innehåll:

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

Du kan också använda metoden locate() för att få den absoluta sökvägen till filen:

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

I fallet där metadatafilen som listar filer (RECORD eller SOURCES.txt) saknas, kommer files() att returnera None. Den som anropar kan vilja linda in anrop till files() i always_iterable eller på annat sätt skydda sig mot detta villkor om det inte är känt att måldistributionen har metadata närvarande.

Krav på distribution

importlib.metadata.requires(distribution_name)

Returnerar de deklarerade beroendespecifikationerna för det namngivna distributionspaketet.

Utlöser PackageNotFoundError om det angivna distributionspaketet inte är installerat i den aktuella Python-miljön.

För att få den fullständiga uppsättningen krav för ett Distributionspaket, använd funktionen requires():

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

Mappning av import till distributionspaket

importlib.metadata.packages_distributions()

Returnerar en mappning från toppnivåmodulen och importpaketnamnen som hittas via sys.meta_path till namnen på distributionspaketen (om sådana finns) som tillhandahåller motsvarande filer.

För att möjliggöra namnrymdspaket (som kan ha medlemmar som tillhandahålls av flera distributionspaket) mappas varje importnamn på högsta nivån till en lista med distributionsnamn i stället för att mappas direkt till ett enda namn.

En bekvämlighetsmetod för att lösa Distribution Package-namnet (eller namnen, i fallet med ett namnrymdspaket) som tillhandahåller varje importerbar Python-modul på högsta nivån eller Import Package:

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

Vissa redigerbara installationer, levererar inte toppnamn, och därför är denna funktion inte tillförlitlig med sådana installationer.

Tillagd i version 3.10.

Utdelningar

importlib.metadata.distribution(distribution_name)

Returnerar en Distribution-instans som beskriver det namngivna distributionspaketet.

Utlöser PackageNotFoundError om det angivna distributionspaketet inte är installerat i den aktuella Python-miljön.

class importlib.metadata.Distribution

Detaljer om ett installerat distributionspaket.

Observera: olika Distribution-instanser jämförs för närvarande inte på samma sätt, även om de hänför sig till samma installerade distribution och därmed har samma attribut.

Även om API:et på modulnivå som beskrivs ovan är den vanligaste och mest praktiska användningen, kan du få all denna information från klassen Distribution. Distribution är ett abstrakt objekt som representerar metadata för ett Python Distribution Package. Du kan få den konkreta Distribution-underklassinstansen för ett installerat distributionspaket genom att anropa distribution()-funktionen:

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

Ett alternativt sätt att få versionsnumret är alltså genom Distribution-instansen:

>>> dist.version
'0.32.3'

Det finns alla typer av ytterligare metadata tillgängliga för Distribution-instanser:

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

För redigerbara paket kan en egenskap origin presentera PEP 610 metadata:

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

Den fullständiga uppsättningen tillgängliga metadata beskrivs inte här. Se PyPA:s Core metadata specification för ytterligare detaljer.

Tillagd i version 3.13: Egenskapen .origin har lagts till.

Distribution Discovery

Som standard ger det här paketet inbyggt stöd för sökning av metadata för filsystem och zip-filer Distribution Packages. Denna metadatasökning använder som standard sys.path, men skiljer sig något i hur den tolkar dessa värden från hur andra importmaskiner gör. I synnerhet:

  • importlib.metadata respekterar inte bytes-objekt på sys.path.

  • importlib.metadata kommer av en händelse att hedra pathlib.Path-objekt på sys.path även om sådana värden kommer att ignoreras för import.

Implementering av anpassade providers

importlib.metadata adresserar två API-ytor, en för konsumenter och en annan för leverantörer. De flesta användare är konsumenter och konsumerar metadata som tillhandahålls av paketen. Det finns dock andra användningsfall där användare vill exponera metadata genom någon annan mekanism, t.ex. tillsammans med en anpassad importör. Ett sådant användningsfall kräver en custom provider.

Eftersom metadata för Distributionspaket inte är tillgängliga via sys.path-sökningar eller paketladdare direkt, hittas metadata för en distribution via importsystemets finders. För att hitta metadata för ett distributionspaket frågar importlib.metadata listan över meta path finderssys.meta_path.

Implementeringen har hooks integrerade i PathFinder, som serverar metadata för distributionspaket som finns i filsystemet.

Den abstrakta klassen importlib.abc.MetaPathFinder definierar det gränssnitt som Pythons importsystem förväntar sig av sökare. importlib.metadata utökar detta protokoll genom att leta efter en valfri find_distributions anropbar på sökarna från sys.meta_path och presenterar detta utökade gränssnitt som den abstrakta basklassen DistributionFinder, som definierar denna abstrakta metod:

@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``.
    """

Objektet DistributionFinder.Context tillhandahåller egenskaperna .path och .name som anger sökvägen som ska sökas och namnet som ska matchas och kan tillhandahålla annan relevant kontext som efterfrågas av konsumenten.

I praktiken, för att stödja sökning av metadata för distributionspaket på andra platser än filsystemet, kan du underordna dig Distribution och implementera de abstrakta metoderna. Sedan från en anpassad sökare, returnera instanser av denna härledda Distribution i metoden find_distributions().

Exempel

Tänk dig en anpassad sökare som laddar Python-moduler från en databas:

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(...)))

Den importören tillhandahåller nu antagligen importerbara moduler från en databas, men den tillhandahåller inga metadata eller ingångspunkter. För att denna anpassade importör ska kunna tillhandahålla metadata skulle den också behöva implementera 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)

På detta sätt skulle query_distributions returnera poster för varje distribution som betjänas av den databas som matchar frågan. Om till exempel requests-1.0 finns i databasen, skulle find_distributions ge en DatabaseDistribution för Context(name='requests') eller Context(name=None).

För enkelhetens skull ignorerar detta exempel context.path. Attributet path är som standard sys.path och är den uppsättning importvägar som ska beaktas i sökningen. En DatabaseImporter skulle potentiellt kunna fungera utan att behöva ta hänsyn till någon sökväg. Om importören inte gör någon partitionering skulle ”path” vara irrelevant. För att illustrera syftet med path skulle exemplet behöva illustrera en mer komplex DatabaseImporter vars beteende varierar beroende på sys.path/PYTHONPATH. I det fallet bör find_distributions respektera context.path och endast ge Distributions som är relevanta för den sökvägen.

DatabaseDistribution skulle då se ut ungefär som:

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")

Denna grundläggande implementering bör tillhandahålla metadata och ingångspunkter för paket som betjänas av DatabaseImporter, förutsatt att record tillhandahåller lämpliga attribut för .name, .version och .entry_points.

DatabaseDistribution kan också tillhandahålla andra metadatafiler, som RECORD (krävs för Distribution.files) eller åsidosätta implementeringen av Distribution.files. Se källan för mer inspiration.