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

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

Related

Flask run with argument

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.

pytest - command line argument outside test function

How to pass command line option as variable to my pytest non test file e.g. database name.
When i try:
import sys
import getopt
"""argv = sys.argv[1:]
opts, args = getopt.getopt(argv, "e:")
for opt, arg in opts:
if opt in ['-e']:
if arg == "test1":
base_url = "url-test1.com"
db_name = "db_test1"
elif arg == 'test2':
base_url = "url-test2.com"
db_name = "db_test2"
elif arg == 'test3':
base_url = "url-test3.com"
db_name = "db_test3"
return base_url
and run
python -m pytest -e test1
looks like pytest can't get -e flag
ERROR: usage: main.py [options] [file_or_dir] [file_or_dir] [...]
main.py: error: unrecognized arguments: -e
inifile: None
I also try pytest addoption and passing variable to test files works fine but how to pass cmnd line option as value to non test file?
def pytest_addoption(parser):
parser.addoption("--url", action="store", default="url-test1.com")
parser.addoption("--db", action="store", default="test1")
#pytest.fixture()
def url(request):
return request.config.getoption("--url")
def db_name(request):
return request.config.getoption("--db") #I want to pass this value to mysql.connector as database=db_name
EDIT 1
so my db_connect.py lokks like that
import mysql.connector
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--db', required=True, type=str, help="Your database name")
return parser.parse_args()
def main():
args = parse_args()
db = args.db
return db
mydb = mysql.connector.connect(
user='username',
password='password',
host='host',
database=main()
)
if __name__ == '__main__':
main()
and when i try to run
py.test --db test1
I got this error
ERROR: usage: py.test [options] [file_or_dir] [file_or_dir] [...]
py.test: error: unrecognized arguments: --db
inifile: None
but when i run
py.test
i got
usage: py.test [-h] --db DB
py.test: error: the following arguments are required: --db
argument is required but when i pass it is unrecognized. How to handle it?
Welcome!
Specifically to "override" variables, modules and objects you should mock them. Mocking in testing refers to creating a fake object with similar behavior to the original one when creating the real object is expensive. Such as database connections. But, naturally, it isn't restricted to just databases. You can mock any object, as well as sys.argv.
You can read more extensively about mocking in the pytest docs but here's a short example
import module_to_test
def mytest(monkeypatch):
"""
Mocks the configuration parameters values.
"""
monkeypatch.setattr(module_to_test.sys, 'argv', ['somescript.py', '--db'])
That being said, I strongly recommend you do not use getopt. That is a deprecated method to parse arguments from the world of bash. There is a strong package called argparse that entirely replaces any such argument boilerplate code.
import argpase
def parse_args():
"""
Parse arguments given in the command line. Expects just "--db"
"""
parser = argparse.ArgumentParser()
parser.add_argument('--db', required=True, type=str, help="Your DB name")
return parser.parse_args()
def main():
args = parse_args()
db = args.db
print(f"Wrap 10 to {db}. Engage!")
if __name__ == '__main__':
main()
argparse docs
edit 1
Great work on that argparse!
Now, you can simply mock it. You don't need it parsing the command line, anymore. You want to control what it returns. So, this time, when you use monkeypatch to mock argparse.ArgumentParser, you'll pass in your own "dummy class" that does nothing but return fixed arguments when parser.parse_args() is called. Here's an example of such a class (you'll probably want to tweak it)
from collections import namedtuple
class DummyParser:
def add_argument(self, *_, **__):
"""
We know what arguments we want, we don't need to implement this.
"""
pass
def parse_args():
"""
Money time!
"""
fake_return_class = namedtuple('Namespace',
['db', 'the value we want for db'])
args = fake_return_class(db="the value we want")
return args
fake_parser = DummyParser()
fake_args = fake_parser.parse_args()
print(fake_args.db)
One tweak could be to make it a little more reusable and add your own constructor of what'll db be equal to.

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

How to pass arguments in uwsgi configuration?

I'm trying to pass arguments in a Flask application with argparse:
app = Flask(__name__)
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--environ", dest='environ', default='production',
help="Server environment")
args = parser.parse_args()
if args.environ == 'dev':
app.config.from_pyfile("dev.cfg", silent=True)
else:
app.config.from_pyfile("product.cfg", silent=True)
Everything is OK when I run the script directly.
However I don't know how to pass "-e dev" argument in uwsgi config file, pyargv cannot handle this kind of argument.
You can use this code:
parser.add_argument("-e", "--environ", dest='environ', default='production', help="Server environment", required=False)

Running web.py app with custom cmd options

I would like to use web.py to build an http interface for some larger library, which also provides a command line script that takes optional parameters.
When I tried the simple web.py tutorial example in combination with optparse, I have the problem that web.py always takes the first cmd argument as port, which is not what I want. Is there a way to tell web-py not to check the command line args. Here is an example:
#!/usr/bin/env python
# encoding: utf-8
"""
web_interface.py: A simple Web interface
"""
import optparse
import web
urls = ("/.*", "hello")
app = web.application(urls, globals())
class hello:
def GET(self):
return 'Hello, world!\n'
if __name__ == "__main__":
p = optparse.OptionParser()
p.add_option('--test', '-t', help="the number of seed resources")
options, arguments = p.parse_args()
print options.test
app.run()
...which I want to run as follows:
python web_interface.py -t 10
It's a bit of a hack, but I guess you could do:
import sys
...
if __name__ == "__main__":
p = optparse.OptionParser()
p.add_option('--test', '-t', help="the number of seed resources")
options, arguments = p.parse_args()
print options.test
# set sys.argv to the remaining arguments after
# everything consumed by optparse
sys.argv = arguments
app.run()

Categories