[BUG]/[FEATURE] multiple path's on the same method results in errors
falkben opened this issue · comments
Not quite clear to me if this is a bug or a feature request as I may be doing something out of the ordinary.
Using class based views, my tests return errors using methods (inside cbv
) that have multiple path decorators attached to them. When using vanilla FastAPI without cbv
the tests pass.
To Reproduce
Credit to @smparekh for the demo below, which I modified slightly to show the issue.
Simple main.py
app:
from fastapi import FastAPI, APIRouter
from fastapi_utils.cbv import cbv
router = APIRouter()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@cbv(router)
class RootHandler:
@router.get("/items/?")
@router.get("/items/{item_path:path}")
@router.get("/database/{item_path:path}")
def root(self, item_path: str = None, item_query: str = None):
if item_path:
return {"item_path": item_path}
elif item_query:
return {"item_query": item_query}
else:
return fake_items_db
app = FastAPI()
app.include_router(router)
simple test_main.py
from .main import router
from starlette.testclient import TestClient
from .main import fake_items_db
client = TestClient(router)
def test_item_path():
resp = client.get("items/Bar")
assert resp.status_code == 200
assert resp.json() == {"item_path": "Bar"}
def test_item_query():
resp = client.get("items/?item_query=Bar")
assert resp.status_code == 200
assert resp.json() == {"item_query": "Bar"}
def test_list():
resp = client.get("items/")
assert resp.status_code == 200
assert resp.json() == fake_items_db
def test_database():
resp = client.get("database/")
assert resp.status_code == 200
assert resp.json() == fake_items_db
traceback
traceback from the first test (others are the same)
=================================== FAILURES ===================================
________________________________ test_item_path ________________________________
def test_item_path():
> resp = client.get("items/Bar")
app/test_main_mult_decorators.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/requests/sessions.py:546: in get
return self.request('GET', url, **kwargs)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/testclient.py:413: in request
return super().request(
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/requests/sessions.py:533: in request
resp = self.send(prep, **send_kwargs)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/requests/sessions.py:646: in send
r = adapter.send(request, **kwargs)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/testclient.py:243: in send
raise exc from None
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/testclient.py:240: in send
loop.run_until_complete(self.app(scope, receive, send))
../../../miniconda3/envs/web-server-eval/lib/python3.8/asyncio/base_events.py:612: in run_until_complete
return future.result()
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/routing.py:550: in __call__
await route.handle(scope, receive, send)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/routing.py:227: in handle
await self.app(scope, receive, send)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/starlette/routing.py:41: in app
response = await func(request)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.requests.Request object at 0x118cd3670>
async def app(request: Request) -> Response:
try:
body = None
if body_field:
if is_body_form:
body = await request.form()
else:
body_bytes = await request.body()
if body_bytes:
body = await request.json()
except Exception as e:
logger.error(f"Error getting request body: {e}")
raise HTTPException(
status_code=400, detail="There was an error parsing the body"
) from e
solved_result = await solve_dependencies(
request=request,
dependant=dependant,
body=body,
dependency_overrides_provider=dependency_overrides_provider,
)
values, errors, background_tasks, sub_response, _ = solved_result
if errors:
> raise RequestValidationError(errors, body=body)
E fastapi.exceptions.RequestValidationError: 1 validation error for Request
E query -> self
E field required (type=value_error.missing)
../../../miniconda3/envs/web-server-eval/lib/python3.8/site-packages/fastapi/routing.py:145: RequestValidationError
Expected behavior
Ideally tests work as in vanilla FastAPI without cbv.
Environment:
- OS: tested on macOS and Linux
>>> import fastapi_utils
>>> import fastapi
>>> import pydantic.utils
>>> import pytest
>>>
>>> print(fastapi_utils.__version__)
0.2.0
>>> print(fastapi.__version__)
0.52.0
>>> print(pydantic.utils.version_info())
pydantic version: 1.4
pydantic compiled: False
install path: /Users/bfalk/miniconda3/envs/web-server-eval/lib/python3.8/site-packages/pydantic
python version: 3.8.1 (default, Jan 8 2020, 16:15:59) [Clang 4.0.1 (tags/RELEASE_401/final)]
platform: macOS-10.14.6-x86_64-i386-64bit
optional deps. installed: ['typing-extensions']
>>> print(pytest.__version__)
5.3.5
Yeah, I think the approach currently in use might not play nice with using multiple decorators on the same method. I didn’t expect that to be a common use case but since someone wants to use it I’m happy to try to make it work.
PR definitely welcome if you want to get your hands dirty but I’ll take a look at this as soon as I can.
Okay yeah I see the problem and it should be an easy fix. I can’t promise, but I should be able to release 0.2.1 with a fix for this some time tomorrow evening.
Just pushed v0.2.1 to PyPI with this fix. Let me know if it doesn't solve your problem!
Yup, works great, thanks!