How do I mock PyMongo for testing with a Flask app? - python

I've found similar questions, but they seem to only cover mocking MongoDB and don't mention Flask.
I have a Flask app and I'm trying to unit test it with PyTest (including PyTest-Mongo and PyTest-Flask). However, before I can even get to the point of writing any tests, my test script crashes. The crash happens when importing the script with my Flash app: It's trying to create the PyMongo object without a url.
My question is: How can I ensure that PyMongo is mocked correctly at this point? According to the PyTest-Mongo documentation, the MongoDB test fixture should be passed to each of the test functions, but that doesn't help me if it's crashing on import.
test_app.py:
import pytest
import pytest_mongodb
from app import app
#pytest.fixture
def client():
app.config['TESTING'] = True
return client
app.py:
import ...
app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')
mongo = PyMongo(app)
...
if __name__ == '__main__':
app.run(host=os.environ.get('IP'),
port=int(os.environ.get('PORT')),
debug=False)

we can wrap app and mongo in a function
This works because mongo is used as a local variable.
app.py
from flask import Flask
from flask_pymongo import PyMongo
def get_app_with_config(config):
app = Flask(__name__)
app.config.from_object(config)
mongo = PyMongo(app)
#app.route("/")
def index():
pass
.
.
return app, mongo
then we can create a test file and an application execution file with different databases:
test_app.py
from app import get_app_with_config
from config import TestConfig
app, mongo = get_app_with_config(TestConfig)
run.py
from app import get_app_with_config
from config import RunConfig
app, mongo = get_app_with_config(RunConfig)
if __name__ == '__main__':
app.run(port=8000)
Sample of config.py file:
class RunConfig:
MONGO_HOST = '192.168.1.37'
MONGO_PORT = 27017
MONGO_DBNAME = 'my_database'
MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"
class TestConfig:
MONGO_HOST = '192.168.1.37'
MONGO_PORT = 27017
MONGO_DBNAME = 'my_database_test'
MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"
TESTING = True

Needed a quick fix so I edited app.py so that it only hard-fails if PyMongo doesn't initialise when the file is executed (i.e. it ignores PyMongo's failed initialisation when running unit-tests.)
app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')
try:
mongodb = PyMongo(app).db
except ValueError:
"""We don't provide a URI when running unit tests, so PyMongo will fail to initialize.
This is okay because we replace it with a version for testing anyway. """
print('PyMongo not initialized!')
mongodb = None
.
.
.
if __name__ == '__main__':
if not mongodb:
print('Cannot run. PyMongo failed to initialize. Double check environment variables.')
exit(1)
app.run(host=os.environ.get('IP'),
port=int(os.environ.get('PORT')),
debug=False)
In my tests file, I just assign the mocked mongoDB client to the app in the tests that need it. Definitely not the ideal solution.
def test_redacted(client, mongodb):
app.mongodb = mongodb
...

Related

Attributes of flask.g not exist in cli command with app context

I use flask app factory:
app.py
from flask import Flask, g
from . import commands
from .database import DatabaseManager
from .extensions import db
from .settings import ProdConfig
def create_app(config_object=ProdConfig):
'''Flask app factory'''
app = Flask(__name__)
app.url_map.strict_slashes = False
app.config.from_object(config_object)
register_extensions(app)
register_blueprints(app)
register_commands(app)
#app.before_request
def create_context():
g.db = db.database
g.db_manager = DatabaseManager()
return app
# Another functions
# ...
def register_commands(app):
app.cli.add_command(commands.root_group)
Then i wrote command in:
commands.py
from flask import g
from flask.cli import AppGroup
root_group = AppGroup('myapp')
#root_group.command('test')
def test():
print(g.db)
Then when i run command flask myapp test i get AttributeError on g.db. So somehow flask.g dont receive attrs g.db and g.db_manager initialized in before_request. As i understand it have to receive, because app_context must be pushed when cli command based on AppGroup runs. How to fix this?
From testing i understand that when CLI command executes, Flask not push Request context, so #before_request hook not executes. I dont find good way to push Request context in my cli command and make this hack:
#root_group.command()
def test():
exec_before_request_hooks()
print(g.db_manager)
# HACK: run #app.before_request for CLI command
def exec_before_request_hooks():
current_app.test_client().get()
If anybody know better solution, i will be very appreciate to hear it)

