FastAPI - dependency inside Middleware? - fastapi

I am building a browser game where every user has 4 types of ressources and each users produce more ressources based on the level of their farms.
What I am trying to do, is whenever a given user is logged in, I want to recalculate it's current ressources whenever he is refreshing the page or performing any action.
Seems the middleware is the right tool for my need but I am a bit confused on the implementation with my current architecture (multiple routers). What would be the cleanest way to call a function to perform ressources recalculation before performing any other API calls?
This is what I have tried so far (example middleware):
app.py (without middleware):
from fastapi import FastAPI, Depends, Request
from src.api.v1.village import village_router
from src.api.v1.auth import auth_router
from src.api.v1.admin import admin_router
from src.core.auth import get_current_user
from src.core.config import *
def create_app() -> FastAPI:
root_app = FastAPI()
root_app.include_router(
auth_router,
prefix="/api/v1",
tags=["auth"],
)
root_app.include_router(
admin_router,
prefix="/api/v1",
tags=["admin"],
dependencies=[Depends(get_current_user)],
)
root_app.include_router(
village_router,
prefix="/api/v1",
tags=["village"],
)
return root_app
I then added an helloworld middleware and add the get_current_user as a dependency because a user must be logged_in to perform the calculations.
app.py (with middleware):
from fastapi import FastAPI, Depends, Request
from src.api.v1.village import village_router
from src.api.v1.auth import auth_router
from src.api.v1.admin import admin_router
from src.core.auth import get_current_user
from src.core.config import *
import time
def create_app() -> FastAPI:
root_app = FastAPI()
root_app.include_router(
auth_router,
prefix="/api/v1",
tags=["auth"],
)
root_app.include_router(
admin_router,
prefix="/api/v1",
tags=["admin"],
dependencies=[Depends(get_current_user)],
)
root_app.include_router(
village_router,
prefix="/api/v1",
tags=["village"],
)
#root_app.middleware("http")
async def add_process_time_header(
request: Request, call_next, current_user=Depends(get_current_user)
):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
print("middleware call")
return response
return root_app
Seems the dependency is ignored because the middleware is called even when I am not logged in, which is not the case for my protected_routes (I am getting a 401 error on my routes if I a not logged in).
async def get_current_user(
session=Depends(get_db), token: str = Depends(oauth2_scheme)
) -> UserAuth:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[AUTH_TOKEN_ALGO])
email: str = payload.get("email")
user_id: str = payload.get("user_id")
if email is None:
raise ValueError("A very specific bad thing happened.")
token_data = UserJWTToken(user_id=user_id, email=email)
except jwt.PyJWTError:
raise ValueError("A very specific bad thing happened.")
user = get_user_by_email(session, token_data.email)
if user is None:
raise ValueError("A very specific bad thing happened.")
return user

You can make use of the Global Dependencies. Here is one example that may help you in this situation
from fastapi import Depends, FastAPI, Request
def get_db_session():
print("Calling 'get_db_session(...)'")
return "Some Value"
def get_current_user(session=Depends(get_db_session)):
print("Calling 'get_current_user(...)'")
return session
def recalculate_resources(request: Request, current_user=Depends(get_current_user)):
print("calling 'recalculate_resources(...)'")
request.state.foo = current_user
app = FastAPI(dependencies=[Depends(recalculate_resources)])
#app.get("/")
async def root(request: Request):
return {"foo_from_dependency": request.state.foo}

Related

FastAPI Custom Websocket Object

