Python: Request handler in Flask - node.js

I'm learning Flask, and the request handling seems to be like:
#app.route("/")
def hello():
return "Hello World!"
So I end up defining the functions for all my routes in a single file. I'd much rather have functions for a model in its own file, e.g. get_user, create_user in user.py. I've used Express (node.js) in the past, and I can do:
user = require('./models/user')
app.get('/user', user.list)
where user.coffee (or .js) has a list function.
How do I do the same in Flask?

From the docs:
A decorator that is used to register a view function for a given URL rule. This does the same thing as add_url_rule() but is intended for decorator usage
The add_url_rule docs elaborate:
#app.route('/')
def index():
pass
Is equivalent to the following:
def index():
pass
app.add_url_rule('/', 'index', index)
You can just as easily import your view functions into a urls.py file and call add_url_rule once for each view function there instead of defining the rules along side the functions or use the lazy loading pattern.

Related

Python Flask Blueprint inject a variable value to all contexts globally?

I have the following app.py:
def create_app(env_name) -> Flask:
app = Flask(__name__)
app.register_blueprint(foo1, url_prefix="/foo1")
app.register_blueprint(foo2, url_prefix="/foo2")
app.register_blueprint(foo3, url_prefix="/foo3")
return app
and the following main.py:
from .app import create_app
app = create_app(env_name)
app.run(HOST, PORT)
and inside the individual blueprints / controllers / contexts, I defined a function which is needed by the template of the application:
foo1app = Blueprint("foo1", __name__)
#foo1app.context_processor
def inject_now():
return {'now': datetime.utcnow()}
Currently I copy-paste the code snippet above to ALL blueprints / controllers / contexts. Is there a way to inject the now globally without this code duplication? Thanks.
foo1app = Blueprint("foo1", __name__)
#foo1app.app_context_processor
def inject_now():
return {'now': datetime.utcnow()}
From the Blueprint class:
context_processor: Like Flask.context_processor but for a blueprint. This function is only executed for requests handled by a blueprint.
app_context_processor: Like Flask.context_processor but for a blueprint. Such a function is executed each request, even if outside of the blueprint.

How to serve static files in FastAPI

