setup.py
Python packaging
non-Python files
package distribution
Python projects

Including non-Python files with setup.py

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Python packages often need to ship more than .py files. Templates, static assets, schemas, configuration defaults, and example data all need packaging rules of their own. When you are using setup.py, the important question is whether the files live inside a package directory or outside it, because that determines whether package_data, include_package_data, or MANIFEST.in is the right tool.

Understand the Two Packaging Questions

There are really two different concerns here.

  1. Should the non-Python files be included in the source distribution and wheel.
  2. Will your code be able to access those files correctly at runtime.

The first concern is solved by packaging configuration. The second is solved by loading the files through package-aware APIs instead of assuming a working-directory path.

Use package_data for Files Inside a Package

If the files live inside an importable package directory, package_data is the most direct setup.py option.

Suppose your project looks like this:

text
1myproj/
2  setup.py
3  mypkg/
4    __init__.py
5    templates/
6      page.html
7    data/
8      defaults.json

Then setup.py can include those files like this:

python
1from setuptools import setup, find_packages
2
3setup(
4    name="mypkg",
5    version="0.1.0",
6    packages=find_packages(),
7    package_data={
8        "mypkg": ["templates/*.html", "data/*.json"],
9    },
10)

That tells setuptools to include matching files that live under mypkg.

Use include_package_data and MANIFEST.in When Needed

If you want setuptools to honor file-inclusion rules from MANIFEST.in, enable include_package_data=True.

python
1from setuptools import setup, find_packages
2
3setup(
4    name="mypkg",
5    version="0.1.0",
6    packages=find_packages(),
7    include_package_data=True,
8)

Then create a MANIFEST.in file:

text
recursive-include mypkg/templates *.html
recursive-include mypkg/data *.json

This is especially useful when you want broader declarative file rules or already maintain MANIFEST.in for source distributions.

A simple mental model is:

  • 'package_data is explicit in setup.py'
  • 'MANIFEST.in is a file-list language that setuptools can apply when include_package_data=True'

Files Outside the Package Need Extra Thought

If a file sits outside the actual package directory, including it in a source distribution is not the same thing as making it accessible as package data at runtime. This is where many setups go wrong.

For example, a top-level README.md can be included in the source package, but your installed code should not assume it lives next to the importable package.

That is why runtime assets are usually best placed inside the package tree itself. If the code needs the file later, keep it under the package rather than beside setup.py.

Access the Files Correctly at Runtime

After including the files, read them through package-aware APIs. In modern Python, importlib.resources is the cleanest approach.

python
1from importlib.resources import files
2
3html = files("mypkg.templates").joinpath("page.html").read_text(encoding="utf-8")
4print(html[:60])

This is much safer than using relative paths from the current working directory, which often fail once the package is installed.

Verify What Actually Got Packaged

Do not assume the packaging rules worked. Build the distributions and inspect them.

bash
python setup.py sdist bdist_wheel

Then check the contents under dist/. If the file is missing there, the problem is in packaging configuration. If it is present there but missing at runtime, the problem is probably how the file is being accessed.

That distinction saves time during debugging.

A Good Practical Rule

If your application code needs the file at runtime, place it inside the package, declare it in package_data or via MANIFEST.in, and load it with importlib.resources. That combination is much more reliable than scattering resource files around the repository and hoping installation paths line up later.

Common Pitfalls

  • Putting runtime resource files outside the package directory and then assuming they will behave like package data after installation.
  • Using MANIFEST.in without enabling include_package_data=True when that integration is required.
  • Including files in the distribution but still loading them at runtime with fragile working-directory-relative paths.
  • Forgetting to inspect the built sdist or wheel and therefore debugging the wrong part of the packaging flow.
  • Treating source-distribution inclusion and runtime resource access as though they were the same problem.

Summary

  • Non-Python files can be included from setup.py, but the correct mechanism depends on where the files live.
  • Use package_data for files inside the package.
  • Use include_package_data=True together with MANIFEST.in when you want declarative inclusion rules.
  • Keep runtime assets inside the package tree whenever possible.
  • Load packaged resources with importlib.resources, not fragile relative file paths.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.