Mocking module global variable while import - python

I am writing unit tests for the api, which connects to MongoDB. In my API module it looks like this:
from flask import Flask, jsonify
from MyApp import MongoData
api = Flask(__name__)
DB_CONN = MongoData()
#api.route('/bla', methods=['GET'])
def alive():
return jsonify({'response': true})
I have a problem while importing this module in my unittest. I want to mock collection from MongoData() with special mock class, which uses mongomock. The problem is that I cannot mock DB_CONN while importing in tests:
from MyApp import api
I was trying to do this with mock:
DB_CONN = MockMongoData()
with mock.patch('MyApp.api.DB_CONN', DB_CONN):
from MyApp import api
but it still tries to connect to the database as specified in the config file.
Any advice how to mock DB_CONN from MyApp.api module?
Thanks in advance!
EDIT:
This will work:
import sys
from MyApp import MongoData, MockMongoData
sys.modules['MyApp'].MongoData = MockMongoData
from MyApp import api
But is there a better (more pythonic) way to do this?

Import the module first, then monkeypatch its members:
DB_CONN = MockMongoData()
from MyApp import api
with mock.patch('MyApp.api.DB_CONN', DB_CONN):
api.run()

Related

Python 3.8 module duplication and app object scope within imported functions

I'm confused about import of custom modules. As you can see in the code below, in main I first import all libraries needed by everything AND that I duplicated those imports in my i_setup_functions.py file. Leaving any of them out of either file created errors. Same with duplication of "app = Flask(name)". I really hope that redundancy is not correct and there is some simple way to fix this. All I want to do is include setup for sessions, email, data connection, etc. Only showing sessions here for simplicity sake.
BTW: The entire app worked bug-free until I tried to modularize.
Error message:
RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret.
That error message points to a line in a function in the middle of main.py that tries to create a session.
Thanks for any ideas you all can share!
main.py:
from flask import session
import random
from datetime import datetime, timedelta
from i_setup_functions import setup_sessions
app = Flask(__name__)
# is the following line even necessary in either module?
application = app
setup_sessions()
setup_mail()
setup_logging()
[snip]
# Error here:
​session["id_user"] = id_user
i_setup_functions.py
from flask import session
import random
from datetime import datetime, timedelta
from i_setup_functions import setup_sessions
app = Flask(__name__)
application = app
def setup_sessions():
random.seed(datetime.now())
app.config['SECRET_KEY'] = str(random.randint(1, 500)) + "jibber" + str(random.randint(1, 500)) + "jabber"
app.permanent_session_lifetime = timedelta(days=30)
return True
You are creating two (or more?) separate apps and setting the SECRET_KEY to the one that isn't serving your application.
To fix this remove all app = Flask(__name__) calls from all modules except main.py. Then, pass the app you create in main.py to all the places you need it.
from flask import session
import random
from datetime import datetime, timedelta
from i_setup_functions import setup_sessions
app = Flask(__name__)
setup_sessions(app)
setup_mail(app)
setup_logging(app)
[snip]
​session["id_user"] = id_user

How to mock in a python unittest a library not installed locally?

I am trying to test using unittest a python 3.6 script that starts with (my_app.py):
import sys
from awsglue.utils import getResolvedOptions
args = getResolvedOptions(sys.argv, ['opt1', 'opt2', 'opt3'])
opt1 = args['opt1']
opt2 = args['opt2']
opt3 = args['opt3']
....
so in my test I did something like:
import unittest
import datetime
from mock import patch
import my_app
class TestMyApp(unittest.TestCase):
#patch('awsglue.utils.getResolvedOptions')
def test_mock_stubs(self, patch_opts):
patch_opts.return_value = {}
....
but the test soon fails at import my_app with:
ModuleNotFoundError: No module named 'awsglue'
as there is not awsglue locally installed. How can I test a module that import a not locally installed library and also mock it in my test?
You'll need to mock the imported module before the import for my_app happens. patch won't work here, because patch imports the module in order to patch it. And in this case, that import itself would cause an error.
To do this the simplest way is to trick python into thinking that awsglue is already imported. You can do that by putting your mock directly in the sys.modules dictionary. After this you can do the my_app import.
import unittest
import sys
from unittest import mock
class TestMyApp(unittest.TestCase):
def test_mock_stubs(self):
# mock the module
mocked_awsglue = mock.MagicMock()
# mock the import by hacking sys.modules
sys.modules['awsglue.utils'] = mocked_awsglue
mocked_awsglue.getResolvedOptions.return_value = {}
# move the import here to make sure that the mocks are setup before it's imported
import my_app
You can move the import hack part to a setup fixture method.
However I would suggest just installing the package. The import hack can be very difficult to maintain.

Cannot import name 'socker'

