How to set "/*" path to capture all routes in FastAPI? - python

Like in ExpressJS, app.get("/*") works for all routes.
My code -
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
#app.get('/*')
def user_lost():
return "Sorry You Are Lost !"
I tried it but the webpage result shows {"detail":"Not Found"}
How can I do the same in FastApi?

You can use /{full_path} to capture all routes in one path (See documentation).
#app.route("/{full_path:path}")
async def capture_routes(request: Request, full_path: str):
...

I edited Yagiz's code to:
#app.get("/{full_path:path}")
async def capture_routes(request: Request, full_path: str):
...

Best explained in https://sureshdsk.dev/how-to-implement-catch-all-route-in-fast-api
The only additional comment is;
if you want to mix catch-all and handle certain paths like /hello or /api .. make sure that catch all method definition is at the last .... order of route definitions matter

Related

Calling a JavaScript file function from script tag in a Visual Studio Flask project

I have a Flask web page that is using a library that do not load properly when the Flask decorator '#app.after_request' is used. Is there a way to disable that decorator only for certain pages?
You could make your own decorator function that checks for the request path or just check in another function.
#app.route('/whatever/path')
def check_func():
if request.path == "doesn't/work":
return view_func()
else:
return app.after_request(view_func)
I'm not sure if this is best practice.
You could add a condition in your #app.after_request decorator to exit it if the path in request is the one you want to exclude from decorator.
#app.after_request
def myAfterRequest():
if ( request.path == 'path/to/exclude' ):
return
#decorator code
return
Thank you so much for your replies. Just a side note (because it took me a while to figure out why :-)): For those that are also using 'request' from 'urllib' in addition to 'flask', the proposed solutions only works if you make aliases. This will work:
from flask import Flask, render_template, request
This will not work:
from urllib import request
from flask import Flask, render_template

Using FastAPI in a sync way, how can I get the raw body of a POST request?

Using FastAPI in a sync, not async mode, I would like to be able to receive the raw, unchanged body of a POST request.
All examples I can find show async code, when I try it in a normal sync way, the request.body() shows up as a coroutine object.
When I test it by posting some XML to this endpoint, I get a 500 "Internal Server Error".
from fastapi import FastAPI, Response, Request, Body
app = FastAPI()
#app.get("/")
def read_root():
return {"Hello": "World"}
#app.post("/input")
def input_request(request: Request):
# how can I access the RAW request body here?
body = request.body()
# do stuff with the body here
return Response(content=body, media_type="application/xml")
Is this not possible with FastAPI?
Note: a simplified input request would look like:
POST http://127.0.0.1:1083/input
Content-Type: application/xml
<XML>
<BODY>TEST</BODY>
</XML>
and I have no control over how input requests are sent, because I need to replace an existing SOAP API.
Using async def endpoint
If an object is a co-routine, it needs to be awaited. FastAPI is actually Starlette underneath, and Starlette methods for returning the request body are async methods (see the source code here as well); thus, one needs to await them (inside an async def endpoint). For example:
from fastapi import Request
#app.post("/input")
async def input_request(request: Request):
return await request.body()
Update 1 - Using def endpoint
Alternatively, if you are confident that the incoming data is a valid JSON, you can define your endpoint with def instead, and use the Body field, as shown below (for more options on how to post JSON data, see this answer):
from fastapi import Body
#app.post("/input")
def input_request(payload: dict = Body(...)):
return payload
If, however, the incoming data are in XML format, as in the example you provided, one option is to pass them using Files instead, as shown below—as long as you have control over how client data are sent to the server (have a look here as well). Example:
from fastapi import File
#app.post("/input")
def input_request(contents: bytes = File(...)):
return contents
Update 2 - Using def endpoint and async dependency
As described in this post, you can use an async dependency function to pull out the body from the request. You can use async dependencies on non-async (i.e., def) endpoints as well. Hence, if there is some sort of blocking code in this endpoint that prevents you from using async/await—as I am guessing this might be the reason in your case—this is the way to go.
Note: I should also mention that this answer—which explains the difference between def and async def endpoints (that you might be aware of)—also provides solutions when you are required to use async def (as you might need to await for coroutines inside a route), but also have some synchronous expensive CPU-bound operation that might be blocking the server. Please have a look.
Example of the approach described earlier can be found below. You can uncomment the time.sleep() line, if you would like to confirm yourself that a request won't be blocking other requests from going through, as when you declare an endpoint with normal def instead of async def, it is run in an external threadpool (regardless of the async def dependency function).
from fastapi import FastAPI, Depends, Request
import time
app = FastAPI()
async def get_body(request: Request):
return await request.body()
#app.post("/input")
def input_request(body: bytes = Depends(get_body)):
print("New request arrived.")
#time.sleep(5)
return body
For convenience, you can simply use asgiref, this package supports async_to_sync and sync_to_async:
from asgiref.sync import async_to_sync
sync_body_func = async_to_sync(request.body)
print(sync_body_func())
async_to_sync execute an async function in a eventloop, sync_to_async execute a sync function in a threadpool.

