MypyFastAPIStaticTyping

    Mypy for FastAPI: A Practical Beginner Guide (Using This Project)

    If you are building a FastAPI app, mypy helps you catch type bugs early—before runtime and before tests fail in surprising ways. This guide shows: how to add mypy to a project how...

    May 12, 2026
    4 min read
    174 views
    Mypy for FastAPI: A Practical Beginner Guide (Using This Project)

    If you are building a FastAPI app, mypy helps you catch type bugs early—before runtime and before tests fail in surprising ways.

    This guide shows:

    • how to add mypy to a project
    • how to run type checking
    • how to exclude paths from checks
    • how to enable additional useful checks
    • examples from this pytest-fastapi application

    1) What is mypy?

    mypy is a static type checker for Python.
    It reads type hints (like str, int, list[ProductRead], ProductRead | None) and reports mismatches.

    Example:

    • returning str from a function declared as -> int
    • passing None where a non-optional type is required
    • calling methods on values that may be None

    2) Add mypy to your project

    In this project, mypy is already added in pyproject.toml:

    toml
    [project]
    dependencies = [
        "mypy>=2.1.0",
    ]

    If you’re starting fresh with uv:

    bash
    uv add mypy

    Or with pip:

    bash
    pip install mypy

    3) Basic mypy config

    This project uses mypy.ini:

    ini
    [mypy]
    python_version = 3.12
    strict = True
    
    warn_return_any = True
    warn_unused_configs = True
    disallow_untyped_defs = True
    
    exclude = versions/

    Why these options matter

    • python_version = 3.12: checks with Python 3.12 typing rules.
    • strict = True: enables a strong baseline of safety checks.
    • warn_return_any = True: warns when a function leaks Any.
    • warn_unused_configs = True: catches config typos/unused sections.
    • disallow_untyped_defs = True: forces every function to have type annotations.
    • exclude = versions/: skip files under versions/ (commonly Alembic migrations).

    4) Run mypy checks

    Run type checks for app code:

    bash
    uv run mypy app

    Check both app and tests:

    bash
    uv run mypy app tests

    Tip: In CI, fail the pipeline if mypy reports errors.


    5) Real examples from this app

    Example A: Typed service method

    From app/services/product_service.py, this method is clearly typed:

    python
    def get_product(self, product_id: int) -> ProductRead:
        product = self.repository.get(product_id)
        if product is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Product not found")
        return ProductRead.model_validate(product)

    Benefits:

    • input is guaranteed int
    • return type is guaranteed ProductRead
    • None is handled before returning

    Example B: Typed route response

    From app/routes/product.py:

    python
    @router.get("/", response_model=list[ProductRead])
    def list_products(
        service: ProductService = Depends(get_product_service),
    ) -> list[ProductRead]:
        return service.list_products()

    mypy ensures the function actually returns list[ProductRead].

    Example C: Optional return handling

    This route handles a potentially missing record safely:

    python
    def update_product(...) -> ProductRead:
        product = service.update_product(product_id, payload)
        if product is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Product not found")
        return product

    Good pattern: check None explicitly before returning.


    6) How to exclude files/folders

    Sometimes you want to skip generated or legacy files (for example migrations).

    Current config:

    ini
    exclude = versions/

    You can expand this using regex:

    ini
    exclude = (?x)(
        ^versions/|
        ^.*/migrations/|
        ^tests/fixtures/
    )

    Keep excludes minimal. Over-excluding hides real issues.


    If you want stricter validation, add these in mypy.ini:

    ini
    [mypy]
    no_implicit_optional = True
    check_untyped_defs = True
    warn_redundant_casts = True
    warn_unused_ignores = True
    warn_unreachable = True

    What they catch:

    • no_implicit_optional: forces explicit | None
    • check_untyped_defs: checks function bodies even if partially untyped
    • warn_redundant_casts: flags unnecessary cast(...)
    • warn_unused_ignores: removes dead # type: ignore
    • warn_unreachable: catches impossible code paths

    8) Useful pattern: per-module tuning

    You can keep strict mode globally, then relax specific areas:

    ini
    [mypy]
    strict = True
    
    [mypy-tests.*]
    disallow_untyped_defs = False

    This is useful when tests are still being gradually typed.


    9) Suggested mypy workflow for this project

    1. Keep strict = True in mypy.ini
    2. Run uv run mypy app tests locally before commit
    3. Exclude only generated code (like migrations)
    4. Add extra warnings incrementally
    5. Fix new type errors immediately to avoid backlog

    Final thoughts

    For FastAPI projects, type hints are already close to your business logic (schemas, routes, services).
    That makes mypy a high-value, low-cost quality gate.

    Start with the config you already have in this app, then add stricter checks step by step.

    J
    Written by

    Jobi S S

    Portfolio

    admin

    Sharing technical insights, engineering concepts, and practical modern software development guides.

    Community Discussion

    Enjoyed this read? Show your support or share your thoughts.

    Comments (0)

    No comments yet. Be the first to comment!

    📬 Enjoyed this article?

    Get new posts on Django, FastAPI, and system design straight to your inbox. No spam — unsubscribe whenever you want.