Python Project Structure Best Practices

Learn how to structure your Python projects for scalability and maintainability. This guide covers a standard layout including source directories, tests, and configuration files.

How you structure your project is one of the first and most important decisions you'll make. A well-organized project is easier to navigate, maintain, and package for distribution. While there's no single official layout, a standard, community-accepted structure has emerged that works for most small to medium-sized projects.

Let's break down this recommended structure and the purpose of each component.

The Recommended Project Layout

A typical Python project should look something like this:

my_project/
├── .gitignore
├── pyproject.toml
├── README.md
├── src/
│   └── my_package/
│       ├── __init__.py
│       ├── module1.py
│       └── subpackage/
│           ├── __init__.py
│           └── module2.py
└── tests/
    ├── test_module1.py
    └── test_module2.py

Let's go through each part.

pyproject.toml

This is the modern, unified configuration file for Python projects, introduced in PEP 518. It's the new standard for specifying your project's build dependencies and metadata.

It replaces the need for older files like setup.py, setup.cfg, and requirements.txt for many use cases. Your project's dependencies, author information, and tool configurations (like for pytest or black) all live here.

Example pyproject.toml:

[project]
name = "my_package"
version = "0.1.0"
description = "A short description of the project."
authors = [
    { name = "Your Name", email = "your@email.com" },
]
dependencies = [
    "requests>=2.28",
    "pydantic>2.0",
]

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
testpaths = [
    "tests",
]

The src/ Layout

This is one of the most important conventions. Placing your main package code inside a src/ directory (a "source" layout) provides several key advantages:

  1. Prevents Accidental Imports: Without a src/ layout, Python might accidentally import your local package code instead of the installed version. This can lead to confusing bugs where your tests pass locally but fail in a clean environment. The src/ layout forces you to install your package (e.g., with pip install -e .) to run your tests, which is a much more realistic simulation of how it will be used.
  2. Clear Separation: It creates a clean separation between your actual package code and other project files like tests, documentation, and configuration.

Inside src/, you have your main package directory (e.g., my_package/). This is the directory that will be distributed when you publish your package.

tests/ Directory

Your tests should live in a top-level tests/ directory, separate from your source code. This makes it clear that tests are not part of the distributable package.

The structure of your tests/ directory can mirror the structure of your my_package/ directory, which helps keep your tests organized as your project grows.

README.md

Your README file is the front page of your project. It should be written in Markdown and contain:

  • A brief description of what the project does.
  • Instructions on how to install and configure it.
  • A basic usage example.
  • Information on how to run tests and contribute.

.gitignore

This file tells Git which files and directories to ignore. A standard Python .gitignore file will include entries for:

  • Virtual environment directories (.venv/)
  • Python bytecode files (__pycache__/, *.pyc)
  • Build artifacts (dist/, build/, *.egg-info)
  • Local configuration files (e.g., .idea/, .vscode/)

GitHub provides excellent default .gitignore templates for Python.

Why Not a Flat Layout?

A "flat" layout places your package directory at the root of the project, next to pyproject.toml:

my_project/
├── my_package/
│   ├── __init__.py
│   └── ...
└── tests/

While simpler for very small projects, this is the layout that can lead to the import problems mentioned earlier. For any project that you intend to be installable or that has tests, the src/ layout is strongly recommended.

Conclusion

Adopting a standard project structure is a hallmark of a professional developer. It makes your code more predictable and easier for others (and your future self) to understand. By using a src/ layout, consolidating your configuration in pyproject.toml, and separating your tests, you set your project up for success from day one.