Using MANIFEST.in for Python Source Distributions
When distributing Python projects, code alone is rarely sufficient;
packages often require non-code assets such as configuration files,
documentation, data sets, and templates. This article provides a focused
explanation of the MANIFEST.in file, its role in packaging
Python source distributions (sdists), the mechanics of how it controls
file inclusion, and how it interacts with modern packaging
configurations to ensure reproducible builds.
Default Packaging Behavior
When creating a Python source distribution using build backends such
as setuptools, the packaging tool automatically includes a
baseline set of files. By default, these typically consist of:
- Python source files discovered within specified packages or modules.
- Core metadata files such as
pyproject.toml,setup.py, andsetup.cfg. - Standard project files like
README,LICENSE, andAUTHORS(depending on file naming conventions). - Files tracked by version control systems, if relevant build plugins
(like
setuptools-scm) are active.
However, arbitrary assets—such as JSON schemas, HTML templates, SQL seed files, or C extension headers—are often ignored by the default packaging discovery mechanism.
The Purpose of MANIFEST.in
A MANIFEST.in file is a text file located in the
project's root directory that provides explicit instructions on which
files and directories to include or exclude when generating a source
distribution (.tar.gz).
During the source distribution build process (such as executing
python -m build --sdist), the build tool reads
MANIFEST.in to modify the default list of files. It
essentially acts as a rule-based filter that constructs the final
distribution manifest (SOURCES.txt), guaranteeing that end
users or build systems receiving the source tarball have all the
ancillary files required to build, test, or install the package.
Common MANIFEST.in Directives
MANIFEST.in uses a declarative syntax processed
line-by-line from top to bottom. The most frequently used directives
include:
include <pattern> ...: Adds individual files matching the specified shell patterns.
Example:include config.yaml docs/*.txtexclude <pattern> ...: Removes individual files matching the specified patterns.
Example:exclude run_local.shrecursive-include <dir> <pattern> ...: Recursively traverses<dir>and includes all files matching the pattern.
Example:recursive-include mypackage/templates *.html *.cssrecursive-exclude <dir> <pattern> ...: Recursively removes files under<dir>matching the pattern.
Example:recursive-exclude tests *.pycgraft <dir>: Unconditionally includes all files located under the directory tree<dir>.
Example:graft docsprune <dir>: Unconditionally excludes the entire directory tree<dir>.
Example:prune docs/_buildglobal-include <pattern> ...: Adds all files across the entire workspace matching the pattern, regardless of directory location.
Example:global-include *.typed
Because rules are evaluated sequentially, later directives override earlier ones.
Distinction Between Source Distributions and Wheels
It is critical to distinguish between a source distribution
(sdist) and a built distribution (wheel):
MANIFEST.intargets the sdist: It dictates what exists inside the source tarball. It does not directly configure what gets installed into Python'ssite-packageson an end user's system.package_datatargets wheels and runtime: Non-code assets intended to be accessible at runtime must be bundled into the wheel. Forsetuptools, enablinginclude_package_data = trueinsidepyproject.tomlorsetup.cfginstructs the wheel builder to include any runtime package files already captured in the source distribution byMANIFEST.in.
If a file is missing from MANIFEST.in, it will not be
present in the source distribution, making it impossible for downstream
builders to access or include it when compiling the package from
source.