FastAPI: CORS Middleware not working with GET method - fastapi

I try to use CORS on the FastAPi framework but it dose not working with GET method
Here's the code I'm working on:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_methods=["*"],
allow_headers=["*"],
)
#app.get("/test1")
async def test1():
return {"message": "Hello World"}

I had the same issue and the solution is to not use add_middelware but do the following:
First import from Starlette:
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
Create the middleware:
middleware = [
Middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*']
)
]
and then:
app = FastAPI(middleware=middleware)
This should work

Thanks #Sam_Ste, I had the same problem! I set my imports back to FastAPI and it still works. I think they are just proxies for the starlette modules (IMHO). The method is the vital thing, not using app_middleware.
from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware

For me, none of the above mentioned ideas worked. I had to create a custom middleware like this.
#app.middleware("http")
async def cors_handler(request: Request, call_next):
response: Response = await call_next(request)
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Origin'] = os.environ.get('allowedOrigins')
response.headers['Access-Control-Allow-Methods'] = '*'
response.headers['Access-Control-Allow-Headers'] = '*'
return response

When testing, make sure you add Origin header to your request. Otherwise CORSMiddleware will not send back the cors headers.
It may not be clear at first, but it is written here in the documentation:
Simple requests
Any request with an Origin header. In this case the middleware will
pass the request through as normal, but will include appropriate CORS
headers on the response.
So any request without an Origin will be ignored by CORSMiddleware and no CORS headers will be added.

Related

FastAPI/Starlette middleware returns a StreamingResponse instead of JSONResponse

