Use Flask's Click CLI with the app factory pattern - python

I define my Flask application using the app factory pattern. When using Flask-Script, I can pass the factory function to the Manager. I'd like to use Flask's built-in Click CLI instead. How do I use the factory with Click?
My current code uses Flask-Script. How do I do this with Click?
from flask import Flask
from flask_script import Manager, Shell
def create_app():
app = Flask(__name__)
...
return app
manager = Manager(create_app)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command('shell', Shell(make_context=make_shell_context))
if __name__ == '__main__':
manager.run()

The flask command is a Click interface created with flask.cli.FlaskGroup. Create your own group and pass it the factory function. Use app.shell_context_processor to add objects to the shell.
from flask import Flask
from flask.cli import FlaskGroup
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(script_info=None):
app = Flask(__name__)
db.init_app(app)
...
#app.shell_context_processor
def shell_context():
return {'app': app, 'db': db}
return app
cli = FlaskGroup(create_app=create_app)
#cli.command
def custom_command():
pass
if __name__ == '__main__':
cli()
Run your file instead of the flask command. You'll get the Click interface using your factory.
FLASK_DEBUG=1 python app.py run
Ideally, create an entry point and install your package in your env. Then you can call the script as a command. Create a setup.py file with at least the following.
project/
app/
__init__.py
setup.py
from setuptools import setup, find_packages
setup(
name='my_app',
version='1.0.0',
packages=find_packages(),
entry_points={
'console_scripts': [
'app=app:cli',
],
},
)
pip install -e /path/to/project
FLASK_DEBUG=1 app run
Using your own CLI is less robust than the built-in flask command. Because your cli object is defined with your other code, a module-level error will cause the reloader to fail because it can no longer import the object. The flask command is separate from your project, so it's not affected by errors in your module.

