"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" é uma biblioteca que fornece acesso aos metadados
de um Pacote de Distribuição instalado, como seus pontos de entrada ou
seus nomes de nível superior (Pacotes de Importação, módulos, se
houver). Construída em parte no sistema de importação do Python, esta
biblioteca pretende substituir funcionalidades semelhantes na API de
ponto de entrada e API de metadados de "pkg_resources". Junto com
"importlib.resources", este pacote pode eliminar a necessidade de usar
o pacote "pkg_resources" mais antigo e menos eficiente.

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

Por padrão, os metadados de distribuição podem residir no sistema de
arquivos ou em arquivos zip em "sys.path". Através de um mecanismo de
extensão, os metadados podem residir em praticamente qualquer lugar.

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

Digamos que você queira obter a string da versão de um Pacote de
Distribuição que você instalou usando "pip". Começamos criando um
ambiente virtual e instalando algo nele:

   $ python -m venv example
   $ source example/bin/activate
   (example) $ 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'

Você também pode obter uma coleção de pontos de entrada selecionáveis
pelas propriedades do EntryPoint (normalmente 'group' ou 'name'), como
"console_scripts", "distutils.commands" e outros. Cada grupo contém
uma coleção de objetos EntryPoint.

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

   Subclass of "ModuleNotFoundError" raised by several functions in
   this module when queried for a distribution package which is not
   installed in the current Python environment.


API funcional
=============

Este pacote fornece a seguinte funcionalidade por meio de sua API
pública.


Pontos de entrada
-----------------

importlib.metadata.entry_points(**select_params)

   Returns a "EntryPoints" instance describing entry points for the
   current environment. Any given keyword parameters are passed to the
   "select()" method for comparison to the attributes of the
   individual entry point definitions.

   Note: it is not currently possible to query for entry points based
   on their "EntryPoint.dist" attribute (as different "Distribution"
   instances do not currently compare equal, even if they have the
   same attributes)

class importlib.metadata.EntryPoints

   Details of a collection of installed entry points.

   Also provides a ".groups" attribute that reports all identifed
   entry point groups, and a ".names" attribute that reports all
   identified entry point names.

class importlib.metadata.EntryPoint

   Details of an installed entry point.

   Each "EntryPoint" instance has ".name", ".group", and ".value"
   attributes and a ".load()" method to resolve the value. There are
   also ".module", ".attr", and ".extras" attributes for getting the
   components of the ".value" attribute, and ".dist" for obtaining
   information regarding the distribution package that provides the
   entry point.

Consultar todos os pontos de entrada:

   >>> eps = entry_points()  

The "entry_points()" function returns a "EntryPoints" object, a
collection of all "EntryPoint" objects with "names" and "groups"
attributes for convenience:

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

"EntryPoints" has a "select()" method to select entry points matching
specific properties. Select entry points in the "console_scripts"
group:

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

Equivalently, since "entry_points()" passes keyword arguments through
to select:

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

O "group" e "name" são valores arbitrários definidos pelo autor do
pacote e normalmente um cliente desejará resolver todos os pontos de
entrada para um grupo específico. Leia a documentação do setuptools
para obter mais informações sobre pontos de entrada, sua definição e
uso.

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)

   Return the distribution metadata corresponding to the named
   distribution package as a "PackageMetadata" instance.

   Raises "PackageNotFoundError" if the named distribution package is
   not installed in the current Python environment.

class importlib.metadata.PackageMetadata

   A concrete implementation of the
      PackageMetadata protocol.

      In addition to providing the defined protocol methods and
      attributes, subscripting the instance is equivalent to calling
      the "get()" method.

Every Distribution Package includes some metadata, which you can
extract using the "metadata()" function:

   >>> wheel_metadata = metadata('wheel')  

The keys of the returned data structure name the metadata keywords,
and the values are returned unparsed from the distribution metadata:

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

"PackageMetadata" also presents a "json" attribute that returns all
the metadata in a JSON-compatible form per **PEP 566**:

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

The full set of available metadata is not described here. See the PyPA
Core metadata specification for additional details.

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)

   Return the installed distribution package version for the named
   distribution package.

   Raises "PackageNotFoundError" if the named distribution package is
   not installed in the current Python environment.

