Standard Library Essentials — os, pathlib, datetime and more

Python’s standard library — the collection of modules that ship with every Python installation — contains battle-tested, well-documented tools for nearly every common programming task. You do not need to install anything to use them. For FastAPI development, a handful of standard library modules appear in almost every project: os and pathlib for file paths and environment variables, datetime for timestamps, json for serialisation, hashlib and secrets for security, uuid for unique IDs, and re for pattern matching. Mastering these saves you from reaching for unnecessary third-party packages.

os and pathlib — File System Operations

import os
from pathlib import Path

# ── os — environment variables ─────────────────────────────────────────────────
db_url   = os.getenv("DATABASE_URL", "postgresql://localhost/dev")
debug    = os.getenv("DEBUG", "false").lower() == "true"
port     = int(os.getenv("PORT", "8000"))
all_env  = os.environ    # dict-like object of ALL env vars

# Set an env var (for current process only)
os.environ["MY_KEY"] = "my_value"

# ── pathlib — modern file path handling ───────────────────────────────────────
# Path is cross-platform — uses / on Linux/Mac, \ on Windows automatically
base_dir    = Path(__file__).parent          # directory of current file
project_dir = base_dir.parent               # parent directory
uploads_dir = project_dir / "uploads"       # join with / operator
config_file = project_dir / "config.json"

# Check existence
uploads_dir.exists()    # True/False
config_file.is_file()   # True/False
uploads_dir.is_dir()    # True/False

# Create directories
uploads_dir.mkdir(parents=True, exist_ok=True)   # mkdir -p equivalent

# Read and write files
content  = config_file.read_text(encoding="utf-8")
config_file.write_text('{"debug": true}', encoding="utf-8")
raw_bytes = config_file.read_bytes()

# Path properties
config_file.name       # "config.json"
config_file.stem       # "config"
config_file.suffix     # ".json"
config_file.parent     # Path to parent directory
str(config_file)       # "/home/user/project/config.json"

# List files in directory
py_files = list(project_dir.glob("**/*.py"))   # recursive .py file search
Note: Always use pathlib.Path instead of string-based path manipulation (os.path.join(), string concatenation) for new code. pathlib provides a cleaner, more readable API — the / operator for joining paths is much clearer than os.path.join(base, "subdir", "file.txt"). It also handles cross-platform path differences automatically. FastAPI’s StaticFiles and file upload handling both work with pathlib.Path objects.
Tip: Use Path(__file__).parent to get the directory containing the current Python file — this gives you a reliable base path regardless of which directory you run the application from. Store it as a module-level constant: BASE_DIR = Path(__file__).parent.parent in your config module, then reference BASE_DIR / "uploads" elsewhere. This is more robust than os.getcwd() which changes depending on where you launch the application.
Warning: Never hardcode absolute file paths like /home/alice/project/uploads in your application code. They will break on any machine other than yours, in any deployment environment, and in Docker containers. Always build paths relative to __file__ or a configuration variable. For the uploads directory in a FastAPI application, use UPLOAD_DIR = Path(settings.upload_dir) where upload_dir comes from an environment variable.

datetime — Timestamps and Date Arithmetic

from datetime import datetime, timedelta, timezone, date

# ── Current time ──────────────────────────────────────────────────────────────
now_local = datetime.now()                    # local time (no timezone info)
now_utc   = datetime.now(timezone.utc)        # UTC with timezone info (preferred)
now_utc2  = datetime.utcnow()                 # UTC without timezone info (deprecated)

# Always use timezone-aware datetimes in production:
from datetime import timezone
utc = timezone.utc

# ── Format and parse ──────────────────────────────────────────────────────────
now = datetime.now(utc)
iso_str  = now.isoformat()                    # "2025-08-06T14:30:00+00:00"
fmt_str  = now.strftime("%Y-%m-%d %H:%M:%S")  # "2025-08-06 14:30:00"
parsed   = datetime.strptime("2025-08-06", "%Y-%m-%d")

# ── Date arithmetic ───────────────────────────────────────────────────────────
one_hour   = timedelta(hours=1)
one_day    = timedelta(days=1)
one_week   = timedelta(weeks=1)

tomorrow   = datetime.now(utc) + one_day
last_week  = datetime.now(utc) - one_week
in_7_days  = datetime.now(utc) + timedelta(days=7)

# Token expiry — common FastAPI pattern
def make_token_expiry(minutes: int = 30) -> datetime:
    return datetime.now(timezone.utc) + timedelta(minutes=minutes)

def is_expired(expiry: datetime) -> bool:
    return datetime.now(timezone.utc) > expiry

# ── Date only (no time) ───────────────────────────────────────────────────────
today      = date.today()
birthday   = date(1990, 6, 15)
age_days   = (today - birthday).days
age_years  = age_days // 365

hashlib and secrets — Security Utilities

import hashlib
import secrets

# ── hashlib — cryptographic hashing ───────────────────────────────────────────
# Hash a string (e.g. for email verification tokens stored in DB)
token_raw    = "some_random_token_string"
token_hashed = hashlib.sha256(token_raw.encode()).hexdigest()
# "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"

