CodeSampleX

샘플

fastapi 0.141.1: Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence

검증된 샘플 — pypi fastapi 0.141.1: Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence. python 3.12 …

sha256:f0072757a2777940751ef402b9be2ab1a50015019493ad42d2c8972723843885

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
1
빌드한 서명 키
1
선언된 환경 python linux 24 · ubuntu · glibc 2.39 x64 python python pip

검증 실행 환경

환경 컨트랙트 단계 실행일
python 3.12 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1python:3.12-alpine@sha256:d09d15e60962…
2026-08-27

케이스

HOW
목표
Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence
패키지
심벌
  • fastapi.APIRouter
  • fastapi.FastAPI
  • fastapi.Depends
  • fastapi.HTTPException
  • fastapi.testclient.TestClient
환경
python
생성일
2026-08-27T21:40:39Z

컨트랙트

  1. assert APIRouter prefix concatenates with include_router prefix so child router routes are exposed under parent and app prefixes
  2. assert router-level dependencies execute before endpoint-level dependencies and block unauthorized requests with HTTPException
  3. assert nested routers inherit parent router dependencies and execute them in top-down hierarchical order
  4. assert static route registered before parameterized route matches exact path instead of capturing parameter
  5. assert route response_model on APIRouter filters dictionary output to declared Pydantic schema fields
  6. assert APIRouter status_code parameter sets HTTP status code on response
  7. assert app.dependency_overrides intercepts dependencies declared at the APIRouter level
  8. assert direct endpoint function call bypasses APIRouter dependency resolution and routing decorators

파일

  • NOTES.md
  • PROMPT.md
  • csx.json
  • requirements.txt
  • spec.json
  • src/__init__.py
  • src/app.py
  • test/contract.py

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# FastAPI APIRouter Modular Routing, Dependency Cascading, and Path Precedence

## Search Results
`search_known_solution` returned `NO_SAFE_MATCH` for `fastapi.APIRouter` modular routing with nested router hierarchies, router-level dependency cascading, and path evaluation precedence. Existing samples for `pkg:pypi/fastapi@0.141.1` covered general TestClient error payloads, query parameter binding, header validation, and generator dependency lifecycles, but lacked modular APIRouter composition contracts.

## What a Model Would Have Written
A model writing FastAPI APIRouter applications often assumes:
1. Static routes defined after dynamic path parameter routes (e.g. `/users/{user_id}` placed before `/users/me`) will still resolve `/users/me` specifically to the `/me` handler.
2. Router-level dependencies (`APIRouter(dependencies=[...])`) only apply to direct routes, not cascaded down to child routers attached via `parent_router.include_router(child_router)`.
3. Dependency overrides via `app.dependency_overrides` only intercept endpoint-level `Depends(...)` declarations and miss router-level `dependencies` lists.

## How the Wrong Version Fails
- Placing dynamic path parameter routes before static routes causes FastAPI to eagerly match `/users/me` against `/users/{user_id}` with `user_id="me"`, either returning unexpected data or failing type validation (e.g. 422 Unprocessable Entity if `user_id: int`).
- In nested routers, assuming parent dependencies are not inherited leads to missing authentication/authorization checks on sub-router endpoints if they aren't explicitly duplicated.
- Direct endpoint function invocation outside the ASGI pipeline bypasses APIRouter dependency verification and response schema filtering entirely.
PROMPT.md
Clean-room public code sample — generation instructions

Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.

A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.

Goal: Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence
Kind: HOW

Use EXACTLY these public packages and versions:
  - pkg:pypi/fastapi@0.141.1
Demonstrate these symbols/APIs:
  - fastapi.APIRouter

Rules:
  - One focused purpose; the smallest project that proves the goal.
  - Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
  - Pin every dependency with a lockfile so resolution is reproducible.
  - No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
  - No personal names, emails, company names, or project identifiers of any kind.
  - No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
  - Keep it under 200 files and 256KB packed.
