dmontagu / fastapi-utils

Reusable utilities for FastAPI
MIT License
1.89k stars 165 forks source link

[BUG] InferringRouter throws exception with FileResponse return type #229

Open johnthagen opened 3 years ago

johnthagen commented 3 years ago

Describe the bug

Setting the return type to -> FileResponse generates an exception:

Traceback (most recent call last):
  File "/venv/lib/python3.9/site-packages/fastapi/utils.py", line 65, in create_response_field
    return response_field(field_info=field_info)
  File "pydantic/fields.py", line 342, in pydantic.fields.ModelField.__init__
  File "pydantic/fields.py", line 456, in pydantic.fields.ModelField.prepare
  File "pydantic/fields.py", line 670, in pydantic.fields.ModelField.populate_validators
  File "pydantic/validators.py", line 715, in find_validators
RuntimeError: no validator found for <class 'starlette.responses.FileResponse'>, see `arbitrary_types_allowed` in Config

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/venv/lib/python3.9/site-packages/uvicorn/__main__.py", line 4, in <module>
    uvicorn.main()
  File "/venv/lib/python3.9/site-packages/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/venv/lib/python3.9/site-packages/click/core.py", line 1062, in main
    rv = self.invoke(ctx)
  File "/venv/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/venv/lib/python3.9/site-packages/click/core.py", line 763, in invoke
    return __callback(*args, **kwargs)
  File "/venv/lib/python3.9/site-packages/uvicorn/main.py", line 371, in main
    run(app, **kwargs)
  File "/venv/lib/python3.9/site-packages/uvicorn/main.py", line 393, in run
    server.run()
  File "/venv/lib/python3.9/site-packages/uvicorn/server.py", line 50, in run
    loop.run_until_complete(self.serve(sockets=sockets))
  File "asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/venv/lib/python3.9/site-packages/uvicorn/server.py", line 57, in serve
    config.load()
  File "/venv/lib/python3.9/site-packages/uvicorn/config.py", line 318, in load
    self.loaded_app = import_from_string(self.app)
  File "/venv/lib/python3.9/site-packages/uvicorn/importer.py", line 22, in import_from_string
    module = importlib.import_module(module_str)
  File "importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 855, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/./main.py", line 14, in <module>
    async def root() -> FileResponse:
  File "/venv/lib/python3.9/site-packages/fastapi/routing.py", line 551, in decorator
    self.add_api_route(
  File "/venv/lib/python3.9/site-packages/fastapi_utils/inferring_router.py", line 16, in add_api_route
    return super().add_api_route(path, endpoint, **kwargs)
  File "/venv/lib/python3.9/site-packages/fastapi/routing.py", line 496, in add_api_route
    route = route_class(
  File "/venv/lib/python3.9/site-packages/fastapi/routing.py", line 324, in __init__
    self.response_field = create_response_field(
  File "/venv/lib/python3.9/site-packages/fastapi/utils.py", line 67, in create_response_field
    raise fastapi.exceptions.FastAPIError(
fastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that <class 'starlette.responses.FileResponse'> is a valid pydantic field type

To Reproduce

from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi_utils.inferring_router import InferringRouter

app = FastAPI()
router = InferringRouter()

@router.get("/")
async def root() -> FileResponse:
    return FileResponse("file.txt")

app.include_router(router)

Launch with:

$ python -m uvicorn main:app

Expected behavior

A return type of -> FileReponse should not throw an exception. Allowing this allows static type checkers such as mypy to validate that all code paths in the path function return the correct type.

Environment:

pip list                  
Package           Version
----------------- --------
aiofiles          0.7.0
asgiref           3.3.4
click             8.0.1
fastapi           0.65.1
fastapi-utils     0.2.1
greenlet          1.1.0
h11               0.12.0
pip               21.1.2
pydantic          1.8.2
setuptools        57.0.0
SQLAlchemy        1.4.17
starlette         0.14.2
typing-extensions 3.10.0.0
uvicorn           0.14.0
0.2.1
0.65.1
             pydantic version: 1.8.2
            pydantic compiled: True
                 install path: /Users/hagenjt1/PycharmProjects/fastapi-test/venv/lib/python3.9/site-packages/pydantic
               python version: 3.9.5 (default, May  4 2021, 03:33:11)  [Clang 12.0.0 (clang-1200.0.32.29)]
                     platform: macOS-10.15.7-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']

Additional context

Directly setting the response_class on vanilla FastAPI does not throw an exception:

# This launches fine:
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get("/", response_class=FileResponse)
async def root():
    return FileResponse("file.txt")