Python’s Third-Party Ecosystem for Web Development

Python’s third-party ecosystem โ€” packages available on PyPI and installed with pip โ€” is one of its greatest strengths. For FastAPI web development, a small set of well-maintained packages handles nearly every concern: validation, database access, migrations, authentication, email, file storage, and testing. Knowing which package to reach for โ€” and crucially, which packages are standard choices in the FastAPI community vs alternatives โ€” lets you build on proven foundations rather than reinventing wheels. This lesson surveys the core packages you will use throughout the rest of this series.

The Core FastAPI Ecosystem

Package Purpose Install
fastapi The web framework โ€” routing, validation, docs pip install fastapi
uvicorn ASGI server โ€” runs the FastAPI app pip install uvicorn[standard]
pydantic Data validation, settings management (comes with FastAPI) pip install pydantic
pydantic-settings Environment variable / .env file settings pip install pydantic-settings
sqlalchemy ORM + query builder for PostgreSQL pip install sqlalchemy
alembic Database schema migrations pip install alembic
psycopg2-binary PostgreSQL driver for Python pip install psycopg2-binary
asyncpg Async PostgreSQL driver (faster) pip install asyncpg
python-jose JWT creation and verification pip install "python-jose[cryptography]"
passlib Password hashing (bcrypt) pip install "passlib[bcrypt]"
python-multipart File upload support for FastAPI pip install python-multipart
httpx Async HTTP client + test client pip install httpx
python-dotenv Load .env files into environment pip install python-dotenv
Note: FastAPI installs Pydantic v2 as a dependency automatically โ€” you do not need to install Pydantic separately. However, Pydantic v1 and v2 have different APIs. This series uses Pydantic v2 throughout. If you see from pydantic import validator in older tutorials, that is v1 syntax โ€” the v2 equivalent is @field_validator. Always check which Pydantic version a tutorial or Stack Overflow answer targets before copying code.
Tip: Install the entire FastAPI stack in one command with extras: pip install "fastapi[all]" installs FastAPI along with Uvicorn, email-validator, and other optional extras that FastAPI can use. For production, be more selective โ€” only install what you actually use. Unnecessary packages increase image size in Docker deployments and create more surface area for security vulnerabilities.
Warning: The Python ecosystem moves fast โ€” package APIs change between major versions. SQLAlchemy 2.0 has a significantly different API from 1.4, and Pydantic v2 has a different API from v1. When following tutorials, always check the package version the tutorial uses and compare it to the version you have installed. Use pip show packagename to see the installed version and check the package’s migration guide for any breaking changes.

pydantic-settings โ€” Configuration Management

from pydantic_settings import BaseSettings
from pydantic import AnyHttpUrl
from typing import List

class Settings(BaseSettings):
    # Database
    database_url: str = "postgresql://localhost/blogdb"

    # Auth
    secret_key: str = "changeme-in-production"
    algorithm: str  = "HS256"
    access_token_expire_minutes: int = 30

    # CORS
    allowed_origins: List[AnyHttpUrl] = ["http://localhost:5173"]

    # App
    app_name: str = "Blog API"
    debug: bool   = False

    class Config:
        env_file = ".env"            # load from .env file
        case_sensitive = False       # DATABASE_URL or database_url both work

# Singleton โ€” create once, import everywhere
settings = Settings()

# Usage anywhere:
# from app.config import settings
# print(settings.database_url)

Quick Overview of Key Packages

# โ”€โ”€ passlib โ€” password hashing โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(plain: str) -> str:
    return pwd_context.hash(plain)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

# โ”€โ”€ python-jose โ€” JWT tokens โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
from jose import jwt, JWTError
from datetime import datetime, timedelta, timezone

SECRET_KEY = "your-256-bit-secret"
ALGORITHM  = "HS256"

def create_access_token(data: dict, expires_minutes: int = 30) -> str:
    payload = {
        **data,
        "exp": datetime.now(timezone.utc) + timedelta(minutes=expires_minutes)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def decode_token(token: str) -> dict:
    return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

# โ”€โ”€ httpx โ€” async HTTP client โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import httpx

async def fetch_external_api(url: str) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(url, timeout=10.0)
        response.raise_for_status()    # raises on 4xx/5xx
        return response.json()

# httpx also provides a sync test client for FastAPI tests:
# from httpx import TestClient (used in pytest fixtures)

Development Tools

Tool Purpose Command
pytest Test runner pip install pytest pytest-asyncio
black Code formatter (auto-format) pip install black
ruff Fast linter (replaces flake8, isort) pip install ruff
mypy Static type checker pip install mypy
pre-commit Run tools before every commit pip install pre-commit
# Format code with black
black app/ tests/

# Lint with ruff (fast)
ruff check app/
ruff check --fix app/    # auto-fix fixable issues

# Type check with mypy
mypy app/

# Run tests with pytest
pytest tests/ -v
pytest tests/ -v --cov=app   # with coverage report

Common Mistakes

Mistake 1 โ€” Using Pydantic v1 syntax with v2 installed

โŒ Wrong โ€” v1 validator syntax in v2:

from pydantic import validator   # ImportError in Pydantic v2!

โœ… Correct โ€” v2 syntax:

from pydantic import field_validator   # โœ“ Pydantic v2

Mistake 2 โ€” Using SQLAlchemy 1.x query syntax with 2.0 installed

โŒ Wrong โ€” legacy query API (deprecated in 2.0):

db.query(Post).filter(Post.id == 1).first()   # legacy โ€” still works but deprecated

โœ… Correct โ€” SQLAlchemy 2.0 style:

from sqlalchemy import select
db.execute(select(Post).where(Post.id == 1)).scalar_one_or_none()   # โœ“ 2.0

Mistake 3 โ€” Hardcoding secrets in code instead of using pydantic-settings

โŒ Wrong โ€” secret in source code:

SECRET_KEY = "super-secret-do-not-commit"   # committed to git!

โœ… Correct โ€” read from environment:

class Settings(BaseSettings):
    secret_key: str   # required โ€” must be in .env or environment โœ“

Quick Reference โ€” Install Command

# Install the complete FastAPI + PostgreSQL stack
pip install fastapi uvicorn[standard] sqlalchemy alembic \
    psycopg2-binary pydantic-settings \
    "python-jose[cryptography]" "passlib[bcrypt]" \
    python-multipart httpx python-dotenv

# Install dev tools
pip install pytest pytest-asyncio black ruff mypy

🧠 Test Yourself

Your FastAPI app reads its database URL from an environment variable. Which approach correctly loads it using pydantic-settings?