csx.json
{"case":{"caseId":"case:sha256:3775d81e18907b504305ec9c9b9ba9538e28585019469743385bbe397a307c74","contract":["assert APIRouter prefix concatenates with include_router prefix so child router routes are exposed under parent and app prefixes","assert router-level dependencies execute before endpoint-level dependencies and block unauthorized requests with HTTPException","assert nested routers inherit parent router dependencies and execute them in top-down hierarchical order","assert static route registered before parameterized route matches exact path instead of capturing parameter","assert route response_model on APIRouter filters dictionary output to declared Pydantic schema fields","assert APIRouter status_code parameter sets HTTP status code on response","assert app.dependency_overrides intercepts dependencies declared at the APIRouter level","assert direct endpoint function call bypasses APIRouter dependency resolution and routing decorators"],"goal":"Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence","kind":"HOW","packages":["pkg:pypi/fastapi@0.141.1","pkg:pypi/starlette@1.6.0","pkg:pypi/pydantic@2.13.4","pkg:pypi/httpx@0.28.1"],"schemaVersion":1,"symbols":["fastapi.APIRouter","fastapi.FastAPI","fastapi.Depends","fastapi.HTTPException","fastapi.testclient.TestClient"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","distro":"ubuntu","ecosystem":"pypi","executionContext":"python","language":"python","libc":"glibc","libcVersion":"2.39","os":"linux","osVersionBucket":"24","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/fastapi@0.141.1","pkg:pypi/starlette@1.6.0","pkg:pypi/pydantic@2.13.4","pkg:pypi/httpx@0.28.1"],"schemaVersion":1,"subject":"pkg:pypi/fastapi@0.141.1","symbols":["fastapi.APIRouter","fastapi.FastAPI","fastapi.Depends","fastapi.HTTPException","fastapi.testclient.TestClient"],"verifierAdapter":"python@1"}
requirements.txt
fastapi==0.141.1
starlette==1.6.0
annotated-doc==0.0.5
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.16.0
typing-inspection==0.4.4
annotated-types==0.8.0
anyio==4.14.2
idna==3.18
httpx2==2.10.0
httpcore2==2.10.0
truststore==0.10.4
httpx==0.28.1
httpcore==1.0.9
h11==0.16.0
certifi==2026.7.22
spec.json
{
  "schemaVersion": 1,
  "goal": "Modular routing with FastAPI APIRouter prefix nesting route dependencies response models and path precedence",
  "kind": "HOW",
  "packages": [
    "pkg:pypi/fastapi@0.141.1"
  ],
  "symbols": [
    "fastapi.APIRouter"
  ]
}
src/__init__.py
"""Package root for src."""
src/app.py
"""FastAPI application demonstrating APIRouter modular routing, dependency cascading, and path precedence."""

from typing import Any
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Header, status
from pydantic import BaseModel

app = FastAPI()

AUDIT_TRAIL: list[str] = []


# --- Router & Endpoint Dependencies ---
def verify_api_key(x_api_key: str = Header(default="")) -> str:
    AUDIT_TRAIL.append(f"auth:api_key:{x_api_key}")
    if x_api_key != "secret-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or missing API key",
        )
    return x_api_key


def verify_admin_role(x_role: str = Header(default="user")) -> str:
    AUDIT_TRAIL.append(f"auth:role:{x_role}")
    if x_role != "admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin role required",
        )
    return x_role


# --- Pydantic Models for Response Filtering ---
class ItemCreate(BaseModel):
    name: str
    price: float
    secret_code: str


class ItemResponse(BaseModel):
    id: int
    name: str
    price: float


# --- Root / Base Routers ---
# 1. Parent API Router with router-level dependency
api_v1_router = APIRouter(
    prefix="/api/v1",
    dependencies=[Depends(verify_api_key)],
    tags=["v1"],
)

# 2. Child Router for Items, nested inside api_v1_router
items_router = APIRouter(prefix="/items", tags=["items"])

# Path Precedence: Static route "/me" registered BEFORE parameterized route "/{item_id}"
@items_router.get("/me")
def get_current_user_items() -> dict[str, str]:
    AUDIT_TRAIL.append("endpoint:get_current_user_items")
    return {"owner": "current_user", "status": "active"}


@items_router.get("/{item_id}")
def get_item_by_id(item_id: int) -> dict[str, Any]:
    AUDIT_TRAIL.append(f"endpoint:get_item_by_id:{item_id}")
    return {"id": item_id, "name": f"Item-{item_id}"}


@items_router.post(
    "",
    response_model=ItemResponse,
    status_code=status.HTTP_201_CREATED,
)
def create_item(item: ItemCreate) -> dict[str, Any]:
    AUDIT_TRAIL.append(f"endpoint:create_item:{item.name}")
    return {
        "id": 101,
        "name": item.name,
        "price": item.price,
        "secret_code": item.secret_code,
    }


# 3. Admin Child Router with additional router-level dependency
admin_router = APIRouter(
    prefix="/admin",
    dependencies=[Depends(verify_admin_role)],
    tags=["admin"],
)


@admin_router.get("/dashboard")
def get_admin_dashboard() -> dict[str, str]:
    AUDIT_TRAIL.append("endpoint:get_admin_dashboard")
    return {"status": "ok", "panel": "admin_main"}


# Mount child routers onto parent router
api_v1_router.include_router(items_router)
api_v1_router.include_router(admin_router)

# Include parent router into the main FastAPI application
app.include_router(api_v1_router)


# 4. Standalone Public Router without auth
public_router = APIRouter(prefix="/public")


@public_router.get("/health")
def health_check() -> dict[str, str]:
    return {"status": "healthy"}


app.include_router(public_router)
test/contract.py
import sys
from pathlib import Path

# Insert project root for module imports
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import fastapi
from fastapi import __version__ as fastapi_version
from fastapi.testclient import TestClient

from src.app import (
    AUDIT_TRAIL,
    app,
    get_item_by_id,
    verify_api_key,
)

client = TestClient(app)

# 1. Router prefix concatenation and sub-router routing
# Routes should be available under /api/v1/items/...
AUDIT_TRAIL.clear()
res_health = client.get("/public/health")
assert res_health.status_code == 200
assert res_health.json() == {"status": "healthy"}

# 2. Router-level dependencies block unauthorized requests
# /api/v1/... requires X-API-Key header defined at api_v1_router level
res_unauth = client.get("/api/v1/items/42")
assert res_unauth.status_code == 401
assert res_unauth.json()["detail"] == "Invalid or missing API key"

# Authorized request executes router-level dependency before route handler
AUDIT_TRAIL.clear()
res_auth = client.get("/api/v1/items/42", headers={"x-api-key": "secret-token"})
assert res_auth.status_code == 200
assert res_auth.json() == {"id": 42, "name": "Item-42"}
assert AUDIT_TRAIL == [
    "auth:api_key:secret-token",
    "endpoint:get_item_by_id:42",
], "Router dependency must execute before endpoint handler"

# 3. Nested router dependency cascading (parent dependency + child dependency)
# /api/v1/admin/dashboard inherits api_v1_router auth and adds admin_router auth
AUDIT_TRAIL.clear()
res_admin_no_role = client.get(
    "/api/v1/admin/dashboard",
    headers={"x-api-key": "secret-token", "x-role": "user"},
)
assert res_admin_no_role.status_code == 403
assert res_admin_no_role.json()["detail"] == "Admin role required"
assert AUDIT_TRAIL == [
    "auth:api_key:secret-token",
    "auth:role:user",
], "Parent router dependency executes first, followed by nested router dependency"

AUDIT_TRAIL.clear()
res_admin_ok = client.get(
    "/api/v1/admin/dashboard",
    headers={"x-api-key": "secret-token", "x-role": "admin"},
)
assert res_admin_ok.status_code == 200
assert res_admin_ok.json() == {"status": "ok", "panel": "admin_main"}
assert AUDIT_TRAIL == [
    "auth:api_key:secret-token",
    "auth:role:admin",
    "endpoint:get_admin_dashboard",
]

# 4. Path precedence: static route before parameterized route
# /api/v1/items/me matches get_current_user_items, NOT get_item_by_id with item_id="me"
AUDIT_TRAIL.clear()
res_me = client.get("/api/v1/items/me", headers={"x-api-key": "secret-token"})
assert res_me.status_code == 200
assert res_me.json() == {"owner": "current_user", "status": "active"}
assert AUDIT_TRAIL == [
    "auth:api_key:secret-token",
    "endpoint:get_current_user_items",
]

# Parameterized route with invalid type triggers 422
res_invalid_id = client.get(
    "/api/v1/items/invalid-number",
    headers={"x-api-key": "secret-token"},
)
assert res_invalid_id.status_code == 422

# 5. Response model filtering and status_code on APIRouter endpoints
AUDIT_TRAIL.clear()
res_create = client.post(
    "/api/v1/items",
    headers={"x-api-key": "secret-token"},
    json={"name": "Widget", "price": 19.99, "secret_code": "TOP_SECRET"},
)
assert res_create.status_code == 201
body = res_create.json()
assert body == {"id": 101, "name": "Widget", "price": 19.99}
assert "secret_code" not in body, "response_model must filter out undeclared secret fields"

# 6. Dependency overrides intercept router-level dependencies
app.dependency_overrides[verify_api_key] = lambda: "mocked-key"
AUDIT_TRAIL.clear()
res_overridden = client.get("/api/v1/items/99")
assert res_overridden.status_code == 200
assert res_overridden.json() == {"id": 99, "name": "Item-99"}
app.dependency_overrides.clear()

# 7. Direct function invocation bypasses APIRouter dependency and routing pipeline
direct_val = get_item_by_id(item_id=7)
assert direct_val == {"id": 7, "name": "Item-7"}

print(f"APIRouter contract verified successfully against fastapi {fastapi_version}")

오리진 시더

익명