Skip to content

Project Structure

โ† Voltar para Python | ๐Ÿ  Home

src/ layout, .gitignore, and README patterns that scale from scripts to packages.

A well-organized project structure makes code easier to find, test, and maintain. This guide covers modern Python project organization that works for everything from simple scripts to complex packages.

๐ŸŽฏ Goals

  • Consistent structure across all Python projects
  • Scalable organization from scripts to packages
  • Clean separation of source code, tests, and documentation
  • Modern conventions that work with current tooling

๐Ÿ” Quick Verification

A well-structured Python project should have:

# Check project structure
ls -la
# Should see: src/, tests/, README.md, .gitignore, requirements.txt

# Verify package installation works
pip install -e .
# Should install without errors

# Test import works
python -c "import your_package_name"
# Should import without errors

Modern Python Project Layout

my-python-project/
โ”œโ”€โ”€ .gitignore              # Git ignore patterns
โ”œโ”€โ”€ .python-version         # Python version for pyenv
โ”œโ”€โ”€ README.md              # Project documentation
โ”œโ”€โ”€ pyproject.toml         # Modern Python configuration
โ”œโ”€โ”€ requirements.txt       # Dependencies (or use pyproject.toml)
โ”œโ”€โ”€ src/                   # Source code directory
โ”‚   โ””โ”€โ”€ my_package/        # Main package
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ main.py        # Entry point
โ”‚       โ”œโ”€โ”€ core/          # Core functionality
โ”‚       โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚       โ”‚   โ””โ”€โ”€ utils.py
โ”‚       โ””โ”€โ”€ data/          # Data processing
โ”‚           โ”œโ”€โ”€ __init__.py
โ”‚           โ””โ”€โ”€ loader.py
โ”œโ”€โ”€ tests/                 # Test files
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ test_main.py
โ”‚   โ””โ”€โ”€ test_utils.py
โ”œโ”€โ”€ docs/                  # Documentation (optional)
โ”‚   โ””โ”€โ”€ index.md
โ””โ”€โ”€ scripts/              # Utility scripts (optional)
    โ””โ”€โ”€ setup.py

Why src/ Layout?

The src/ layout is preferred because: - Prevents accidental imports during development - Forces proper installation testing - Clear separation between source and other files - Industry standard for Python packages

๐Ÿš€ Setting Up a New Project

1. Create Directory Structure

# Create project directory
mkdir my-python-project && cd my-python-project

# Create main directories
mkdir -p src/my_package/core src/my_package/data
mkdir tests docs scripts

# Create Python files
touch src/my_package/__init__.py
touch src/my_package/main.py
touch src/my_package/core/__init__.py
touch src/my_package/core/utils.py
touch tests/__init__.py
touch tests/test_main.py

2. Essential Configuration Files

.python-version (pyenv)

echo "3.12" > .python-version

.gitignore (Python-specific)

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Virtual environments
.venv/
venv/
env/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Testing
.pytest_cache/
.coverage
htmlcov/

# Distribution / packaging
build/
dist/
*.egg-info/

# Environment variables
.env
.env.local

pyproject.toml (Modern configuration)

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
version = "0.1.0"
description = "A sample Python package"
authors = [
    {name = "Your Name", email = "your.email@example.com"},
]
dependencies = [
    "pandas>=2.0.0",
    "numpy>=1.24.0",
]
requires-python = ">=3.11"

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
    "ruff>=0.1.0",
]

[project.scripts]
my-package = "my_package.main:main"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]

[tool.black]
line-length = 88
target-version = ['py311']

[tool.ruff]
line-length = 88
target-version = "py311"

README.md Template

# My Python Project

Brief description of what your project does.

## Installation

```bash
# Clone repository
git clone https://github.com/username/my-python-project.git
cd my-python-project

# Create virtual environment
uv venv .venv
source .venv/bin/activate

# Install package in development mode
pip install -e .

Usage

import my_package

# Example usage
result = my_package.do_something()
print(result)

Development

# Install development dependencies
pip install -e .[dev]

# Run tests
pytest

# Format code
black src/ tests/

# Lint code
ruff check src/ tests/

License

MIT License - see LICENSE file for details.

## ๐Ÿ“ Code Organization Patterns

### Package `__init__.py`
```python
"""My Package - A sample Python package."""

from .main import main_function
from .core.utils import utility_function

__version__ = "0.1.0"
__all__ = ["main_function", "utility_function"]

Main Module Pattern

# src/my_package/main.py
"""Main module with CLI entry point."""

import argparse
from .core.utils import process_data

def main():
    """Main function - entry point for CLI."""
    parser = argparse.ArgumentParser(description="My Python Package")
    parser.add_argument("--input", "-i", help="Input file")
    parser.add_argument("--output", "-o", help="Output file")

    args = parser.parse_args()

    if args.input:
        result = process_data(args.input)
        print(f"Processed: {result}")

if __name__ == "__main__":
    main()

Utility Module Pattern

# src/my_package/core/utils.py
"""Utility functions for data processing."""

from pathlib import Path
from typing import Union

def process_data(input_path: Union[str, Path]) -> dict:
    """Process input data and return results.

    Args:
        input_path: Path to input file

    Returns:
        Dictionary with processed results
    """
    input_path = Path(input_path)

    if not input_path.exists():
        raise FileNotFoundError(f"Input file not found: {input_path}")

    # Processing logic here
    return {"status": "processed", "file": str(input_path)}

๐Ÿงช Testing Structure

Test Organization

# tests/test_main.py
"""Tests for main module."""

import pytest
from my_package.main import main_function

def test_main_function():
    """Test main function works correctly."""
    result = main_function("test_input")
    assert result is not None
    assert isinstance(result, dict)

def test_main_function_with_invalid_input():
    """Test main function handles invalid input."""
    with pytest.raises(ValueError):
        main_function(None)

Running Tests

# Install test dependencies
pip install -e .[dev]

# Run all tests
pytest

# Run with coverage
pytest --cov=src/my_package --cov-report=html

# Run specific test file
pytest tests/test_main.py

# Run with verbose output
pytest -v

๐Ÿ”ง Development Workflow

1. Initial Setup

mkdir new-project && cd new-project
echo "3.12" > .python-version
uv venv .venv
source .venv/bin/activate

2. Create Structure

mkdir -p src/my_package tests
touch src/my_package/__init__.py
touch src/my_package/main.py
# ... create other files

3. Install in Development Mode

pip install -e .
# Now you can import your package from anywhere

4. Development Cycle

# Make changes to code
# Run tests
pytest

# Format code
black src/ tests/

# Check linting
ruff check src/ tests/

# Install new dependencies
uv pip install new-package

โšก Quick Reference

New Project Checklist: - [ ] Create src/package_name/ directory - [ ] Add __init__.py files - [ ] Create tests/ directory
- [ ] Set up .gitignore - [ ] Write README.md - [ ] Configure pyproject.toml - [ ] Install with pip install -e .

Daily Commands:

pip install -e .           # Install in development mode
pytest                     # Run tests
black src/ tests/          # Format code
ruff check src/ tests/     # Lint code


Next: Learn about Environments & Dependencies for advanced environment management, or jump to Testing to set up proper testing workflows.