Using Pytest to monkeypatch an initialized redis connection in a flask application - python

I've been struggling with this for awhile now. I Have a flask app that is executed in my app.py file. In this file I have a bunch of endpoints that call different functions from other files. In another file, extensions.py, I've instantiated a class that contains a redis connection. See the file structure below.
#app.py
from flask import Flask
from extensions import redis_obj
app = Flask(__name__)
#app.route('/flush-cache', methods=['POST'])
def flush_redis():
result = redis_obj.flush_redis_cache()
return result
# extensions.py
from redis_class import CloudRedis
redis_obj = CloudRedis()
# redis_class
import redis
class CloudRedis:
def __init__(self):
self.conn = redis.Redis(connection_pool=redis.ConnectionPool.from_url('REDIS_URL',
ssl_cert_reqs=None))
def flush_redis_cache(self):
try:
self.conn.flushdb()
return 'OK'
except:
return 'redis flush failed'
I've been attempting to use monkeypatching in a test patch flush_redis_cache, so when I run flush_redis() the call to redis_obj.flush_redis_cache() will just return "Ok", since I've already tested the CloudRedis class in other pytests. However, no matter what I've tried I haven't been able to successfully patch this. This is what I have below.
from extensions import redis_obj
from app import app
#pytest.fixture()
def client():
yield app.test_client()
def test_flush_redis_when_redis_flushed(client, monkeypatch):
# setup
def get_mock_flush_redis_cache():
return 'OK'
monkeypatch.setattr(cloud_reids, 'flush_redis_cache', get_mock_flush_redis_cache)
cloud_redis.flush_redis = get_mock_flush_redis_cache
# act
res = client.post('/flush-cache')
result = flush_redis()
Does anyone have any ideas on how this can be done?

Related

cant run function, because signal only works in main thread of the main interpreter?

I'm trying to build an API using Flask. If the endpoint /register/string:token is retrieved and the token is in a defined list, the function register_user(username) should be executed. This function sends a command to another server using the rcon protocol. However, as soon as the function is called, I get the following error: ValueError: signal only works in main thread of the main interpreter. How can I fix the problem? Or, how can I run the function in the main thread?
My code looks something like this (Note: The script runs fine on Windows):
from flask import Flask, send_file
from mcrcon import MCRcon as rcon
import configparser
api = Flask(__name__)
# #api.route('/register/<string:token>', methods=['GET'])
def register(token):
cache = utils.load_cache()
if token in list(cache['register_tokens'].keys()):
utils.register_user(username=cache['register_tokens'][token]) # problem!
del cache['register_tokens'][token]
utils.update_cache(obj=cache)
return {'status':'registration successful'}
else:
return {'status':'registration failed'}
class utils:
#staticmethod
def load_cache(file='../cache.pkl'):
...
#staticmethod
def update_cache(obj, file='../cache.pkl'):
...
#staticmethod
def clear_cache():
...
#staticmethod
def register_user(username):
with rcon(config['global']['server_ip'], config['global']['server_rcon_secret']) as console:
r = console.command(...)
console.disconnect()
if __name__ == '__main__':
config = configparser.ConfigParser()
config.read('../config.conf')
if bool(config['discordbot.py']['enable'])==True:
api.add_url_rule('/register/<string:token>', 'register', register)
api.run(host=config['global']['server_ip'], debug=False, use_reloader=False)

Outside context error when working from blueprint flask python

