How to create routes with FastAPI within a class

This can be done by using an APIRouter‘s add_api_route method:

from fastapi import FastAPI, APIRouter


class Hello:

    def __init__(self, name: str):
        self.name = name
        self.router = APIRouter()
        self.router.add_api_route("/hello", self.hello, methods=["GET"])

    def hello(self):
        return {"Hello": self.name}


app = FastAPI()
hello = Hello("World")
app.include_router(hello.router)

Example:

$ curl 127.0.0.1:5000/hello
{"Hello":"World"}

add_api_route‘s second argument (endpoint) has type Callable[..., Any], so any callable should work (as long as FastAPI can find out how to parse its arguments HTTP request data). This callable is also known in the FastAPI docs as the path operation function (referred to as “POF” below).

Why decorating methods doesn’t work

WARNING: Ignore the rest of this answer if you’re not interested in a technical explanation of why the code in the OP’s answer doesn’t work

Decorating a method with @app.get and friends in the class body doesn’t work because you’d be effectively passing Hello.hello, not hello.hello (a.k.a. self.hello) to add_api_route. Bound and unbound methods (a.k.a simply as “functions” since Python 3) have different signatures:

import inspect
inspect.signature(Hello.hello)  # <Signature (self)>
inspect.signature(hello.hello)  # <Signature ()>

FastAPI does a lot of magic to try to automatically parse the data in the HTTP request (body or query parameters) into the objects actually used by the POF.

By using an unbound method (=regular function) (Hello.hello) as the POF, FastAPI would either have to:

  1. Make assumptions about the nature of the class that contains the route and generate self (a.k.a call Hello.__init__) on the fly. This would likely add a lot of complexity to FastAPI and is a use case that FastAPI devs (understandably) don’t seem interested in supporting. It seems the recommended way of dealing with application/resource state is deferring the whole problem to an external dependency with Depends.

  2. Somehow be able to generate a self object from the HTTP request data (usually JSON) sent by the caller. This is not technically feasible for anything other than strings or other builtins and therefore not really usable.

What happens in the OP’s code is #2. FastAPI tries to parse the first argument of Hello.hello (=self, of type Hello) from the HTTP request query parameters, obviously fails and raises a RequestValidationError which is shown to the caller as an HTTP 422 response.

Parsing self from query parameters

Just to prove #2 above, here’s a (useless) example of when FastAPI can actually “parse” self from the HTTP request:

(Disclaimer: Do not use the code below for any real application)

from fastapi import FastAPI

app = FastAPI()

class Hello(str):
    @app.get("/hello")
    def hello(self):
        return {"Hello": self}

Example:

$ curl '127.0.0.1:5000/hello?self=World'
{"Hello":"World"}

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)