I want to be able to create a custom WebSocket object rather than using Starlette's so that I can add some more things in the constructor and add some more methods. In FastAPI, you're able to subclass the APIRoute and pass in your own Request object. How would I do the same for the WebSocket router?
As you say, there doesn't seem to be an easy way to set the websocket route class (short of a lot of subclassing and rewriting). I think the simplest way to do this would be to define your own wrapper class around the websocket, taking whatever extra data you want, and then define the methods you need. Then you can inject that as a dependency, either with a separate function, or use the class itself as a dependency, see the documentation for details, which is what I'm doing below.
I've put together a minimal example, where the URL parameter name is passed to the wrapper class:
# main.py
from fastapi import Depends, FastAPI, WebSocket
app = FastAPI()
class WsWrapper:
def __init__(self, websocket: WebSocket, name: str) -> None:
self.name = name
self.websocket = websocket
# You can define all your custom logic here, I'm just adding a print
async def receive_json(self, mode: str = "text"):
print(f"Hello from {self.name}", flush=True)
return await self.websocket.receive_json(mode)
#app.websocket("/{name}")
async def websocket(ws: WsWrapper = Depends()):
await ws.websocket.accept()
while True:
data = await ws.receive_json()
print(data, flush=True)
You can test it by running uvicorn main:app and connecting to ws://localhost:8000/test, and it should print "Hello from test" when receiving JSON.
Ended up just monkeypatching the modules. Track this PR for when monkeypatching isn't necessary: https://github.com/tiangolo/fastapi/pull/4968
from typing import Callable
from fastapi import routing as fastapi_routing
from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool
from starlette.requests import Request as StarletteRequest
from starlette.websockets import WebSocket as StarletteWebSocket
from starlette.types import ASGIApp, Receive, Scope, Send
class Request(StarletteRequest):
pass
class WebSocket(StarletteWebSocket):
pass
def request_response(func: Callable) -> ASGIApp:
"""
Takes a function or coroutine `func(request) -> response`,
and returns an ASGI application.
"""
is_coroutine = is_async_callable(func)
async def app(scope: Scope, receive: Receive, send: Send) -> None:
request = Request(scope, receive=receive, send=send)
# Force all views to be a coroutine
response = await func(request)
if is_coroutine:
response = await func(request)
else:
response = await run_in_threadpool(func, request)
await response(scope, receive, send)
return app
fastapi_routing.request_response = request_response
def websocket_session(func: Callable) -> ASGIApp:
"""
Takes a coroutine `func(session)`, and returns an ASGI application.
"""
# assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async"
async def app(scope: Scope, receive: Receive, send: Send) -> None:
session = WebSocket(scope, receive=receive, send=send)
await func(session)
return app
fastapi_routing.websocket_session = websocket_session

Can't get Fastapi websocket and celery to play nice

import asyncio
from fastapi import APIRouter, WebSocket
from app.sherlock import main
import asyncio
import billiard as multiprocessing
from queue import Empty
from celery import Celery
from fastapi import WebSocket, WebSocketDisconnect
router = APIRouter()
#router.websocket_route("/{username}")
async def handles(websocket: WebSocket):
username = websocket.path_params["username"]
await websocket.accept()
q = multiprocessing.Queue()
p = multiprocessing.Process(target=main, args=(q, username))
p.start()
while True:
await asyncio.sleep(0)
try:
q_result = q.get(block=False)
except Empty:
q_result = None
if q_result:
try:
await websocket.send_json(q_result)
except WebSocketDisconnect:
p.terminate()
break
if not p.is_alive():
try:
await websocket.send_json(q_result)
await websocket.close()
except WebSocketDisconnect:
p.terminate()
finally:
break
So this is what I have so far, a user inputs a username, which then the backend picks up, makes about 400 requests to different websites, and returns data. I used multithreading for that.
My issue is that when I host the site and have multiple users on it, the website drastically slows down. My thought is that I may need to use Celery for this but dont really know where to start. Does anyone have any ideas? let me know!

run fast api from a class

I want to run fastapi from a custom class
it's ok when I pass self.app as first argument to run method, but to use reload=True it is
necessary to use "module:app" pattern
what is the correct app name (string) when it is initialized in a class?
here is a simple file named main.py
from fastapi import FastAPI
class ApiServer:
def __init__(self):
self.app = FastAPI()
#self.app.get("/")
async def read_root():
return {"Hello": "World"}
def run(self):
# uvicorn.run(self.app, host="0.0.0.0", port=8000) #work but i wanna use
# reload=True which nedd to pass app in pattern "module:app"
uvicorn.run("main:self.app", host="0.0.0.0", port=8000, reload=True, use_colors=False)
if __name__ == "__main__":
server = ApiServer()
server.run()
You can run it as uvicorn main:server.app --reload, but you need to move the definition of server out of the if __name__ == "__main__" block:
from fastapi import FastAPI
class ApiServer:
def __init__(self):
self.app = FastAPI()
#self.app.get("/")
async def read_root():
return {"Hello": "World"}
server = ApiServer()
if __name__ == "__main__":
server.run()
This if because uvicorn doesn't run your python file as the main file, so otherwise, server (and therefore your FastAPI app) will never get instantiated.
The real question is as jossefaz wrote, think about why do you want to do this. As your API grows, the __init__ method will keep growing and to me, it seems like a hassle to define all your methods two extra indentation levels in. What would be the benefit?

My Discord bot isn't responding to a command to pull data from timezonedb

