Unit testing Flask app running under uwsgi - python

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.

Related

Running Flask from an imported module

I'm trying to run Flask from an imported module (creating a wrapper using decorators).
Basically I have:
app.py:
import mywrapper
#mywrapper.entrypoint
def test():
print("HEYO!")
mywrapper.py
from flask import Flask
ENTRYPOINT = None
app = Flask(__name__)
#app.route("/")
def listen():
"""Start the model API service"""
ENTRYPOINT()
def entrypoint(f):
global ENTRYPOINT
ENTRYPOINT = f
return f
FLASK_APP=app
Running python -m flask, however, results in:
flask.cli.NoAppException: Failed to find Flask application or factory in module "app". Use "FLASK_APP=app:name to specify one.
Is there any trick to getting Flask to run like this? Or is it just not possible? The purpose of this is to abstract Flask away in this situation.
In my head flask should try to import mywrapper.py, which imports app.py which should generate the app and route, yet this doesn't seem to be what occurs.
Any help would be appreciated.
So I've since learnt that Flask searches only in the chosen module's namespace for a variable containing a Flask object.
There may be a smart way to avoid this limitation, but I instead decided that it was more sensible to instead just wrap the Flask class itself. If people want direct Flask functionality, I don't really care in this situation, so the only real limitation I have from this is some function names are off limits.
Basically:
wrapper.py:
class Wrapper(Flask):
def __init__(self, name):
super().__init__(name)
self.entrypoint_func = None
#self.route("/")
def listen():
return self.entrypoint_func()
def entrypoint(self, f):
assert self.entrypoint_func is None, "Entrypoint can only be set once"
self.entrypoint_func = f
return f
and app.py:
from mywrapper import Wrapper
app = Wrapper(__name__)
#app.entrypoint
def test():
print("HEYO!")
return "SUCCESS"
This is still abstracted enough that I am happy with the results.

Configuring WSGI (flask factory) with PythonAnywhere

I'm having issues with deploying a Flask Application with WSGI and PythonAnywhere. I assume I have done something wrong but I wasn't able to find exactly what I did do wrong.
Here's my WSGI config file
import sys
# add your project directory to the sys.path
project_home = u'/home/{redacted}/src'
if project_home not in sys.path:
sys.path = [project_home] + sys.path
from start import run as application
Here's start.py
from bin import app
run = app.register().run()
Although if I try it this way it still does not work
from bin import app
run = app.register()
And here's what I'm doing in bin/main.py
class Application():
# Some required code redacted, although not necessary for this example
def __init__(self):
self.app = Flask(__name__, instance_relative_config=True)
def register(self):
""" Starts Flask App """
try:
self.configure_app()
self.register_blueprints()
except Exception as e:
print(f"Error configuring Application: {e}")
return self.app
app = Application()
Just in case you're interested, here's how it's imported through bin/__ init __.py
from .main import app
I do understand that the method in which I am doing this is over complicating it a little bit, and that I could get it working with a more normal method. Although I want it done this way and I'm fairly sure it can work this way, because the example I've shown 100% works on my machine.
Help is very appreciated. I am 100% aware I am doing something wrong here I just need help figuring out what it is.

Flask app.config during unit testing

What is the best way handle unit tests that rely on calling code that in turn relies on the current app's configuration?
eg
code.py
from flask import current_app
def some_method():
app = current_app._get_current_object()
value = (app.config['APP_STATIC_VAR'])*10
return value
test_code.py
class TestCode(unittest.TestCase):
def test_some_method(self):
app = create_app('app.settings.TestConfig')
value = some_method()
self.assertEqual(10, value)
Running the test above I get an 'RuntimeError: working outside of application context' error when the app = create_app('app.settings.TestConfig') line is executed.
Calling app = create_app during the test doesn't do the trick. What is the best way to unit test in this case where I am needing the config to be read in the the application?
You are using accessing the app within an app context when you call some_method() to fix it replace your call with:
with app.app_context():
value = some_method()

Flask, WSGI, and Global Variables to Hold Database Pool

