Skip to content

Python Development Overview

Modern Python development in 2025 is faster, simpler, and more reliable than ever. This section covers the essential tools and practices for building production Python applications.

Core Philosophy

Guiding Principles

  • Modern is better: Use Python 3.13+ for new projects
  • Fast tooling: Tools like Ruff and uv are 10-100x faster than predecessors
  • Simplicity wins: Fewer tools, less configuration, more productivity
  • Type hints help: Gradual typing improves code quality
  • Test everything: But be pragmatic about coverage

The Modern Python Stack (2025)

Core Tools

Tool Purpose Why Replaces
Python 3.13+ Runtime Better performance, error messages, security Python 3.6-3.12
uv Dependency management 10-100x faster than pip pip-tools, pipenv, poetry*
Ruff Linting + Formatting Single tool, extremely fast Black, isort, flake8, pylint*
pytest Testing Industry standard, excellent plugins unittest
Bandit Security scanning Find security issues automatically Manual review
Vulture Dead code detection Keep codebase clean Manual cleanup

* Can complement, not always replace

Development Workflow

graph LR
    A[Write Code] --> B[Ruff Format]
    B --> C[Ruff Lint]
    C --> D[pytest]
    D --> E[Bandit Security]
    E --> F{All Pass?}
    F -->|Yes| G[Commit]
    F -->|No| A
    G --> H[Pre-commit Hooks]
    H --> I[CI/CD]

This entire workflow runs in seconds thanks to modern tools.

Quick Start

1. Install uv (Dependency Manager)

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create project
mkdir myproject && cd myproject
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

2. Configure Ruff (Linting & Formatting)

Create pyproject.toml:

[tool.ruff]
line-length = 180
target-version = "py313"

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "S", "B", "A", "C4", "T20", "RET", "SIM", "ARG"]
ignore = []

3. Set Up pytest

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=myproject --cov-report=html --cov-report=term"

4. Create justfile

# Development commands
test:
    pytest

lint:
    ruff check .

format:
    ruff format .

check: format lint test

What Changed from 2019?

Python Version

Old (2019): - Python 3.6-3.8 - End of life for 3.6 was December 2021 - Missing modern features

New (2025): - Python 3.13+ (released October 2024) - 15-20% faster than 3.10 - Better error messages - Pattern matching (3.10+) - Improved type hints - Active security support

Dependency Management

Old (2019):

# pip-tools workflow
pip install pip-tools
pip-compile requirements.in
pip-sync requirements.txt

New (2025):

# uv workflow (much faster)
uv pip compile requirements.in -o requirements.txt
uv pip sync requirements.txt

Why uv? - 10-100x faster than pip - Better dependency resolution - Compatible with existing workflows - Single binary, no Python dependency

Code Quality

Old (2019):

# Multiple tools, slow
black .
isort .
flake8 .
pylint myproject/

New (2025):

# Single tool, extremely fast
ruff check --fix .
ruff format .

Why Ruff? - 10-100x faster than Black/flake8 - Replaces multiple tools - Compatible with Black/flake8 configs - Written in Rust

Task Running

Old (2019):

# Makefile or invoke
invoke lint
invoke test

New (2025):

# just - cleaner syntax
just lint
just test

myproject/
├── .devcontainer/          # Devcontainer configuration
│   └── devcontainer.json
├── docs/                   # Documentation
├── myproject/              # Source code
│   ├── __init__.py
│   ├── models/
│   ├── views/
│   └── utils/
├── tests/                  # Tests
│   ├── unit/
│   ├── integration/
│   └── conftest.py
├── .env.example            # Environment variable template
├── .gitignore
├── .pre-commit-config.yaml # Pre-commit hooks
├── .python-version         # Pin Python version
├── justfile                # Task automation
├── pyproject.toml          # Project configuration
├── README.md
└── requirements.in         # Direct dependencies

Best Practices

Version Management

  • ✅ Use .python-version file to pin exact Python version
  • ✅ Use pyenv or asdf for managing multiple Python versions
  • ✅ Target Python 3.13+ for new projects
  • ✅ Plan upgrades for projects on older versions

Dependency Management

  • ✅ Use uv for speed and reliability
  • ✅ Split requirements: base.in, dev.in, test.in
  • ✅ Pin direct dependencies, let uv resolve transitive ones
  • ✅ Run pip-audit regularly for security vulnerabilities
  • ❌ Don't check in __pycache__ or .venv

Code Quality

  • ✅ Use Ruff for linting and formatting
  • ✅ Configure in pyproject.toml for centralization
  • ✅ Run via pre-commit hooks
  • ✅ Address lint errors, don't just disable them
  • ✅ Use type hints gradually (don't need 100% coverage)

Testing

  • ✅ Use pytest exclusively
  • ✅ Organize tests by type (unit, integration, ui)
  • ✅ Aim for 80%+ coverage (be pragmatic)
  • ✅ Use fixtures for setup/teardown
  • ✅ Mock external services
  • ❌ Don't test third-party libraries

Common Patterns

Environment Variables

import os
from pathlib import Path

# Development
DEBUG = os.getenv("DEBUG", "False") == "True"

# Database
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///db.sqlite3")

# Secrets (never hardcode!)
SECRET_KEY = os.getenv("SECRET_KEY")
if not SECRET_KEY:
    raise ValueError("SECRET_KEY environment variable is required")

Logging

import logging
import structlog

# Structured logging for production
structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.JSONRenderer()
    ],
    logger_factory=structlog.stdlib.LoggerFactory(),
)

logger = structlog.get_logger()
logger.info("user_login", user_id=123, ip_address="1.2.3.4")

Error Handling

from typing import Optional

def get_user(user_id: int) -> Optional[User]:
    """Get user by ID.

    Args:
        user_id: The user's ID

    Returns:
        User object if found, None otherwise

    Raises:
        DatabaseError: If database connection fails
    """
    try:
        return User.objects.get(id=user_id)
    except User.DoesNotExist:
        logger.warning("user_not_found", user_id=user_id)
        return None
    except DatabaseError as e:
        logger.error("database_error", user_id=user_id, error=str(e))
        raise

Performance Considerations

Python 3.13 Improvements

  • 15-20% faster than Python 3.10
  • Better memory management
  • Faster startup time
  • Improved GC performance

Tool Performance

Operation Old Tools New Tools Improvement
Formatting Black (1.2s) Ruff (0.01s) 100x faster
Linting flake8 (3s) Ruff (0.02s) 150x faster
Dependency resolution pip (30s) uv (0.5s) 60x faster

These speed improvements dramatically improve developer experience.

Next Steps


Migration Strategy

If you have existing projects on older Python or tools: 1. Start with Ruff (easiest, fastest wins) 2. Migrate to uv (improves CI/CD times) 3. Upgrade Python version (plan carefully, test thoroughly)