So it can actually show the info I want it to in the terminal. But when I prompt it to send it as a discord message it appears to be attempting to send a blank message. It's probably something stupid, but thank you for looking. The language is Python.
import os
import discord
import requests
import json
import pprint
client = discord.Client()
def get_time():
response = requests.get("http://api.timezonedb.com/v2.1/get-time-zone?key=W9BJQ3QMGG69&format=json&by=position&lat=37.9838&lng=23.7275")
return pprint.pprint(response.json())
#client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('$petertest'):
clock = get_time()
await message.channel.send(clock)
client.run(os.environ['TOKEN'])
You are using the pprint module to print the data to the console itself. That is the issue there
Changing your code to simply return the data will fix the error.
return response.json()
If you want to send the formatted json data to discord, you can use json.dumps:
if message.content.startswith('test'):
clock = get_time()
clock = json.dumps(clock, indent=4)
await message.channel.send(clock)

how to set fast API version to allow HTTP can specify version in accept header?

I am working on a project that requires me to version fast API endpoints. We want to version the endpoint through HTTP accept header, e.g. headers={'Accept': 'application/json;version=1.0.1'}, headers={'Accept': 'application/json;version=1.0.2'}. Only set up the api version like this seem not work:
app = FastAPI(
version=version,
title="A title",
description="Some description.",
)
Does anyone know what else I need to do with this ?
Well maybe the version in path url could be better
sub apps docs
from fastapi import FastAPI
app = FastAPI()
v1 = FastAPI()
#v1.get("/app/")
def read_main():
return {"message": "Hello World from api v1"}
v2 = FastAPI()
#v2.get("/app/")
def read_sub():
return {"message": "Hello World from api v2"}
app.mount("/api/v1", v1)
app.mount("/api/v2", v2)
You will see the auto docs for each app
localhost:8000/api/v1/docs
localhost:8000/api/v2/docs
But you always get the headers in request
from starlette.requests import Request
from fastapi import FastAPI
app = FastAPI()
#app.post("/hyper_mega_fast_service")
def fast_service(request: Request, ):
aceept = request.headers.get('Accept')
value = great_fuction_to_get_version_from_header(aceept)
if value == '1.0.1':
"Do something"
if value == '1.0.2':
"Do something"
Try api versioning for fastapi web applications
Installation
pip install fastapi-versioning
Examples
from fastapi import FastAPI
from fastapi_versioning import VersionedFastAPI, version
app = FastAPI(title="My App")
#app.get("/greet")
#version(1, 0)
def greet_with_hello():
return "Hello"
#app.get("/greet")
#version(1, 1)
def greet_with_hi():
return "Hi"
app = VersionedFastAPI(app)
this will generate two endpoints:
/v1_0/greet
/v1_1/greet
as well as:
/docs
/v1_0/docs
/v1_1/docs
/v1_0/openapi.json
/v1_1/openapi.json
There's also the possibility of adding a set of additional endpoints that
redirect the most recent API version. To do that make the argument
enable_latest true:
app = VersionedFastAPI(app, enable_latest=True)
this will generate the following additional endpoints:
/latest/greet
/latest/docs
/latest/openapi.json
In this example, /latest endpoints will reflect the same data as /v1.1.
Try it out:
pip install pipenv
pipenv install --dev
pipenv run uvicorn example.annotation.app:app
# pipenv run uvicorn example.folder_name.app:app
Usage without minor version
from fastapi import FastAPI
from fastapi_versioning import VersionedFastAPI, version
app = FastAPI(title='My App')
#app.get('/greet')
#version(1)
def greet():
return 'Hello'
#app.get('/greet')
#version(2)
def greet():
return 'Hi'
app = VersionedFastAPI(app,
version_format='{major}',
prefix_format='/v{major}')
this will generate two endpoints:
/v1/greet
/v2/greet
as well as:
/docs
/v1/docs
/v2/docs
/v1/openapi.json
/v2/openapi.json
Extra FastAPI constructor arguments
It's important to note that only the title from the original FastAPI will be
provided to the VersionedAPI app. If you have any middleware, event handlers
etc these arguments will also need to be provided to the VersionedAPI function
call, as in the example below
from fastapi import FastAPI, Request
from fastapi_versioning import VersionedFastAPI, version
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI(
title='My App',
description='Greet uses with a nice message',
middleware=[
Middleware(SessionMiddleware, secret_key='mysecretkey')
]
)
#app.get('/greet')
#version(1)
def greet(request: Request):
request.session['last_version_used'] = 1
return 'Hello'
#app.get('/greet')
#version(2)
def greet(request: Request):
request.session['last_version_used'] = 2
return 'Hi'
#app.get('/version')
def last_version(request: Request):
return f'Your last greeting was sent from version {request.session["last_version_used"]}'
app = VersionedFastAPI(app,
version_format='{major}',
prefix_format='/v{major}',
description='Greet users with a nice message',
middleware=[
Middleware(SessionMiddleware, secret_key='mysecretkey')
]
)

Resources