Software Development Directory Structure
Software Development Directory Structure
A well-organized directory structure is just as important as clean, consistent code style — it significantly improves a project’s readability and maintainability. This article covers the recommended directory layout for Python projects and the modern toolchain that supports them.
Traditional Directory Structure
Suitable for small-to-medium scripts and application projects:
myapp/
├── core/ # Core business logic
│ └── engine.py
├── api/ # External interface layer
│ └── routes.py
├── db/ # Database operations
│ └── models.py
├── lib/ # Internal shared library
│ └── utils.py
├── conf/ # Configuration files
│ └── settings.py
├── tests/ # Test code
│ ├── __init__.py
│ └── test_engine.py
├── run.py # Application entry point
├── requirements.txt # Dependency list
└── README.md # Project descriptioncore/: Business logic; no I/O or network code here, making unit testing straightforward.api/: External interfaces that call into the logic incore/.db/: ORM models and queries, decoupled from business logic.lib/: Utility functions, decorators, custom exceptions, and other cross-module reusable code.conf/: Central location for all configuration; separate dev/prod environments here.run.py: The startup script at the project root; its directory is automatically added tosys.pathat runtime.
Modern Python Package Structure (src Layout)
Recommended for libraries intended for public release or projects installed via pip install:
mypackage/
├── src/
│ └── mypackage/ # Package code lives under src/ to prevent accidental local imports
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── conftest.py
│ └── test_core.py
├── pyproject.toml # Modern project configuration (replaces setup.py)
├── README.md
└── .gitignorepyproject.toml — Modern Project Configuration
Python 3.11+ recommends pyproject.toml as the single source of truth for project metadata, dependencies, and tool configuration:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
version = "0.1.0"
description = "An example Python package"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.4", "mypy>=1.9"]
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I"] # pycodestyle, pyflakes, isort
[tool.mypy]
python_version = "3.12"
strict = truerequirements.txt
Used to record dependencies so the environment can be reproduced quickly:
requests==2.31.0
pydantic==2.7.0
fastapi==0.111.0
uvicorn==0.29.0How to generate it:
# Traditional approach
pip freeze > requirements.txt
# Recommended: use uv (a faster pip alternative)
uv pip freeze > requirements.txtModern Toolchain
| Tool | Purpose |
|---|---|
uv | Blazing-fast package manager and virtual environment tool (written in Rust) |
ruff | Blazing-fast linter and formatter (replaces flake8 + isort + black) |
mypy / pyright | Static type checking |
pytest | Testing framework |
hatch / flit | Package build tools |
Getting Started with uv
# Install uv
pip install uv
# Create a virtual environment and install dependencies
uv venv
uv pip install -r requirements.txt
# Run the project
uv run python run.py
# Add a dependency
uv add requestsKey Conventions
- One virtual environment per project — avoid polluting the global Python installation with project-specific packages.
- Separate configuration from code — sensitive data (API keys, database passwords) should be injected via environment variables or a
.envfile and must never be committed to the repository. __init__.py— marks a directory as a Python package; can be empty or can export the package’s public API.if __name__ == "__main__":— place script logic under this guard so a module can be both imported and run directly.- README.md — every project must include: a description of what it does, installation steps, a quick-start example, and an explanation of the directory structure.
Last updated on