# `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.

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

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

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

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

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'

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'

O conjunto completo de metadados disponíveis não é descrito aqui. Consulte as Especificações de metadados principais 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.

## 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.Distributon):
        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.