I am trying to write a FastAPI middleware to add a header to a response. The contents of the header is based on the value of the response body. This is what I have so far:
from starlette.middleware.base import BaseHTTPMiddleware
class FooMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers["X-Foo"] = Foo(response.content)
return response
According to the docs, FastAPI returns JSON responses as a default (https://fastapi.tiangolo.com/advanced/custom-response/?h=stream#jsonresponse), and I have done nothing to override that.
The strange thing is that the type of the response object seems to be StreamingResponse (https://fastapi.tiangolo.com/advanced/custom-response/?h=stream#streamingresponse). What is causing that?

How to handle path parametes with hyphen(-) in a FastAPI application?

How to handle path parametes with hyphen(-) in FastAPI, as python does not allow hyphen in identifiers?
You can use alias in your definitions. It’s all documented here
As stated in the comments below, if the links stops working here is the code:
from typing import Union
from fastapi import FastAPI, Query
app = FastAPI()
#app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Trace failed fastapi requests with opencensus

I'm using opencensus-python to track requests to my python fastapi application running in production, and exporting the information to Azure AppInsights using the opencensus exporters. I followed the Azure Monitor docs and was helped out by this issue post which puts all the necessary bits in a useful middleware class.
Only to realize later on that requests that caused the app to crash, i.e. unhandled 5xx type errors, would never be tracked, since the call to execute the logic for the request fails before any tracing happens. The Azure Monitor docs only talk about tracking exceptions through the logs, but this is separate from the tracing of requests, unless I'm missing something. I certainly wouldn't want to lose out on failed requests, these are super important to track! I'm accustomed to using the "Failures" tab in app insights to monitor any failing requests.
I figured the way to track these requests is to explicitly handle any internal exceptions using try/catch and export the trace, manually setting the result code to 500. But I found it really odd that there seems to be no documentation of this, on opencensus or Azure.
The problem I have now is: this middleware function is expected to pass back a "response" object, which fastapi then uses as a callable object down the line (not sure why) - but in the case where I caught an exception in the underlying processing (i.e. at await call_next(request)) I don't have any response to return. I tried returning None but this just causes further exceptions down the line (None is not callable).
Here is my version of the middleware class - its very similar to the issue post I linked, but I'm try/catching over await call_next(request) rather than just letting it fail unhanded. Scroll down to the final 5 lines of code to see that.
import logging
from fastapi import Request
from opencensus.trace import (
attributes_helper,
execution_context,
samplers,
)
from opencensus.ext.azure.trace_exporter import AzureExporter
from opencensus.trace import span as span_module
from opencensus.trace import tracer as tracer_module
from opencensus.trace import utils
from opencensus.trace.propagation import trace_context_http_header_format
from opencensus.ext.azure.log_exporter import AzureLogHandler
from starlette.types import ASGIApp
from src.settings import settings
HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES["HTTP_HOST"]
HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES["HTTP_METHOD"]
HTTP_PATH = attributes_helper.COMMON_ATTRIBUTES["HTTP_PATH"]
HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES["HTTP_ROUTE"]
HTTP_URL = attributes_helper.COMMON_ATTRIBUTES["HTTP_URL"]
HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES["HTTP_STATUS_CODE"]
module_logger = logging.getLogger(__name__)
module_logger.addHandler(AzureLogHandler(
connection_string=settings.appinsights_connection_string
))
class AppInsightsMiddleware:
"""
Middleware class to handle tracing of fastapi requests and exporting the data to AppInsights.
Most of the code here is copied from a github issue: https://github.com/census-instrumentation/opencensus-python/issues/1020
"""
def __init__(
self,
app: ASGIApp,
excludelist_paths=None,
excludelist_hostnames=None,
sampler=None,
exporter=None,
propagator=None,
) -> None:
self.app = app
self.excludelist_paths = excludelist_paths
self.excludelist_hostnames = excludelist_hostnames
self.sampler = sampler or samplers.AlwaysOnSampler()
self.propagator = (
propagator or trace_context_http_header_format.TraceContextPropagator()
)
self.exporter = exporter or AzureExporter(
connection_string=settings.appinsights_connection_string
)
async def __call__(self, request: Request, call_next):
# Do not trace if the url is in the exclude list
if utils.disable_tracing_url(str(request.url), self.excludelist_paths):
return await call_next(request)
try:
span_context = self.propagator.from_headers(request.headers)
tracer = tracer_module.Tracer(
span_context=span_context,
sampler=self.sampler,
exporter=self.exporter,
propagator=self.propagator,
)
except Exception:
module_logger.error("Failed to trace request", exc_info=True)
return await call_next(request)
try:
span = tracer.start_span()
span.span_kind = span_module.SpanKind.SERVER
span.name = "[{}]{}".format(request.method, request.url)
tracer.add_attribute_to_current_span(HTTP_HOST, request.url.hostname)
tracer.add_attribute_to_current_span(HTTP_METHOD, request.method)
tracer.add_attribute_to_current_span(HTTP_PATH, request.url.path)
tracer.add_attribute_to_current_span(HTTP_URL, str(request.url))
execution_context.set_opencensus_attr(
"excludelist_hostnames", self.excludelist_hostnames
)
except Exception: # pragma: NO COVER
module_logger.error("Failed to trace request", exc_info=True)
try:
response = await call_next(request)
tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, response.status_code)
tracer.end_span()
return response
# Explicitly handle any internal exception here, and set status code to 500
except Exception as exception:
module_logger.exception(exception)
tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, 500)
tracer.end_span()
return None
I then register this middleware class in main.py like so:
app.middleware("http")(AppInsightsMiddleware(app, sampler=samplers.AlwaysOnSampler()))
Explicitly handle any exception that may occur in processing the API request. That allows you to finish tracing the request, setting the status code to 500. You can then re-throw the exception to ensure that the application raises the expected exception.
try:
response = await call_next(request)
tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, response.status_code)
tracer.end_span()
return response
# Explicitly handle any internal exception here, and set status code to 500
except Exception as exception:
module_logger.exception(exception)
tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, 500)
tracer.end_span()
raise exception

How to authenticate static routes in FastAPI

I am statically serving a folder via FastAPI following the documentation:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
How can I add basic authentication (user, password) to this route /static?
Pulled directly from the FastAPI docs: https://fastapi.tiangolo.com/advanced/security/http-basic-auth/
import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
correct_username = secrets.compare_digest(credentials.username, "stanleyjobson")
correct_password = secrets.compare_digest(credentials.password, "swordfish")
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
#app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}
Im not sure you can add basic authentication to the route itself I add it directly to the endpoint. But here's a link with the best auth modules for fastapi. Hope it helps. I like FastAPI Login.
FastAPI Auth

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