"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
     de "importlib.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 de "pkg_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étodo "select()" 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 de "Distribution" 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
   adicionais "dist", "size" e "hash" 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 objetos "bytes" em "sys.path".

* "importlib.metadata" irá incidentalmente honrar objetos
  "pathlib.Path" em "sys.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.