Initial note: I know there are many of these type posts for python but I've tried many of the solutions and they have not worked for me.
File structure:
/nickdima
__init__.py
/test_pong
__init__.py
pong.py
/nickdima/__init__.py:
from flask_socketio import Socketio
socker = SocketIO()
from test_pong import pong
def create_app():
app = Flask(__name__)
socker.init_app(app)
return app
/nickdima/test_pong/pong.py
from __main__ import socker
#socker.on('connect')
def handle_connect():
print('connected')
When I run this code on Heroku I get the error:
from __main__ import socker
ImportError: cannot import name 'socker'
I'm fairly certain this is related to a circular import but I cannot solve it.
I've tried putting: from test_pong import pong
inside the create_app() function to "delay the import" locally but to no avail and I get the same error cannot import name 'socker'
Ok, so after further inspection here's what I suggest:
/nickdima
__init__.py
socker.py
/test_pong
__init__.py
pong.py
/nickdima/socker.py
from flask_socketio import Socketio
socker = SocketIO()
/nickdima/__init__.py:
from nickidima.socker import socker
from nickidima.test_pong import pong
def create_app():
app = Flask(__name__)
socker.init_app(app)
return app
/nickdima/test_pong/pong.py
from nickidima.socker import socker
#socker.on('connect')
def handle_connect():
print('connected')
This way you no longer have circular dependencies!
Imports are relative to the root directory where nickidima is placed. I'm not sure how Heroku works with this kind of importing (actually I've never used Heroku) but I hope you get the idea and will be able to tweak it to your needs.
The most important lesson: circular dependencies are almost always a sign of a bad design and almost always can be replaced with non-circular dependencies. And when they can: do it.
Side note: I'm following your naming convention (socker?) but seriously, you should fix it. :)

Flask CLI command not in __init__.py file

I'm trying to add commands to my Flask app.
This is my __init__.py file:
import os
from flask import Flask
from flask_cors import CORS
from flask_mongoengine import MongoEngine
app = Flask(__name__)
CORS(app)
app.config.from_object(os.environ['APP_SETTINGS'])
db = MongoEngine(app)
import slots_tracker_server.views # noqa
If I add to it:
#app.cli.command()
def test_run():
print('here')
I can run flask test_run with no problems, but if I try to move the code to another file, for example: commands.py, I get the error Error: No such command "test_run". when trying to run the command.
What I'm missing?
Use click (the library behind Flask commands) to register your commands:
# commands.py
import click
#click.command()
def test_run():
print('here')
In your __init__.py import and register the commands:
# __init__.py
from yourapp import commands
from flask import Flask
app = Flask(__name__)
app.cli.add_command(commands.test_run)
Take a look at the official docs
In you commands.py you can add register function like that:
def register(app):
#app.cli.command(name='test_run', help="test_run command")
def register_test_run():
test_run()
# other commands to register
And then in your __init__.py import this register function
from flask import Flask
app = Flask(__name__)
from commands import register
register(app)
Notice, that you should import register after you've initialized your app.

Using flask extensions in flask blueprints

I want to create a blueprint; not an issue with the current blueprint I have. I can do this.
But, say I wanted to use a flask extension in my application (for my case I want to integrate flask-Cache)?
Everything I've done so far has errored:
cache = Cache(my_blueprint)
importing Cache and various parts of Cache in varying guises
so something like flask-cache is simple enough to wrap around my app:
from flask.ext.cache import Cache
cache = Cache(app)
but using this in a blueprint or using with a blueprint I don't quite understand how right now.
EDIT: the less obvious solution was to crib from the extension and build my own library to import into the blueprint, but it is more work and I'm not quite done yet. extensions / blueprints don't seem to be compatible from my level of understanding right now.
In order to avoid circular imports you will want to create your cache instance separate from your application instance (you may want to consider switching to the app factory module if you are building something more complex).
cache.py
from flask_cache import Cache
cache = Cache()
foo.py
from flask import Blueprint
from cache import cache
mod = Blueprint(...)
#mod.route("/")
#cache.cached(timeout=50)
def index():
return datetime.now().strfmtime("%Y-%m-%d %H:%M:%S")
app.py
from flask import Flask
from yourapp.cache import cache
from yourapp.foo import mod
app = Flask("yourapp")
# ... snip ...
cache.init_app(app)
# ... snip ...
app.register_blueprint(mod)
The only thing the application needs access to is the app-instance to create a cache.
Let's assume your code: cache = Cache(app) is in foo.py. But you wanna use the cache in bar.py which uses a Blueprint to register the routes.
foo.py:
from flask.ext.cache import Cache
cache = Cache(app)
from bar import mod
app.register_blueprint(mod)
The only thing you have to do in bar.py is importing the cache from foo.py and use it:
bar.py:
from foo import chache
mod = Blueprint(...)
#mod.route('/')
#cache.cached(timeout=50)
def index():
return str(datetime.now())
Flask Cache Docs and Examples
EDIT: The example above has a problem with circiular imports. The way to go here is separate the app from the cache:
pack/__init__.py:
app = Flask(__name__)
from pack.views.general import mod
app.register_blueprint(mod)
pack/cache.py:
from flask.ext.cache import Cache
from pack import app
cache = Cache(app)
pack/views/general.py:
from flask import Blueprint
from pack.chache import chache
mod = Blueprint(...)
#mod.route('/')
#cache.cached(timeout=50)
def index():
return str(datetime.now())

Categories

Resources