"importlib.metadata" -- Acessando metadados do pacote
*****************************************************

Novo 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" operates on third-party *distribution packages*
installed into Python's "site-packages" directory via tools such as
pip. Specifically, it works with distributions with discoverable
"dist-info" or "egg-info" directories, and metadata defined by the
Core metadata specifications.

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 mapear para vários
  *pacotes de distribuição* se for um pacote de espaço de nomes. Você
  pode usar package_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.


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

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


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

A função "entry_points()" retorna uma coleção de pontos de entrada. Os
pontos de entrada são representados por instâncias "EntryPoint"; cada
"EntryPoint" tem atributos ".name", ".group" e ".value" e um método
".load()" para resolver o valor. Existem também atributos ".module",
".attr" e ".extras" para obter os componentes do atributo ".value".

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 que correspondam a propriedades específicas. Selecione pontos
de entrada no grupo "console_scripts":

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

Equivalentemente, já que "entry_points" passa argumento nomeado para
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>

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.

*Notas de compatibilidade*

The "selectable" entry points were introduced in "importlib_metadata"
3.6 and Python 3.10. Prior to those changes, "entry_points" accepted
no parameters and always returned a dictionary of entry points, keyed
by group. For compatibility, if no parameters are passed to
entry_points, a "SelectableGroups" object is returned, implementing
that dict interface. In the future, calling "entry_points" with no
parameters will return an "EntryPoints" object. Users should rely on
the selection interface to retrieve entry points by group.


Metadados de distribuição
-------------------------

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, um "PackageMetadata",
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.*'

Nota:

  O tipo real do objeto retornado por "metadata()" é um detalhe de
  implementação e deve ser acessado somente através da interface
  descrita pelo protocolo PackageMetadata.

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

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

Você também pode obter o conjunto completo de arquivos contidos em uma
distribuição. A função "files()" pega um nome Pacote de Distribuição e
retorna todos os arquivos instalados por este distribuição. Cada
objeto de arquivo retornado é um "PackagePath", um objeto derivado de
"pathlib.PurePath" com propriedades adicionais "dist", "size" e "hash"
conforme indicado pelos metadados. 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
--------------------------

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

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'], ...}

Novo na versão 3.10.


Distribuições
=============

Embora a API acima seja o uso mais comum e conveniente, você pode
obter todas essas informações na classe "Distribution". Uma
"Distribution" é um objeto abstrato que representa os metadados de um
Pacote de Distribuição do Python. Você pode obter a instância
"Distribution":

   >>> from importlib.metadata import distribution  
   >>> dist = distribution('wheel')  

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 na
instância "Distribution":

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

O conjunto completo de metadados disponíveis não é descrito aqui.
Consulte as Especificações de metadados principais para obter detalhes
adicionais.


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

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

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