Typorithm · medium

Check OpenAPI against behavior and clean up overrides

pythonfastapiindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Check OpenAPI against behavior and clean up overrides

0%

Concept and contract

Use documentation as a contract artifact, not as proof that a branch executes.

Generated OpenAPI describes the declared API, while runtime tests show what requests actually do. The two should agree, but a response description alone does not implement a failure branch. A useful test suite inspects both artifacts and the real endpoint response.

The fixture declares a public response model and a documented 404, then actually raises that failure for missing roles. A dependency supplies a synthetic principal name. Tests can replace it with another owned value through dependency_overrides, without contacting an identity service.

Mechanism and worked example

The override map uses the original callable as its key.

The override map uses the original callable as its key. The request runs with the chosen override, and finally clears that test state. The helper then inspects the OpenAPI response declarations and the public schema, reporting whether a private internal note appears.

The result includes overrides_cleared so cleanup is an observable requirement. A successful isolated request is not enough when leftover overrides can change later tests. Test setup should preserve any pre-existing state in a shared application; this fixture creates a new app and therefore begins with an empty override map.

Complete worked example

python

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

class PublicRole(BaseModel):
    id: int
    title: str
    user: str

def inspect_example(request):
    app = FastAPI()
    def principal():
        return "demo"
    @app.get("/roles/{role_id}", response_model=PublicRole,
             responses={404: {"description": "Role not found"}})
    def read(role_id: int, user: Annotated[str, Depends(principal)]):
        if role_id != 1:
            raise HTTPException(404, "Role not found")
        return {"id": 1, "title": "QA", "user": user, "private_note": "hidden"}
    if request.get("override"):
        app.dependency_overrides[principal] = lambda: "review"
    try:
        with TestClient(app) as client:
            response = client.get(f"/roles/{request.get('id', 1)}")
    finally:
        app.dependency_overrides.clear()
    schema = app.openapi()
    declared = schema["paths"]["/roles/{role_id}"]["get"]["responses"]
    properties = schema["components"]["schemas"]["PublicRole"]["properties"]
    return {"status": response.status_code, "body": response.json(),
            "documented_404": "404" in declared, "documented_410": "410" in declared,
            "private_in_schema": "private_note" in properties,
            "overrides_cleared": not app.dependency_overrides}
Reasoning
The override map uses the original callable as its key. The request runs with the chosen override, and finally clears that test state. The helper then inspects the OpenAPI response declarations and the public schema, reporting whether a private internal note appears. The result includes overrides_cleared so cleanup is an observable requirement. A successful isolated request is not enough when leftover overrides can change later tests. Test setup should preserve any pre-existing state in a shared application; this fixture creates a new app and therefore begins with an empty override map.

Focused decision: use the complete example for context

python

app.dependency_overrides[principal] = lambda: "review"
try:
    with TestClient(app) as client:
        response = client.get("/roles/1")
finally:
    app.dependency_overrides.clear()
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample omits cleanup.

The counterexample omits cleanup. Its immediate HTTP response can still look correct, but the override remains installed. This can cause order-dependent tests or leave privileged test context active in an improperly shared application. Explicit cleanup catches that failure before another test happens to expose it.

Do not infer that a documented 404 or 410 exists at runtime merely because it appears in responses. Likewise, an absent private field in OpenAPI does not prove every manually returned Response is sanitized. Check actual emitted bodies and failure branches separately.

Controlled mistake: observe the actual wrong behavior

python

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

class PublicRole(BaseModel):
    id: int
    title: str
    user: str

def inspect_example(request):
    app = FastAPI()
    def principal():
        return "demo"
    @app.get("/roles/{role_id}", response_model=PublicRole,
             responses={404: {"description": "Role not found"}})
    def read(role_id: int, user: Annotated[str, Depends(principal)]):
        if role_id != 1:
            raise HTTPException(404, "Role not found")
        return {"id": 1, "title": "QA", "user": user, "private_note": "hidden"}
    if request.get("override"):
        app.dependency_overrides[principal] = lambda: "review"
    try:
        with TestClient(app) as client:
            response = client.get(f"/roles/{request.get('id', 1)}")
    finally:
        pass
    schema = app.openapi()
    declared = schema["paths"]["/roles/{role_id}"]["get"]["responses"]
    properties = schema["components"]["schemas"]["PublicRole"]["properties"]
    return {"status": response.status_code, "body": response.json(),
            "documented_404": "404" in declared, "documented_410": "410" in declared,
            "private_in_schema": "private_note" in properties,
            "overrides_cleared": not app.dependency_overrides}
Reasoning
The counterexample omits cleanup. Its immediate HTTP response can still look correct, but the override remains installed. This can cause order-dependent tests or leave privileged test context active in an improperly shared application. Explicit cleanup catches that failure before another test happens to expose it. Do not infer that a documented 404 or 410 exists at runtime merely because it appears in responses. Likewise, an absent private field in OpenAPI does not prove every manually returned Response is sanitized. Check actual emitted bodies and failure branches separately.

