Error exception in asgi application fastapi

I've successfully used the Fullstack fastapi template in the past. With the recent version of fastapi installed through the Dockerfile, I get the following error when I log on to docker-compose...

Can any one explain where to add the changes ??
i am little bit confused :)
P.S: I am a complete rookie into fastapi
I am using POSTman to upload an image to the server ..but showing internal server error..can anyone help me with this!!
using the latest version of every library .

Error Log:

Started server process [4532]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
INFO: ::1:49810 — «GET /ping HTTP/1.1» 200 OK
INFO: ::1:49811 — «GET /predict HTTP/1.1» 405 Method Not Allowed
INFO: ::1:49819 — «POST /predict HTTP/1.1» 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesuvicornprotocolshttph11_impl.py», line 366, in run_asgi
result = await app(self.scope, self.receive, self.send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesuvicornmiddlewareproxy_headers.py», line 75, in call
return await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapiapplications.py», line 261, in call
await super().call(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteapplications.py», line 112, in call
await self.middleware_stack(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarlettemiddlewareerrors.py», line 181, in call
raise exc
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarlettemiddlewareerrors.py», line 159, in call
await self.app(scope, receive, _send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteexceptions.py», line 82, in call
raise exc
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteexceptions.py», line 71, in call
await self.app(scope, receive, sender)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapimiddlewareasyncexitstack.py», line 21, in call
raise e
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapimiddlewareasyncexitstack.py», line 18, in call
await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 656, in call
await route.handle(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 259, in handle
await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 61, in app
response = await func(request)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapirouting.py», line 227, in app
raw_response = await run_endpoint_function(
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapirouting.py», line 160, in run_endpoint_function
return await dependant.call(**values)
File «C:/Users/ajith/Desktop/Project/api/main.py», line 22, in predict
image = read_file_as_image( file.read())
File «C:/Users/ajith/Desktop/Project/api/main.py», line 16, in read_file_as_image
image = np.array(Image.open(BytesIO(data)))
TypeError: a bytes-like object is required, not ‘coroutine’

CODE:

import uvicorn
from fastapi import FastAPI,File,UploadFile
import numpy as np
from io import BytesIO
from PIL import Image
import tensorflow as tf

app = FastAPI()

MODEL = tf.keras.models.load_model(«C:/Users/ajith/Desktop/Project/Models/1»)
[‘Healthy’, ‘Kole Roga’]
@app.get(«/ping»)
async def ping():
return «Hello World:)»
def read_file_as_image(data) -> np.ndarray:
image = np.array(Image.open(BytesIO(data)))
return image

@app.post(«/predict»)
async def predict( file:UploadFile=File(…) ):

image = read_file_as_image( file.read())
img_batch = np.expand_dims(image, 0)

predictions = MODEL.predict(img_batch)
pass

if name==»main«:
uvicorn.run(app, host=»localhost», port=8000)

Содержание

  1. Exception in ASGI application with newest Fastapi #45
  2. Comments
  3. Exception in ASGI application #2856
  4. Comments
  5. [BUG] openapi render crashes with response_class of Tuple #466
  6. Comments
  7. @app.get(«/», response_model=Tuple[bool,Test]) async def root(): return True, navigate to h**p://localhost:8000/docs Expected behavior I’d expect openapi docs import fastapi print(fastapi.version) 0.35.0 (fastapi) [toppk@static fastapi]$ python —version Python 3.6.9 Python version, get it with: Additional context Add any other context about the problem here. For reference here is the ouput I’m getting on the console: $ uvicorn test-fastapi:app INFO: Started server process [1976904] INFO: Waiting for application startup. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: (‘127.0.0.1’, 56070) — «GET / HTTP/1.1» 200 INFO: (‘127.0.0.1’, 56070) — «GET /docs HTTP/1.1» 200 INFO: (‘127.0.0.1’, 56070) — «GET /openapi.json HTTP/1.1» 500 ERROR: Exception in ASGI application Traceback (most recent call last): File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py», line 375, in run_asgi result = await app(self.scope, self.receive, self.send) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/applications.py», line 133, in call await self.error_middleware(scope, receive, send) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/middleware/errors.py», line 177, in call raise exc from None File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/middleware/errors.py», line 155, in call await self.app(scope, receive, send) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y/lib/python3.6/site-packages/starlette/exceptions.py», line 73, in call raise exc from None File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/exceptions.py», line 62, in call await self.app(scope, receive, sender) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 592, in call await route(scope, receive, send) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 208, in call await self.app(scope, receive, send) File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 41, in app response = await func(request) File «./fastapi/applications.py», line 87, in openapi return JSONResponse(self.openapi()) File «./fastapi/applications.py», line 79, in openapi openapi_prefix=self.openapi_prefix, File «./fastapi/openapi/utils.py», line 272, in get_openapi return jsonable_encoder(OpenAPI(**output), by_alias=True, include_none=False) File «pydantic/main.py», line 270, in pydantic.main.BaseModel.init File «pydantic/main.py», line 697, in pydantic.main.validate_model pydantic.error_wrappers.ValidationError: 7 validation errors paths -> / -> get -> responses -> default field required (type=value_error.missing) content -> application/json -> schema -> items value is not a valid dict (type=type_error.dict) content -> application/json -> schema -> items value is not none (type=type_error.none.allowed) content -> application/json -> schema -> $ref field required (type=value_error.missing) content -> application/json -> schema value is not none (type=type_error.none.allowed) responses -> 200 -> content value is not none (type=type_error.none.allowed) paths -> / -> get value is not none (type=type_error.none.allowed) The text was updated successfully, but these errors were encountered: Источник Incorrect requests to websockets raise validation error exception #3702 Comments First Check I added a very descriptive title to this issue. I used the GitHub search to find a similar issue and didn’t find it. I searched the FastAPI documentation, with the integrated search. I already searched in Google «How to X in FastAPI» and didn’t find any information. I already read and followed all the tutorial in the docs and didn’t find an answer. I already checked if it is not related to FastAPI but to Pydantic. I already checked if it is not related to FastAPI but to Swagger UI. I already checked if it is not related to FastAPI but to ReDoc. Commit to Help I commit to help with one of those options 👆 Example Code Description Hello, When i connect to websocket with prefix query parameter all works fine. But when i connect without prefix parameter, a few bad things happen. Console 1 Console 2 Console 1 Intercepted traffic Problems I do not want invalid requests to lead to exceptions on the server side, as happens with http requests. I want to get detaild error in response to an upgrade request instead of an empty 403 response. I’m sure this is technically possible, but it might be against the websocket spec. Operating System Operating System Details FastAPI Version Python Version Additional Context websockets 9.1 (pip install -U websockets) The text was updated successfully, but these errors were encountered: From what you’ve written here you are trying to prevent a missing query parameter prefix causing issues upon connection. Currently your WebSocket endpoint always expects the prefix query parameter to exist because by default it is required and you have not defined it as being optional. See the documentation around web-sockets and the Path here. I tested the code below locally and can safely handle the parameter not existing without the connection closing. Note: you could replace what I have down here with Query( ) and handle prefix is not . Console 1 Example 1 console 2 Console 1 Console 2 Console 1 Example 2 console 2 Console 1 Console 2 Console 1 Explanation In example 1, server never raise exceptions, and if validation fails, server sends validation error to the client. In example2 server raise exception, if validation fails, and client receives empty 403 response, that do not contain useful information about validation error. I don’t want a request from the client to be able to raise exceptions on the server, is it possible to avoid raising exceptions? Also, ideally, I would like to return a response to invalid requests containing information about what exactly is wrong. I am having the same issue and agree with @Danstiv’s conclusion. From what I recall, the WebSocket spec says that the server should respond with 403 if the path is unexpected. Try, for example, connecting to some random path that you haven’t specified in your FastAPI code — it will respond with 403. So I think that is expected. What isn’t expected is the server raising exceptions. FastAPI provides the ability to specify input parameters and automatically validates them. This happens outside the scope of a path operation function, so from my eyes if it is raising an exception there then that is a bug as there’s no way for us to catch it. It seems like this should either be handled and the 403 response given for us, or maybe this functionality should be unavailable for WebSocket endpoints. As it stands, I’m not sure how to proceed as there seems to be no way to prevent clients being able to trigger exceptions on the server (in my case, the parameter is an int so passing an invalid integer can trigger the exception). Источник
  8. Incorrect requests to websockets raise validation error exception #3702
  9. Comments
  10. First Check
  11. Commit to Help
  12. Example Code
  13. Description
  14. Console 1
  15. Console 2
  16. Console 1
  17. Intercepted traffic
  18. Problems
  19. Operating System
  20. Operating System Details
  21. FastAPI Version
  22. Python Version
  23. Additional Context
  24. Console 1
  25. Example 1
  26. console 2
  27. Console 1
  28. Console 2
  29. Console 1
  30. Example 2
  31. console 2
  32. Console 1
  33. Console 2
  34. Console 1
  35. Explanation

Exception in ASGI application with newest Fastapi #45

I’ve successfully used the Fullstack fastapi template in the past. With the recent version of fastapi installed through the Dockerfile, I get the following error when I log on to docker-compose logs -f backend and open up https://localhost/:

There’s a small chance that this error relates to some code edits I have introduced, but I suspect this is related to the latest changes to how pydantic validates models in newer Fastapi versions. Anyone else experiencing this? I haven’t tested it with a fresh full-stack-fastapi-postgresql, but will check as soon as possible..

Just a note: login still works, perhaps this is just a backend error with no consequences, it is still annoying seeing this error every time the login screen is requested.

The text was updated successfully, but these errors were encountered:

Just noticed that this seems already addressed in PR 43. I’ve tested the PR and it works! This can be closed..

@Sieboldianus I ran into this issue, it still comes up running the current master . Can I suggest to keep this issue open until #43 is closed?

I can confirm I have the same issue running the current master . Thanks.

Edit:
I can also confirm that #43 seems to address the «Exception in ASGI».

This is awesome! Thanks.

Thanks for the report and discussion here everyone!

This should be fixed in the current master. 🎉 🚀

So I guess we could close this issue, right @Sieboldianus ?

Can any one explain where to add the changes ??
i am little bit confused 🙂
P.S: I am a complete rookie into fastapi
I am using POSTman to upload an image to the server ..but showing internal server error..can anyone help me with this!!
using the latest version of every library .

Started server process [4532]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
INFO: ::1:49810 — «GET /ping HTTP/1.1» 200 OK
INFO: ::1:49811 — «GET /predict HTTP/1.1» 405 Method Not Allowed
INFO: ::1:49819 — «POST /predict HTTP/1.1» 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesuvicornprotocolshttph11_impl.py», line 366, in run_asgi
result = await app(self.scope, self.receive, self.send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesuvicornmiddlewareproxy_headers.py», line 75, in call
return await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapiapplications.py», line 261, in call
await super().call(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteapplications.py», line 112, in call
await self.middleware_stack(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarlettemiddlewareerrors.py», line 181, in call
raise exc
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarlettemiddlewareerrors.py», line 159, in call
await self.app(scope, receive, _send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteexceptions.py», line 82, in call
raise exc
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletteexceptions.py», line 71, in call
await self.app(scope, receive, sender)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapimiddlewareasyncexitstack.py», line 21, in call
raise e
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapimiddlewareasyncexitstack.py», line 18, in call
await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 656, in call
await route.handle(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 259, in handle
await self.app(scope, receive, send)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesstarletterouting.py», line 61, in app
response = await func(request)
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapirouting.py», line 227, in app
raw_response = await run_endpoint_function(
File «C:UsersajithAppDataLocalProgramsPythonPython310libsite-packagesfastapirouting.py», line 160, in run_endpoint_function
return await dependant.call(**values)
File «C:/Users/ajith/Desktop/Project/api/main.py», line 22, in predict
image = read_file_as_image( file.read())
File «C:/Users/ajith/Desktop/Project/api/main.py», line 16, in read_file_as_image
image = np.array(Image.open(BytesIO(data)))
TypeError: a bytes-like object is required, not ‘coroutine’

import uvicorn
from fastapi import FastAPI,File,UploadFile
import numpy as np
from io import BytesIO
from PIL import Image
import tensorflow as tf

Источник

Exception in ASGI application #2856

Traceback (most recent call last):
File «c:userssortolanaconda3libsite-packagesuvicornprotocolshttph11_impl.py», line 394, in run_asgi
result = await app(self.scope, self.receive, self.send)
File «c:userssortolanaconda3libsite-packagesuvicornmiddlewareproxy_headers.py», line 45, in call
return await self.app(scope, receive, send)
File «c:userssortolanaconda3libsite-packagesfastapiapplications.py», line 199, in call
await super().call(scope, receive, send)
File «c:userssortolanaconda3libsite-packagesstarletteapplications.py», line 111, in call
await self.middleware_stack(scope, receive, send)
File «c:userssortolanaconda3libsite-packagesstarlettemiddlewareerrors.py», line 181, in call
raise exc from None
File «c:userssortolanaconda3libsite-packagesstarlettemiddlewareerrors.py», line 159, in call
await self.app(scope, receive, _send)
File «c:userssortolanaconda3libsite-packagesstarletteexceptions.py», line 82, in call
raise exc from None
File «c:userssortolanaconda3libsite-packagesstarletteexceptions.py», line 71, in call
await self.app(scope, receive, sender)
File «c:userssortolanaconda3libsite-packagesstarletterouting.py», line 566, in call
await route.handle(scope, receive, send)
File «c:userssortolanaconda3libsite-packagesstarletterouting.py», line 227, in handle
await self.app(scope, receive, send)
File «c:userssortolanaconda3libsite-packagesstarletterouting.py», line 41, in app
response = await func(request)
File «c:userssortolanaconda3libsite-packagesfastapirouting.py», line 201, in app
raw_response = await run_endpoint_function(
File «c:userssortolanaconda3libsite-packagesfastapirouting.py», line 148, in run_endpoint_function
return await dependant.call(**values)
File «.main.py», line 21, in predict_image
predictions = predict(flower_img)
File «.helpers.py», line 42, in predict
response = requests.post(URL, data=data, headers=headers)
File «c:userssortolanaconda3libsite-packagesrequestsapi.py», line 119, in post
return request(‘post’, url, data=data, json=json, **kwargs)
File «c:userssortolanaconda3libsite-packagesrequestsapi.py», line 61, in request
return session.request(method=method, url=url, **kwargs)
File «c:userssortolanaconda3libsite-packagesrequestssessions.py», line 530, in request
resp = self.send(prep, **send_kwargs)
File «c:userssortolanaconda3libsite-packagesrequestssessions.py», line 643, in send
r = adapter.send(request, **kwargs)
File «c:userssortolanaconda3libsite-packagesrequestsadapters.py», line 516, in send
raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host=’localhost’, port=8501): Max retries exceeded with url: /v1/models/flower-classification:predict (Caused by NewConnectionError(‘ : Failed to establish a new connection: [WinError 10061] No connection
could be made because the target machine actively refused it’))

The text was updated successfully, but these errors were encountered:

Источник

[BUG] openapi render crashes with response_class of Tuple #466

Describe the bug
openapi schema generation crashes when given a tuple as response_class. all the other behavior is working properly, just the openapi renderer. I think this is because tuples in openapi aren’t supported that well. see this comment for a suggestion

There should be some way to properly degrade this.

To Reproduce
Steps to reproduce the behavior:

  1. run this code:
    ====
    from fastapi import FastAPI
    from pydantic import BaseModel
    from typing import Tuple

class Test(BaseModel):
name: str

@app.get(«/», response_model=Tuple[bool,Test])
async def root():
return True,

  1. navigate to h**p://localhost:8000/docs

Expected behavior

I’d expect openapi docs

import fastapi
print(fastapi.version)
0.35.0

(fastapi) [toppk@static fastapi]$ python —version
Python 3.6.9

  • Python version, get it with:

Additional context
Add any other context about the problem here.

For reference here is the ouput I’m getting on the console:

$ uvicorn test-fastapi:app
INFO: Started server process [1976904]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: (‘127.0.0.1’, 56070) — «GET / HTTP/1.1» 200
INFO: (‘127.0.0.1’, 56070) — «GET /docs HTTP/1.1» 200
INFO: (‘127.0.0.1’, 56070) — «GET /openapi.json HTTP/1.1» 500
ERROR: Exception in ASGI application
Traceback (most recent call last):
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py», line 375, in run_asgi
result = await app(self.scope, self.receive, self.send)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/applications.py», line 133, in call
await self.error_middleware(scope, receive, send)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/middleware/errors.py», line 177, in call
raise exc from None
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/middleware/errors.py», line 155, in call
await self.app(scope, receive, send)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y
/lib/python3.6/site-packages/starlette/exceptions.py», line 73, in call
raise exc from None
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/exceptions.py», line 62, in call
await self.app(scope, receive, sender)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 592, in call
await route(scope, receive, send)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 208, in call
await self.app(scope, receive, send)
File «/home/toppk/.local/share/virtualenvs/fastapi-R7xES2y_/lib/python3.6/site-packages/starlette/routing.py», line 41, in app
response = await func(request)
File «./fastapi/applications.py», line 87, in openapi
return JSONResponse(self.openapi())
File «./fastapi/applications.py», line 79, in openapi
openapi_prefix=self.openapi_prefix,
File «./fastapi/openapi/utils.py», line 272, in get_openapi
return jsonable_encoder(OpenAPI(**output), by_alias=True, include_none=False)
File «pydantic/main.py», line 270, in pydantic.main.BaseModel.init
File «pydantic/main.py», line 697, in pydantic.main.validate_model
pydantic.error_wrappers.ValidationError: 7 validation errors
paths -> / -> get -> responses -> default
field required (type=value_error.missing)
content -> application/json -> schema -> items
value is not a valid dict (type=type_error.dict)
content -> application/json -> schema -> items
value is not none (type=type_error.none.allowed)
content -> application/json -> schema -> $ref
field required (type=value_error.missing)
content -> application/json -> schema
value is not none (type=type_error.none.allowed)
responses -> 200 -> content
value is not none (type=type_error.none.allowed)
paths -> / -> get
value is not none (type=type_error.none.allowed)

The text was updated successfully, but these errors were encountered:

Источник

Incorrect requests to websockets raise validation error exception #3702

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google «How to X in FastAPI» and didn’t find any information.
  • I already read and followed all the tutorial in the docs and didn’t find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

Description

Hello,
When i connect to websocket with prefix query parameter all works fine.
But when i connect without prefix parameter, a few bad things happen.

Console 1

Console 2

Console 1

Intercepted traffic

Problems

  1. I do not want invalid requests to lead to exceptions on the server side, as happens with http requests.
  2. I want to get detaild error in response to an upgrade request instead of an empty 403 response. I’m sure this is technically possible, but it might be against the websocket spec.

Operating System

Operating System Details

FastAPI Version

Python Version

Additional Context

websockets 9.1 (pip install -U websockets)

The text was updated successfully, but these errors were encountered:

From what you’ve written here you are trying to prevent a missing query parameter prefix causing issues upon connection.
Currently your WebSocket endpoint always expects the prefix query parameter to exist because by default it is required and you have not defined it as being optional.

See the documentation around web-sockets and the Path here.

I tested the code below locally and can safely handle the parameter not existing without the connection closing.

Note: you could replace what I have down here with Query( ) and handle prefix is not .

Console 1

Example 1

console 2

Console 1

Console 2

Console 1

Example 2

console 2

Console 1

Console 2

Console 1

Explanation

In example 1, server never raise exceptions, and if validation fails, server sends validation error to the client.
In example2 server raise exception, if validation fails, and client receives empty 403 response, that do not contain useful information about validation error.
I don’t want a request from the client to be able to raise exceptions on the server, is it possible to avoid raising exceptions?
Also, ideally, I would like to return a response to invalid requests containing information about what exactly is wrong.

I am having the same issue and agree with @Danstiv’s conclusion.

From what I recall, the WebSocket spec says that the server should respond with 403 if the path is unexpected. Try, for example, connecting to some random path that you haven’t specified in your FastAPI code — it will respond with 403. So I think that is expected.

What isn’t expected is the server raising exceptions. FastAPI provides the ability to specify input parameters and automatically validates them. This happens outside the scope of a path operation function, so from my eyes if it is raising an exception there then that is a bug as there’s no way for us to catch it. It seems like this should either be handled and the 403 response given for us, or maybe this functionality should be unavailable for WebSocket endpoints.

As it stands, I’m not sure how to proceed as there seems to be no way to prevent clients being able to trigger exceptions on the server (in my case, the parameter is an int so passing an invalid integer can trigger the exception).

Источник

Description

I’ve seen similar issues about self-referencing Pydantic models causing RecursionError: maximum recursion depth exceeded in comparison but as far as I can tell there are no self-referencing models included in the code. I’m just just using Pydantic’s BaseModel class.

The code runs successfully until the function in audit.py below tries to return the output from the model.

I’ve included the full traceback as I’m not sure where to begin with this error. I’ve run the code with PyCharm and without an IDE and it always produces the traceback below but doesn’t crash the app but returns a http status code of 500 to the front end.

Any advice would be much appreciated.

As suggested I have also tried sys.setrecursionlimit(1500) to increase the recursion limit.

Environment

  • OS: Windows 10
  • FastAPI Version: 0.61.1
  • Pydantic Version: 1.6.1
  • Uvicorn Version: 0.11.8
  • Python Version: 3.7.1
  • Pycharm Version: 2020.2

App

main.py

import uvicorn
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware


from app.api.routes.router import api_router
from app.core.logging import init_logging
from app.core.config import settings

init_logging()


def get_app() -> FastAPI:
    application = FastAPI(title=settings.APP_NAME, version=settings.APP_VERSION, debug=settings.DEBUG)

    if settings.BACKEND_CORS_ORIGINS:
        # middleware support for cors
        application.add_middleware(
            CORSMiddleware,
            allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )
    application.include_router(api_router, prefix=settings.API_V1_STR)
    return application


app = get_app()

if __name__ == "__main__":
    uvicorn.run("main:app", host="127.0.0.1", port=80)

router.py

from fastapi import APIRouter

from app.api.routes import audit

api_router = APIRouter()
api_router.include_router(audit.router, tags=["audit"], prefix="/audit")

audit.py

import validators
from fastapi import APIRouter, HTTPException
from loguru import logger

from app.api.dependencies.audit import analyzer
from app.schemas.audit import AuditPayload, AuditResult

router = APIRouter()


@router.post("/", response_model=AuditResult, name="audit", status_code=200)
async def post_audit(payload: AuditPayload) -> AuditResult:
    logger.info("Audit request received")
    # validate URL
    try:
        logger.info("Validating URL")
        validators.url(payload.url)
    except HTTPException:
        HTTPException(status_code=404, detail="Invalid URL.")
        logger.exception("HTTPException - Invalid URL")

    # generate output from route audit.py
    logger.info("Running audit analysis. This could take up to 10 minutes. Maybe grab a coffee...")
    analyzed_output = analyzer.analyze(url=payload.url,
                                       brand=payload.brand,
                                       twitter_screen_name=payload.twitter_screen_name,
                                       facebook_page_name=payload.facebook_page_name,
                                       instagram_screen_name=payload.instagram_screen_name,
                                       youtube_user_name=payload.youtube_user_name,
                                       ignore_robots=payload.ignore_robots,
                                       ignore_sitemap=payload.ignore_sitemap,
                                       google_analytics_view_id=payload.google_analytics_view_id)
    output = AuditResult(**analyzed_output)
    return output 

audit_models.py

from pydantic import BaseModel


class AuditPayload(BaseModel):
    url: str
    brand: str
    twitter_screen_name: str
    facebook_page_name: str
    instagram_screen_name: str
    youtube_user_name: str
    ignore_robots: bool
    ignore_sitemap: bool
    google_analytics_view_id: str


class AuditResult(BaseModel):
    base_url: str
    run_time: float
    website_404: dict
    website_302: dict
    website_h1_tags: dict
    website_duplicate_h1: dict
    website_h2_tags: dict
    website_page_duplications: dict
    website_page_similarities: dict
    website_page_desc_duplications: dict
    website_page_title_duplications: dict
    pages: list
    pages_out_links_404: dict = None
    pages_canonicals: dict
    seo_phrases: dict
    social: dict
    google_analytics_report: dict
    google_psi_desktop: dict
    google_psi_mobile: dict
    google_algo_updates: dict
    google_sb: list
    robots_txt: list

This line throws the error in the logs:
2020-09-10 10:02:31.483 | ERROR | uvicorn.protocols.http.h11_impl:run_asgi:391 — Exception in ASGI application

I believe this bit is the most relevant to understanding why this error is occuring:

  File "pydanticmain.py", line 623, in pydantic.main.BaseModel._get_value
  [Previous line repeated 722 more times]

Full traceback:

Traceback (most recent call last):
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydevpydevconsole.py", line 483, in <module>
    pydevconsole.start_client(host, port)
    │            │            │     └ 50488
    │            │            └ '127.0.0.1'
    │            └ <function start_client at 0x000001BCEDC19D08>
    └ <module 'pydevconsole' from 'C:\Users\<user>\AppData\Local\JetBrains\Toolbox\apps\PyCharm-P\ch-0\202.6948.78\pl...
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydevpydevconsole.py", line 411, in start_client
    process_exec_queue(interpreter)
    │                  └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x000001BCEDC1BF98>
    └ <function process_exec_queue at 0x000001BCEDC19A60>
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydevpydevconsole.py", line 258, in process_exec_queue
    more = interpreter.add_exec(code_fragment)
           │           │        └ <_pydev_bundle.pydev_console_types.CodeFragment object at 0x000001BCEDCFE748>
           │           └ <function BaseCodeExecutor.add_exec at 0x000001BCECF38488>
           └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x000001BCEDC1BF98>
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydev_pydev_bundlepydev_code_executor.py", line 106, in add_exec
    more = self.do_add_exec(code_fragment)
           │    │           └ <_pydev_bundle.pydev_console_types.CodeFragment object at 0x000001BCEDCFE748>
           │    └ <function InterpreterInterface.do_add_exec at 0x000001BCEDC15D90>
           └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x000001BCEDC1BF98>
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydev_pydev_bundlepydev_ipython_console.py", line 36, in do_add_exec
    res = bool(self.interpreter.add_exec(code_fragment.text))
               │    │           │        │             └ "runfile('E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py', wdir='E:/Users/<user>/Docume...
               │    │           │        └ <_pydev_bundle.pydev_console_types.CodeFragment object at 0x000001BCEDCFE748>
               │    │           └ <function _PyDevFrontEnd.add_exec at 0x000001BCEDC15A60>
               │    └ <_pydev_bundle.pydev_ipython_console_011._PyDevFrontEnd object at 0x000001BCEDC350B8>
               └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x000001BCEDC1BF98>
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydev_pydev_bundlepydev_ipython_console_011.py", line 483, in add_exec
    self.ipython.run_cell(line, store_history=True)
    │    │       │        └ "runfile('E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py', wdir='E:/Users/<user>/Docume...
    │    │       └ <function InteractiveShell.run_cell at 0x000001BCED5E7268>
    │    └ <_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell object at 0x000001BCEDC350F0>
    └ <_pydev_bundle.pydev_ipython_console_011._PyDevFrontEnd object at 0x000001BCEDC350B8>
  File "C:Program FilesPython37libsite-packagesIPythoncoreinteractiveshell.py", line 2843, in run_cell
    raw_cell, store_history, silent, shell_futures)
    │         │              │       └ True
    │         │              └ False
    │         └ True
    └ "runfile('E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py', wdir='E:/Users/<user>/Docume...
  File "C:Program FilesPython37libsite-packagesIPythoncoreinteractiveshell.py", line 2869, in _run_cell
    return runner(coro)
           │      └ <generator object InteractiveShell.run_cell_async at 0x000001BCEDC49C78>
           └ <function _pseudo_sync_runner at 0x000001BCED5D0C80>
  File "C:Program FilesPython37libsite-packagesIPythoncoreasync_helpers.py", line 67, in _pseudo_sync_runner
    coro.send(None)
    │    └ <method 'send' of 'generator' objects>
    └ <generator object InteractiveShell.run_cell_async at 0x000001BCEDC49C78>
  File "C:Program FilesPython37libsite-packagesIPythoncoreinteractiveshell.py", line 3044, in run_cell_async
    interactivity=interactivity, compiler=compiler, result=result)
                  │                       │                └ <ExecutionResult object at 1bcedcd3470, execution_count=2 error_before_exec=None error_in_exec=None info=<ExecutionInfo objec...
                  │                       └ <IPython.core.compilerop.CachingCompiler object at 0x000001BCEDC356D8>
                  └ 'last_expr'
  File "C:Program FilesPython37libsite-packagesIPythoncoreinteractiveshell.py", line 3215, in run_ast_nodes
    if (yield from self.run_code(code, result)):
                   │    │        │     └ <ExecutionResult object at 1bcedcd3470, execution_count=2 error_before_exec=None error_in_exec=None info=<ExecutionInfo objec...
                   │    │        └ <code object <module> at 0x000001BCEDCDADB0, file "<ipython-input-2-086756a0f1dd>", line 1>
                   │    └ <function InteractiveShell.run_code at 0x000001BCED5E76A8>
                   └ <_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell object at 0x000001BCEDC350F0>
  File "C:Program FilesPython37libsite-packagesIPythoncoreinteractiveshell.py", line 3291, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
         │         │    │               │    └ {'__name__': 'pydev_umd', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None,...
         │         │    │               └ <_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell object at 0x000001BCEDC350F0>
         │         │    └ <property object at 0x000001BCED5D8958>
         │         └ <_pydev_bundle.pydev_ipython_console_011.PyDevTerminalInteractiveShell object at 0x000001BCEDC350F0>
         └ <code object <module> at 0x000001BCEDCDADB0, file "<ipython-input-2-086756a0f1dd>", line 1>
  File "<ipython-input-2-086756a0f1dd>", line 1, in <module>
    runfile('E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py', wdir='E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app')
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydev_pydev_bundlepydev_umd.py", line 197, in runfile
    pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
    │             │        │         │            └ {'__name__': '__main__', '__doc__': "nMain entry point into API for endpoints related to HawkSense's main functionality.nto...
    │             │        │         └ {'__name__': '__main__', '__doc__': "nMain entry point into API for endpoints related to HawkSense's main functionality.nto...
    │             │        └ 'E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py'
    │             └ <function execfile at 0x000001BCECC521E0>
    └ <module '_pydev_bundle.pydev_imports' from 'C:\Users\<user>\AppData\Local\JetBrains\Toolbox\apps\PyCharm-P\ch-0\...
  File "C:Users<user>AppDataLocalJetBrainsToolboxappsPyCharm-Pch-0202.6948.78pluginspythonhelperspydev_pydev_imps_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"n", file, 'exec'), glob, loc)
                 │              │              │     └ {'__name__': '__main__', '__doc__': "nMain entry point into API for endpoints related to HawkSense's main functionality.nto...
                 │              │              └ {'__name__': '__main__', '__doc__': "nMain entry point into API for endpoints related to HawkSense's main functionality.nto...
                 │              └ 'E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/app/main.py'
                 └ '#!/usr/bin/env pythonnn"""nMain entry point into API for endpoints related to HawkSense's main functionality.ntodo: htt...
  File "E:/Users/<user>/Documents/GitHub/HawkSense/backend/app/appmain.py", line 47, in <module>
    uvicorn.run("main:app", host="127.0.0.1", port=80)  # for debug only
    │       └ <function run at 0x000001BCEDE041E0>
    └ <module 'uvicorn' from 'C:\Program Files\Python37\lib\site-packages\uvicorn\__init__.py'>
  File "C:Program FilesPython37libsite-packagesuvicornmain.py", line 362, in run
    server.run()
    │      └ <function Server.run at 0x000001BCEDE4B510>
    └ <uvicorn.main.Server object at 0x000001BCFC722198>
  File "C:Program FilesPython37libsite-packagesuvicornmain.py", line 390, in run
    loop.run_until_complete(self.serve(sockets=sockets))
    │    │                  │    │             └ None
    │    │                  │    └ <function Server.serve at 0x000001BCEDE4B598>
    │    │                  └ <uvicorn.main.Server object at 0x000001BCFC722198>
    │    └ <function BaseEventLoop.run_until_complete at 0x000001BCED49FE18>
    └ <_WindowsSelectorEventLoop running=True closed=False debug=False>
  File "C:Program FilesPython37libasynciobase_events.py", line 560, in run_until_complete
    self.run_forever()
    │    └ <function BaseEventLoop.run_forever at 0x000001BCED49FD90>
    └ <_WindowsSelectorEventLoop running=True closed=False debug=False>
  File "C:Program FilesPython37libasynciobase_events.py", line 528, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x000001BCED4A27B8>
    └ <_WindowsSelectorEventLoop running=True closed=False debug=False>
  File "C:Program FilesPython37libasynciobase_events.py", line 1764, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x000001BCED43AB70>
    └ <Handle <TaskStepMethWrapper object at 0x000001BCFC7D4B00>()>
  File "C:Program FilesPython37libasyncioevents.py", line 88, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle <TaskStepMethWrapper object at 0x000001BCFC7D4B00>()>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle <TaskStepMethWrapper object at 0x000001BCFC7D4B00>()>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle <TaskStepMethWrapper object at 0x000001BCFC7D4B00>()>
> File "C:Program FilesPython37libsite-packagesuvicornprotocolshttph11_impl.py", line 388, in run_asgi
    result = await app(self.scope, self.receive, self.send)
                   │   │    │      │    │        │    └ <function RequestResponseCycle.send at 0x000001BCFC757840>
                   │   │    │      │    │        └ <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>
                   │   │    │      │    └ <function RequestResponseCycle.receive at 0x000001BCFC7578C8>
                   │   │    │      └ <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>
                   │   │    └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
                   │   └ <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>
                   └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x000001BCFC722BA8>
  File "C:Program FilesPython37libsite-packagesuvicornmiddlewareproxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
                 │    │   │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>>
                 │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
                 │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
                 │    └ <fastapi.applications.FastAPI object at 0x000001BCFC722710>
                 └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x000001BCFC722BA8>
  File "C:Program FilesPython37libsite-packagesfastapiapplications.py", line 149, in __call__
    await super().__call__(scope, receive, send)
                           │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>>
                           │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
                           └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
  File "C:Program FilesPython37libsite-packagesstarletteapplications.py", line 102, in __call__
    await self.middleware_stack(scope, receive, send)
          │    │                │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4A90>>
          │    │                │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │                └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x000001BCFC7B8FD0>
          └ <fastapi.applications.FastAPI object at 0x000001BCFC722710>
  File "C:Program FilesPython37libsite-packagesstarlettemiddlewareerrors.py", line 181, in __call__
    raise exc from None
  File "C:Program FilesPython37libsite-packagesstarlettemiddlewareerrors.py", line 159, in __call__
    await self.app(scope, receive, _send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x000001BCFC72AE18>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <starlette.middleware.cors.CORSMiddleware object at 0x000001BCFC7B8F60>
          └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x000001BCFC7B8FD0>
  File "C:Program FilesPython37libsite-packagesstarlettemiddlewarecors.py", line 84, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
          │    │               │      │        │                     └ Headers({'host': '127.0.0.1', 'connection': 'keep-alive', 'content-length': '295', 'accept': 'application/json', 'user-agent'...
          │    │               │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x000001BCFC72AE18>
          │    │               │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │               └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <function CORSMiddleware.simple_response at 0x000001BCEE53DC80>
          └ <starlette.middleware.cors.CORSMiddleware object at 0x000001BCFC7B8F60>
  File "C:Program FilesPython37libsite-packagesstarlettemiddlewarecors.py", line 140, in simple_response
    await self.app(scope, receive, send)
          │    │   │      │        └ functools.partial(<bound method CORSMiddleware.send of <starlette.middleware.cors.CORSMiddleware object at 0x000001BCFC7B8F60...
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <starlette.exceptions.ExceptionMiddleware object at 0x000001BCFC7B8E48>
          └ <starlette.middleware.cors.CORSMiddleware object at 0x000001BCFC7B8F60>
  File "C:Program FilesPython37libsite-packagesstarletteexceptions.py", line 82, in __call__
    raise exc from None
  File "C:Program FilesPython37libsite-packagesstarletteexceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x000001BCFC7C18C8>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <fastapi.routing.APIRouter object at 0x000001BCFC7220F0>
          └ <starlette.exceptions.ExceptionMiddleware object at 0x000001BCFC7B8E48>
  File "C:Program FilesPython37libsite-packagesstarletterouting.py", line 550, in __call__
    await route.handle(scope, receive, send)
          │     │      │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x000001BCFC7C18C8>
          │     │      │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │     │      └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │     └ <function Route.handle at 0x000001BCEE4FF6A8>
          └ <fastapi.routing.APIRoute object at 0x000001BCFC7B8E80>
  File "C:Program FilesPython37libsite-packagesstarletterouting.py", line 227, in handle
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x000001BCFC7C18C8>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x000001BCFC7D4...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('127.0.0.1', 80), 'clie...
          │    └ <function request_response.<locals>.app at 0x000001BCFC7C1A60>
          └ <fastapi.routing.APIRoute object at 0x000001BCFC7B8E80>
  File "C:Program FilesPython37libsite-packagesstarletterouting.py", line 41, in app
    response = await func(request)
                     │    └ <starlette.requests.Request object at 0x000001BCFC7D4588>
                     └ <function get_request_handler.<locals>.app at 0x000001BCFC7C19D8>
  File "C:Program FilesPython37libsite-packagesfastapirouting.py", line 213, in app
    is_coroutine=is_coroutine,
                 └ True
  File "C:Program FilesPython37libsite-packagesfastapirouting.py", line 113, in serialize_response
    exclude_none=exclude_none,
                 └ False
  File "C:Program FilesPython37libsite-packagesfastapirouting.py", line 65, in _prepare_response_content
    exclude_none=exclude_none,
                 └ False
  File "pydanticmain.py", line 386, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 706, in _iter
  File "pydanticmain.py", line 623, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 623, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 623, in pydantic.main.BaseModel._get_value
  [Previous line repeated 722 more times]
  File "pydanticmain.py", line 605, in pydantic.main.BaseModel._get_value
  File "C:Program FilesPython37libabc.py", line 139, in __instancecheck__
    return _abc_instancecheck(cls, instance)
           │                  │    └ 8
           │                  └ <class 'pydantic.main.BaseModel'>
           └ <built-in function _abc_instancecheck>
RecursionError: maximum recursion depth exceeded in comparison```

My team and I really fell in love with FastAPI when we started using it almost a year and a half ago. It really is a great framework with its great performance, how easy it is to use with async code, its tight integration with Pydantic, and how easy it is to use BackgroundTasks.

We still love it and I personally think it’s currently the best Python web framework for building APIs. However, we are now moving away from using BackgroundTasks due to a few drawbacks that come with using it.

What is BackgroundTasks?

You can use BackgroundTasks to do a «task» that takes some time to do, but the person doing a request to your backend does not really have to wait for the task to complete before you respond back to them. An example of this could be to send a «forgot your password»-email, copy files between two servers, or do some costly calculations of some kind. Let’s use the «send forgot your password email» as an example simply because it’s a quite common thing to do.

Assume we have the following code and are not using background task:

# user_view.py
@app.post("/forgot-password/{email}")
async def send_forget_password_email(email: str):
  await user_domain.send_forget_password_email(email)

  return {
    "message": "A reset-password email has been sent "
    "if an account with that email exists on our platform."
  }

# user_domain.py
async def send_forget_password_email(email: str) -> None:
  user_exists = await user_model.user_exists(email=email) # 1
  if user_exists:
    password_reset_token = await auth_service.get_password_reset_token(
      email
    ) # 2
    await email_service.send_forget_password_email(
      email=email, password_reset_token=password_reset_token
    ) # 3

In the code above, the user has to wait for #1, #2, & #3 to complete before they get the message that the email has been sent. Let’s say that step #1 takes 20ms, #2 takes 700ms, and #3 takes 250 ms. Let’s also say that it takes 100ms for the user’s request to reach your backend and the same time for your response to reach the user. That means that it would take 1170ms (20+700+250+100+100) for the user to be informed that the request was successful.

Let’s now instead look at the code if we use background tasks.

# user_view.py
@app.post(
  "/forgot-password/{email}",
  status_code=status.HTTP_202_ACCEPTED
)
async def send_forget_password_email(
  email: str, background_tasks: BackgroundTasks
):
  background_tasks.add_task(
    user_domain.send_forget_password_email, email
  )

  return {
    "message": "A reset-password email has been sent "
    "if an account with that email exists on our platform."
  }

# user_domain.py
async def send_forget_password_email(email: str) -> None:
  user_exists = await user_model.user_exists(email=email) # 1
  if user_exists:
    password_reset_token = await auth_service.get_password_reset_token(
      email
    ) # 2
    await email_service.send_forget_password_email(
      email=email, password_reset_token=password_reset_token
    ) # 3

When using a background task, we do no longer need to wait for #1, #2, & #3 to complete before responding. This means that the user only has to wait (around) 200ms before they are informed that the request was successful. That is an 82.9% reduction in time. And with such a small code change! Amazing!

Now when we know better what BackgroundTasks is, let’s take a look at the gotcha/drawbacks

The gotcha

No presistency

If your service for some reason crashes or is restarted while the background task is in the queue to be run, or is being run, then there is no way for the task to continue or be restarted after the service comes back up again. Not even that, but you won’t even know that the background task was started but not finished.

This might not be super important when sending a «forgotten password»-email, but there might be other things you do in background tasks that you definitely want to know if thet were not completed.

Untracable errors

Everyone hates when an error occurs. But if you just use background tasks as above, then you will get the worst kind of errors that exist; errors that contain no information about what caused them.

Let’s say that we use background task and user_model.user_exists raises InvalidEmailException(f"'{email}' is not a valid email") (you should probably have checked email validity earlier, but let’s go with this as an example), then the raised error that you will see in the console will like something like this:

example-api | ERROR:    Exception in ASGI application
example-api | Traceback (most recent call last):
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 369, in run_asgi
example-api |     result = await app(self.scope, self.receive, self.send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 59, in __call__
example-api |     return await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/fastapi/applications.py", line 208, in __call__
example-api |     await super().__call__(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/applications.py", line 112, in __call__
example-api |     await self.middleware_stack(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
example-api |     raise exc from None
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
example-api |     await self.app(scope, receive, _send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/cors.py", line 86, in __call__
example-api |     await self.simple_response(scope, receive, send, request_headers=headers)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/cors.py", line 142, in simple_response
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 26, in __call__
example-api |     await response(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 224, in __call__
example-api |     await run_until_first_complete(
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in run_until_first_complete
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in <listcomp>
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 216, in stream_response
example-api |     async for chunk in self.body_iterator:
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 56, in body_stream
example-api |     task.result()
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 38, in coro
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 26, in __call__
example-api |     await response(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 224, in __call__
example-api |     await run_until_first_complete(
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in run_until_first_complete
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in <listcomp>
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 216, in stream_response
example-api |     async for chunk in self.body_iterator:
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 56, in body_stream
example-api |     task.result()
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 38, in coro
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/exceptions.py", line 86, in __call__
example-api |     raise RuntimeError(msg) from exc
example-api | RuntimeError: Caught handled exception, but response already started.
example-api | 2021-08-09 10:29:31,539 ERROR [uvicorn.error:372][MainThread] Exception in ASGI application
example-api | Traceback (most recent call last):
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 369, in run_asgi
example-api |     result = await app(self.scope, self.receive, self.send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 59, in __call__
example-api |     return await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/fastapi/applications.py", line 208, in __call__
example-api |     await super().__call__(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/applications.py", line 112, in __call__
example-api |     await self.middleware_stack(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
example-api |     raise exc from None
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
example-api |     await self.app(scope, receive, _send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/cors.py", line 86, in __call__
example-api |     await self.simple_response(scope, receive, send, request_headers=headers)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/cors.py", line 142, in simple_response
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 26, in __call__
example-api |     await response(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 224, in __call__
example-api |     await run_until_first_complete(
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in run_until_first_complete
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in <listcomp>
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 216, in stream_response
example-api |     async for chunk in self.body_iterator:
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 56, in body_stream
example-api |     task.result()
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 38, in coro
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 26, in __call__
example-api |     await response(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 224, in __call__
example-api |     await run_until_first_complete(
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in run_until_first_complete
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/concurrency.py", line 24, in <listcomp>
example-api |     [task.result() for task in done]
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/responses.py", line 216, in stream_response
example-api |     async for chunk in self.body_iterator:
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 56, in body_stream
example-api |     task.result()
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/middleware/base.py", line 38, in coro
example-api |     await self.app(scope, receive, send)
example-api |   File "/home/python/.cache/pypoetry/virtualenvs/example-o9msT97p-py3.9/lib/python3.9/site-packages/starlette/exceptions.py", line 86, in __call__
example-api |     raise RuntimeError(msg) from exc
example-api | RuntimeError: Caught handled exception, but response already started.

Isn’t that helpful? /s This error is raised by starlette, one of FastAPI’s dependencies. And as you see, there is no way to figure out what actually caused the error, so there is no good way of debugging this and finding the cause. Especially if you have a lot of traffic on your server.

Global error handlers prevent BackgroundTasks from being triggered

Thank you to hexarobi for making me aware of this gotcha after reading this blog.

If you have an error that is raised to the global error handler, then the background task won’t be triggered. If this is a problem or not depends on your use case.

To exemplify this, let’s say that you want to send a warning to every user if someone tries to log in to their account with the wrong password. Also, assume that we have a global error handler that returns a 403 on the exception InvalidCredentials.

# auth_view.py
@router.post(
  "/oauth/token",
  response_model=auth_view_schemas.UserAuthTokenResponse,
)
async def login(
  credentials: auth_view_schemas.PasswordGrantTypePayload,
  response: Response,
  background_tasks: BackgroundTasks
) -> dto.JSON:
  authentication_tokens = await auth_domain.get_tokens(
    credentials, background_tasks
  )

  response.set_cookie(
    # ... omitted
  )

  return authentication_tokens.dict()

# auth_domain.py
async get_tokens(
  credentials: auth_view_schemas.PasswordGrantTypePayload,
  background_tasks: BackgroundTasks
):
  try:
    await verify_credentials(credentials)
  except exceptions.InvalidCredentials as e:
    background_tasks.add_task(
      send_invalid_password_warning, credentials.email
    )
    raise e

  return await generate_authentication_tokens(credentials.email)
`

With the above code, the global error handler will take care of the raised exception exceptions.InvalidCredentials if verify_credentials raises it. The global error handler will create a new response and return it. This means that the background task that was a part of the original response will never be triggered since the response was never returned. In other words, the user will never be notified that someone tried to log in with their email.

Mitigating the drawbacks

Making sure background tasks run even on raised errors.

This is easiest mitigated by catching the error in the view layer and then return the appropriate response.

# auth_view.py
@router.post(
  "/oauth/token",
  response_model=auth_view_schemas.UserAuthTokenResponse,
)
async def login(
  credentials: auth_view_schemas.PasswordGrantTypePayload,
  response: Response,
  background_tasks: BackgroundTasks
) -> dto.JSON:
  try:
    authentication_tokens = await auth_domain.get_tokens(
      credentials, background_tasks
    )
  except exceptions.InvalidCredentials:
    return JSONResponse(
      status_code=status_code,
      content={"errors": "Incorrect username or password"},
    )

  # ... rest of function

Another way would be to create a new background task in the global error handler and return that with the response. I leave how to do that up to the reader ;)

Making the untraceable errors traceable

There is actually a very nice way to make the error traceable by using a decorator.

The following decorator:

import inspect
import logging
import uuid
from functools import wraps
from typing import Any, Callable

logger = logging.getLogger(__name__)


def background_task_wrapper(func: Callable) -> Callable:
  task_name = func.__name__

  @wraps(func)
  async def wrapper(*args: Any, **kwargs: Any) -> None:
    task_id = uuid.uuid4()

    func_args = inspect.signature(func).bind(*args, **kwargs).arguments
    func_args_str = (
      ", ".join("{}={!r}".format(*item) for item in func_args.items())
    )

    logger.info(
      f"[{task_id}] Started {task_name} with arguments: {func_args_str}"
    )

    try:
      await func(*args, **kwargs)
      logger.info(f"[{task_id}] Finished {task_name} Successfully")
    except Exception as e: #4
      logger.error(
        f"[{task_id}] Failed Permanently {task_name} with error: {e}"
      )
      # 5
  return wrapper

gives the following log:

example-api | 2021-08-09 12:25:05,263 INFO  [example.user_model.user_exists:20][MainThread] [4a683449-f5eb-463d-8eef-f7dc8ac8ba57] Started user_exists with arguments: email='hello there@examle.com'
example-api | 2021-08-09 12:25:05,263 ERROR [example.user_model.user_exists:26][MainThread] [4a683449-f5eb-463d-8eef-f7dc8ac8ba57] Failed Permanently user_exists with error: 'hello there@examle.com' is not a valid email

You don’t get the whole traceback but it contains (what I think is) some important information; the name of the function that failed and the arguments that were passed to the function when it failed. (Please note that the line numbers are «incorrect» as they are the line number of where the logger statement is inside the wrapper.)

An important thing to notice is that we want to catch ALL exceptions (# 4) and we do not want to raise the exception we catch (#5). That is because if we don’t catch all exceptions, or raise the exception again, then we will still get the unhelpful but super long error log in our terminal.

Making the task persistent

I’m sorry but this can’t be mitigated by just using a decorator or some other simple thing. You could semi-manually look through the logs to try to identify what background tasks started before the crash but were never logged successfully. You could save the calls to a DB and then mark them as completed (or delete them) from the database once they are completed. But that would just be stupid.

What we ended up doing is starting to use RabbitMQ which is a message broker. We have started to need a message broker for other reasons. Using it to replace BackgroundTaks was therefore an easy choice for us to solve the task persistency problem.

Понравилась статья? Поделить с друзьями:
  • Error exception handling console input java io ioexception неверный дескриптор
  • Error exception handlers complete
  • Error exception access violation blender
  • Error event operation timed out 60 for i o session closing it
  • Error event id 7000