Include possible HTTPExceptions in OpenAPI spec #9124
-
First check
ExampleHere's a self-contained, minimal, reproducible, example with my use case: from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/")
def read_root(gimme_coffee: bool = False):
if gimme_coffee:
raise HTTPException(status_code=418, detail="I'm a teapot.")
return {"Hello": "World"} Description
How do I set the status code for 418 in the OpenAPI docs? I was expecting to be able to pass in a list of possible HTTPExceptions and have them be automatically converted, but there does not appear to be any way to do that. The closest thing I found was Additional Status Codes but that seemed at best tangentially relevant since I am raising HTTPExceptions, not looking to build out a custom exception JSON response. Properly documenting error states is just as important as documenting the success state. I really hope FastAPI provides an easy way to do this. Environment
|
Beta Was this translation helpful? Give feedback.
Replies: 20 comments 1 reply
-
Yes, you can declare additional responses with additional status codes etc. You just need to add it as a path parameter then those additional responses will be included in the OpenAPI schema, here is a example @app.get("/",responses={418: {"detail": "I'm a teapot"}, 413: {"Too large": "...The Payload"}})
def read_root(gimme_coffee: bool = False):
if gimme_coffee:
raise HTTPException(status_code=418, detail="I'm a teapot.")
return {"Hello": "World"} Documentation for Additional Responses |
Beta Was this translation helpful? Give feedback.
-
That's the additional status codes I mentioned above, and it is a long way from easy. I was hoping I could instantiate an error, pass it in similar to Nuts. I guess this is actually a feature request: we need a better way to handle error message documentation. My preference would be to just pass in a list of some base error classes and have it figure out the status code and output formatting based on that (having the actual error output isn't necessary, particularly since it may well change depending on the situation). That way, I could setup a sub-class of HTTPException that defaults to a 418 status code, and by passing that in and using it my exception is fully documented. |
Beta Was this translation helpful? Give feedback.
-
@onecrayon On my APIs I'm using custom exceptions that include references to the error code, message and Pydantic model to be returned. I have an example over here: https://github.com/David-Lor/FastAPI-Pydantic-Mongo_Sample_CRUD_API On a nutshell:
Probably far from ideal and might be better ways to achieve it, but for now it's been working pretty fine for me. It's more complex though. |
Beta Was this translation helpful? Give feedback.
-
@onecrayon Does something like this would work for you?` from fastapi import FastAPI, APIRouter
app = FastAPI()
router = APIRouter()
@router.get("/dummy1")
async def dummy1():
...
@router.get("/dummy2")
async def dummy2():
...
@router.get("/dummy3")
async def dummy3():
...
@router.get("/dummy4")
async def dummy4():
...
app.include_router(
router,
responses={
418: {
"description": "I'm a teapot",
"content": {"application/json": {"example": {"dummy": "dumdum"}}},
},
413: {
"description": "I'm too large",
"content": {"application/json": {"example": {"dummy": "dumdum"}}},
},
},
) |
Beta Was this translation helpful? Give feedback.
-
@ycd Per my comment here, that's what I'm doing. It's also unnecessary code duplication. My feature request is for an easy way to pass in children of HTTPException and just have FastAPI automatically figure out what the error output should look like. So for example here's some pseudo-code that's based on my current project: from fastapi import status, HTTPException
class APIException(HTTPException):
"""Light wrapper around HTTPException that allows specifying defaults via class property"""
status_code = status.HTTP_400_BAD_REQUEST
detail = None
headers = None
def __init__(self, *args, **kwargs):
if "status_code" not in kwargs:
kwargs["status_code"] = self.status_code
if "detail" not in kwargs:
kwargs["detail"] = self.detail
if "headers" not in kwargs:
kwargs["headers"] = self.headers
super().__init__(*args, **kwargs)
class TeapotException(APIException):
status_code = 418
detail = "I'm a teapot."
@app.get("/")
def read_root(gimme_coffee: bool = False, response_exceptions=[TeapotException]):
if gimme_coffee:
raise TeapotException()
return {"Hello": "World"} Even better would be to have FastAPI able to introspect the method and look for HTTPException-derived exceptions to auto-populate the list, but that's possibly edging a little too far into magical territory. In any case, it's silly that I can have FastAPI automatically discover query params and such based on passed classes, but need to explicitly duplicate all of the information that's already stored in my exception classes in order to document exceptions. |
Beta Was this translation helpful? Give feedback.
-
Is this issue on the planning, or can I create a PR for this? |
Beta Was this translation helpful? Give feedback.
-
Does this helps? https://github.com/Kludex/fastapi-responses |
Beta Was this translation helpful? Give feedback.
-
@Kludex That is exactly the sort of thing that I'm looking for! Granted, it's a little more "magical" than I usually like, but I would happily take it over the current setup where I have to explicitly define status codes and error strings multiple times. |
Beta Was this translation helpful? Give feedback.
-
@onecrayon - given that the main use case for this is setting some expectations for API users on what HTTP responses to handle, would this suffice?
|
Beta Was this translation helpful? Give feedback.
-
@jtv8 No, that's insufficient. The API spec needs to be able to output not only the status code, but also the specification for the response body (since different exceptions often have different JSON body responses that get rendered, or other methods for reporting what went wrong). |
Beta Was this translation helpful? Give feedback.
-
+1 here. I'm currently designing various exceptions, each having a slightly different response payloads. I would love to have a convenient way to specify those different exceptions in our swagger docs. I like @onecrayon's example usage... it seems pretty consistent with existing FastAPI paradigms. However, in my case I'm also modifying the payload, so I'm not sure how that could be handled. In my case:
I feel like this could be achieved somehow combining a pydantic |
Beta Was this translation helpful? Give feedback.
-
Any update here? |
Beta Was this translation helpful? Give feedback.
-
Bump. In the absence of any decisions, what is the proper pattern for documenting the HTTPExceptions? |
Beta Was this translation helpful? Give feedback.
-
Have there been any advancements on most DRY compliant pattern for ensuring custom HTTPExceptions are displayed in OpenAPI docs? |
Beta Was this translation helpful? Give feedback.
-
What I would like to see is using Pydantic model to define contents of error response body. In my case I have client and server that share common library which contains all request and response bodies defined as Pydantic models. This helps in interpreting req&resp bodies on both sides. |
Beta Was this translation helpful? Give feedback.
-
Just for anyone passing by, here's how I'm handling it, I don't think it's ideal, but does the trick. def APIExcpetion(error_name, msg):
error_name += 'Error'
return create_model(error_name, detail=(str, msg))
def ObjNotFoundError(obj: Type[BaseModel], lookup_field=None):
name = obj.__name__
msg = f'{name} with {lookup_field} not found' if lookup_field else f'{name} not found'
return APIExcpetion('NotFound', msg)
def ObjExistError(obj, lookup_field=None):
name= obj.__name__
msg = f'{name} with {lookup_field} already exists' if lookup_field else f'{name} already exists'
return APIExcpetion('ObjectExist', msg) Example Usage: @router.post(
'/{client_id}/channels',
response_model=ClientChannel,
responses={404: {'model': ObjNotFoundError(Client, 'id')},
400: {'model': ObjExistError(ClientChannel, 'name')}}
)
async def create_channel(
user: Annotated[User, Depends(get_current_glix_user)],
client_id: PydanticObjectId,
channel_form: ClientChannelForm
) -> ClientChannel:
"""
Create a new channel for a client
"""
# I'm using Beanie, so this is just some pseudo functions to get your models...
client, channel_exist = await asyncio.gather(get_client(client_id), check_channel_exists(client_id,channel_form.name))
if not client:
raise HTTPException(status_code=404, detail='Client not found')
if channel_exist:
raise HTTPException(status_code=400, detail='Channel with this name and client already exists')
# do stuff and return model... |
Beta Was this translation helpful? Give feedback.
-
+1 For me it would be enough to have extra responses coupled to a dependency, so that when an endpoint is using the dependency, it automatically adds the response to it. (see #11144) |
Beta Was this translation helpful? Give feedback.
-
Hi there. As many others have mentioned, the only way so far is following this section from the doc additional-responses or even including the responses at a router level. app.include_router(
router,
responses={
418: {
"description": "I'm a teapot",
"content": {"application/json": {"example": {"dummy": "dumdum"}}},
},
413: {
"description": "I'm too large",
"content": {"application/json": {"example": {"dummy": "dumdum"}}},
},
},
) I'm marking this question as answered and labeling as possible feature. We don't have any due date or whether is gonna be included or not yet |
Beta Was this translation helpful? Give feedback.
-
Correct me if I am wrong, but out of all the attempts described here, there is no way to properly document two identical HTTP status codes with different "example" messages? |
Beta Was this translation helpful? Give feedback.
-
https://pypi.org/project/fastapi_errors/ |
Beta Was this translation helpful? Give feedback.
Hi there.
As many others have mentioned, the only way so far is following this section from the doc additional-responses
or even including the responses at a router level.
I'm marking this question as answered and labeling as possible feature. We don't have any due date or whether is gonna be included or not yet