Flask 404 error when initializing a connection with a react applicaiont

import os, re
from flask import Flask, send_from_directory, json,request
from flask_socketio import SocketIO
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
import random
app = Flask(__name__, static_folder='./build/static')
# Point SQLAlchemy to your Heroku database
uri = os.getenv("DATABASE_URL") # or other relevant config var
if uri and uri.startswith("postgres://"):
uri = uri.replace("postgres://", "postgresql://", 1)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
# Gets rid of a warning
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
import models
cors = CORS(app, resources={r"/*": {"origins": "*"}})
socketio = SocketIO(
app,
cors_allowed_origins="*",
json=json,
manage_session=False
)
#app.route('/', defaults={"filename": "index.html"})
#app.route('/<path:filename>')
def index(filename):
return send_from_directory('./build', filename)
# When a client connects from this Socket connection, this function is run
#socketio.on('connect')
def on_connect():
print('User connected!')
# When a client disconnects from this Socket connection, this function is run
#socketio.on('disconnect')
def on_disconnect():
print('User disconnected!')
#socketio.on('index')
def on_index():
all_students = db.Students.query.all()
random1 = random.randint(0,models.Students.query().count())
random2 = random.randint(0,models.Students.query().count())
total_table = []
for student in all_students:
total_table.append(student)
firstStudent = []
secondStudent = []
while(random2 == random1):
random2 = random.randint(0,models.Students.query().count())
firstStudent.append(total_table[random1])
secondStudent.append(total_table[random2])
twoStudents = [firstStudent,secondStudent]
socketio.emit('students', {twoStudents:twoStudents})
# Note we need to add this line so we can import app in the python shell
if __name__ == "__main__":
# Note that we don't call app.run anymore. We call socketio.run with app arg
socketio.run(
app,
)
The react end of the application launches fine with no errors, and the database has no issues. I've used this skeleton for the base of a bunch of other projects and i've never had any issues. But for some reason i'm stuck at a brick wall setting this up. I know the issue is within the #app.route('/') part of the code. i've tried hard coding the html file into the url path, but that just causes other errors like missing 1 required positional argument: 'filename' flask. Any help is greatly appreciated
I tested your configuration and it doesn't give me any errors. Nevertheless I offer you an alternative solution.
You can provide the entire react build folder as a static folder. You can then deliver the "index.html" as a single file in the root route. The prerequisite is that the prefix for the static url path is empty.
# ...
app = Flask(__name__,
static_url_path='',
static_folder='./build'
)
# ...
#app.route('/')
def index():
return app.send_static_file('index.html')
# ...

How do I import my Flask app into my Pytest tests?

I'm using a application factory pattern, and when I tried to run my test, I get "Attempted to generate a URL without the application context being". I created a fixture to create the application:
#pytest.fixture
def app():
yield create_app()
but when I run my test
def test_get_activation_link(self, app):
user = User()
user.set_password(self.VALID_PASS)
generated_link = user.get_activation_link()
I get the above error (from the line of code url = url_for("auth.activate")). I'm also trying to figure out to have the app creation run for every test, without having to import it into every test, but I can't seem to find if that's possible.
This works for my app
import pytest
from xxx import create_app
#pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.app_context():
with app.test_client() as client:
yield client
def smoke_test_homepage(client):
"""basic tests to make sure test setup works"""
rv = client.get("/")
assert b"Login" in rv.data
So, you missed the application context.
At this year's Flaskcon there was an excellent talk about the Flask context - I highly recommend this video.
https://www.youtube.com/watch?v=fq8y-9UHjyk

