All articles
2 min read

Accessing Files from a Zip Archive with Python Package Resources

Learn how to read bundled data files (images, configs, templates) from a Python package stored as a ZIP archive, using both pkg_resources and the modern importlib.resources API.

When you distribute a Python package, data files like templates, config files, or images need to travel with your code. If the package is installed as a ZIP archive (common with zipapp or certain pip installations), you can't use plain open() with file paths — the files don't exist as individual files on disk. Instead, use Python's package resource APIs to read them.

The Problem

# This works in development but breaks if the package is installed as a zip
with open("mypackage/data/config.json") as f:
    data = f.read()

When your package is zipped, mypackage/data/config.json has no filesystem path. You need an API that reads from the zip directly.

Modern Approach: importlib.resources (Python 3.9+)

The standard library's importlib.resources module is the current recommended way to access package data:

from importlib import resources

# Read a text file bundled with mypackage
text = resources.read_text("mypackage", "README.txt")

# Read a binary file (image, etc.)
data = resources.read_binary("mypackage.data", "logo.png")

# Get a file-like object (useful for PIL, json.load, etc.)
with resources.open_binary("mypackage.data", "logo.png") as f:
    from PIL import Image
    img = Image.open(f)
    img.show()

For Python 3.9+, importlib.resources.files() offers a more flexible path-like API:

from importlib.resources import files

data_path = files("mypackage") / "data" / "config.json"
content = data_path.read_text()

Legacy Approach: pkg_resources

pkg_resources (from setuptools) was the standard before importlib.resources. You'll encounter it in older codebases:

import pkg_resources
from PIL import Image

# Read a text file
text = pkg_resources.resource_string(__name__, "README.txt")
print(text.decode("utf-8"))

# Open an image from a subpackage
stream = pkg_resources.resource_stream("mypackage.data", "logo.png")
img = Image.open(stream)
img.rotate(45).show()

Project Structure

For either approach, ensure your data files are inside a Python package directory (a directory with __init__.py):

mypackage/
├── __init__.py
├── README.txt
└── data/
    ├── __init__.py   ← required for importlib.resources to find files here
    └── logo.png

Also declare the data files in your setup.py or pyproject.toml so they're included when the package is built:

pyproject.toml:

[tool.setuptools.package-data]
"mypackage" = ["*.txt"]
"mypackage.data" = ["*.png", "*.json"]

Creating a Runnable Zip Archive

If you're using zipapp to create a single-file executable, you can still access bundled data files. See the related guide on Mastering the Python ZipApp Module for how to structure and run such archives.

Which API Should You Use?

| | importlib.resources | pkg_resources | |---|---|---| | Python version | 3.7+ (full API in 3.9+) | Any (requires setuptools) | | Status | Standard library, actively developed | Legacy, maintenance mode | | Performance | Faster | Slower (large startup overhead) |

For new code, always use importlib.resources. Use pkg_resources only when maintaining older packages or when compatibility with Python 3.6 is required.

Conclusion

Reading data files from a packaged Python application requires using the package resource APIs rather than plain file paths. Use importlib.resources for new projects — it's part of the standard library, faster, and actively maintained. Fall back to pkg_resources only for legacy code that predates Python 3.7.