# Hash a file (e.g. for integrity checking)
def hash_file(filepath):
    h = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()

# Note: Use passlib/bcrypt for PASSWORD hashing — not hashlib!
# hashlib is for tokens, file checksums, etc.
# Passwords need bcrypt/argon2 which are slow by design.

# ── secrets — cryptographically secure random generation ─────────────────────
# For tokens, API keys, verification codes — use secrets, NOT random!
token        = secrets.token_hex(32)          # 64 hex chars = 256 bits of randomness
url_token    = secrets.token_urlsafe(32)      # base64url safe — good for URLs
bytes_token  = secrets.token_bytes(32)        # 32 raw random bytes

# Generate a secure verification code
email_code   = secrets.token_hex(3).upper()   # 6-char uppercase hex code: "A3F9B2"

# Constant-time comparison (prevents timing attacks)
def verify_token(provided: str, expected: str) -> bool:
    return secrets.compare_digest(provided, expected)   # ✓ timing-safe

uuid — Unique Identifiers

import uuid

# UUID4 — random, no info leaked, most common choice
user_id  = uuid.uuid4()               # UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
id_str   = str(user_id)              # "a8098c1a-f86e-11da-bd1a-00112444be1e"
id_hex   = user_id.hex               # "a8098c1af86e11dabd1a00112444be1e" (no dashes)

# UUID1 — based on time + MAC address (leaks machine info — avoid)
# UUID5 — deterministic from namespace + name (good for generating stable IDs)
namespace = uuid.NAMESPACE_URL
stable_id = uuid.uuid5(namespace, "https://example.com/posts/42")
# Same input → always same UUID ✓

# FastAPI: use UUID as path parameter type
# @app.get("/users/{user_id}")
# async def get_user(user_id: uuid.UUID):  ← FastAPI validates + parses automatically
#     ...

# PostgreSQL UUID column (in SQLAlchemy):
# id = Column(UUID(as_uuid=True), default=uuid.uuid4)

re — Regular Expressions

import re

# ── Common patterns ───────────────────────────────────────────────────────────
EMAIL_RE   = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
URL_RE     = re.compile(r"^https?://[^\s/$.?#].[^\s]*$")
SLUG_RE    = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
PHONE_RE   = re.compile(r"^\+?[1-9]\d{7,14}$")

# ── match vs search vs findall ────────────────────────────────────────────────
text = "Contact us at support@example.com or sales@company.org"

# match — only checks at the START of string
re.match(r"\w+", "hello world")   # Match('hello')
re.match(r"\w+", "123 abc")       # Match('123')

# search — finds FIRST match anywhere in string
re.search(r"[\w.]+@[\w.]+", text).group()  # "support@example.com"

# findall — returns ALL matches as a list
emails = re.findall(r"[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}", text)
# ["support@example.com", "sales@company.org"]

# sub — replace pattern
clean = re.sub(r"<[^>]+>", "", "<p>Hello <b>world</b></p>")   # strip HTML tags
slug  = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")

# Validate with compiled pattern
def validate_email(email: str) -> bool:
    return bool(EMAIL_RE.match(email.strip()))

def is_valid_slug(slug: str) -> bool:
    return bool(SLUG_RE.match(slug))

Common Mistakes

Mistake 1 — Using datetime.utcnow() without timezone info

❌ Wrong — naive datetime (no timezone), deprecated in Python 3.12:

expires = datetime.utcnow() + timedelta(hours=1)
# naive datetime — comparisons with timezone-aware datetimes will fail

✅ Correct — timezone-aware datetime:

expires = datetime.now(timezone.utc) + timedelta(hours=1)   # ✓

Mistake 2 — Using random for security tokens

❌ Wrong — random is predictable:

import random, string
token = ''.join(random.choices(string.ascii_letters, k=32))
# Predictable! random uses a deterministic PRNG seeded by time

✅ Correct — use secrets for all security-sensitive values:

import secrets
token = secrets.token_urlsafe(32)   # cryptographically secure ✓

Mistake 3 — Compiling regex inside a function called frequently

❌ Wrong — recompiles the pattern on every call:

def validate(email):
    return bool(re.match(r"^[\w.]+@[\w.]+$", email))   # compiled every call!

✅ Correct — compile once at module level:

EMAIL_RE = re.compile(r"^[\w.]+@[\w.]+$")   # compiled once
def validate(email):
    return bool(EMAIL_RE.match(email))   # reuses compiled pattern ✓

Quick Reference

Module Key Usage in FastAPI
os.getenv() Read environment variables
pathlib.Path File paths, upload directories
datetime.now(timezone.utc) UTC timestamps for DB fields
timedelta(minutes=30) Token expiry arithmetic
secrets.token_urlsafe(32) Secure random tokens
hashlib.sha256(...).hexdigest() Hash tokens before DB storage
uuid.uuid4() Unique resource IDs
re.compile(pattern) Input validation patterns

🧠 Test Yourself

You need to generate a secure password reset token that will be stored as a hash in the database and sent as a URL parameter in the reset email. Which combination is correct?