Is it possible to pass Path arguments into FastAPI dependency functions?

Is there anyway for a FastAPI "dependency" to interpret Path parameters?
I have a lot of functions of the form:
#app.post("/item/{item_id}/process", response_class=ProcessResponse)
async def process_item(item_id: UUID, session: UserSession = Depends(security.user_session)) -> ProcessResponse:
item = await get_item(client_id=session.client_id, item_id=item_id)
await item.process()
Over and over, I need to pass in [multiple] arguments to fetch the required item before doing something with it. This is very repetitive and makes the code very verbose. What I'd really like to do is pass the item in as an argument to the method.
Ideally I'd like to make get_item a dependency or embed it somehow in the router. This would dramatically reduce the repetitive logic and excessively verbose function arguments. The problem is that some critical arguments are passed by the client in the Path.
Is it possible to pass Path arguments into a dependency or perhaps execute the dependency in the router and pass the result?
A FastAPI dependency function can take any of the arguments that a normal endpoint function can take.
So in a normal endpoint you might define a path parameter like so:
from fastapi import FastAPI
app = FastAPI()
#app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
Now if you want to use that parameter in a dependency, you can simply do:
from fastapi import Depends, FastAPI
app = FastAPI()
async def my_dependency_function(item_id: int):
return {"item_id": item_id}
#app.get("/items/{item_id}")
async def read_item(item_id: int, my_dependency: dict = Depends(my_dependency_function)):
return my_dependency
The parameters will simply be passed on through to the dependency function if they are present there. You can also use things like Path and Query within the dependency function to define where these are coming from.
It will just analyze the request object to pull these values.
Here is an example using the Path function from FastAPI:
from fastapi import Depends, FastAPI, Path
app = FastAPI()
async def my_dependency_function(item_id: int = Path(...)):
return {"item_id": item_id}
#app.get("/items/{item_id}")
async def read_item(my_dependency: dict = Depends(my_dependency_function)):
return my_dependency
As for your concern of implementing it as a dependency in the router, you can do something like this when creating the router:
items_router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(my_dependency_function)],
)
Or you can do it when you run include_router on the app like:
app.include_router(
items_router,
prefix="/items",
dependencies=[Depends(my_dependency_function)],
)
For more on dependencies and more examples like this see https://fastapi.tiangolo.com/tutorial/dependencies/

How can I list all defined URL paths in FastAPI?

