Errors, Testing & Tooling

Exceptions, pytest, logging, ruff/black, and typing with mypy or pyright.

Python errors are exceptions, not return codes. Test with pytest; lint/format with ruff + black in modern repos.

Core concepts

  1. try/except/else/finally: Catch specific exceptions; avoid bare except:. — Python docs: Errors and exceptions
  2. Custom exceptions: Subclass Exception with clear names. — Python docs: User-defined exceptions
  3. pytest: assert driven tests; fixtures for DB/API setup. — pytest getting started
  4. logging: logging.getLogger(__name__) — not print in production. — Python docs: Logging HOWTO
  5. Static typing: Gradual typing with mypy/pyright on CI. — mypy getting started
python
import pytest

def divide(a: int, b: int) -> float:
    if b == 0:
        raise ValueError("b must be non-zero")
    return a / b

def test_divide():
    assert divide(10, 2) == 5
    with pytest.raises(ValueError):
        divide(1, 0)

Concept videos

YouTube lessons for these topics. On a large screen they open in the reading panel next to the roadmap, same as the article links on each core concept.

Explanation

Exceptions are red flags on the playfield — they stop normal flow and jump to a handler. Testing and logging turn chaos into observable, fixable systems.

try/except/else/finally

Catch specific exceptions (ValueError, HTTPError) instead of bare except: which hides bugs. else runs when no exception occurred; finally always runs for cleanup. Structure mirrors real workflows: try the risky step, handle known failures, always close resources.

Custom exceptions

Domain errors (PaymentDeclined, InvalidToolArguments) make logs searchable and APIs clearer than generic Exception. Subclass Exception, not BaseException, unless you know why.

pytest

pytest discovers tests automatically, uses plain assert, and supports fixtures for shared setup (database, HTTP client). Parametrize tests to cover edge cases without copy-paste.

logging

print disappears in production log aggregators. The logging module levels (DEBUG, INFO, WARNING) let you tune verbosity. Include context (request id, user id) in messages for tracing.

Static typing

Type hints document contracts; mypy/pyright catch mismatches before runtime. Gradual typing means you can annotate hot paths first without rewriting the entire codebase.

What Is It?

Python signals errors with exceptions propagated up the stack until handled.

pytest discovers tests, asserts outcomes, and supports fixtures for setup/teardown.

ruff lints quickly; black formats; mypy/pyright optional for types.

Why Should I Care?

Untested exception paths kill production agents when tool calls fail.

Consistent formatting keeps diffs readable in GitHub.

Real World Analogy

pytest is a spellchecker for behavior — it underlines sentences that no longer mean what you think after an edit.

Visual Mental Model

text
ruff/black -> pytest -> CI

Beginner Explanation

Raise specific exceptions, log context, and test with pytest fixtures. Let ruff and black handle style so reviews focus on logic.

Smallest Possible Example

python
def divide(a: float, b: float) -> float:
    if b == 0:
        raise ValueError("b must be non-zero")
    return a / b

Common Mistakes

❌ Bare except: catching everything including KeyboardInterrupt.

Catch specific exceptions; let process signals through.

❌ Tests that depend on execution order.

pytest runs tests in isolation — share state via fixtures, not globals.

❌ Skipping logging configuration in services.

Structured logs are how you debug prod without pdb.

Real Project Example

GitHub Actions runs pytest on most Python open source.

Sentry captures stack traces from raised exceptions in FastAPI apps.

Build This Mini Project

Add pytest tests for a function's happy path and ValueError path; run ruff check and black on the folder.

Interview Question

How do pytest fixtures help?

Fixtures provide reusable setup (DB connections, temp files, clients) injected into tests, keeping tests small and avoiding duplicate boilerplate.

Key Takeaways

  • Raise meaningful exception types.
  • pytest for automated behavior checks.
  • Lint/format in CI.
  • Log structured context in services.

Resources

The 60-second story

A cashier’s drawer jams. You do not flip the whole store’s power. You deal with a jammed drawer. try is the sale. except ValueError is “this specific jam.” A bare except: is catching every problem including “the building is on fire” and then smiling. else runs when nothing jammed. finally closes the drawer either way.

Custom exceptions are name tags on the jam. class DrawerJam(Exception) tells the next person which aisle. Exception as a class name in a log is how on-call engineers lose an hour.

pytest is a friend who walks in before opening and says “charge $4.50 for a coffee” and fails the build if you charge $45 because of a decimal slip. Fixtures are the pre-opened drawer and the fake card reader so the test does not call a real bank.

print is shouting in the kitchen. logging.getLogger(__name__) is a logbook with levels. Production can keep warnings and hide the play-by-play. Prints stay in the binary until someone is embarrassed.

Type checkers are the person who reads the menu labels. mypy and pyright do not stop Python from running a lie. They stop the lie from merging when you ask them to. Gradual typing means you label the hot aisle first, not the whole mall in a weekend.

Map the store.

  • try / except is the sale and the specific jam.
  • A bare except is ignoring the fire alarm.
  • A custom exception is a name tag.
  • pytest is the friend before opening.
  • A fixture is the prepped drawer.
  • logging is the logbook.
  • mypy or pyright is the label check.
python
class DrawerJam(Exception):
    pass

def charge(amount):
    if amount < 0:
        raise DrawerJam("negative charge")
    return amount

The test is one line of intent: assert charge(4.5) == 4.5. If it raises, pytest shows you the name tag instead of a novel.

These tools exist because “it worked when I clicked” is not a system. Exceptions were shaped so you can catch the jam you understand and let the rest crash loudly. Loud crashes get fixed. Swallowed crashes become folklore.

Mental model: name the jam, write it in the logbook, and let a friend try the sale before customers do.

You have the page when bare except feels rude, and when you would rather see a failing test on your laptop than a polite empty catch in production. If a teammate asks why the logbook matters, tell them a print disappears into the noise and a named jam does not.

Threads in the same domain as this chapter — go deeper on iHateReading without leaving the roadmap.

Python AI agent SDKs in 2026: OpenAI vs Pydantic-AI vs smolagents vs Strands cover

Python AI agent SDKs in 2026: OpenAI vs Pydantic-AI vs smolagents vs Strands

Same three-tool agent in four frameworks — pick by execution model and testing needs.

Making AI applications without any AI slop cover

Making AI applications without any AI slop

Ship agent features with human gates instead of unreviewed automation.