I have this simple webapp written in python (Flask)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Coin(db.Model):
__tablename__ = "coins"
id = db.Column(db.Integer, primary_key=True)
pair = db.Column(db.String)
sell_amt = db.Column(db.Float)
buy_amt = db.Column(db.Float)
app.py
from flask import Flask
from ui import ui
from models import db , Coin
app = Flask(__name__)
app.register_blueprint(ui)
db.init_app(app)
if __name__ == "__main__":
app.run(port=8080)
__init__.py in ui folder
from flask import Blueprint ,current_app
from models import db, Coin
from threading import Thread
ui = Blueprint('ui', __name__)
def intro():
global bot_state
with current_app.app_context():
all_coins = Coin.query.filter_by().all()
while bot_state:
sleep(3)
print (f" Current time : {time()}")
#ui.route('/startbot')
def start_bot():
global bot_thread, bot_state
bot_state = True
bot_thread = Thread(target=intro ,daemon=True)
bot_thread.start()
return "bot started "
#ui.route('/stopbot')
def stop_bot():
global bot_state
bot_state = False
bot_thread.join()
return " bot stopped"
When create a request to /startbot the app throws the error the it is working outside the app context
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
but when trying to create a database object for example new = Coin() it works fine, how do you give a function the context of the application without making a function that returns the app, because doing so creates another error that is (circular import)
Note this is the bare minimum example and there are other files that require access to the models.py folder (to add orders to the data base created by the bot )
There has to be a better way of doing it but this is what I managed to do, we create two apps the first one is the main web app, and looks sth like this
app = Flask(__name__)
app.register_blueprint(some_blueprint)
db.init_app(app)
and the second app will be for the bot and will be declared in the same file where the bot core code is written and can be imported into the blueprint and looks like this
bot_app = Flask(__name__)
db.init_app(app)
Now intro will look sth like this
from bot_file import bot_app
def intro(app):
with bot_app.app_context():
all_coins = Coin.query.all()
this way we can use the bot_app in the bot_core class with out importing the main web app
This isn't the most preferable code out there but it does solve this problem
The trick is to pass the application object to the thread. This also works with the proxy current_app. In this case, however, you need access to the underlying application object. You can find a short note on this within the documentation here.
from flask import current_app
# ...
def intro(app):
with app.app_context():
all_coins = Coin.query.all()
#ui.route('/startbot')
def start_bot():
bot_thread = Thread(
target=intro,
args=(current_app._get_current_object(),), # <- !!!
daemon=True
)
bot_thread.start()
return "bot started"
Since you don't seem to have fully understood my explanations, the following is how the complete contents of the __init__.py file would look like.
from flask import Blueprint, current_app, render_template
from models import Coin, db
from threading import Event, Lock, Thread
from time import sleep, time
ui = Blueprint('ui', __name__)
thread = None
thread_event = Event()
thread_lock = Lock()
def intro(app, event):
app.logger.info('bot started')
try:
while event.is_set():
tm = time()
app.logger.info('current time %s', tm)
with app.app_context():
all_coins = Coin.query.all()
# ...
dur = 3 - (time() - tm)
if dur > 0: sleep(dur)
finally:
event.clear()
app.logger.info('bot stopped')
#ui.route('/startbot')
def start_bot():
global thread
thread_event.set()
with thread_lock:
if thread is None:
thread = Thread(
target=intro,
args=(current_app._get_current_object(), thread_event),
daemon=True
)
thread.start()
return '', 200
#ui.route('/stopbot')
def stop_bot():
global thread
thread_event.clear()
with thread_lock:
if thread is not None:
thread.join()
thread = None
return '', 200
Have fun and success with the further implementation of your project.

RuntimeError: working outside of application context

