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?

SilleBille
  • 605
  • 5
  • 21

1 Answers1

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

Matt Eding
  • 917
  • 1
  • 8
  • 15
  • 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. – SilleBille Dec 22 '19 at 22:46
  • 1
    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. – Matt Eding Dec 23 '19 at 00:55