Good project structure makes code easier to navigate and maintain. Here's how to organize Python projects.
Modules vs Packages
Module: A single .py file
# utils.py is a module
import utilsPackage: A directory with __init__.py
mypackage/
├── __init__.py
├── core.py
└── helpers.py
import mypackage
from mypackage import coreBasic Project Structure
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── core.py
│ ├── utils.py
│ └── cli.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_utils.py
├── pyproject.toml
├── README.md
└── .gitignore
The src Layout
Putting code in src/ prevents accidental imports from the project root:
# Without src/
myproject/
├── mypackage/ # Might import local instead of installed
└── tests/
# With src/
myproject/
├── src/
│ └── mypackage/ # Must be installed to import
└── tests/
init.py
Controls what's exposed when importing the package:
# mypackage/__init__.py
# Make these available at package level
from .core import process
from .utils import helper
# Package metadata
__version__ = "1.0.0"
__all__ = ["process", "helper"]Now users can:
from mypackage import process # Instead of mypackage.core.processSubpackages
Organize large projects into subpackages:
mypackage/
├── __init__.py
├── api/
│ ├── __init__.py
│ ├── routes.py
│ └── middleware.py
├── models/
│ ├── __init__.py
│ ├── user.py
│ └── order.py
└── utils/
├── __init__.py
└── helpers.py
Import paths:
from mypackage.api import routes
from mypackage.models.user import UserRelative Imports
Within a package, use relative imports:
# mypackage/api/routes.py
# Relative imports (preferred within package)
from ..models import User
from ..utils.helpers import format_date
# Absolute imports (also work)
from mypackage.models import Userpyproject.toml
Modern Python projects use pyproject.toml:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "1.0.0"
description = "My awesome package"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"requests>=2.28.0",
"click>=8.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=22.0.0",
"mypy>=0.990",
]
[project.scripts]
mycommand = "mypackage.cli:main"
[tool.setuptools.packages.find]
where = ["src"]Installing for Development
Install in editable mode:
pip install -e ".[dev]"Changes to your code are immediately available without reinstalling.
Entry Points (CLI)
Create command-line tools:
# src/mypackage/cli.py
import click
@click.command()
@click.argument("name")
def main(name):
"""Greet someone."""
click.echo(f"Hello, {name}!")
if __name__ == "__main__":
main()# pyproject.toml
[project.scripts]
greet = "mypackage.cli:main"After installing:
greet World # Hello, World!main.py
Make packages runnable with python -m:
# src/mypackage/__main__.py
from .cli import main
if __name__ == "__main__":
main()python -m mypackage World # Hello, World!Organizing by Feature
For larger projects, organize by feature instead of type:
mypackage/
├── __init__.py
├── users/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── services.py
├── orders/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── services.py
└── shared/
├── __init__.py
└── database.py
Configuration
Keep config separate:
mypackage/
├── __init__.py
├── config.py # Configuration classes
├── settings.py # Load from environment
└── core.py
# config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
api_key: str
debug: bool = False
class Config:
env_file = ".env"What Goes Where
| Type | Location |
|---|---|
| Package code | src/mypackage/ |
| Tests | tests/ |
| Documentation | docs/ |
| Configuration | pyproject.toml, .env |
| Scripts | scripts/ |
| Type stubs | src/mypackage/py.typed |
Quick Reference
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py # Package exports
│ ├── __main__.py # python -m entry
│ ├── core.py # Main functionality
│ ├── cli.py # Command-line interface
│ └── utils.py # Helpers
├── tests/
│ └── test_core.py
├── pyproject.toml # Package metadata
├── README.md
└── .gitignore
Good structure scales. Start simple, add subpackages as complexity grows, and keep related code together.