Python Import errors and package static files (resources)

How to debug import errors (if they occur)

Let's talk about the difference between modules and packages.

  • A module is just a file which contains some python code;
  • A package is just a folder which contains one or more modules plus a "special" __init__.py file.

Sometimes you may get an ugly ImportError: cannot import .... error. In this case, you can investigate that error by using the special (dunder) methods available at runtime.

Just add, at the top level of the modules that cause issues, this print (or log):

print(
    f"PKG: {__package__}
    MODULE: {__name__}, 
    SPEC: {__spec__} DIR: {dir()}"
)

#Python stuff bellow

The above print should look similar to this in the console:

PKG: htmgem 
MODULE: htmgem.doc, 
SPEC: ModuleSpec(name='htmgem.doc', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7ff724f1ec70>, origin='/path/to/htmgem/doc.py') 
DIR: ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']

Python knows that the package's name is htmgem, while the module's name is htmgem.doc:

  • PKG: htmgem
  • MODULE: htmgem.doc

So, if in the module doc.py you have an import like from doc import func, you should be able to fix the import with from htmgem.doc import func.

Try to avoid relative imports (the ones with from ..module import func) and use only absolute imports.

The issue from above may not fit your case, but by printing to console the current package name (__package__) and the module name (__name__), you could find some clues on how to fix the import error issue.

Adding static files aka resources to a python package

In your package, create a resources folder, mark it as a package by creating a __init__.py file and add the static files you need. In my case the static file is index.html.

.
├── htmgem
│   ├── doc.py
│   ├── __init__.py
│   ├── resources
│   │   ├── index.html
│   │   └── __init__.py
│   └── tags.py
├── README.md
└── tests
    ├── test_doc.py
    └── test_tags.py

Now, to access that static file, you need to use importlib.resources module.

In my case I need to read index.html contents and generate some files, here is a snippet:

import importlib.resources as pkg_resources
from htmgem import resources

class Doc:
    def __init__(self, base_html=None):
        self.base_html = base_html or pkg_resources.read_text(resources, "index.html")

If the read_text method is not what you need, you can use read_binary instead. For more available methods check out python docs.