Difficulty implementing server side session storage using redis and flask

I have a setup where a node.js app is making ajax requests to a flask based python server. Since ajax requests lack cookie data, I can't use the simple flask session object to persist data across requests. To remedy this, I'd like to implement a redis based server side implementation of a session storage system, but the solutions I've found so far do not work.
One solution I've tried is the following this snippet.
But this doesn't work. Is there more setup I need to do to configure redis beyond what is mentioned in the quickstart guide? Here is my attempt:
...
from flask import session
# Snippet code is copy pasted here verbatum
import session_interface
...
app = Flask(__name__)
app.session_interface = session_interface.RedisSessionInterface()
...
# Can't access this as session['key'] across requests
session['key'] = value
...
if __name__ == '__main__':
app.secret_key = '123456789012345678901234'
app.run(debug=True)
Another solution I've tried is importing the Flask-Session extention.
However, I can't get this to work either. The section I'm confused about is the following:
"We are not supplying something like SESSION_REDIS_HOST and SESSION_REDIS_PORT, if you want to use the RedisSessionInterface, you should configure SESSION_REDIS to your own redis.Redis instance. This gives you more flexibility, like maybe you want to use the same redis.Redis instance for cache purpose too, then you do not need to keep two redis.Redis instance in the same process."
What is meant by this section and how would I have figured this out? Here is my attempt to make this extension work:
...
from flask import session
from flask_session import Session
import redis
...
app = Flask(__name__)
SESSION_TYPE = 'redis'
app.config.from_object(__name__)
Session(app)
...
# Can't access this as session['key'] across requests
session['key'] = value
...
if __name__ == '__main__':
app.secret_key = '123456789012345678901234'
app.run(debug=True)
Has anyone successfully implemented manual session storage on a server running flask? Are there other options for getting this functionality?
Thanks for your input.
I think that's because you missed the URL configuration for your storage Redis, to check that, you can use Redis-CLI to see if there is anything being inserted into Redis.
I use this code and it worked:
from flask import Flask
from flask_session import Session
import redis
……
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('127.0.0.1:6379')
sess = Session()
sess.init_app(app)
def getSession():
return session.get('key', 'not set')
def setSession():
session.set('key')=123
return 'ok'
……
The following works for me:
...
from flask_session import Session
import redis
...
app = Flask(__name__)
SECRET_KEY = '123456789012345678901234'
SESSION_TYPE = 'redis'
SESSION_REDIS = redis.from_url('localhost:6379')
app.config.from_object(__name__)
sess = Session()
sess.init_app(app)
...
# Should be available accross requests now
session['key'] = value
...
if __name__ == '__main__':
app.run(debug=True)
Using sess.init_app(app) instead of Session(app) did the trick.

How to run flask and mongoengine with a remote host?

I am following this example here:
http://docs.mongodb.org/manual/tutorial/write-a-tumblelog-application-with-flask-mongoengine/
My problem lies here:
from flask import Flask
from flask.ext.mongoengine import MongoEngine
app = Flask(__name__)
app.config["MONGODB_DB"] = "my_tumble_log"
app.config["SECRET_KEY"] = "KeepThisS3cr3t"
db = MongoEngine(app)
if __name__ == '__main__':
app.run()
This assumes that the MONGODB_DB is "my_tumble_log" on my local machine and default port. What if my mongo database is on a remote machine with a different port? How would I modify the example to allow this?
I have tried adding a line before the MONGODB_DB app.config:
app.config['MONGODB_CONNSTRING'] = "mongodb://myremotehost:myport"
Though it has no effect whatsoever.
The following configuration settings are available:
MONGODB_DB
MONGODB_USERNAME
MONGODB_PASSWORD
MONGODB_HOST
MONGODB_PORT

Categories