app.py
from flask import Flask, render_template, request,jsonify,json,g
import mysql.connector
app = Flask(__name__)
**class TestMySQL():**
#app.before_request
def before_request():
try:
g.db = mysql.connector.connect(user='root', password='root', database='mysql')
except mysql.connector.errors.Error as err:
resp = jsonify({'status': 500, 'error': "Error:{}".format(err)})
resp.status_code = 500
return resp
#app.route('/')
def input_info(self):
try:
cursor = g.db.cursor()
cursor.execute ('CREATE TABLE IF NOT EXISTS testmysql (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(40) NOT NULL, \
email VARCHAR(40) NOT NULL UNIQUE)')
cursor.close()
test.py
from app import *
class Test(unittest.TestCase):
def test_connection1(self):
with patch('__main__.mysql.connector.connect') as mock_mysql_connector_connect:
object=TestMySQL()
object.before_request() """Runtime error on calling this"
I am importing app into test.py for unit testing.On calling 'before_request' function into test.py ,it is throwing RuntimeError: working outside of application context
same is happening on calling 'input_info()'
Flask has an Application Context, and it seems like you'll need to do something like:
def test_connection(self):
with app.app_context():
#test code
You can probably also shove the app.app_context() call into a test setup method as well.
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
app.app_context().push()
Run in terminal
>python
>>>from app import app
>>>from app import db
>>>db.create_all()
Now it should work
I followed the answer from #brenns10 when I ran into a similar problem when using pytest.
I followed the suggestion of putting it into test setup, this works:
import pytest
from src.app import app
#pytest.fixture
def app_context():
with app.app_context():
yield
def some_test(app_context):
# <test code that needs the app context>
I am using python3.8 and had to use a small variation to the answers already posted. I included the the below in pytests and didn't have to change anything else in the rest of the test file.
from flask import Flask
#pytest.fixture(autouse=True)
def app_context():
app = Flask(__name__)
with app.app_context():
yield
This can also be used with a context manager as well.
The main different to note here is the creation of the Flask app within the test file rather than it being imported from the main application file.

How to test that Flask is using test_client rather than client?

I'm using Pytest fixtures with Flask. My application is instantiated using an application factory.
#conftest.py
#pytest.fixture(scope='session')
def app(request):
'''Session-wide test application'''
app = create_app('testing')
app.client = app.test_client()
app_context = app.app_context()
app_context.push()
def teardown():
app_context.pop()
request.addfinalizer(teardown)
return app
I wanted to verify that the app created by my fixture uses Flask's built-in test_client, so I wrote a test:
#test_basics.py
def test_app_client_is_testing(app):
assert app.client() == app.test_client()
When I run this test, I get: TypeError: 'FlaskClient' object is not callable
What am I doing wrong?
Is the test incorrect, or is the fixture incorrect?
app.client is already an instance, you shouldn't call it again. Ultimately, this test makes no sense. Of course client is a test client, that's how you just created it in the fixture. Also, the clients will never be equal, they are different instances.
from flask.testing import FlaskClient
assert app.client == app.test_client() # different instances, never true
assert isinstance(app.client, app.test_client_class or FlaskClient) # still pointless, but correct
What you probably want is two fixtures: app and client, rather than creating a client on the app.
#pytest.yield_fixture
def app():
a = create_app('testing')
a.testing = True
with a.app_context():
yield a
#pytest.yield_fixture
def client(app):
with app.test_client() as c:
yield c
from flask.testing import FlaskClient
def test_app_client_is_client(app, client):
# why?
assert isinstance(client, app.test_client_class or FlaskClient)

Object Oriented Python with Flask Server?

I'm using Flask to expose some data-crunching code as a web service.
I'd like to have some class variables that my Flask functions can access.
Let me walk you through where I'm stuck:
from flask import Flask
app = Flask(__name__)
class MyServer:
def __init__(self):
globalData = json.load(filename)
#app.route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
if __name__ == "__main__":
app.run(host='0.0.0.0')
When I run getSomeData() outside of Flask, it works fine. But, when I run this with Flask, I get 500 internal server error. There's no magic here, and Flask has no idea that it's supposed to initialize a MyServer object. How can I feed an instance of MyServer to the app.run() command?
I could admit defeat and put globalData into a database instead. But, is there an other way?
You can create an instance of MyServer just outside the scope of your endpoints and access its attributes. This worked for me:
class MyServer:
def __init__(self):
self.globalData = "hello"
from flask import Flask
app = Flask(__name__)
my_server = MyServer()
#app.route("/getSomeData")
def getSomeData():
return my_server.globalData
if __name__ == "__main__":
app.run(host="0.0.0.0")
I know this is a late reply, but I came across this question while facing a similar issue. I found flask-classful really good.
You inherit your class from FlaskView and register the Flask app with your MyServer class
http://flask-classful.teracy.org/#
In this case, with flask-classful, your code would look like this:
from flask import Flask
from flask_classful import FlaskView, route
app = Flask(__name__)
class MyServer(FlaskView):
def __init__(self):
globalData = json.load(filename)
#route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
MyServer.register(app, base_route="/")
if __name__ == "__main__":
app.run(host='0.0.0.0')
The least-coupled solution is to apply the routes at runtime (instead of at load time):
def init_app(flask_app, database_interface, filesystem_interface):
server = MyServer(database_interface, filesystem_interface)
flask_app.route('get_data', methods=['GET'])(server.get_data)
This is very testable--just invoke init_app() in your test code with the mocked/faked dependencies (database_interface and filesystem_interface) and a flask app that has been configured for testing (app.config["TESTING"]=True or something like that) and you're all-set to write tests that cover your entire application (including the flask routing).
The only downside is this isn't very "Flasky" (or so I've been told); the Flask idiom is to use #app.route(), which is applied at load time and is necessarily tightly coupled because dependencies are hard-coded into the implementation instead of injected into some constructor or factory method (and thus complicated to test).
The following code is a simple solution for OOP with Flask:
from flask import Flask, request
class Server:
def __init__(self, name):
self.app = Flask(name)
#self.app.route('/')
def __index():
return self.index()
#self.app.route('/hello')
def __hello():
return self.hello()
#self.app.route('/user_agent')
def __user_agent():
return self.user_agent()
#self.app.route('/factorial/<n>', methods=['GET'])
def __factorial(n):
return self.factorial(n)
def index(self):
return 'Index Page'
def hello(self):
return 'Hello, World'
def user_agent(self):
return request.headers.get('User-Agent')
def factorial(self, n):
n = int(n)
fact = 1
for num in range(2, n + 1):
fact = fact * num
return str(fact)
def run(self, host, port):
self.app.run(host=host, port=port)
def main():
server = Server(__name__)
server.run(host='0.0.0.0', port=5000)
if __name__ == '__main__':
main()
To test the code, browse the following urls:
http://localhost:5000/
http://localhost:5000/hello
http://localhost:5000/user_agent
http://localhost:5000/factorial/10
a bit late but heres a quick implementation that i use to register routes at init time
from flask import Flask,request,render_template
from functools import partial
registered_routes = {}
def register_route(route=None):
#simple decorator for class based views
def inner(fn):
registered_routes[route] = fn
return fn
return inner
class MyServer(Flask):
def __init__(self,*args,**kwargs):
if not args:
kwargs.setdefault('import_name',__name__)
Flask.__init__(self,*args ,**kwargs)
# register the routes from the decorator
for route,fn in registered_routes.items():
partial_fn = partial(fn,self)
partial_fn.__name__ = fn.__name__
self.route(route)(partial_fn)
#register_route("/")
def index(self):
return render_template("my_template.html")
if __name__ == "__main__":
MyServer(template_folder=os.path.dirname(__file__)).run(debug=True)
if you wish to approach MyServer class as a resource
I believe that flask_restful can help you:
from flask import Flask
from flask_restful import Resource, Api
import json
import numpy as np
app = Flask(__name__)
api = Api(app)
class MyServer(Resource):
def __init__(self):
self.globalData = json.load(filename)
def get(self):
return np.random.choice(self.globalData)
api.add_resource(MyServer, '/')
if __name__ == '__main__':
app.run()

Categories

Resources