The "version()" function is the quickest way to get a Distribution
Package's version number, as a string:

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


Arquivos de distribuição
------------------------

importlib.metadata.files(distribution_name)

   Return the full set of files contained within the named
   distribution package.

   Raises "PackageNotFoundError" if the named distribution package is
   not installed in the current Python environment.

   Returns "None" if the distribution is found but the installation
   database records reporting the files associated with the
   distribuion package are missing.

class importlib.metadata.PackagePath

   A "pathlib.PurePath" derived object with additional "dist", "size",
   and "hash" properties corresponding to the distribution package's
   installation metadata for that file.

The "files()" function takes a Distribution Package name and returns
all of the files installed by this distribution. Each file is reported
as a "PackagePath" instance. For example:

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

You can also use the "locate()" method to get the absolute path to the
file:

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

In the case where the metadata file listing files ("RECORD" or
"SOURCES.txt") is missing, "files()" will return "None". The caller
may wish to wrap calls to "files()" in always_iterable or otherwise
guard against this condition if the target distribution is not known
to have the metadata present.


Requisitos de distribuição
--------------------------

importlib.metadata.requires(distribution_name)

   Return the declared dependency specifiers for the named
   distribution package.

   Raises "PackageNotFoundError" if the named distribution package is
   not installed in the current Python environment.

To get the full set of requirements for a Distribution Package, use
the "requires()" function:

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


Mapeando importação pra pacotes de distribuição
-----------------------------------------------

importlib.metadata.packages_distributions()

   Return a mapping from the top level module and import package names
   found via "sys.meta_path" to the names of the distribution packages
   (if any) that provide the corresponding files.

   To allow for namespace packages (which may have members provided by
   multiple distribution packages), each top level import name maps to
   a list of distribution names rather than mapping directly to a
   single name.

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)

   Return a "Distribution" instance describing the named distribution
   package.

   Raises "PackageNotFoundError" if the named distribution package is
   not installed in the current Python environment.

class importlib.metadata.Distribution

   Details of an installed distribution package.

   Note: different "Distribution" instances do not currently compare
   equal, even if they relate to the same installed distribution and
   accordingly have the same attributes.

While the module level API described above is the most common and
convenient usage, you can get all of that information from the
"Distribution" class. "Distribution" is an abstract object that
represents the metadata for a Python Distribution Package. You can get
the concreate "Distribution" subclass instance for an installed
distribution package by calling the "distribution()" function:

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

Thus, an alternative way to get the version number is through the
"Distribution" instance:

   >>> dist.version  
   '0.32.3'

There are all kinds of additional metadata available on "Distribution"
instances:

   >>> 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:///path/to/wheel-0.32.3.editable-py3-none-any.whl'

The full set of available metadata is not described here. See the PyPA
Core metadata specification for additional details.

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.


Estendendo o algoritmo de pesquisa
==================================

Como os metadados de Pacote de Distribuição não estão disponíveis por
meio de pesquisas a "sys.path" ou carregadores de pacotes diretamente,
os metadados de uma distribuição são encontrados através de
localizadores do sistema de importação. Para encontrar os metadados de
um pacote de distribuição, "importlib.metadata" consulta a lista de
*localizadores de metacaminho* em "sys.meta_path".

Por padrão "importlib.metadata" instala um localizador para pacotes de
distribuição encontrados no sistema de arquivos. Na verdade, esse
localizador não encontra nenhuma *distribuição*, mas pode encontrar
seus metadados.

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()):
       """Return an iterable of all Distribution instances capable of
       loading the metadata for packages for the indicated ``context``.
       """

O objeto "DistributionFinder.Context" fornece propriedades ".path" e
".name" indicando o caminho para pesquisar e o nome a ser
correspondido e pode fornecer outro contexto relevante.

O que isso significa na prática é que para prover suporte à
localização de metadados de pacotes de distribuição em outros locais
fora do sistema de arquivos, crie uma subclasse "Distribution" e
implemente os métodos abstratos. Então, a partir de um localizador
personalizado, retorne instâncias deste derivado de "Distribution" no
método "find_distributions()".


Exemplo
-------

Considere, por exemplo, um localizador personalizado que carrega
módulos Python de um banco de dados:

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

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.
