run fast api from a class - fastapi

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?

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

In gRPC python, the Service class and the Serve method are always in the same file, why?

In gRPC python, the Service-class and the Serve method are always in the same file, why?
for example -
the service class - link and the serve() - link
Similarly - service-class and serve()
I am new to python and grpc. In my project I wrote the serve method in different file importing the service-class, the server seems started but when I invoke it from client code (postman)
it doesn't work
Here is my code - ems_validator_service.py contains the service class
and main.py has the serve() method
File: ems_validator_service.py -
from validator.src.grpc import ems_validator_service_pb2
from validator.src.grpc.ems_validator_service_pb2_grpc import EmsValidatorServiceServicer
class EmsValidatorServiceServicer(EmsValidatorServiceServicer):
def Validate(self, request, context):
# TODO: logic to validate
return ems_validator_service_pb2.GetStatusResponse(
validation_status=ems_validator_service_pb2.VALIDATION_STATUS_IN_PROGRESS)
def GetStatus(self, request, context):
# TODO: logic to get actual status
return ems_validator_service_pb2.GetStatusResponse(
validation_status=ems_validator_service_pb2.VALIDATION_STATUS_IN_PROGRESS)
File: main.py -
from validator.src.grpc.ems_validator_service_pb2_grpc import (
EmsValidatorServiceServicer,
add_EmsValidatorServiceServicer_to_server
)
from concurrent import futures
import grpc
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
add_EmsValidatorServiceServicer_to_server(EmsValidatorServiceServicer(), server)
server.add_insecure_port('localhost:50051') # todo change it
server.start()
server.wait_for_termination()
if __name__ == "__main__":
serve()
For the above code I can't invoke the rpc.
but If I move the serve method to the ems_validator_service.py file and call that method from main.py then it works fine. Not sure if it is a python thing or gRPC thing?
The error I get from client.py -
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/grpc/_channel.py", line 946, in __call__
return _end_unary_response_blocking(state, call, False, None)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/grpc/_channel.py", line 849, in _end_unary_response_blocking
raise _InactiveRpcError(state)
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.UNIMPLEMENTED
details = "Method not implemented!"
debug_error_string = "UNKNOWN:Error received from peer ipv6:%5B::1%5D:50051 {created_time:"2022-10-19T22:10:18.898439-07:00", grpc_status:12, grpc_message:"Method not implemented!"}"
>
As already mentioned, the same client works fine if I move the above serve() method to the service class
These are just simple examples. It's totally fine to call serve() from a different file.

FastAPI - dependency inside Middleware?

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}

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')
]
)

How to set an OpenTelemetry span attribute for a FastAPI route?

Adding a tag to a trace's span can be very useful to later analyse the tracing data and slice & dice it by the wanted tag.
After reading the OpenTelemetry docs, I couldn't figure out a way to add a custom tag to a span.
Here is my sample FastAPI application, already instrumented with OpenTelemetry:
"""main.py"""
from typing import Dict
import fastapi
from opentelemetry import trace
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
)
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(SimpleExportSpanProcessor(ConsoleSpanExporter()))
app = fastapi.FastAPI()
#app.get("/user/{id}")
async def get_user(id: int) -> Dict[str, str]:
"""Test endpoint."""
return {"message": "hello user!"}
FastAPIInstrumentor.instrument_app(app)
You can run it with uvicorn main:app --reload
How can I add the user id to the span?
After reading the source code of the ASGI instrumentation's OpenTelemetryMiddleware (here), I realised you can simply get the current span, set the tag (or attribute), and the current span will be returned with all its attributes.
#app.get("/user/{id}")
async def get_user(id: int) -> Dict[str, str]:
"""Test endpoint."""
# Instrument the user id as a span tag
current_span = trace.get_current_span()
if current_span:
current_span.set_attribute("user_id", id)
return {"message": "hello user!"}

Resources