importlib.metadata
– Acessando metadados do pacote¶
Adicionado na versão 3.8.
Alterado na versão 3.10: importlib.metadata
não é mais provisório.
Código-fonte: Lib/importlib/metadata/__init__.py
importlib.metadata
is a library that provides access to
the metadata of an installed Distribution Package,
such as its entry points
or its top-level names (Import Packages, modules, if any).
Built in part on Python’s import system, this library
intends to replace similar functionality in the entry point
API and metadata API of pkg_resources
. Along with
importlib.resources
,
this package can eliminate the need to use the older and less efficient
pkg_resources
package.
importlib.metadata
opera em pacotes de distribuição de terceiros instalados no diretório site-packages
do Python através de ferramentas como pip. Especificamente, ele funciona com distribuições com diretórios dist-info
ou egg-info
detectáveis, e metadados definidos pelas Especificações de metadados principais.
Importante
Eles não são necessariamente equivalentes ou correspondem 1:1 aos nomes de pacotes de importação de nível superior que podem ser importados dentro do código Python. Um pacote de distribuição pode conter vários pacotes de importação (e módulos únicos), e um pacote de importação de nível superior pode ser mapeado para vários pacotes de distribuição se for um pacote de espaço de nomes. Você pode usar packages_distributions() para obter um mapeamento entre eles.
By default, distribution metadata can live on the file system
or in zip archives on
sys.path
. Through an extension mechanism, the metadata can live almost
anywhere.
Ver também
- https://importlib-metadata.readthedocs.io/
A documentação para
importlib_metadata
, que fornece um backport deimportlib.metadata
. Isso inclui uma referência de API para as classes e funções deste módulo, bem como um guia de migração para usuários existentes depkg_resources
.
Visão Geral¶
Let’s say you wanted to get the version string for a
Distribution Package you’ve installed
using pip
. We start by creating a virtual environment and installing
something into it:
$ python -m venv exemplo
$ source exemplo/bin/activate
(exemplo) $ python -m pip install wheel
Você pode obter a string de versão para wheel
executando o seguinte:
(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'0.32.3'
You can also get a collection of entry points selectable by properties of the EntryPoint (typically ‘group’ or ‘name’), such as
console_scripts
, distutils.commands
and others. Each group contains a
collection of EntryPoint objects.
Você pode obter os metadados para uma distribuição:
>>> 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']
Você também pode obter uma número da versão da distribuição, listar seus arquivos constituintes e obter uma lista dos Requisitos de distribuição da distribuição.
- exception importlib.metadata.PackageNotFoundError¶
Subclasse de
ModuleNotFoundError
levantada por diversas funções neste módulo quando consultada sobre um pacote de distribuição que não está instalado no ambiente Python atual.
API funcional¶
Este pacote fornece a seguinte funcionalidade por meio de sua API pública.
Pontos de entrada¶
- importlib.metadata.entry_points(**select_params)¶
Retorna uma instância de
EntryPoints
descrevendo pontos de entrada para o ambiente atual. Quaisquer parâmetros nomeados fornecidos são passados para o métodoselect()
para comparação com os atributos das definições de pontos de entrada individuais.Observação: atualmente não é possível consultar pontos de entrada com base no atributo
EntryPoint.dist
(já que instâncias diferentes deDistribution
não são comparadas como iguais, mesmo que tenham os mesmos atributos)
- class importlib.metadata.EntryPoints¶
Detalhes de uma coleção de pontos de entrada instalados.
Também fornece um atributo
.groups
que relata todos os grupos de pontos de entrada identificados e um atributo.names
que relata todos os nomes de pontos de entrada identificados.
- class importlib.metadata.EntryPoint¶
Detalhes de um ponto de entrada instalado.
Cada instância de
EntryPoint
tem os atributos.name
,.group
e.value
e um método.load()
para resolver o valor. Há também os atributos.module
,.attr
e.extras
para obter os componentes do atributo.value
e.dist
para obter informações sobre o pacote de distribuição que fornece o ponto de entrada.
Consultar todos os pontos de entrada:
>>> eps = entry_points()
A função entry_points()
retorna um objeto EntryPoints
, uma coleção de todos os objetos EntryPoint
com atributos names
e groups
por conveniência:
>>> sorted(eps.groups)
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
EntryPoints
possui um método select()
para selecionar pontos de entrada correspondendo a propriedades específicas. Selecione pontos de entrada no grupo console_scripts
:
>>> scripts = eps.select(group='console_scripts')
Equivalentemente, já que entry_points()
passa argumentos nomeados para a seleção:
>>> scripts = entry_points(group='console_scripts')
Escolha um script específico chamado “wheel” (encontrado no projeto wheel):
>>> 'wheel' in scripts.names
True
>>> wheel = scripts['wheel']
De forma equivalente, consulte esse ponto de entrada durante a seleção:
>>> (wheel,) = entry_points(group='console_scripts', name='wheel')
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')
Inspecione o ponto de entrada resolvido:
>>> 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>
The group
and name
are arbitrary values defined by the package author
and usually a client will wish to resolve all entry points for a particular
group. Read the setuptools docs
for more information on entry points, their definition, and usage.
Alterado na versão 3.12: Os pontos de entrada “selecionáveis” foram introduzidos em importlib_metadata
3.6 e Python 3.10. Antes dessas mudanças, entry_points
não aceitava parâmetros e sempre retornava um dicionário de pontos de entrada, chaveado por grupo. Com importlib_metadata
5.0 e Python 3.12, entry_points
sempre retorna um objeto EntryPoints
. Veja backports.entry_points_selectable para opções de compatibilidade.
Alterado na versão 3.13: Objetos EntryPoint
não apresentam mais uma interface tupla ou similar (__getitem__()
).
Metadados de distribuição¶
- importlib.metadata.metadata(distribution_name)¶
Retorna os metadados de distribuição correspondentes ao pacote de distribuição nomeado como uma instância de
PackageMetadata
.Levanta
PackageNotFoundError
se o pacote de distribuição nomeado não estiver instalado no ambiente Python atual.
- class importlib.metadata.PackageMetadata¶
Uma implementação concreta do protocolo PackageMetadata.
Além de fornecer os métodos e atributos de protocolo definidos, subscrever a instância é equivalente a chamar o método
get()
.
Cada Pacote de Distribuição inclui alguns metadados, que você pode extrair usando a função metadata()
:
>>> wheel_metadata = metadata('wheel')
As chaves da estrutura de dados retornada nomeiam as palavras reservadas dos metadados, e os valores são retornados sem análise dos metadados de distribuição:
>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
PackageMetadata
também apresenta um atributo json
que retorna todos os metadados em um formato compatível com JSON pela PEP 566:
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
O conjunto completo de metadados disponíveis não é descrito aqui. Consulte a especificação de metadados principais do PyPA para obter detalhes adicionais.
Alterado na versão 3.10: A Description
agora é incluída nos metadados quando apresentada através do payload. Os caracteres de continuação de linha foram removidos.
O atributo json
foi adicionado.
Versões de distribuição¶
- importlib.metadata.version(distribution_name)¶
Retorna a versão do pacote de distribuição instalado para o pacote de distribuição nomeado.
Levanta
PackageNotFoundError
se o pacote de distribuição nomeado não estiver instalado no ambiente Python atual.
A função version()
é a maneira mais rápida de obter o número de versão de um Pacote de Distribuição, como uma string:
>>> version('wheel')
'0.32.3'
Arquivos de distribuição¶
- importlib.metadata.files(distribution_name)¶
Retorna o conjunto completo de arquivos contidos no pacote de distribuição nomeado.
Levanta
PackageNotFoundError
se o pacote de distribuição nomeado não estiver instalado no ambiente Python atual.Retorna
None
se a distribuição for encontrada, mas os registros do banco de dados de instalação que relatam os arquivos associados ao pacote de distribuição estiverem ausentes.
- class importlib.metadata.PackagePath¶
Um objeto derivado de
pathlib.PurePath
com propriedades adicionaisdist
,size
ehash
correspondentes aos metadados de instalação do pacote de distribuição para esse arquivo.
A função files()
pega um nome Pacote de Distribuição e retorna todos os arquivos instalados por este distribuição. Cada arquivo é é relatado como uma instância de PackagePath
. Por exemplo:
>>> 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>
Uma vez que tenha o arquivo, você também pode ler seu conteúdo:
>>> print(util.read_text())
import base64
import sys
...
def as_bytes(s):
if isinstance(s, text_type):
return s.encode('utf-8')
return s
Você também pode usar o método locate()
para obter o caminho absoluto para o arquivo:
>>> util.locate()
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
No caso em que o arquivo de metadados que lista os arquivos (RECORD
ou SOURCES.txt
) estiver faltando, files()
retornará None
. O chamador pode querer agrupar chamadas para files()
em always_iterable ou de outra forma se proteger contra isso condição se a distribuição de destino não for conhecida por ter os metadados presentes.
Requisitos de distribuição¶
- importlib.metadata.requires(distribution_name)¶
Retorna os especificadores de dependência declarados para o pacote de distribuição nomeado.
Levanta
PackageNotFoundError
se o pacote de distribuição nomeado não estiver instalado no ambiente Python atual.
Para obter o conjunto completo de requisitos para um Pacote de Distribuição, use a função requires()
:
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
Mapeando importação pra pacotes de distribuição¶
- importlib.metadata.packages_distributions()¶
Retorna um mapeamento do módulo de nível superior e importa os nomes dos pacotes encontrados via
sys.meta_path
para os nomes dos pacotes de distribuição (se houver) que fornecem os arquivos correspondentes.Para permitir pacotes de espaço de nomes (que podem ter membros fornecidos por vários pacotes de distribuição), cada nome de importação de nível superior é mapeado para uma lista de nomes de distribuição em vez de mapear diretamente para um único nome.
Um método conveniente para resolver o nome do Pacote de Distribuição (ou nomes, no caso de um pacote de espaço de nomes) que fornece cada módulo Python de nível superior importável ou Pacote de Importação:
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
Algumas instalações editáveis não fornecem nomes de nível superior e, portanto, esta função não é confiável com tais instalações.
Adicionado na versão 3.10.
Distribuições¶
- importlib.metadata.distribution(distribution_name)¶
Retorna uma instância de
Distribution
descrevendo o pacote de distribuição nomeado.Levanta
PackageNotFoundError
se o pacote de distribuição nomeado não estiver instalado no ambiente Python atual.
- class importlib.metadata.Distribution¶
Detalhes de um pacote de distribuição instalado.
Observação: instâncias diferentes de
Distribution
não são atualmente consideradas iguais em uma comparação, mesmo que estejam relacionadas à mesma distribuição instalada e, portanto, tenham os mesmos atributos.
Embora a API de nível de módulo descrita acima seja o uso mais comum e conveniente, você pode obter todas essas informações da classe Distribution
. Distribution
é um objeto abstrato que representa os metadados para um Pacote de Distribuição Python. Você pode obter a instância concreta de subclasse de Distribution
para um pacote de distribuição instalado chamando a função distribution()
:
>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
Assim, uma forma alternativa de obter o número da versão é através da instância Distribution
:
>>> dist.version
'0.32.3'
Existem todos os tipos de metadados adicionais disponíveis em instâncias de Distribution
:
>>> dist.metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> dist.metadata['License']
'MIT'
Para pacotes editáveis, uma propriedade origin
pode apresentar metadados conforme a PEP 610:
>>> dist.origin.url
'file:///caminho/para/wheel-0.32.3.editable-py3-none-any.whl'
O conjunto completo de metadados disponíveis não é descrito aqui. Consulte a especificação de metadados principais do PyPA para obter detalhes adicionais.
Adicionado na versão 3.13: A propriedade .origin
foi adicionada.
Descoberta de distribuição¶
Por padrão, este pacote fornece suporte embutido para descoberta de metadados para sistema de arquivos e arquivo zip Pacotes de Distribuição. Esta pesquisa do localizador de metadados tem como padrão sys.path
, mas varia um pouco na maneira como ela interpreta esses valores em relação a outras mecanismo de importação. Em particular:
importlib.metadata
não honra objetosbytes
emsys.path
.importlib.metadata
irá incidentalmente honrar objetospathlib.Path
emsys.path
mesmo que tais valores sejam ignorados para importações.
Implementing Custom Providers¶
importlib.metadata
address two API surfaces, one for consumers
and another for providers. Most users are consumers, consuming
metadata provided by the packages. There are other use-cases, however,
where users wish to expose metadata through some other mechanism,
such as alongside a custom importer. Such a use case calls for a
custom provider.
Because Distribution Package metadata
is not available through sys.path
searches, or
package loaders directly,
the metadata for a distribution is found through import
system finders. To find a distribution package’s metadata,
importlib.metadata
queries the list of meta path finders on
sys.meta_path
.
The implementation has hooks integrated into the PathFinder
,
serving metadata for distribution packages found on the file system.
A classe abstrata importlib.abc.MetaPathFinder
define a interface esperada dos localizadores pelo sistema de importação do Python. importlib.metadata
estende este protocolo procurando por um chamável find_distributions
opcional nos localizadores de sys.meta_path
e apresenta esta interface estendida como a classe base abstrata DistributionFinder
, que define este método abstrato:
@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``.
"""
The DistributionFinder.Context
object provides .path
and .name
properties indicating the path to search and name to match and may
supply other relevant context sought by the consumer.
In practice, to support finding distribution package
metadata in locations other than the file system, subclass
Distribution
and implement the abstract methods. Then from
a custom finder, return instances of this derived Distribution
in the
find_distributions()
method.
Exemplo¶
Imagine a custom finder that loads Python modules from a database:
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(...)))
Esse importador agora provavelmente fornece módulos importáveis de um banco de dados, mas não fornece metadados ou pontos de entrada. Para que este importador personalizado forneça metadados, ele também precisaria implementar 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)
Desta forma, query_distributions
retornaria registros para cada distribuição servida pelo banco de dados correspondente à consulta. Por exemplo, se requests-1.0
estiver no banco de dados, find_distributions
produziria um DatabaseDistribution
para Context(name='requests')
ou Context(name=None)
.
Por uma questão de simplicidade, este exemplo ignora context.path
. O atributo path
tem como padrão sys.path
e é o conjunto de caminhos de importação a serem considerados na pesquisa. Um DatabaseImporter
poderia funcionar potencialmente sem qualquer preocupação com um caminho de pesquisa. Supondo que o importador não faça particionamento, o caminho “path” seria irrelevante. Para ilustrar o propósito de path
, o exemplo precisaria ilustrar um DatabaseImporter
mais complexo cujo comportamento variasse dependendo de sys.path
/PYTHONPATH
. Nesse caso, find_distributions
deve respeitar o context.path
e produzir apenas Distribution
s pertinentes a esse caminho.
DatabaseDistribution
, então, se pareceria com algo como:
class DatabaseDistribution(importlib.metadata.Distribution):
def __init__(self, record):
self.record = record
def read_text(self, filename):
"""
Lê um arquivo como "METADATA" para a atual distribuição.
"""
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")
Esta implementação básica deve fornecer metadados e pontos de entrada para pacotes servidos pelo DatabaseImporter
, presumindo que o record
forneça os atributos .name
, .version
e .entry_points
adequados.
O DatabaseDistribution
também pode fornecer outros arquivos de metadados, como RECORD
(necessário para Distribution.files
) ou substituir a implementação de Distribution.files
. Veja o código-fonte para mais inspiração.