I am trying to serve static files that I have in a package_docs directory. When I open in the browzer:
http://127.0.0.1:8001/packages/docs/index.html , the page is running.
But I want to open the page: http://127.0.0.1:8001/packages/docs/
without the source file. And the output is 404 Not Found
app.mount("/packages/docs",
StaticFiles(directory=pkg_resources.resource_filename(__name__, 'package_docs')
),
name="package_docs")
#app.get("/packages/docs/.*", include_in_schema=False)
def root():
return HTMLResponse(pkg_resources.resource_string(__name__, "package_docs/index.html"))
app.include_router(static.router)
app.include_router(jamcam.router, prefix="/api/v1/cams", tags=["jamcam"])
How can I change my code? Any advice will be helpful. Thank you in advance.
There's a html option in Starlette that can be used within FastAPI. Starlette Documentation
This would let you have something such as:
app.mount("/site", StaticFiles(directory="site", html = True), name="site")
Which would parse /site to /site/index.html, /site/foo/ to /site/foo/index.html, etc.
The other answers can help you redirect if you want to change the folder names in a way not handled by using "directory = /foo", but this is the simplest option if you simply want to load the associated .html files.
You need to use FastAPI's TemplateResponse (actually Starlette's):
from fastapi import Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="package_docs")
#app.get("/items/{id}")
async def example(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
Request as part of the key-value pairs in the context for Jinja2. So, you also have to declare it as query parameter. and you have to specify a html file you want to render it with Jinja ("your.html", {"request": request})
Also to return a HTMLResponse directly you can use HTMLResponse from fastapi.responses
from fastapi.responses import HTMLResponse
#app.get("/items/", response_class=HTMLResponse)
async def read_items():
return """
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
"""
You can read more about custom response from FastAPI Custom Responses
If app.mount is not an option,
you can always manually read the file and respond with its content...
For example, if your static files are inside /site then:
from os.path import isfile
from fastapi import Response
from mimetypes import guess_type
#app.get("/site/{filename}")
async def get_site(filename):
filename = './site/' + filename
if not isfile(filename):
return Response(status_code=404)
with open(filename) as f:
content = f.read()
content_type, _ = guess_type(filename)
return Response(content, media_type=content_type)
#app.get("/site/")
async def get_site_default_filename():
return await get_site('index.html')
From the docs
The first "/static" refers to the sub-path this "sub-application" will be "mounted" on. So, any path that starts with "/static" will be handled by it.
This means that you mount your directory at http://127.0.0.1:8001/packages/docs/ , but you then need to either specify a file in the URL, or add a handler as you did. The problem though, is that since you mounted the path first, it will not take into consideration the following paths that include part of the path.
A possibility is to first specify the path for http://127.0.0.1:8001/packages/docs/ so that it is handled by fastapi and then to mount the folder, serving static files.
Also, I would redirect users asking for http://127.0.0.1:8001/packages/docs/ to http://127.0.0.1:8001/packages/docs/index.html
▶️ 1. You don't need to create route to serve/render homepage/static-folder explicitly. When you mark a directory as static it will automatically get the first argument as route of the app.mount() in this case it's app.mount("/"). So, when you enter the base url like http://127.0.0.1:8000/ you will get the static files.
▶️ 2. app instance of the FastAPI() class. Yes, you can have as many instance as you want in a single FASTAPI app.
▶️ 3. api_app instance of the FastAPI() for other api related task.
The reason 2 & 3 need because if you want to stick with just app instance, when user req for the url http://127.0.0.1:8000/, user will get the the static file, then when user will req for http://127.0.0.1:8000/hello then the server will try to find hello inside static-folder, but there is no hello inside static-folder!, eventually it will be a not-found type response. That's why it's need to be created another instance of FastAPI api_app, and registered with prefix /api(▶️ -> 5), so every req comes from http://127.0.0.1:8000/api/xx, decorator #api_app (▶️ -> 4) will be fired!
instance declaration and mount() method calling order is important.
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
templates = Jinja2Templates(directory='homepage-app')
# for ui
# 🔽 -> 2
app = FastAPI(title="homepage-app")
# for api
# 🔽 -> 3
api_app = FastAPI(title="api-app")
# for api and route that starts with "/api" and not static
# 🔽 -> 5
app.mount("/api", api_app)
# for static files and route that starts with "/"
# 🔽 -> 1
app.mount("/", StaticFiles(directory="static-folder", html=True), name="static-folder")
# for your other api routes you can use `api_app` instance of the FastAPI
# 🔽 -> 4
#api_app.get("/hello")
async def say_hello():
print("hello")
return {"message": "Hello World"}

Call Method of another class in Flask API

I am trying to expose a data service as an API for a PHP application. I have written the API logic in a file called APILogic.py. The code in this looks like this
class APILogic(object):
def __init__(self):
# the initialization is done here
...
def APIfunction(self):
# the API logic is built here, it uses the class variables and methods
...
I have created another file for the API purpose. Its called API.py. The code in this file looks like this
import APILogic from APILogic
class MyFlask:
def __init__(self):
self.test = APILogic()
from flask import Flask
app = Flask(__name__)
my_flask = MyFlask()
#app.route("/Test")
def Test():
return my_flask.test.APIfunction
if __name__ == "__main__":
app.run(debug=True,port=9999)
When I run the code, I get the error
> TypeError: APIfunction() takes 1 positional argument but 3 were given
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
There are no arguments for the APIfunction though.
Please help.
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
Looks like you're returning the method, but it sounds like you want to return the result of that method:
#app.route("/Test")
def Test():
return my_flask.test.APIfunction()
View function should return valid response.
Sample API code
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()

How to make put request with nested dictionaries to flask-restful?

The documentation example for a simple restful api is:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(host="0.0.0.0",port="80",debug=True)
However, suppose I made a put request with a nested dictionary,ie {'data':{'fruit':'orange'}}. The TodoSimple would have request.form.to_dict() = {'data':'fruit'}. How can I work with the full nested dictionary?
You should probably use Schemas to achieve this goal. Take a good look at this first example of marshmallow docs:
https://marshmallow.readthedocs.io/en/3.0/
As flask-restful docs says:
The whole request parser part of Flask-RESTful is slated for removal
and will be replaced by documentation on how to integrate with other
packages that do the input/output stuff better (such as marshmallow).

Flask/Flasgger - Document does not appear if `endpoint` parameter is set

I have a Blueprint which I wrote an OpenAPI documentation for. Without the endpoint definition, it's working just fine but it doesn't with the endpoint definition.
Working code:
#my_blueprint.route('/')
#swag_from('open_api/root.yml')
def main():
return str('This is the root api')
Not Working (notice how I defined the endpoint in parameters):
#my_blueprint.route('/', endpoint='foo')
#swag_from('open_api/root.yml', endpoint='foo')
def main():
return str('This is the root api')
You have working code, why'd you ask?
The use case for me is when I have multi-endpoint for just a single function which I have to define multiple yml file for each docs.
#my_blueprint.route('/', endpoint='foo')
#my_blueprint.route('/<some_id>', endpoint='foo_with_id')
#swag_from('open_api/root.yml', endpoint='foo')
#swag_from('open_api/root_with_id.yml', endpoint='foo_with_id')
def main(some_id):
if (some_id):
return str('Here's your ID')
return str('This is the root api')
Setting an endpoint in #swag_from should also contain the name of the Blueprint. Example: #swag_from('my_yaml.yml', endpoint='{}.your_endpoint'.format(my_blueprint.name))
Full example:
#my_blueprint.route('/', endpoint='foo') # endpoint is foo
#my_blueprint.route('/<some_id>', endpoint='foo_with_id') # endpoint is foo_with_id
#swag_from('open_api/root.yml', endpoint='{}.foo'.format(my_blueprint.name)) # blueprint is set as the prefix for the endpoint
#swag_from('open_api/root_with_id.yml', endpoint='{}.foo_with_id'.format(my_blueprint.name)) # same goes here
def main(some_id):
if (some_id):
return str("Here's your ID")
return str('This is the root api')

Resources