Browse Source

🐛 Fix using `Annotated` in routers or path operations decorated multiple times (#9315)

* Fix: copy FieldInfo from Annotated arguments

We need to copy the field_info to prevent ourselves from
mutating it.  This allows multiple path or nested routers ,etc.

* 📝 Add comment in fastapi/dependencies/utils.py

Co-authored-by: Nadav Zingerman <[email protected]>

*  Extend and tweak tests for Annotated

*  Tweak coverage, it's probably covered by a different version of Python

---------

Co-authored-by: Sebastián Ramírez <[email protected]>
Co-authored-by: Nadav Zingerman <[email protected]>
pull/9388/head
Sharon Yogev 2 years ago
committed by GitHub
parent
commit
fdf66c825e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      fastapi/dependencies/utils.py
  2. 43
      tests/test_annotated.py

5
fastapi/dependencies/utils.py

@ -1,7 +1,7 @@
import dataclasses
import inspect
from contextlib import contextmanager
from copy import deepcopy
from copy import copy, deepcopy
from typing import (
Any,
Callable,
@ -383,7 +383,8 @@ def analyze_param(
), f"Cannot specify multiple `Annotated` FastAPI arguments for {param_name!r}"
fastapi_annotation = next(iter(fastapi_annotations), None)
if isinstance(fastapi_annotation, FieldInfo):
field_info = fastapi_annotation
# Copy `field_info` because we mutate `field_info.default` below.
field_info = copy(fastapi_annotation)
assert field_info.default is Undefined or field_info.default is Required, (
f"`{field_info.__class__.__name__}` default value cannot be set in"
f" `Annotated` for {param_name!r}. Set the default value with `=` instead."

43
tests/test_annotated.py

@ -1,5 +1,5 @@
import pytest
from fastapi import FastAPI, Query
from fastapi import APIRouter, FastAPI, Query
from fastapi.testclient import TestClient
from typing_extensions import Annotated
@ -224,3 +224,44 @@ def test_get(path, expected_status, expected_response):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_multiple_path():
@app.get("/test1")
@app.get("/test2")
async def test(var: Annotated[str, Query()] = "bar"):
return {"foo": var}
response = client.get("/test1")
assert response.status_code == 200
assert response.json() == {"foo": "bar"}
response = client.get("/test1", params={"var": "baz"})
assert response.status_code == 200
assert response.json() == {"foo": "baz"}
response = client.get("/test2")
assert response.status_code == 200
assert response.json() == {"foo": "bar"}
response = client.get("/test2", params={"var": "baz"})
assert response.status_code == 200
assert response.json() == {"foo": "baz"}
def test_nested_router():
app = FastAPI()
router = APIRouter(prefix="/nested")
@router.get("/test")
async def test(var: Annotated[str, Query()] = "bar"):
return {"foo": var}
app.include_router(router)
client = TestClient(app)
response = client.get("/nested/test")
assert response.status_code == 200
assert response.json() == {"foo": "bar"}

Loading…
Cancel
Save