Suppose I have a FastAPI project that contains 100+ API endpoints. How can I list all APIs/paths?
To get all possible URL patterns, we need to get access to the defined URL routes which is an attribute of running app instance.
We can do that in at least two ways,
Using FastAPI app: This is handy when you have access to the FastAPi instance
Using Request instance: This is handy when you have access to the incoming requests, but not to the FastAPI instance.
Complete Example
from fastapi import FastAPI, Request
app = FastAPI()
#app.get(path="/", name="API Foo")
def foo():
return {"message": "this is API Foo"}
#app.post(path="/bar", name="API Bar")
def bar():
return {"message": "this is API Bar"}
# Using FastAPI instance
#app.get("/url-list")
def get_all_urls():
url_list = [{"path": route.path, "name": route.name} for route in app.routes]
return url_list
# Using Request instance
#app.get("/url-list-from-request")
def get_all_urls_from_request(request: Request):
url_list = [
{"path": route.path, "name": route.name} for route in request.app.routes
]
return url_list
I tried to provide an edit the original answer but wouldn't let me.
Another use case: Suppose you are not in the main app file and don't have access to app in the namespace. In that case Starlette documentation says that we also have access to the app instance from the request as request.app. For example if in the main file you only have the app instance and don't want to have any endpoints in the main file but all of them to be in separate routers.
main.py
from fastapi import FastAPI
# then let's import all the various routers we have
# please note that api is the name of our package
from api.routers import router_1, router_2, router_3, utils
app = FastAPI()
app.include_router(router_1)
app.include_router(router_2)
app.include_router(router_3)
app.include_router(utils)
I have my list_endpoints endpoint in the utils router. To be able to list all of the app routes, I would do the following:
utils.py
from fastapi import APIRouter, Request
router = APIRouter(
prefix="/utils",
tags=["utilities"]
)
#router.get('/list_endpoints/')
def list_endpoints(request: Request):
url_list = [
{'path': route.path, 'name': route.name}
for route in request.app.routes
]
return url_list
Note that rather than using app.routes I used request.app.routes and I have access to all of them. If you now access /utils/list_endpoints you will get all your routes.
The accepted answer works great when you have just one app, but unfortunately, in our project we have a number of submounted ones which makes things a bit tricker in terms of traversal.
app.mount("/admin", admin_app)
...
We also have a lot of routes and their unqualified names can be the same even within one app, let alone different ones. So I wanted to get an overview of all the routes and their matching functions.
That's how I approached it, hope it will be helpful to others. :)
Great framework, but really miss django-extensions which had that covered, too bad there's nothing of the sort in fastapi land. Please correct me if I'm wrong!
from __future__ import annotations
from typing import Iterable
from app.main import app
from fastapi import FastAPI
from starlette.routing import Mount
def gen_routes(app: FastAPI | Mount) -> Iterable[tuple[str, str]]:
for route in app.routes:
if isinstance(route, Mount):
yield from (
(f"{route.path}{path}", name) for path, name in gen_routes(route)
)
else:
yield (
route.path,
"{}.{}".format(route.endpoint.__module__, route.endpoint.__qualname__),
)
def list_routes(app: FastAPI) -> None:
import tabulate
routes = sorted(set(gen_routes(app))) # also readable enough
print(tabulate.tabulate(routes, headers=["path", "full name"]))
if __name__ == "__main__":
list_routes(app)

aiohttp middleware and updating the url for a request

I am looking for help with an aiohttp middleware I'm working on that will automatically add trailing slashes to a uri if it's missing. Aiohttp requires that when you define a url, you define two routes for each path in your list of routes, one with a trailing slash and one without. They have an example middleware that addresses this by finding if the uri does not end with a / and adding it if not, but then they use an http 302 redirect to tell the client to go to the new uri. Otherwise a relative uri like /endpoint will 404 while /endpoint/ will work.
This is their middleware:
https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_middlewares.py
Using their middleware as a basis, I'm trying to accomplish the same thing but without a redirect on the client side. I want the handling to only be on the server. My current attempt at this is shown below and seems to update the request in flight, but I still get a 404:
from aiohttp.web_urldispatcher import SystemRoute
def middleware(f):
f.__middleware_version__ = 1
return f
def trailing_slashes():
#middleware
async def impl(request, handler):
if isinstance(request.match_info.route, SystemRoute):
rel_url = str(request.rel_url)
if '?' in rel_url:
path, query = rel_url.split('?', 1)
query = f'?{query}'
else:
path = rel_url
query = ''
if not path.endswith('/'):
rel_url = f'{path}/{query}'
request = request.clone(rel_url=rel_url)
return await handler(request)
return impl
This is a class that implements the middleware and illustrates the problem.
from aiohttp import web
import slashes_mw
class ClassName(object):
def __init__(self):
self.app = web.Application(middlewares=[slashes_mw.trailing_slashes()])
self.app.add_routes(self.get_routes())
web.run_app(self.app, port=80, host='0.0.0.0')
def get_routes(self):
return [
web.get('/', self.handler),
web.get('/{name}/', self.handler)
]
def handler(self, request):
return web.Response(text='hello')
ClassName()
Any ideas? Thanks for your help.
I think the redirect approach that they propose is quite correct, as the routes are simply not the same. But you might try to just register the second "version" of the route as well:
def get_routes(self):
return [
web.get('/', self.handler),
web.get('/{name}/', self.handler),
web.get('/{name}', self.handler)
]
This should handle your problem server-side (NOT TESTED).
As I understand the framework, your approach does not work because the specific handler is selected BEFORE the different middlewares are called. If you call the handler function in the middleware, there is no "routing" lookup done anymore. Or at least that is how I understand their documentation: https://docs.aiohttp.org/en/stable/web_advanced.html#middlewares
Might be interesting as well: https://docs.aiohttp.org/en/stable/web_quickstart.html#variable-resources

Categories