Empacotando pacotes de espaço de nomes#

Os pacotes de espaço de nomes permitem que você divida os subpacotes e módulos dentro de um único pacote em vários pacotes de distribuição (referidos como distribuições em este documento para evitar ambiguidade). Por exemplo, se você tiver a seguinte estrutura de pacote:

mynamespace/
    __init__.py
    subpackage_a/
        __init__.py
        ...
    subpackage_b/
        __init__.py
        ...
    module_b.py
pyproject.toml

E você usa este pacote em seu código assim:

from mynamespace import subpackage_a
from mynamespace import subpackage_b

Então você pode quebrar esses subpacotes em duas distribuições separadas:

mynamespace-subpackage-a/
    pyproject.toml
    src/
        mynamespace/
            subpackage_a/
                __init__.py

mynamespace-subpackage-b/
    pyproject.toml
    src/
        mynamespace/
            subpackage_b/
                __init__.py
            module_b.py

Cada subpacote pode agora ser instalado, usado e versionado separadamente.

Os pacotes de espaço de nomes podem ser úteis para uma grande coleção de pacotes vagamente relacionados (como um grande corpo de bibliotecas de cliente para vários produtos de uma única empresa). No entanto, os pacotes de espaço de nomes vêm com várias ressalvas e não são apropriados em todos os casos. Uma alternativa simples é usar um prefixo em todas as suas distribuições, como import mynamespace_subpackage_a (você pode até usar import mynamespace_subpackage_a as subpackage_a para manter o objeto de importação curto).

Criando um pacote de espaço de nomes#

Atualmente, existem duas abordagens diferentes para a criação de pacotes de espaço de nomes, das quais a última é desencorajado:

  1. Usar pacotes de espaço de nomes nativos. Este tipo de pacote de espaço de nomes é definido na PEP 420 e está disponível em Python 3.3 e posterior. Isso é recomendado se os pacotes em seu espaço de nomes precisarem apenas oferecer suporte ao Python 3 e à instalação via pip.

  2. Use pacotes de espaço de nomes legados. Isso compreende pacotes de espaço de nomes no estilo pkgutil e pacotes de espaço de nomes no estilo pkg_resources.

Pacotes se espaço de nomes nativos#

Python 3.3 adicionou pacotes de espaço de nomes implícitos a partir da PEP 420. Tudo o que é necessário para criar um pacote de espaço de nomes nativo é omitir __init__.py do diretório do pacote de espaço de nomes. Um exemplo de estrutura de arquivo (seguindo src-layout):

mynamespace-subpackage-a/
    pyproject.toml # AND/OR setup.py, setup.cfg
    src/
        mynamespace/ # namespace package
            # No __init__.py here.
            subpackage_a/
                # Regular import packages have an __init__.py.
                __init__.py
                module.py

É extremamente importante que toda distribuição que usa o pacote de espaço de nomes omita o __init__.py ou use um __init__.py no estilo pkgutil. Se alguma distribuição não o fizer, isso fará com que a lógica do espaço de nomes falhe e os outros subpacotes não serão importáveis.

A estrutura de diretórios src-layout permite a descoberta automática de pacotes pela maioria dos backends de construção. Veja layout src vs layout plano para mais informações. Se, no entanto, você mesmo deseja gerenciar exclusões ou inclusões de pacotes, isso é possível de ser configurado no pyproject.toml de nível superior:

[build-system]
...

[tool.setuptools.packages.find]
where = ["src/"]
include = ["mynamespace.subpackage_a"]

[project]
name = "mynamespace-subpackage-a"
...

O mesmo pode ser feito com um setup.cfg:

[options]
package_dir =
    =src
packages = find_namespace:

[options.packages.find]
where = src

Ou setup.py:

from setuptools import setup, find_namespace_packages

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_namespace_packages(where='src/', include=['mynamespace.subpackage_a']),
    package_dir={'': 'src'},
)

Setuptools irá pesquisar na estrutura de diretórios por pacotes de espaço de nomes implícitos por padrão.

Um exemplo de trabalho completo de dois pacotes de espaço de nomes nativos pode ser encontrado no projeto exemplo de pacote de espaço de nomes nativo.

Nota

Como os pacotes de espaço de nomes nativos e no estilo pkgutil são amplamente compatíveis, você pode usar pacotes de espaço de nomes nativos nas distribuições que oferecem suporte apenas a Python 3 e pacotes de espaço de nomes no estilo pkgutil nas distribuições que precisam oferecer suporte a Python 2 e 3.

Pacotes de espaço de nomes legados#

