1

I wrote a small tool (package) that reuses an existing namespace, pki.server. I named my package as pki.server.healthcheck. The old namespace did not use setuptools to install the package, while my package uses it.

Contents of setup.py

from setuptools import setup

setup(
    name='pkihealthcheck',
    version='0.1',
    packages=[
        'pki.server.healthcheck.core',
        'pki.server.healthcheck.meta',
    ],
    entry_points={
        # creates bin/pki-healthcheck
        'console_scripts': [
            'pki-healthcheck = pki.server.healthcheck.core.main:main'
        ]
    },
    classifiers=[
        'Programming Language :: Python :: 3.6',
    ],
    python_requires='!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
    setup_requires=['pytest-runner',],
    tests_require=['pytest',],
)

The installation tree (from scenario 1 below) looks like:

# tree /usr/lib/python3.8/site-packages/pki/
├── __init__.py                     <---- Has methods and classes
├── cli
│   ├── __init__.py                 <---- Has methods and classes
│   ├── <some files>
├── server
│   ├── cli
│   │   ├── __init__.py             <---- Has methods and classes
│   │   ├── <Some files>
│   ├── deployment
│   │   ├── __init__.py             <---- Has methods and classes
│   │   ├── <some files>
│   │   └── scriptlets
│   │       ├── __init__.py         <---- Has methods and classes
│   │       ├── <some files>
│   ├── healthcheck
│   │   ├── core
│   │   │   ├── __init__.py         <---- EMPTY
│   │   │   └── main.py
│   │   └── pki
│   │       ├── __init__.py         <---- EMPTY
│   │       ├── certs.py
│   │       └── plugin.py
│   └── instance.py                 <---- Has class PKIInstance
└── <snip>

# tree /usr/lib/python3.8/site-packages/pkihealthcheck-0.1-py3.8.egg-info/
├── PKG-INFO
├── SOURCES.txt
├── dependency_links.txt
├── entry_points.txt
└── top_level.txt

I read the official documentation and experimented with all 3 suggested methods. I saw the following results:

Scenario 1: Native namespace packages

At first, everything seemed smooth. But:

# This used to work before my package gets installed
>>> import pki.server
>>> instance = pki.server.instance.PKIInstance("pki-tomcat")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pki.server' has no attribute 'instance'

Now, only this works

>>> import pki.server.instance
>>> instance = pki.server.instance.PKIInstance("pki-tomcat")
>>> instance
pki-tomcat

Scenario 2: pkgutil-style namespace packages I am restricted from using this method as my other __init__.py contain classes and functions

Scenario 3: pkg_resources-style namespace packages Though this method was not-recommended, I went ahead and experimented with it by adding namespace=pki.server.healthcheck to my setup.py. This made all pki.* modules invisible

So I am convinced that Scenario 1 seems to be the closest to what I'm trying to achieve. I was reading an old post to understand more on how import in python works.

My question is: Why does a perfectly working snippet break after I install my package?

1 Answer 1

1

Your __init__.py files need to import the files. You have two options--absolute and relative imports:

Relative Imports

pki/__init__.py:
from . import server

pki/server/__init__.py:
from . import instance

Absolute Imports

pki/__init__.py:
import pki.server

pki/server/__init__.py:
import pki.server.instance

Sign up to request clarification or add additional context in comments.

2 Comments

I guess this will resolve the specific scenario I posted. But, there may be multiple modules that need to be corrected. So, I want to learn what's happening behind the screens.
With a package, the /xyz/abc/__init__.py file contents are what gets imported by import xyz.abc. So whatever you want to be publicly available needs to be in the init file. Some packages restrict what is exported from the top level--scipy.sparse must be imported as from scipy import sparse or import scipy.sparse by not including sparse in the init file to prevent lots of code being loaded automatically.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.