Execute Flask CLI command only if app in development mode - python

I integrated Flask-Migrate into my project. When I'm using development mode (FLASK_ENV='development') I would normally call flask db migrate to apply changes to SQLite database. But, in testing mode (FLASK_ENV='testing') I'm using internal memory storage (sqlite:///:memory:) and it has no sense to call db migrate because it will end up throwing error. Is there some way to create "pre_execute" hook in Flask CLI to check which ENV is used before executing command? So for example if FLASK_ENV is set to testing than calling flask db init will result in aborting execution of command. I've tried something like this but it didn't work:
#click.group(cls=FlaskGroup, create_app=create_app)
def cli():
'''
Main entry point.
'''
if app.config.ENV == ENV.TESTING:
print('Running in TESTING mode...Aborting!')
sys.exit(1)
Question: How I can abort execution of cli command under certain FLASK_ENV setting?
Edit: I'm loading FLASK_ENV value from .env file.

Okay, maybe at first I tried to solve problem with wrong approach, but I finally found way to deal with an error mentioned in my question. Because I load value of FLASK_ENV from file I need manually change it every time I want to switch environment. So what I did is I modified my test CLI command to set value of FLASK_ENV to testing every time before executing pytest:
#click.command()
def test():
'''
Run tests.
'''
os.environ['FLASK_ENV'] = ENV.TESTING
pytest.main(['--rootdir', './tests'])
Now even if FLASK_ENV set to development in .env file I still can run tests in testing mode without changing value in the file.

Related

Flask: How to pass a parameter to create_app when running a CLI command

My create_app is structured as follow:
def create_app(my_condition = True):
...
if (my_condition):
...
When running the server this is fine and I want to use the default value of True for my_condition. I have also set up a custom command in a blueprint:
#blueprint.cli.command('do_this')
def do_this():
...
How could I set it up so that when running this custom command as flask do_this, the create_app could be passed a value of False to the my_condition parameter.
Alternatively, could I have a different create_app for running as a server and for running as CLI?
I tried to find a way of doing this from the documentation and google but couldn't really figure something out.
My goal is basically to have a create_app that executes some database checking code when starting up the server but doesn't execute that code when running my CLI commands (which includes a command to create the database) or my unit tests (which use a separate database). As structured now I can pass parameters to create_app in my unit tests but not when running a CLI command.

Generate Flask secret key with a CLI command

I want to make the setup of my Flask application as easy as possible by providing a custom CLI command for automatically generating SECRET_KEY and saving it to .env. However, it seems that invoking a command, creates an instance of the Flask app, which then needs the not-yet-existing configuration.
Flask documentation says that it's possible to run commands without application context, but it seems like the app is still created, even if the command doesn't have access to it. How can I prevent the web server from running without also preventing the CLI command?
Here's almost-working code. Note, that it requires the python-dotenv package, and it runs with flask run instead of python3 app.py.
import os
import secrets
from flask import Flask, session
app = Flask(__name__)
#app.cli.command(with_appcontext=False)
def create_dotenv():
cwd = os.getcwd()
filename = '.env'
env_path = os.path.join(cwd, filename)
if os.path.isfile(env_path):
print('{} exists, doing nothing.'.format(env_path))
return
with open(env_path, 'w') as f:
secret_key = secrets.token_urlsafe()
f.write('SECRET_KEY={}\n'.format(secret_key))
#app.route('/')
def index():
counter = session.get('counter', 0)
counter += 1
session['counter'] = counter
return 'You have visited this site {} time(s).'.format(counter)
# putting these under if __name__ == '__main__' doesn't work
# because then they are not executed during `flask run`.
secret_key = os.environ.get('SECRET_KEY')
if not secret_key:
raise RuntimeError('No secret key')
app.config['SECRET_KEY'] = secret_key
Some alternative options I considered:
I could allow running the app without a secret key, so the command works fine, but I don't want to make it possible to accidentally run the actual web server without a secret key. That would result in an error 500 when someone tries to use routes that have cookies and it might not be obvious at the time of starting the server.
The app could check the configuration just before the first request comes in as suggested here, but this approach would not be much better than the previous option for the ease of setup.
Flask-Script is also suggested, but Flask-Script itself says it's no longer mainained and points to Flask's built-in CLI tool.
I could use a short delay before killing the application if the configuration is missing, so the CLI command would be able to run, but a missing secret key would be easy to notice when trying to run the server. This would be quite a cursed approach though and who knows maybe even be illegal.
Am I missing something? Is this a bug in Flask or should I do something completely different for automating secret key generation? Is this abusing the Flask CLI system's philosophy? Is it bad practice to generate environment files in the first place?
As a workaround, you can use a separate Python/shell script file for generating SECRET_KEY and the rest of .env.
That will likely be the only script that needs to be able to run without the configuration, so your repository shouldn't get too cluttered from doing so. Just mention the script in README and it probably doesn't result in a noticeably different setup experience either.
I think you are making it more complicated than it should be.
Consider the following code:
import os
app.config['SECRET_KEY'] = os.urandom(24)
It should be sufficient.
(But I prefer to use config files for Flask).
AFAIK the secret key does not have to be permanent, it should simply remain stable for the lifetime of Flask because it will be used for session cookies and maybe some internal stuff but nothing critical to the best of my knowledge.
If your app were interrupted users would lose their session but no big deal.
It depends on your current deployment practices, but if you were using Ansible for example you could automate the creation of the config file and/or the environment variables and also make some sanity checks before starting the service.
The problem with your approach is that as I understand it is that you must give the web server privileges to write to the application directory, which is not optimal from a security POV. The web user should not have the right to modify application files.
So I think it makes sense to take care of deployment separately, either using bash scripts or Ansible, and you can also tighten permissions and automate other stuff.
I agree with precedent answers, anyway you could write something like
secret_key = os.environ.get('SECRET_KEY') or secrets.token_urlsafe(32)
so that you can still use your configured SECRET_KEY variable from environment. If, for any reason, python doesn't find the variable it will be generated by the part after the 'or'.

How to run flask along side my tests in PyTest?

The usual flags: I'm new to Python, I'm new to PyTest, I'm new to Flask.
I need to create some server independent tests to test an api which calls a third-party.
I cannot access that api directly, but I can tell it what url to use for each third-party.
So what I want to do is to have a fake api running on the side (localhost) while I'm running my tests, so when the api that I'm testing needs to consume the third-parties, it uses my fake-api instead.
So I created the following app.py:
from flask import Flask
from src.fakeapi.routes import configure_routes
app = Flask(__name__)
configure_routes(app)
def start_fake_api():
app.run(debug=True)
And my_test.py:
from src.fakeapi.app import start_fake_api
#start_fake_api()
def test_slack_call():
send_request_to_api_to_configure_which_url_to_use_to_call_third_party("http://127.0.0.1:5000/")
send_request_to_api_to_populate_table_using_third_party()
Now, this might be an oversimplified example, but that's the idea. My problem obviously is that once I run Flask the process just stays in stand by and doesn't continue with the tests.
I want to avoid having to depend on manually running the server before running the tests, and I want to avoid running my tests in parallel.
What's the best way to do this?
Can I somehow execute app.py when I execute pytest? Maybe by altering pytest.ini somehow?
Can I force a new thread just for the server to run?
Thanks in advance!
I don't see a good reason to run a fake server, when you can instead use mock libraries such as requests-mock or responses to respond.
That said, if you really do need to run a real server, you could set up a session scoped fixture with a cleanup.
Adding autouse will make the tests automagically start the server, but you can leave that out and just invoke the fixture in your test, รก la test_foo(fake_api)
Implementing the TODOed bit can be a little tricky; you'd probably need to set up the Werkzeug server in a way that you can signal it to stop; e.g. by having it wait on a threading.Event you can then raise.
#pytest.mark.fixture(scope="session", autouse=True)
def fake_api():
app = ...
port = random.randint(1025, 65535) # here's hoping no one is on that port
t = threading.Thread(target=lambda: app.run(debug=True, port=port))
t.start()
yield port
# TODO: implement cleanly terminating the thread here :)

Conditional Django settings import based on the value of a command line argument

I have the following code at the end of my Django settings:
if not TESTING:
# Don't use local settings for tests, so that tests are always reproducible.
try:
from .local_settings import *
except ImportError:
pass
local_settings.py contains all the external dependencies URLs used by my Django application, such as database server URL, email server URL and external APIs URLs. Currently the only external dependency my test suite uses is a local test database; everything else is mocked.
However, now I'd like to add some tests that validate responses from the external APIs I use, so that I can detect quickly when an external API changes without prior notice. I'd like to add a --external-deps command line argument to "./manage.py test" and only run tests that depend on external APIs if this flag is enabled.
I know that I can process arguments passed to that command by overriding the add_arguments() method of the DiscoverRunner class, as described in Django manage.py : Is it possible to pass command line argument (for unit testing) , but my conditional Django settings loading are run before that, so the following won't work:
if not TESTING or TEST_EXTERNAL_DEPS:
# Don't use local settings for tests, so that tests are always reproducible.
try:
from .local_settings import *
except ImportError:
pass
Is there a way to achieve what I want?

How can I test the functions in this Flask app- write unittests?

POOL = redis.ConnectionPool(host='localhost', port=6379, db=0)
app = Flask(__name__)
#app.route('/get_cohort_curve/', methods=['GET'])```
def get_cohort_curve():
curve = str(request.args.get('curve'))
cohort = str(request.args.get('cohort'))
key = curve+cohort
return get_from_redis(key)
def get_from_redis(key):
try:
my_server = redis.Redis(connection_pool=POOL)
return json.dumps(my_server.get(key))
except Exception, e:
logging.error(e)
app.run()
I need to write unit-tests for this.
How do I test just the route, i.e. a get request goes to the right place?
Do I need to create and destroy instances of the app in the test for each function?
Do I need to create a mock redis connection?
If you are interested in running something in Flask, you could create a virtual environment and test the whole shebang, but in my opinion that is THE HARDEST way to do it.
When I built my site installing Redis locally, setting the port and then depositing some data inside it with an appropriate key was essential. I did all of my development in iPython (jupyter) notebooks so that I could test the functions and interactions with Redis before adding the confounding layer of Flask.
Then you set up a flawless template, solid HTML around it and CSS to style the site. If it works without data as an html page, then you move on to the python part of Flask.
Solidify the Flask directories. Make sure that you house them in a virtual environment so that you can call them from your browser and it will treat your virtual environment as a server.
You create your app.py application. I created each one of the page functions one at a time. tested to see that it was properly pushing the variables to post on the page and calling the right template. After you get on up and running right, with photos and data, then at the next page's template, using #app.route
Take if very slow, one piece at a time with debugging on so that you can see where when and how you are going wrong. You will only get the site to run with redis server on and your virtual environment running.
Then you will have to shut down the VE to edit and reboot to test. At first it is awful, but over time it becomes rote.
EDIT :
If you really want to test just the route, then make an app.py with just the #app.route definition and return just the page (the template you are calling). You can break testing into pieces as small as you like, but you have to be sure that the quanta you pick are executable as either a python script in a notebook or commandline execution or as a compact functional self-contained website....unless you use the package I mentioned in the comment: Flask Unit Testing Applications
And you need to create REAL Redis connections or you will error out.

Categories