Flask run with argument - python

I have a simple single page flask web page. This app requires a cmd line argument during run as follow: python3 example.py home/dir1/dir2.
Now I want to run this app using flask run command. I set FLASK_APP=example.py then executing flask run command will start the flask server but since I am not able to provide the argument, my app will not work. How can I pass this home/dir1/dir2 argument with flask run? I've used argv for the argument instead of argparse.

From the flask --help I saw you can pass arguments to the app from the command line like this:
flask --app 'example:create_app("argument to the app!", 5)' run
To do that you have to have a file called example.py which has a method create_app which inits the app, something like this:
from flask import Flask
def create_app(arg1, arg2):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__)
#app.route("/hello")
def hello():
return "Hello, World!"
app.add_url_rule("/", endpoint="index")
print(arg1) # Prints "argument to the app!"
print(arg2) # Prints "5"
return app
Other option you have is using environment variables, for example in the shell do
export CMD_ARG=56
and you can access that in the app with
import os
cmd_arg = os.environ["CMD_ARG"] # "56"
In the flask --help they also have an option -e to pass in environment variables from a file.

Related

How to run gunicorn inside python not as a command line?

I have a flask application.
I run it in production with this command:
python -m gunicorn -w 1 -b 0.0.0.0:5000 "path.to.wsgi:return_app()"
Instead, I want to run it inside a my_file.py
I need a function to run and it should accept the app object and port binding and number of workers
How can I do that?
I need something like this psudo code:
import gunicorn
app = return_app()
gunicorn(workers=1, ip="0.0.0.0", port=5000, app=app)
the most important part to me is the app=app part
the main point is that I want to use the app object as an instance of Flask(). I want to directly give app object to gunicorn not throough addressing it in a string
What I have tried:
I have opened gunicorn library main.py file
from gunicorn.app.wsgiapp import run
run()
to see how it works but could not figure it out
def run():
"""\
The ``gunicorn`` command line runner for launching Gunicorn with
generic WSGI applications.
"""
from gunicorn.app.wsgiapp import WSGIApplication
WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
Something like this works for me.
First I instantiate the BaseApplication class.
It has a run() method.
The details are on how to create a custom application in gunicorn documentation.
if platform.uname().system.lower()=='linux':
print("Detected Linux, Preparing gunicorn")
import gunicorn.app.base
class StandaloneApplication(gunicorn.app.base.BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
if __name__ == "__main__":
# Use a debugging session in port 5001
if platform.uname().system.lower()=='linux':
print("Detected Linux, Running Gunicorn")
options = {
'bind': '%s:%s' % ('0.0.0.0', '5001'),
'workers': number_of_workers(),
# 'threads': number_of_workers(),
'timeout': 120,
}
initialize()
StandaloneApplication(app, options).run()
else:
print("Detected non Linux, Running in pure Flask")
initialize()
app.run(debug=True, host=socket.gethostbyname(socket.gethostname()), port=5001)
My goals weren't exactly the same: I was fine with specifying the app as a string (same as you do with gunicorn on the command line) rather than passing a python app object. Actually I'm not sure if passing a single app object really makes sense, because shouldn't gunicorn have a different app object in each worker that it spawns?
My main concern was that I run gunicorn without the help of subprocess or similar. I also used FastAPI rather than Flask, and (per the current recommended prod FastAPI setup) told gunicorn to spawn uvicorn workers.
This is what I ended up going with (in myproject/web.py):
import multiprocessing
from gunicorn.app.wsgiapp import WSGIApplication
class StandaloneApplication(WSGIApplication):
def __init__(self, app_uri, options=None):
self.options = options or {}
self.app_uri = app_uri
super().__init__()
def load_config(self):
config = {
key: value
for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def run():
options = {
"bind": "0.0.0.0:8000",
"workers": (multiprocessing.cpu_count() * 2) + 1,
"worker_class": "uvicorn.workers.UvicornWorker",
}
StandaloneApplication("myproject.main:app", options).run()
My StandaloneApplication is very similar to (and is based on) the one in Zaero Divide's answer in this thread. However, I pass app_uri, and I inherit from WsgiApplication instead of from BaseApplication, which results in gunicorn starting up in basically the same way as when it's invoked from the command line.
Note: in myproject/main.py, I have app = FastAPI(). And in pyproject.toml, under [tool.poetry.scripts], I have web = "myproject.web:run" - so I can start gunicorn with poetry run web. I'm also building an artifact with shiv -c web myproject.whl -o web.pyz, so I can then just run /path/to/web.pyz to start gunicorn.
insert the following inside your my_file.py
from subprocess import run
run("gunicorn -w 1 -b 0.0.0.0:5000 'path.to.wsgi:return_app()'".split(' '))

How can I know the args passed to flask_script's Manager

I have a flask application, in one of its script commands I want to know what's the args passed to the Manager (not the command itself), how can I do that?
$ cat manage.py
#!/usr/bin/env python
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
manager.add_option("-d", "--debug", dest="debug", action="store_true")
#manager.option('-n', '--name', dest='name', default='joe')
def hello(name):
# how can I know whether "-d|--debug" is passed in command line
print("hello", name)
if __name__ == "__main__":
manager.run()
If I run:
$ python manage.py --debug hello
I want to detect whether '--debug' is passed via command line args within the func of hello. I can't just change
manager.add_option("-d", "--debug", dest="debug", action="store_true")
to the decorator verion of:
#manager.option('-d', '--debug', action='store_true', dest='debug')
#manager.option('-n', '--name', dest='name', default='joe')
def hello(name, debug=False):
because '-d|--debug' is shared by many commands.
Global options are passed not to command, but to app-creating function.
See add-option docs.
For this to work, the manager must be initialized with a factory function rather than a Flask instance. Otherwise any options you set will be ignored.
So you need to do something like
app = Flask(__name__)
def init_manager(debug):
app.debug = debug
return app
manager = Manager(init_manager)
And then access app.debug

Gunicorn causes Flask's add_url_rule by causing 404

I'm using Gunicorn on Heroku to try to serve a basic webpage, and if I use the normal route decorator it works fine. For example:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def a():
return "b"
if __name__ == "__main__":
app.run()
This code will run fine, and correctly serve 'b' at the index. However, if instead of using the route decorator I use the add_url_route function, it only responds with a 404.
from flask import Flask
app = Flask(__name__)
def a():
return "b"
if __name__ == "__main__":
app.add_url_rule('/', 'index', a)
app.run()
Here's my Procfile:
web: gunicorn test:app --log-file=-
It's worth noting that when I run this from the command line with Python (python test.py), both work normally. Am I doing something wrong here?
I'm using Python 3.6.3 and Flask 0.12.2.
The app.add_url_rule line is only executed when you directly run the python script. When you just import the script (this is what gunicorn does) then no routes are configured at all and any request will result in a 404.
This also explains why both versions worked for you when executing locally.
If you really want to you could move the app.add_url_rule outside of the main block. I don't see why you would want to do that however. The first example is the way to go.
Note that app.run() is correctly placed inside the main block and should remain there even if you want to use your second example.
A side note: your two routes are not identical. The first one is a route called a at the root path and your second one is a route called index at the root path.

How to pass an arbitrary argument to Flask through app.run()?

I would like to pass an object to a newly initiated flask app. I tried following the solution from the question: how-can-i-make-command-line-arguments-visible-to-flask-routes
Edit
I would like to take a value that I pick up from initiating the python script from the command line.
ie.
$ run python flaskTest.py -a goo
I am not seeing the difference between this and the solution to the question I am trying to replicate.
Edit
Thus, I tried the following:
from flask import Flask
app = Flask(__name__)
print('Passed item: ', app.config.get('foo'))
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-a')
args = parser.parse_args()
val = args.a
app.config['foo'] = val
app.run()
Hoping to get the result...
'Passed item: Goo'
Is there a method for passing an arbitrary object through the initialization with app.run()?
Well the script is executing from top to bottom, so you can't print something you don't have yet. Putting the print statement inside a classic flask factory function allow you to first parse command line, then get your object and then use it:
from flask import Flask
def create_app(foo):
app = Flask(__name__)
app.config['foo'] = foo
print('Passed item: ', app.config['foo'])
return app
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-a')
args = parser.parse_args()
foo = args.a
app = create_app(foo)
app.run()
So, the problem is that you're trying to access the value before you define it. You would need to do something like this in your case:
from flask import Flask
app = Flask(__name__)
app.config['foo'] = 'Goo'
print('Passed item: ', app.config['foo'])
if __name__ == '__main__':
app.run()
If you're trying to access that value while loading some third module, you'll need to define the value somewhere ahead of time.
An update for more recent versions of Flask: when running through flask run you can now invoke an app factory and even pass arguments (docs).
Example code:
from flask import Flask
def create_app(foo=None):
app = Flask(__name__)
app.config["foo"] = foo
return app
# if __name__ == "__main__": ... not necessary
Assuming it is saved as flaskTest.py, you can run it using:
export FLASK_APP="flaskTest:create_app('value of foo')"
flask run

Unit testing Flask app running under uwsgi

I’m relatively new to python and am looking for a pythonic way to handle this practice.
I’ve inherited a fairly trivial Python 2.7 Flask app that runs under uwsgi that I want to add some unit tests to. It does some initialization at indentation level 0 that is required when it’s running in uwsgi but needs to be skipped when under test.
I’m given to understand that often python apps use the
if __name__ == '__main__':
pattern to isolate code that should run when the script is run on its own and should not run when it’s imported. In this case, however, both when the script is run under uwsgi and when the script is imported into the unit tests, __name__ is the same; the name of the script, so I can’t use that to differentiate between uwsgi and unit-testing environments.
This sample code illustrates what I'm working with.
In the Flask application (flask_app.py):
import logging
import bcrypt
from flask import Flask, jsonify, abort, make_response, request
from ConfigParser import SafeConfigParser
# some initialization that only makes sense when running from the uwsgi context on the server
PARSER = SafeConfigParser()
PARSER.read('config.ini')
LOG_FILE = PARSER.get('General', 'logfile')
APP = Flask(__name__)
#APP.route('/', methods=['GET'])
def index
...
#APP.route('/<p1>/<p2>', methods=['PUT'])
def put(p1, p2):
...
if __name__ == '__main__':
APP.run(debug = True, host='0.0.0.0')
In the unit tests (tests.py):
import os
import unittest
from flask import json
from flask_app import APP
class FlaskAppTestCase(unittest.TestCase):
def setUp(self):
self.APP = APP.test_client()
def test_GET(self):
resp = self.APP.get('/')
assert 'Some Html' in resp.data
def test_PUT(self):
resp = self.APP.put('/1/2')
assert 'Got 1, 2' in resp.data
if __name__ == '__main__':
unittest.main()
What I was thinking of doing was to move the initialization so that it only runs when flask_app is being executed by uwsgi and not when it's running via tests.py, perhaps by checking name and determining which path to execute based on that, but when I examine the output of print(name) either when running flask_app under uwsgi or by executing tests.py the output is "flask_app", so I can't seem to use that as a discriminator.
Is there an idiomatic way in python to handle this?
As it turns out the Python module for uWSGI actually offers a mechanism to determine if the app is being run under uWSGI. The uwsgi module is available for import if you are in a uWSGI context, so I ended up checking whether i could import that module and only executing the initialization code if I could.
# detect if we're running under uWSGI; only init if we are, not if we're testing
try:
import uwsgi
IN_UWSGI = True
except ImportError:
IN_UWSGI = False
Then wrap the init code with
if IN_UWSGI:
This seems much more reliable then checking the module name of the module that's doing the import, which was the only other thing I could think of to do.

Categories