this question is related to FastAPI with APIRouter plugin system not working , just to give a little bit of context...
So... I'm trying to create a simple pluggable FastAPI application where plugins can add, or not, API endpoints. Now is working fine so every plugin adds its own API endpoints. But what if a plugin can extend other plugins, to add or modify its endpoints...
For that, I build a plugin graph to handle the plugin dependencies and now a plugin can import the router from its dependencies and add some new endpoints. But I can´t overwrite the existing endpoints with the new routes.
To make it simpler I wrote this piece of code to simulate this:
import uvicorn
from fastapi import APIRouter, FastAPI
router = APIRouter()
#router.get("/")
def index():
return "hello world!"
#router.get("/")
def new_index():
return "hello world 2!"
app = FastAPI()
app.include_router(router)
if __name__ == "__main__":
uvicorn.run("server:app")
At this point, I would expect to have the hello world 2! response for the / route but instead of that I have the hello world!
UPDATE
I have also found another interesting thing in the generated api /docs that maybe can be a bug... (I update the routes functions name, to clarify). The code generates this doc... So I may think that it's Ok!. At least the documentation was built from the new_index function
But as I was mentioned before the response is from index
Related
Using Python, I would like to create an API server with the following endpoints:
/metric - needs to return the number of times the API server has been called
/health - needs to return ok
Since you don't specify any framework ou give any initial code, here's a simple example using Flask on how it would be:
from flask import Flask
app = Flask(__name__)
count = 0
#app.route('/metric')
def metric():
global count
count += 1
return str(count)
#app.route('/healthz')
def health():
return "ok"
app.run()
To install Flask, run:
pip3 install flask
Run the python code and access on your browser http://127.0.0.1:5000/metric and http://127.0.0.1:5000/healthz
FastAPI is a great option. FastAPI is a "fast (high-performance), web framework for building APIs". It provides interactive API documentation (provided by Swagger UI) to visualize and interact with the API’s resources.
Working Example
You can access the interactive API autodocs at http://127.0.0.1:8000/docs. You can also access the API endpoints directly from the browser at, for instance, http://127.0.0.1:8000/metric.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
hits = 0
#app.get("/metric")
async def metric():
global hits
hits+=1
return {"hits": hits}
#app.get("/health")
async def health():
return "ok"
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=8000)
I run this example: https://flask-restx.readthedocs.io/en/latest/example.html
(A Python REST-API with Flask-RESTX)
Code snippet
app = Flask(__name__)
api = Api(app, ...)
ns = api.namespace('todos', ...)
#ns.route('/')
...
#ns.route('/<int:id>')
...
Result
I get the following URLs for the REST-API:
http://127.0.0.1:5000 -> Swagger Documentation
http://127.0.0.1:5000/swagger.json
http://127.0.0.1:5000/todos/
http://127.0.0.1:5000/todos/{id}
Question
I would like to implement a Webclient with Javascript, which should be reached under following URLs:
http://127.0.0.1:5000 -> index.html
http://127.0.0.1:5000/style.css
http://127.0.0.1:5000/app.js
The URLs for the REST-API should change to:
http://127.0.0.1:5000/api -> Swagger Documentation
http://127.0.0.1:5000/api/swagger.json
http://127.0.0.1:5000/api/todos/
http://127.0.0.1:5000/api/todos/{id}
How can I expand the example to generate the wanted URLs?
Not sure if you have gotten an answer by now. I often clone the below github project for flask restx boilerplate. It is designed as a rest boilerplate to help get you up and running.
https://github.com/cosmic-byte/flask-restplus-boilerplate
The templates and static files can be added to the application and to the controllers to host the pages. Added the below documentation for the static pages. Every this should be there to get you running with a full site
https://flask.palletsprojects.com/en/1.1.x/tutorial/static/
You can wrap flask-restx application with flask.Blueprint to move swagger logic under a specific url_path.
The following example shows how this can be achieved:
import flask
import flask_restx
APP = flask.Flask("my-app")
api_bp = flask.Blueprint("api", __name__, url_prefix="/api")
API = flask_restx.Api(api_bp)
APP.register_blueprint(api_bp)
NAMESPACE = API.namespace("todos")
#NAMESPACE.route("/")
class TODOSAPI(flask_restx.Resource):
def get(self):
return ['todo-1', 'todo-2']
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)
I saw that Azure now supports Python (preview) in the function apps. I have a existing Flask app and was wondering if it's possible to deploy that one as a function app without major changes?
I have read through the Azure tutorials that uses Python in function apps (https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python), however not with the flask framework...
Has anyone any experience with it?
I tried different ways to integrate Azure Functions for Python with Flask framework. Finally, I did it success in my HttpTrigger function named TryFlask via app.test_client().
Here is my sample code, as below.
import logging
import azure.functions as func
from flask import Flask, request
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
#app.route('/hi')
def hi():
return 'Hi World!'
#app.route('/hello')
#app.route('/hello/<name>', methods=['POST', 'GET'])
def hello(name=None):
return name != None and 'Hello, '+name or 'Hello, '+request.args.get('name')
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
uri=req.params['uri']
with app.test_client() as c:
doAction = {
"GET": c.get(uri).data,
"POST": c.post(uri).data
}
resp = doAction.get(req.method).decode()
return func.HttpResponse(resp, mimetype='text/html')
For testing on local and Azure, to access the urls /, '/hi' and /hello via the url http(s)://<localhost:7071 or azurefunchost>/api/TryFlask with query string ?uri=/, ?uri=/hi and ?uri=/hello/peter-pan in browser, and to do the POST method for the same url above with query string ?uri=/hello/peter-pan, these are all work. Please see the results as the figures locally below, the same on cloud.
Note: In my solution, the url must have to be http(s)://<localhost:7071 or azurefunchost>/<routePrefix defined in host.json, default is api>/<function name>?uri=<uri defined in app.route, like / or /hi or /hello, even /hello/peter-pan?name=peter>.
Flask app is just an WSGI application. WSGI is a rather simple interface (see http://ivory.idyll.org/articles/wsgi-intro/what-is-wsgi.html. So instead of using test_client() as middleware to connect to the Azure function environment, a proper wsgi wrapper implementation should be used, which calls the app=Flask() object.
There is a nice Azure Python wsgi wrapper implementation "azf-wsgi" available in https://github.com/vtbassmatt/azf-wsgi.
In order to use the azf-wsgi wrapper with Flask, I found it useful to use a middleware to rewrite the URL:s from /api/app to / so when developing, I don't need to know where my Flask app gets mounted.
Additional benefit is that my main.py is just a normal Flask application, which I can run locally without using Azure functions environment (way faster).
My HttpTriggerApp/__init__.py of Azure function is attached. The myFlaskApp-folder is located under the HttpTriggerApp. Remember to use rlative import in the http-trigger as well as main.py (from . import myHelperFooBar).
For host.json and function.json, follow the azf-wsgi instructions.
import logging
import azure.functions as func
# note that the package is "azf-wsgi" but the import is "azf_wsgi"
from azf_wsgi import AzureFunctionsWsgi
# Import the Flask wsgi app (note relative import from the folder under the httpTrigger-folder.
from .myFlaskAppFolder.main import app
# rewrite URL:s to Azure function mount point (you can configure this in host.json and function.json)
from werkzeug.middleware.dispatcher import DispatcherMiddleware
app.config["APPLICATION_ROOT"] = "/api/app" # Flask app configuration so it knows correct endpoint urls
application = DispatcherMiddleware(None, {
'/api/app': app,
})
# Wrap the Flask app as WSGI application
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return AzureFunctionsWsgi(application).main(req, context)
Flask can be used now out of the box with Python Azure Functions -> see https://github.com/Azure/azure-functions-python-library/pull/45
I' trying to combine two independent Flask apps like the example below:
from geventwebsocket import WebSocketServer, Resource
...
server = WebSocketServer(('', 8080), Resource({
'/': frontend,
'/one': flask_app_one,
'/two': flask_app_two}))
server.serve_forever()
Inside each Flask app I declare the full path, isn't that suppose to be relative path, inside flask_app_one:
from flask import Flask
app = Flask(__name__)
#app.route('/one/ping')
def ping():
return 'hello\n'
Why I should specify in #app.route('/one/ping') instead of just #app.route('/ping') since all traffic to /one will be forwarded to the corresponding app?
Let me know if you need any additional info I kept my example clean
Thank you
Finally I have managed to do it with the so called Application Dispatching and the resources found in this page:
http://flask.pocoo.org/docs/0.10/patterns/appdispatch/#app-dispatch
Thanks