Esses dois métodos, que eram usados para criar pacotes de espaço de nomes antes de PEP 420, agora são considerados descontinuados e não devem ser usados a menos que você precise de compatibilidade com pacotes que já usam esse método. Além disso, pkg_resources foi descontinuado.

Para migrar um pacote existente, todos os pacotes que compartilham o espaço de nomes devem ser migrados simultaneamente.

Aviso

Embora os pacotes de espaço de nomes nativos e pacotes de espaço de nomes no estilo pkgutil sejam amplamente compatíveis, os pacotes de espaço de nomes no estilo pkg_resources não são compatíveis com os outros métodos. Não é aconselhável usar métodos diferentes em distribuições diferentes que fornecem pacotes para o mesmo espaço de nomes.

Pacotes de espaço de nomes no estilo pkgutil#

Python 2.3 introduziu o módulo pkgutil e a função pkgutil.extend_path(). Isso pode ser usado para declarar pacotes de espaço de nomes que precisam ser compatíveis com Python 2.3+ e Python 3. Esta é a abordagem recomendada para o nível mais alto de compatibilidade.

Para criar um pacote de espaço de nomes no estilo pkgutil, você precisa fornecer um arquivo __init__.py para o pacote de espaço de nomes:

mynamespace-subpackage-a/
    src/
        pyproject.toml # AND/OR setup.cfg, setup.py
        mynamespace/
            __init__.py  # Namespace package __init__.py
            subpackage_a/
                __init__.py  # Regular package __init__.py
                module.py

O arquivo __init__.py para o pacote de espaço de nomes precisa conter o seguinte:

__path__ = __import__('pkgutil').extend_path(__path__, __name__)

Cada distribuição que usa o pacote de espaço de nomes deve incluir um __init__.py. Se alguma distribuição não o fizer, isso fará com que a lógica do espaço de nomes falhe e os outros subpacotes não serão importáveis. Qualquer código adicional no __init__.py ficará inacessível.

Um exemplo completamente funcional de dois pacotes de espaço de nomes no estilo pkgutil pode ser encontrado no projeto exemplo de espaço de nomes de pkgutil.

Pacotes de espaço de nomes no estilo pkg_resources#

Setuptools fornece a função pkg_resources.declare_namespace e o argumento namespace_packages para setup(). Juntos, eles podem ser usados para declarar pacotes de espaço de nomes. Embora essa abordagem não seja mais recomendada, ela está amplamente presente na maioria dos pacotes de espaço de nomes existentes. Se você estiver criando uma nova distribuição dentro de um pacote de espaço de nomes existente que usa esse método, é recomendável continuar usando-o, pois os diferentes métodos não são compatíveis entre si e não é aconselhável tentar migrar um pacote existente.

Para criar um pacote de espaço de nomes no estilo pkg_resources, você precisa fornecer um arquivo __init __. Py para o pacote de espaço de nomes:

mynamespace-subpackage-a/
    src/
        pyproject.toml # AND/OR setup.cfg, setup.py
        mynamespace/
            __init__.py  # Namespace package __init__.py
            subpackage_a/
                __init__.py  # Regular package __init__.py
                module.py

O arquivo __init__.py para o pacote de espaço de nomes precisa conter o seguinte:

__import__('pkg_resources').declare_namespace(__name__)

Cada distribuição que usa o pacote de espaço de nomes deve incluir um __init__.py. Se alguma distribuição não o fizer, isso fará com que a lógica do espaço de nomes falhe e os outros subpacotes não serão importáveis. Qualquer código adicional no __init__.py ficará inacessível.

Nota

Algumas recomendações mais antigas aconselham o seguinte no __init__.py de pacotes de espaço de nomes:

try:
    __import__('pkg_resources').declare_namespace(__name__)
except ImportError:
    __path__ = __import__('pkgutil').extend_path(__path__, __name__)

A ideia por trás disso era que, no caso raro de setuptools não estar disponível, os pacotes recorreriam aos pacotes no estilo pkgutil. Isso não é aconselhável porque os pacotes de espaço de nomes estilo pkgutil e pkg_resources não são compatíveis entre si. Se a presença de setuptools é uma preocupação, então o pacote deve depender explicitamente de setuptools via install_requires.

Finalmente, cada distribuição deve fornecer o argumento namespace_packages para setup() no setup.py. Por exemplo:

from setuptools import find_packages, setup

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_packages()
    namespace_packages=['mynamespace']
)

Um exemplo de completamente funcional de dois pacotes de espaço de nomes no estilo pkg_resources pode ser encontrado no projeto exemplo de espaço de nomes de pkg_resources.