I'm using WSGI/Apache2 and am trying to declare my database pool on init, to be accessible via a global var from my endpoints. I'm using Redis and Cassandra (DSE, specifically). It's my understanding that both the Redis and DSE libs offer pool management so this shouldn't be an issue.
My folder structure for my WSGI app looks something akin to
folder/
tp.wsgi
app/
__init__.py
decorators/
cooldec.py
mod_api/
controllers.py
tp.wsgi looks like the following
#! /usr/bin/env python2.7
import sys
import logging
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0, "/opt/tp")
from app import app
def application(environ, start_response):
return app(environ, start_response)
__init__.py looks like the following
#! /usr/bin/env python2.7
from flask import Flask
from cassandra.cluster import Cluster
# Import our handlers
from app.mod_api.files import mod_files
# Setup routine
def setup():
# Instantiate Flask
app = Flask('app')
# Set up a connection to Cassandra
cassandraSession = Cluster(['an ip address', 'an ip address']).connect('keyspace')
cassandraSession.default_timeout = None
# Register our blueprints
app.register_blueprint(mod_files)
...
return app, cassandraSession
app, cassandraSession = setup()
I'm calling a decorator defined in cooldec.py that handles authentication (I use that term loosely, for a reason. I ask that we not go down the path of using Flask extensions for authentication, that's out of scope for this question and isn't applicable in my use-use [see: loose usage of the term 'authentication'])
In cooldec.py and controllers.py I'm trying to access the cassandraSession global but I keep getting global name 'cassandraSession' is not defined. I know what the error means, but I'm not sure why I'm seeing this. It's my understanding that the way I've set my WSGI app up allows for cassandraSession to be accessible within the scope of the app, no?
I found Preserving state in mod_wsgi Flask application but .. it hasn't really shed any light on to what I'm doing wrong.
My issue was the location of my imports. I made a few changes to tp.wsgi and __init__.py and I've got what I need working. That is, calling from app import cassandraSession from within cooldec.py and controllers.py
Below is how I've set up the aforementioned.
tp.wsgi
#! /usr/bin/env python2.7
import sys
import logging
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0, "/opt/tp")
from app import app as application
__init__.py
#! /usr/bin/env python2.7
# API Module
from flask import Flask, jsonify
from cassandra.cluster import Cluster
# Create our API
app = Flask('app')
# Define a cassandra cluster/session we can use
cassandraSession = Cluster(['an ip address' 'an ip address']).connect('keyspace')
cassandraSession.default_timeout = None
... Register blueprints
These are overly simplified edits, but it gives the idea of what I was doing wrong (eg: declaring in wrong file and trying to import improperly.
In both cooldec.py and controllres.py we can now do
from app import cassandraSession
rows = cassandraSession.execute('select * from table')
Tip for new WSGI developers: Continue to think "in python".
+ WARNING +
I have yet to find an absolute answer on whether or not this is safe to do. Doing this using sqlalchemy is perfectly OK due to how sqlalchemy handles connection pooling. I am, as of yet, unaware if this is safe to do with Cassandra/DSE, so proceed with caution if you utilize this post.

How to make a docstring for gae for python router module?

I want to make a docstring for my router module in gae. I also know it must be the first thing in the module (after the file encoding type).
The thing is, if you run this module alone you get nothing but an import error (No module named webapp2). What I wanted is to print the docstring when running just the file but this import error just don't let me. Is there any way to do this?
I tried:
if __name__ == "__main__":
print help(self)
And other combinations, but no success.
[EDIT]
No specific code. Could be appengine's example:
# coding: utf-8
""" docstring """
import webapp2
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, webapp World!')
app = webapp2.WSGIApplication([('/', MainPage)],
debug=True)
The ImportError happens when you run it as a standalone because it won't include any of the 'magic' that is included when run as an app. For instance, if you look at dev_appserver.py (just the basic one you use to run a dev server), you'll see this function:
def fix_sys_path(extra_extra_paths=()):
"""Fix the sys.path to include our extra paths."""
extra_paths = EXTRA_PATHS[:]
extra_paths.extend(extra_extra_paths)
sys.path = extra_paths + sys.path
Here you can see that sys.path is being modified to include some 'extra' paths, and if we take a look at one of them, you'll see webapp2 (as well as additional libraries provided in the SDK):
EXTRA_PATHS = [
# ...other similar setups...
os.path.join(DIR_PATH, 'lib', 'webapp2'),
# ...other similar setups...
]
You can see GAE is performing some additional steps behind the scenes to let you say import webapp2 without issue. Therefore when you try to run it on its own, you will get that error because your system is just checking the standard paths for webapp2 (which you likely don't have installed).
And that doesn't really answer your question at all :) As for that, I'm sure there are definitely more elegant/appropriate ways of handling this, but one thing you could try is wrapping your import(s) in a try/except block and on ImportError, checking if you're running the module directly. If so, call the module docstring and exit. Note this is just an example - you would want to make this more refined if you were to actually use it:
"""Module information."""
import sys
try:
import webapp2
except ImportError:
if __name__ == '__main__':
print __doc__
else:
print 'Webapp2 not found'
sys.exit(1)
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, webapp World!')
app = webapp2.WSGIApplication([('/', MainPage)],
debug=True)
This will print Module information if you run it directly.

Categories