Independent practice: one bounded change

Add an expired-role case: role ID two should return 410 with Role expired, and the OpenAPI declarations should document 410.

Add an expired-role case: role ID two should return 410 with Role expired, and the OpenAPI declarations should document 410. Keep other missing IDs as 404 and the existing role as 200. Preserve override cleanup and public projection.

Run the expired role under an override, a different missing ID and the valid role. Both the new declaration and runtime status must change. Editing only the documentation or only the test helper leaves half of the requested contract unimplemented.

Step 1: Implement and document an expired-role 410 while retaining public projection and override cleanup. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

class PublicRole(BaseModel):
    id: int
    title: str
    user: str

def inspect_example(request):
    app = FastAPI()
    def principal():
        return "demo"
    @app.get("/roles/{role_id}", response_model=PublicRole,
             responses={404: {"description": "Role not found"}})
    def read(role_id: int, user: Annotated[str, Depends(principal)]):
        if role_id != 1:
            raise HTTPException(404, "Role not found")
        return {"id": 1, "title": "QA", "user": user, "private_note": "hidden"}
    if request.get("override"):
        app.dependency_overrides[principal] = lambda: "review"
    try:
        with TestClient(app) as client:
            response = client.get(f"/roles/{request.get('id', 1)}")
    finally:
        app.dependency_overrides.clear()
    schema = app.openapi()
    declared = schema["paths"]["/roles/{role_id}"]["get"]["responses"]
    properties = schema["components"]["schemas"]["PublicRole"]["properties"]
    return {"status": response.status_code, "body": response.json(),
            "documented_404": "404" in declared, "documented_410": "410" in declared,
            "private_in_schema": "private_note" in properties,
            "overrides_cleared": not app.dependency_overrides}

# Independent task: Implement and document an expired-role 410 while retaining public projection and override cleanup.
Reasoning
Implement and document an expired-role 410 while retaining public projection and override cleanup.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution adds the response description and a specific branch before the generic missing-role branch.

The solution adds the response description and a specific branch before the generic missing-role branch. Cleanup remains in finally. The valid response still uses the resolved principal and excludes the internal note through the public model.

For a larger service, consider schema snapshots alongside semantic assertions rather than blindly accepting every regenerated file. Snapshot changes need review, and test overrides should be scoped to owned test fixtures. No demo authentication bypass belongs in a production deployment.

Practice solution and acceptance checks

python

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

class PublicRole(BaseModel):
    id: int
    title: str
    user: str

def inspect_example(request):
    app = FastAPI()
    def principal():
        return "demo"
    @app.get("/roles/{role_id}", response_model=PublicRole,
             responses={404: {"description": "Role not found"}, 410: {"description": "Role expired"}})
    def read(role_id: int, user: Annotated[str, Depends(principal)]):
        if role_id == 2:
            raise HTTPException(410, "Role expired")
        if role_id != 1:
            raise HTTPException(404, "Role not found")
        return {"id": 1, "title": "QA", "user": user, "private_note": "hidden"}
    if request.get("override"):
        app.dependency_overrides[principal] = lambda: "review"
    try:
        with TestClient(app) as client:
            response = client.get(f"/roles/{request.get('id', 1)}")
    finally:
        app.dependency_overrides.clear()
    schema = app.openapi()
    declared = schema["paths"]["/roles/{role_id}"]["get"]["responses"]
    properties = schema["components"]["schemas"]["PublicRole"]["properties"]
    return {"status": response.status_code, "body": response.json(),
            "documented_404": "404" in declared, "documented_410": "410" in declared,
            "private_in_schema": "private_note" in properties,
            "overrides_cleared": not app.dependency_overrides}
Reasoning
The solution adds the response description and a specific branch before the generic missing-role branch. Cleanup remains in finally. The valid response still uses the resolved principal and excludes the internal note through the public model. For a larger service, consider schema snapshots alongside semantic assertions rather than blindly accepting every regenerated file. Snapshot changes need review, and test overrides should be scoped to owned test fixtures. No demo authentication bypass belongs in a production deployment.

Interview reasoning and next step

Explain which assertion catches stale override state and which assertion catches a documentation-only error implementation.

Explain which assertion catches stale override state and which assertion catches a documentation-only error implementation. Then distinguish schema projection from actual response behavior. These tests should reveal different classes of regressions rather than mirror the same implementation line.

The final capstone combines the small boundaries: an owner-scoped collection, validated country and page size, a public response and honest observed counts. Keep each guarantee testable instead of claiming that framework conventions make the complete service correct automatically.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/python-api-lab-20261105.zip. Use an isolated Python environment and the pinned dependencies. Run verify.py --track fastapi, then edit the working exercise baseline and run --practice fa-contract-tests. The reference suite rejects meaningful counterexamples and checks values, types and unchanged inputs. All data is owned and all HTTP clients are in-process. No production database, feed, account or paid service is used.

Use documentation as a contract artifact, not as proof that a branch executes.