distribution
At some point, you’ll want to share your code, either with teammates inside your organization or with the wider Python community. A thoughtful distribution strategy makes it easy for others to install, upgrade, and use your project. It also helps you manage versions, dependencies, and metadata in a predictable manner, rather than relying on ad hoc scripts and manual instructions.
Modern Python packaging relies on the pyproject.toml file, which describes how your project is built and provides standard metadata, such as the project name, version, dependencies, entry points, and more. While some tools still support configuration in other files, pyproject.toml is now the central, standardized place for packaging configuration.
When it comes to packaging and distributing your Python projects, you should apply some of the best practices below:
- Use
pyproject.tomlas the primary source of packaging configuration. Declare your project’s build settings and core metadata inpyproject.tomlinstead of relying on legacysetup.pyscripts or scattered configuration files. This practice aligns with modern packaging standards (PEP 517 and PEP 621) and makes your build configuration easier to understand and maintain. - Choose a modern, standard-compliant build backend. Use a backend such as setuptools, Hatch (via
hatchling), or poetry-core in yourpyproject.toml. These backends implement modern packaging standards and allow other developers and tools to build your project without custom scripts. Typically, you choose one backend per project. - Version and tag releases consistently. Follow a clear versioning scheme like semantic versioning, and tag releases in your version control system. This practice helps users know when you’ve introduced new features, bug fixes, or breaking changes.
- Be responsible about publishing to PyPI. Double-check metadata, dependencies, and licensing before uploading your packages to the Python Package Index (PyPI). Use a test index like TestPyPI to verify that your builds and installation instructions work as expected.
- Document how to install your project. Provide copy-pasteable installation commands and clearly document any extra setup steps users need to take.
To see the difference that modern packaging makes, compare the following two approaches:
🔴 Avoid this:
The project layout:
resize-images/
└── resize_images.py
The source code:
resize_images.py
import sys
from pathlib import Path
def main():
source = Path(sys.argv[1])
target = Path(sys.argv[2])
# Resize images logic here...
if __name__ == "__main__":
main()
The installation and usage instructions:
$ git clone https://github.com/user/resize-tool.git
$ cd resize-tool/
$ python resize_images.py pictures/ thumbnails/
This project works, but it’s fragile. Users must clone the repository, navigate to the correct directory, and run the script directly with the python command. The project doesn’t declare standard metadata such as its name, version, or dependencies, and it can’t be installed or invoked using standard Python packaging tools or entry points.
✅ Favor this:
The project layout:
resize-images/
├── src
│ └── image_tools
│ ├── __init__.py
│ └── cli.py
└── pyproject.toml
Using the src-layout helps prevent accidental imports from the project root during development and more closely matches how users will import your package once it’s installed.
The source code:
import argparse
from pathlib import Path
def main() -> None:
parser = argparse.ArgumentParser(
description="Resize images from source into target folder."
)
parser.add_argument("source", type=Path)
parser.add_argument("target", type=Path)
args = parser.parse_args()
# Resize images logic here...
The project’s metadata and build configuration:
pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "image-tools"
version = "0.1.0"
dependencies = ["Pillow"]
[project.scripts]
resize-images = "image_tools.cli:main"
The installation and usage instructions:
(venv) $ python -m pip install .
(venv) $ resize-images pictures/ thumbnails/
In this version, your project has a well-structured layout, declares its dependencies and metadata explicitly, and exposes a user-friendly command-line interface via an entry point. Users can install it into a virtual environment using standard tools and invoke it just like any other command-line program.
Related Resources
Tutorial
How to Manage Python Projects With pyproject.toml
Learn how to manage Python projects with the pyproject.toml configuration file. In this tutorial, you'll explore key use cases of the pyproject.toml file, including configuring your build, installing your package locally, managing dependencies, and publishing your package to PyPI.
For additional information on related topics, take a look at the following resources:
- How to Publish an Open-Source Python Package to PyPI (Tutorial)
- What Are Python Wheels and Why Should You Care? (Tutorial)
- Python's zipapp: Build Executable Zip Applications (Tutorial)
- Using PyInstaller to Easily Distribute Python Applications (Tutorial)
- Managing Python Projects With uv: An All-in-One Solution (Tutorial)
- Using Python's pip to Manage Your Projects' Dependencies (Tutorial)
- Everyday Project Packaging With pyproject.toml (Course)
- How to Manage Python Projects With pyproject.toml (Quiz)
- How to Publish Your Own Python Package to PyPI (Course)
- Publishing Python Packages to PyPI (Course)
- Python Project Management With uv (Course)
- Managing Python Projects With uv: An All-in-One Solution (Quiz)
- A Beginner's Guide to pip (Course)
- Using Python's pip to Manage Your Projects' Dependencies (Quiz)