Question
What is setup.py in a Python project, and how can it be configured and used to package, build, and distribute an application or library?
Short Answer
By the end of this page, you will understand the role of setup.py, how it uses setuptools to describe a Python package, and how to build or install a package locally. You will also learn why new projects generally prefer pyproject.toml for configuration.
Concept
setup.py is traditionally a Python file that describes how a project should be packaged and distributed. It commonly imports the setup() function from the setuptools library and passes project metadata to it.
That metadata can include:
- The package name and version
- A short description
- Which Python packages to include
- Required dependencies
- Supported Python versions
- Command-line tools the project provides
For example, a packaging tool needs to know that a folder such as src/weather_tools/ is Python code that should be included when someone installs your library. setup.py can provide that information.
setup.py was the standard packaging configuration approach for many years. It still appears in many existing repositories and is useful to understand when maintaining them. However, for new Python projects, the modern recommendation is to put build configuration and project metadata in pyproject.toml. This avoids asking users or tools to execute a configuration script directly and gives packaging tools a standard place to find build requirements.
In short: setup.py is a legacy-but-common package configuration script, usually powered by setuptools.
Mental Model
Think of a Python package as a product you want to ship in a box.
- Your Python modules are the product.
- Dependencies are the extra parts required for the product to work.
- Package metadata, such as its name and version, is the label on the box.
setup.pyis the shipping instruction sheet that tells packaging tools what goes into the box and how it should be installed.
Without this information, a tool cannot reliably tell which files belong in the installable package or which other libraries must be installed first.
Syntax and Examples
A basic setup.py imports setup and find_packages from setuptools.
from setuptools import find_packages, setup
setup(
name="greeting-tools",
version="0.1.0",
description="Small utilities for creating greetings",
packages=find_packages(),
install_requires=[
"requests>=2.31",
],
python_requires=">=3.9",
)
setup() receives keyword arguments that describe the project:
name: The distribution name used by package indexes and installers.version: The release version of the distribution.description: A brief summary for people and package tools.packages: The importable packages to include.find_packages()searches for package directories.install_requires: Dependencies that should be installed with the package.python_requires: Python versions the package supports.
A typical older project layout might be:
Step by Step Execution
Consider this project:
message-tools/
├── setup.py
└── message_tools/
├── __init__.py
└── formatter.py
Its setup.py is:
from setuptools import find_packages, setup
setup(
name="message-tools",
version="1.0.0",
packages=find_packages(),
)
When a packaging command runs, this is the general sequence:
- Python imports
setupandfind_packagesfromsetuptools. - Python evaluates
find_packages()from the project root. find_packages()findsmessage_toolsbecause it is a package directory.setup()receives the name, version, and discovered package list.- A build or installation tool uses that information to create or install the distribution.
- After installation, another Python program can import the package:
from message_tools import formatter
In modern workflows, avoid manually running python setup.py install. Prefer to install the current project, or use a build frontend such as to create distribution files.
Real World Use Cases
setup.py is most often encountered when working with existing Python packages.
- Internal company libraries: A team packages shared code, such as logging or API-client helpers, so several services can install it with
pip. - Open-source libraries: A project publishes an installable package so users can run
pip install package-name. - Command-line applications: A package declares a command that users can run after installation.
- Dependency management: A library states which third-party packages it needs, such as
requestsorpydantic. - Build customization in older projects: A repository may contain custom
setuptoolsbehavior for generated files or compiled extensions.
For example, a command-line entry point can be declared with entry_points:
from setuptools import find_packages, setup
setup(
name="todo-cli",
version="0.1.0",
packages=find_packages(),
entry_points={
"console_scripts": [
"todo=todo_cli.main:main",
],
},
)
After installation, the todo command calls the main() function in .
Real Codebase Usage
In real codebases, packaging configuration is usually kept declarative and small.
Prefer modern installation commands
Even when a repository has setup.py, install it with:
python -m pip install .
For editable development installation:
python -m pip install -e .
An editable install lets imports use your working copy, so code changes are available without reinstalling the package.
Use version ranges intentionally
Dependencies are usually constrained to compatible ranges:
install_requires=[
"requests>=2.31,<3",
]
This allows compatible updates while avoiding an unknown next major release.
Keep runtime and development dependencies separate
A library's runtime dependencies belong in its package metadata. Test tools, formatters, and linters are usually managed separately through a development dependency group, requirements file, or a tool configuration.
Move new configuration to pyproject.toml
Modern projects commonly use pyproject.toml for metadata:
[build-system]
requires = ["setuptools>=61"]
=
=
=
=
=
= [
,
]
Common Mistakes
Running python setup.py install
This older command may bypass modern installer behavior and is not the recommended installation path.
# Avoid for normal installs
python setup.py install
Use this instead:
python -m pip install .
Forgetting to include packages
This configuration creates metadata but includes no Python package code:
from setuptools import setup
setup(name="my-tools", version="0.1.0")
Add discovered or explicit packages:
from setuptools import find_packages, setup
setup(
name="my-tools",
version="0.1.0",
packages=find_packages(),
)
Listing a package name that does not match the directory
The distribution name does not have to equal the import name. For example, my-tools might be imported as my_tools. Be deliberate and verify the package directory is included.
Putting dependency installation code in setup.py
Comparisons
| Tool or file | Main purpose | Typical use |
|---|---|---|
setup.py | Python-based setuptools package configuration | Maintaining older projects or adding custom build logic |
pyproject.toml | Standard modern build and project configuration | Preferred choice for new Python packages |
requirements.txt | A list of dependencies to install | Reproducing an application or development environment |
setup.cfg | Declarative configuration often used with setuptools | Older projects that avoid Python code for metadata |
setup.py and pyproject.toml can coexist. In a migration, pyproject.toml may define the build system while some configuration remains elsewhere. For a new, straightforward package, put metadata in and avoid creating unless custom Python build behavior is genuinely needed.
Cheat Sheet
from setuptools import find_packages, setup
setup(
name="package-name",
version="0.1.0",
description="Short package description",
packages=find_packages(),
install_requires=["dependency>=1.0"],
python_requires=">=3.9",
)
setup.pyis traditionalsetuptoolspackaging configuration.nameis the installable distribution name; it can differ from the Python import name.find_packages()discovers regular package directories.install_requireslists runtime dependencies.- Install the local project:
python -m pip install . - Install for development:
python -m pip install -e . - Build distributions with:
python -m build - Prefer
pyproject.tomlfor metadata in new projects. - Avoid
python setup.py installfor ordinary installation tasks.
FAQ
Is setup.py required for every Python project?
No. A project that is not intended to be installed as a package may not need packaging metadata. New installable packages usually use pyproject.toml instead.
What does find_packages() do in setup.py?
It searches the project directory for Python packages and returns their names so setuptools can include them in the distribution.
How do I install a project that has setup.py?
From the project root, run:
python -m pip install .
Use python -m pip install -e . when developing the project.
Should I run python setup.py install?
Usually no. Use pip install ., which is the modern installation workflow.
What is the difference between a package name and an import name?
The package distribution name is what users install, such as my-tools. The import package is what Python imports, such as my_tools. They are related but may differ.
Can declare dependencies?
Mini Project
Description
Create a small installable Python package named text-tools. It provides a function for turning text into a URL-friendly slug, demonstrating how setup.py describes package code, metadata, and a runtime dependency-free distribution.
Goal
Package and install a local Python library, then import and use one of its functions.
Requirements
Create a text_tools package containing a slugify function.
Add a setup.py file with a package name, version, description, and discovered packages.
Install the project locally with python -m pip install ..
Verify that slugify can be imported and called from Python.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.