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:
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
.
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"}
session
as a shared dependency, concurrent requests would share the same instance? – Iron