Skip to content

amacfie/fastapi_returns_inferring_router

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Combines InferringRouter from fastapi-utils and Result from returns so that both primary (200) and additional response types can be inferred from the path operation type signature.

Install as user:

pip install git+https://github.com/amacfie/fastapi_returns_inferring_router

Install as developer:

pip install --editable .

How to use

from typing import Literal

from pydantic import BaseModel
from returns.result import Result, Success, Failure
import fastapi

from fastapi_returns_inferring_router import ReturnsInferringRouter


class ForbiddenBecauseOfUser(BaseModel):
    def status_code(*args):
        return 403

    msg: Literal["Forbidden because of user"] = "Forbidden because of user"


class ForbiddenBecauseOfKey(BaseModel):
    def status_code(*args):
        return 403

    msg: Literal["Forbidden because of key"] = "Forbidden because of key"


app = fastapi.FastAPI()

# two params are added to APIRouter
# * get_status_code
#   pass a function that can take either a return type or return value and
#   returns the HTTP status code you wish to use it for
# * merge_with_existing_responses
#   if there's already a responses parameter, if this parameter is True we
#   add to it, otherwise we don't touch it
r = ReturnsInferringRouter(
    get_status_code=lambda x: x.status_code(),
    merge_with_existing_responses=True,
)

# compatible with InferringRouter from fastapi-utils
@r.get("/foo/{bar}")
def foo(bar: str) -> str:
    return bar + "b"


# use the Returns library to write a function with a return type of Result.
# the success type becomes the 200 response type
# any failure types are also added as response types under the status code
# given by get_status_code.
# here we have two types with the same status code so the schema for 403 will
# be the union of the two types.
# currently, as with InferringRouter, only stuff that gets encoded as JSON
# is supported, e.g. having FileResponse as a return type won't work well.
# see https://github.com/amacfie/fastapi_returns_inferring_router/issues/2
@r.get("/baz/{bar}")
def baz(bar: str) -> Result[str, ForbiddenBecauseOfKey | ForbiddenBecauseOfUser]:
    if ...:
        return Failure(ForbiddenBecauseOfKey())
    elif ...:
        return Failure(ForbiddenBecauseOfUser())
    else:
        return Success(bar + "b")


app.include_router(r)

Generated schema (paste to https://editor.swagger.io/ for nicer view):

{
  "openapi": "3.0.2",
  "info": {
    "title": "FastAPI",
    "version": "0.1.0"
  },
  "paths": {
    "/foo/{bar}": {
      "get": {
        "summary": "Foo",
        "operationId": "foo_foo__bar__get",
        "parameters": [
          {
            "required": true,
            "schema": {
              "title": "Bar",
              "type": "string"
            },
            "name": "bar",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Response Foo Foo  Bar  Get",
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/baz/{bar}": {
      "get": {
        "summary": "Baz",
        "operationId": "baz_baz__bar__get",
        "parameters": [
          {
            "required": true,
            "schema": {
              "title": "Bar",
              "type": "string"
            },
            "name": "bar",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Response Baz Baz  Bar  Get",
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Response 403 Baz Baz  Bar  Get",
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ForbiddenBecauseOfKey"
                    },
                    {
                      "$ref": "#/components/schemas/ForbiddenBecauseOfUser"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ForbiddenBecauseOfKey": {
        "title": "ForbiddenBecauseOfKey",
        "type": "object",
        "properties": {
          "msg": {
            "title": "Msg",
            "enum": [
              "Forbidden because of key"
            ],
            "type": "string",
            "default": "Forbidden because of key"
          }
        }
      },
      "ForbiddenBecauseOfUser": {
        "title": "ForbiddenBecauseOfUser",
        "type": "object",
        "properties": {
          "msg": {
            "title": "Msg",
            "enum": [
              "Forbidden because of user"
            ],
            "type": "string",
            "default": "Forbidden because of user"
          }
        }
      },
      "HTTPValidationError": {
        "title": "HTTPValidationError",
        "type": "object",
        "properties": {
          "detail": {
            "title": "Detail",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      },
      "ValidationError": {
        "title": "ValidationError",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "type": "object",
        "properties": {
          "loc": {
            "title": "Location",
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            }
          },
          "msg": {
            "title": "Message",
            "type": "string"
          },
          "type": {
            "title": "Error Type",
            "type": "string"
          }
        }
      }
    }
  }
}

About

InferringRouter + Result = schema inference for all responses

Topics

Resources

License

Stars

Watchers

Forks

Languages