Newly updated for Flask >= 2.1. See my other answer for Flask < 2.1.
In order to pass arguments to our app, we store them in script_info. And in order to do that, we create a custom Click interface using flask.cli.FlaskGroup.
However, passing script_info directly to app factories is deprecated in Flask 2, so we use
Click's get_current_context function to get the current context and then access script_info from that context.
manage.py
#!/usr/bin/env python
import click
import config
from click import get_current_context
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(*args, **kwargs):
app = Flask(__name__)
ctx = get_current_context(silent=True)
if ctx:
script_info = ctx.obj
config_mode = script_info.config_mode
elif kwargs.get("config_mode"):
# Production server, e.g., gunincorn
# We don't have access to the current context, so must
# read kwargs instead.
config_mode = kwargs["config_mode"]
...
return app
#click.group(cls=FlaskGroup, create_app=create_app)
#click.option('-m', '--config-mode', default="Development")
#pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
...
if __name__ == "__main__":
manager()
Now you can run the dev server and set your desired config_mode by using either -m or --config-mode. Note, until Flask 2.1 drops you have to install Flask#aa13521d42bfdb
pip install git+https://github.com/pallets/flask.git#aa13521d42bfdb
python manage.py -m Production run
Production servers like gunincorn don't have access to the current context, so we pass what we need via kwargs.
gunicorn app:create_app\(config_mode=\'Production\'\) -w 3 -k gevent

In order to pass arguments to your app factory, you need to make use of script_info like so...
manage.py
#!/usr/bin/env python
import click
import config
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(script_info):
app = Flask(__name__)
if script_info.config_mode:
obj = getattr(config, script_info.config_mode)
flask_config.from_object(obj)
...
return app
#click.group(cls=FlaskGroup, create_app=create_app)
#click.option('-m', '--config-mode', default="Development")
#pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
if __name__ == "__main__":
manager()
config.py
class Config(object):
TESTING = False
class Production(Config):
DATABASE_URI = 'mysql://user#localhost/foo'
class Development(Config):
DATABASE_URI = 'sqlite:///app.db'
class Testing(Config):
TESTING = True
DATABASE_URI = 'sqlite:///:memory:'
now in the command line you can do manage -m Production run (after either adding the entry_points to setup.py as #davidism mentioned, or running pip install manage.py).

Related

How to run FastAPI application from Poetry?

I have a fastapi project built by poetry. I want to run the application with a scripts section in pyproject.tom like below:
poetry run start
What is inside double quotes in the section?
[tool.poetry.scripts]
start = ""
I tried to run the following script.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
def main():
print("Hello World")
uvicorn.run(app, host="0.0.0.0", port=8000, reload=True, workers=2)
if __name__ == "__main__":
main()
It stops the application and just shows warning like this.
WARNING: You must pass the application as an import string to enable 'reload' or 'workers'.
I found the solution to this problem. See below:
In pyproject.toml
[tool.poetry.scripts]
start = "my_package.main:start"
In your main.py inside my_package folder.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
def start():
"""Launched with `poetry run start` at root level"""
uvicorn.run("my_package.main:app", host="0.0.0.0", port=8000, reload=True)
You will need to pass the module path (module:function) to the start script in project.toml:
[tool.poetry.scripts]
start = "app:main"
Now run the command below will call the main function in the app module:
$ poetry run start
Just as the error message says, do
uvicorn.run("app")
Note also using reload and workers is useless and will just use the reloader. These flags are mutually exclusive
WARNING: You must pass the application as an import string to enable
'reload' or 'workers'.
try using the same way to run a basic script i.e file:variable
ERROR: Error loading ASGI app. Import string "app" must be in
format ":".
uvicorn.run("backend.main:app", host="0.0.0.0", port=8000, reload=True, workers=2)

PythonAnywhere and Flask app keep returning error code

trying to host my flask app (run.py) on PythonAnywhere. Have my virtualenv setup and all of my modules imported via pip. The flask app is stored at this location:
/home/goldsilvermonitor/GSM/run.py
Set up my WSGI file and it keeps giving my the error:
TypeError: 'module' object is not callable
My flask file look like this: (run.py)
from flask import Flask, flash, redirect, render_template, request, session, abort, url_for
app = Flask(__name__)
# ./Home Script:
#app.route("/")
#app.route("/index")
def index():
return render_template('index.html')
# ./Disclaimer Page:
#app.route("/disclaimer")
def disclaimer():
return render_template('disclaimer.html')
# ./data.xml:
app.route("/dataxml")
def dataxml():
return render_template('data.xml')
# ./404 Page
#app.errorhandler(404)
def page_not_found(e):
# 404 status set explicitly
return render_template('404.html'), 404
# FLask Debug Script:s
if __name__ == "__main__":
app.run(host="0.0.0.0", port='5000', debug=True)
And my WSGI file looks like this:
# +++++++++++ FLASK +++++++++++
# Flask works like any other WSGI-compatible framework, we just need
# to import the application. Often Flask apps are called "app" so we
# may need to rename it during the import:
#
#
import sys
#
## The "/home/goldsilvermonitor" below specifies your home
## directory -- the rest should be the directory you uploaded your Flask
## code to underneath the home directory. So if you just ran
## "git clone git#github.com/myusername/myproject.git"
## ...or uploaded files to the directory "myproject", then you should
## specify "/home/goldsilvermonitor/myproject"
path = '/home/goldsilvermonitor/GSM'
if path not in sys.path:
sys.path.append(path)
#
import run as application # noqa
#
# NB -- many Flask guides suggest you use a file called run.py; that's
# not necessary on PythonAnywhere. And you should make sure your code
# does *not* invoke the flask development server with app.run(), as it
# will prevent your wsgi file from working.
I have no idea what is causing this error. Have tried reuploading the files, redoing the WSGI config. But to no avail. If someone could help me then that would be great! Also should I remove the debug=true from the flask file before I go live?
You're trying to import a module (the file run.py) and then use it as an application; the application is the app object in that file, so in the WSGI file you should replace this:
import run as application # noqa
...with this:
from run import app as application # noqa

Different methods of running flask app and setting environment

I have the following app which when I run using
flask run
seems to execute without error but when I perform python app.py gives me the following error:
➣ $ python app.py
Traceback (most recent call last):
File "app.py", line 14, in <module>
app.secret_key = os.environ['SECRET_KEY']
File "/Users/pkaramol/Workspace/second_flask/venv/bin/../lib/python3.7/os.py", line 678, in __getitem__
raise KeyError(key) from None
KeyError: 'SECRET_KEY'
#!/usr/bin/env python
import os
from flask import Flask
from flask_jwt import JWT, jwt_required
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
import settings
from resources.item import Item, ItemList
app = Flask(__name__)
api = Api(app)
app.config.from_pyfile('settings.py')
app.secret_key = os.environ['SECRET_KEY']
db = SQLAlchemy(app)
if __name__ == "__main__":
print("Starting flask app...")
print(os.end['SECRET_KEY'])
db.create_all()
api.add_resource(Item, '/item/<string:name>')
api.add_resource(ItemList, '/items')
What is the difference in the two ways of running the flask app and why in the second case the environment is not rendered appropriately?
I am using python-dotenv to inject env vars from .env file
btw in the first case where the app starts without errors, I do not see the print statement I use for debug.
and if in the case of flask run the code below if __name__ == '__main__' is not called, how will I initialise my db by calling db.create_all()?
Replace app.secret_key assignment with arbitrary string.

Use of `create_app` parameter inside FlaskGroup

I have flask application which uses application factory pattern to instantiate flask application. I am trying to integrate Flask CLI command line to my flask app.
Can any one help me in understanding the use of create_app parameter inside FlaskGroup call.
Following is my code snippet.
from project import create_app, db
from flask.cli import FlaskGroup
app = create_app()
cli = FlaskGroup(create_app=create_app)
If i had used like normal flask application, then i would n't have to use create_app parameter, in which case my code snippet looks like
from project import app, db
from flask.cli import FlaskGroup
cli = FlaskGroup(app)
Upgrade to Flask 1.0. The flask command now understands how to work with factory functions.
FLASK_APP=myapp
flask run
Flask will import myapp, find the create_app function, and call it to get an app object. See the docs for more info.
Assuming you actually want a custom cli object (you most likely don't), then you're using it correctly. You pass the factory function to the group and it will call it when it needs the app object.

Run Flask as threaded on IIS 7

I am running a flask app using celery to offload long running process on a IIS 6.5 server and using python 2.7
The choice of the python 2.7, flask and IIS 7 server are imposed by the Company and cannot be changed.
The flask app works on IIS server (so the server set-up should be correct), except for the following.
I am struggling to find the good implementation to make flask works smoothly on the server.
I searched for similar questions on the site, but none helped so far.
When I am running the flask app on my pc, the application only performs as expected if I use OPTION A or OPTION B.
OPTION A:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello from FastCGI via IIS!"
if __name__ == "__main__":
app.run(threaded=True) # <--- This works
OPTION B:
If I wrap the flask app inside tornado, it works well as well:
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from myapp import app
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()
OPTION C:
However if I run the flask app only with the default parameters, then it does not work the webserver is not returning the view that should be returned after a task has been offloaded to celery.
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello from FastCGI via IIS!"
if __name__ == "__main__":
app.run() # <--- Default App
Example of view working for OPTION A & B but not for OPTION C or on ISS:
Here below a stripped down example of a async task that is picked by celery. Note that I'm using Flask-Classy to generate the views, so the code is slightly different than the traditional routing in Flask.
class AnalysisView(FlaskView):
### Index page ---------------
def index(self):
return render_template('analysis.intro.html')
### Calculation process ------
def calculate(self, run_id):
t_id = task_create_id(run_id)
current_analysis = a_celery_function.apply_async(args=[x, y], task_id=t_id)
# Redirect to another view --> Not working when OPTION C or on ISS server
return redirect(url_for('AnalysisView:inprogress', run_id=run_id))
### Waiting page ---------------
def inprogress(self, run_id=run_id):
return render_template('analysis.inprogress.html')
My main problem is how can I use OPTION A (preferably, as it involves less change for me) or OPTION B to work together with IIS server?
Is there a way to tell flask to run with threaded=True during the Flask() initialization or via config settings?
Thanks in advance for you help.

Categories

Resources