I am writing unit tests in python.
I am trying the following to make it a little more efficient.
caller
class TestTrait(TestCaseCommon):
APP_NAME = 'pytest'
HEADER = {'content-type': 'application/json'}
#mock.patch('dmp_graphql.utils.common_utils.get_test_user', return_value=TestCaseCommon.mock_get_role_check_test_user(role_type=AllUserRoleEnum.SUPERADMIN))
def test_get_all_categories_super_admin(self):
print(self)
implementation
class TestCaseCommon(TestCase):
def create_app(self):
return create_app(self.APP_NAME)
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def mock_get_role_check_test_user(role_type: AllUserRoleEnum, is_not_exist_user: bool = False,
account_id: int = 0):
if is_not_exist_user:
return None
if role_type == AllUserRoleEnum.SUPERADMIN:
query = db.session.query(User).filter(User.is_super_admin == True)
else:
...
pass
return user
The following error occurred.
During handling of the above exception, another exception occurred:
test_create_traits_uploader.py:7: in <module>
class TestTrait(TestCaseCommon):
test_create_traits_uploader.py:11: in TestTrait
#mock.patch('dmp_graphql.utils.common_utils.get_test_user', return_value=TestCaseCommon.mock_get_role_check_test_user(role_type=AllUserRoleEnum.SUPERADMIN))
../common.py:35: in mock_get_role_check_test_user
query = db.session.query(User).filter(User.is_super_admin == True)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/orm/scoping.py:163: in do
return getattr(self.registry(), name)(*args, **kwargs)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/util/_collections.py:1022: in __call__
return self.registry.setdefault(key, self.createfunc())
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/orm/session.py:3309: in __call__
return self.class_(**local_kw)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py:136: in __init__
self.app = app = db.get_app()
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py:987: in get_app
raise RuntimeError(
E RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.
In the above situation, is there a way to share the app with the decorator?
Or I'd like to know if it's possible to implement it in another way.
The framework used is flask and the python version is 3.8.
Related
I have the following endpoint,
#developer_blueprint.route("/init_db", methods=["POST"])
def initialize_database():
try:
upload_data(current_app)
logger.debug("Database entries upload.")
return jsonify({"result": "Database entries uploaded."}), 201
except Exception as e:
return jsonify({"error": str(e)})
def upload_data(app):
with open("src/core/data/data.json") as data_file:
data = json.load(data_file)
try:
current_app.db.put(("somenamespace", "test", "default"), data, None)
except Exception as e:
raise e
I'm trying to figure out how to unit test this (we need to get coverage on our code).
Do I just mock up app.db? How can I do that?
Any suggestions would be appreciated.
It is not uncommon to mock database calls for unit testing using something like unittest.mock and then run Aerospike in a container or VM for end-to-end testing.
However, keep in mind that the Aerospike Python client library is written in C for better performance and thus it is not easy to do partial patching (aka "monkey patching"). For example, you will get a TypeError: can't set attributes of built-in/extension type if you try to simply patch out aerospike.Client.put.
One approach is to create a mock client object to replace or sub-class the Aerospike client object. The implementation of this mock object depends on your code and the cases you are testing for.
Take the following example code in which app.db is an instance of the Aerospike client library:
# example.py
import aerospike
import json
class App(object):
db = None
def __init__(self):
config = {'hosts': [('127.0.0.1', 3000)]}
self.db = aerospike.client(config).connect()
def upload_data(app):
with open("data.json") as data_file:
data = json.load(data_file)
try:
app.db.put(("ns1", "test", "default"), data, None)
except Exception as e:
raise e
if __name__ == "__main__":
app = App()
upload_data(app)
In writing unit tests for the upload_data function let's assume you want to test for a success case which is determined to mean that the put method is called and no exceptions are raised:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
pass
def put(self, *args, **kwargs):
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
if __name__ == '__main__':
main()
In the test_upload_data_success method the App.db property is patched with the MockClient class instead of the aerospike.Client class. The put method of the MockClient instance is also patched so that it can be asserted that the put method gets called after upload_data is called.
To test that an exception raised by the Aerospike client is re-raised from the upload_data function, the MockClient class can be modified to raise an exception explicitly:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
self.put_err = None
if 'put_err' in kwargs:
self.put_err = kwargs['put_err']
def put(self, *args, **kwargs):
if self.put_err:
raise self.put_err
else:
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
def test_upload_data_error(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = MockClient(put_err=exception.AerospikeError)
app = App()
with self.assertRaises(exception.AerospikeError):
upload_data(app)
if __name__ == '__main__':
main()
I am creating bottle rest API and I want to use function decorator at some endpoints to authentificate user. Decorator code is:
def authenticating_decorator(func):
def wrapper():
try:
'''
auth user before execution of the required code
if user is not authenticated bottle.HTTPError is raised
'''
auth()
return func
except HTTPError as e:
return handle_auth_error
return wrapper()
return authenticating_decorator
Handle auth error function:
def handle_auth_error(error):
return {
"code": error.status_code,
"name": error.body.get('name'),
"description": error.body.get('description')
}
Everything is working fine except that I have bottle plugin installed to catch exceptions and convert them to required JSON and API response has content typeapplication/json
When exception occurs in auth method, API return error in known html format because it somehow skips my error plugin. (I mahbe do not fully understand application flow when using both plugins and decorators)
Call method of my error plugin:
def __call__(self, callback):
def wrapper(*a, **kw):
try:
rv = callback(*a, **kw)
return rv
except HTTPError as e:
response.status = e.status_code
return {
"code": e.status_code,
"name": e.body.get('name'),
"description": e.body.get('description')
}
return wrapper
My point is that I have to pass function to the plugin because of line rv = callback(*a, **kw)
and since I have multiple types of exception in auth() method in decorator I want to pass exception as argument to handle_auth_error in decorator
But if I type return handle_auth_error(e) the function returns dict, not the function and I am getting exception dict object is not callable at code line rv = callback(*a, **kw)
How can I return function with argument from decorator withouth calling it in decorator but calling it in plugin?
Or how can I pass exception as parameter to plugin?
The possible solution is creating own function to handle for every possible exception with 'switch' statement based on exception name, but I want to do it more programically:
return {
'HEADER_MISSING': handle_header_missing_exception,
'TOKEN_EXPIRED': handle_expired_token_exception,
etc ... : etc...
}.get(e.body.get('name'))
I think your decorator is not written correctly, shouldn't it be:
def authenticating_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kw):
try:
'''
auth user before execution of the required code
if user is not authenticated bottle.HTTPError is raised
'''
auth()
return func(*args, **kw) #calling func here
except HTTPError as e:
return handle_auth_error(e) #calling the handle_auto_error here
return wrapper #notice - no call here, just return the wrapper
I have the following chunk of code
class APITests(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.app = app.test_client()
app.config['SECRET_KEY'] = 'kjhk'
def test_exn(self, query):
query.all.side_effect = ValueError('test')
rv = self.app.get('/exn/')
assert rv.status_code == 400
I want to check the return code of self.app.get('/exn/). However, I notice that query.all() propagates the exception to the test case as opposed to trapping it and returning an error code.
How do I check the return codes for invalid inputs when exceptions are thrown in Flask?
You would have to do the following to trap the exception.
class APITests(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.app = app.test_client()
app.config['SECRET_KEY'] = 'kjhk'
def test_exn(self, query):
query.all.side_effect = ValueError('test')
with self.assertRaises(ValueError):
rv = self.app.get('/exn/')
assert rv.status_code == 400
For the assertRaises to work, the function call must be contained within some sort of wrapper to catch the exception.
If it is just ran like you did, it is just executed on its own, the exception is not caught and the test case becomes an error instead.
By wrapping the function call within a “with statement”, the exception is passed back to the “assertRaises” in the clause. The “assertRaises” function can now do the comparison on the exception.
To read up more on this, check this link
You can assertRaise the exception from werkzeug.
Example:
from werkzeug import exceptions
def test_bad_request(self):
with self.app as c:
rv = c.get('/exn/',
follow_redirects=True)
self.assertRaises(exceptions.BadRequest)
Here is my code:
import sys
class App(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
print sys.exc_info()
app = App()
with app:
try:
1/0
except:
print 'Caught you'
#sys.exc_clear()
and another example using flask app context:
from flask import Flask
app = Flask(__name__)
#app.teardown_appcontext
def teardown(exception):
print exception
with app.app_context():
try:
1 / 0
except:
pass
It's weird that if i didn't call sys.exc_clear when i handle exception, then when exiting app, sys.exc_info will still return exception info.
Is there any way to avoid this case?
In my project which is based on flask, i will rollback transaction when there is a exception. Although i handled exception, app context can still get it using sys.exc_info like code below showed:
#AppContext pop method
def pop(self, exc=None):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)
I cannot ask everyone in my team to call sys.exc_info when handling every single one exception.
How should i do to avoid this situation?
This is really a bug in how the AppContext handles your case.
The exception is automatically cleared the moment the current frame exits (so outside the with statement). If you called another function from within the with statement frame and handled the exception there, the sys.exc_info() information would be cleared again when that other function exits.
The AppContext.__exit__() method is correctly notified that you handled the exception and passes the exception value on to the AppContext.pop() method.
As such the Flask AppContext.pop() method should use a different sentinel value to detect that no exception was passed in; it could detect that None was passed in not as a default but as an actual value, indicating no exceptions were raised or were properly handled.
I've filed an issue with the project requesting that this is implemented, with accompanying pull request. This was merged and will be part of a future release of Flask.
You could use a monkeypatch to backport this to the current Flask version:
from flask import app, ctx
import sys
if ctx.AppContext.pop.__defaults__ == (None,):
# unpatched
_sentinel = object()
def do_teardown_appcontext(self, exc=_sentinel):
if exc is _sentinel:
exc = sys.exc_info()[1]
for func in reversed(self.teardown_appcontext_funcs):
func(exc)
app.appcontext_tearing_down.send(self, exc=exc)
app.Flask.do_teardown_appcontext = do_teardown_appcontext
def pop(self, exc=_sentinel):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = ctx._app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
ctx.appcontext_popped.send(self.app)
ctx.AppContext.pop = pop
I need to use a function from a class in a different class(different file) and am having issues, not sure how to accomplish this and am struggling with trying to find the right help for what i'm looking for(i may be using the wrong terms).
Directory Structure:
--app
--static
--js
--templates
--main_page.html
--__init__.py
-- MainApp.py
--settings.py
server.py
server.py:
from gevent import monkey
from socketio.server import SocketIOServer
from app import app
monkey.patch_all()
listen_address = '0.0.0.0'
listen_port = 5000
print 'Starting Server on: http://{0}:{1}'.format(listen_address, listen_port)
SocketIOServer((listen_address, listen_port), app, resource="socket.io").serve_forever()
app > init.py
from flask import Flask
from flask import render_template
from flask import request
from socketio import socketio_manage
import settings
from celery import Celery
from redis import Redis
import subprocess
import requests
from socketio.namespace import BaseNamespace
def make_celery(app):
celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
TaskBase = celery.Task
class ContextTask(TaskBase):
abstract = True
def __call__(self, *args, **kwargs):
with app.app_context():
return TaskBase.__call__(self, *args, **kwargs)
celery.Task = ContextTask
return celery
app = Flask(__name__)
app.debug = True
app.config.from_object(settings)
celery = make_celery(app)
r_server = Redis('localhost')
#app.route('/socket.io/<path:remaining>')
def socket(remaining):
socketio_manage(request.environ, {'/testclass': TestClass}, request)
return 'done'
#app.route('/')
def main_page():
return render_template('main_page.html')
class TestClass(BaseNamespace):
def on_submit(self, data):
#start mainapp
import MainApp
MainApp.MainApp()
#celery.task(name='tasks.emitter')
def emitter(self, string):
# emit to receive function in javascript... javascript pulls the 'mytext' field which contains (string)
self.emit('receive', {'mytext': string})
from socketio.namespace import BaseNamespace
import MainApp
MainApp.py
import app
class MainApp(app.TestClass):
def __init__(self):
self.emitter(self, 'test1234')
How can i use self.emit from TestClass in Mainapp? The emitter function runs self.emit that sends a string the the javascript code using websockets... I keep getting errors such as the following ...
TypeError: emitter() takes exactly 2 arguments (1 given)
OR in the case of the above...
Traceback (most recent call last):
File "/.../.../.../lib/python2.7/site-packages/gevent/greenlet.py", line 327, in run
result = self._run(*self.args, **self.kwargs)
File "/.../.../.../lib/python2.7/site-packages/socketio/virtsocket.py", line 403, in _receiver_loop
retval = pkt_ns.process_packet(pkt)
File "/.../.../.../lib/python2.7/site-packages/socketio/namespace.py", line 155, in process_packet
return self.process_event(packet)
File "/.../.../.../lib/python2.7/site-packages/socketio/namespace.py", line 225, in process_event
return self.call_method_with_acl(method_name, packet, *args)
File "/.../.../.../lib/python2.7/site-packages/socketio/namespace.py", line 240, in call_method_with_acl
return self.call_method(method_name, packet, *args)
File "/.../.../.../lib/python2.7/site-packages/socketio/namespace.py", line 282, in call_method
return method(*args)
File "/.../.../.../.../app/__init__.py", line 50, in on_submit
MainApp.MainApp()
File "/.../.../.../.../app/MainApp.py", line 11, in __init__
self.emitter(self, 'test1234')
File "/.../.../.../lib/python2.7/site-packages/celery/local.py", line 167, in <lambda>
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
File "/.../.../.../.../app/__init__.py", line 24, in __call__
return TaskBase.__call__(self, *args, **kwargs)
File "/.../.../.../lib/python2.7/site-packages/celery/app/task.py", line 420, in __call__
return self.run(*args, **kwargs)
File "/.../.../.../.../app/__init__.py", line 55, in emitter
self.emit('receive', {'mytext': string})
File "/.../.../.../lib/python2.7/site-packages/socketio/namespace.py", line 451, in emit
endpoint=self.ns_name)
AttributeError: 'MainApp' object has no attribute 'ns_name'
<Greenlet at 0x1120ad0f0: <bound method Socket._receiver_loop of <socketio.virtsocket.Socket object at 0x111c7c5d0>>> failed with AttributeError
Thanks!
I'd recommend that you explicitly create an instance of classA so avoid confusion.
class MainApp(object):
def __init__(self,emitter):
self.objA = ClassA()
self.emitter = self.objA.emitter
self.emitter('string')
You could also make it a class method, or a separate function if it needs shared:
#classmethod
#celery.task(name='tasks.emitter')
def emitter(cls, string):
cls.emit('receive', {'mytext': string})
class MainApp(object):
def __init__(self,emitter):
self.emitter = ClassA.emitter
self.emitter('string')
There is no issue here with it being in another class. Your MainApp class inherits from TestClass, so you are perfectly correct to refer to the method via self.emitter.
The only problem is that you are passing self twice: any method call, whether inherited or not, automatically passes the self argument, so you just need to pass the other argument:
self.emitter('test1234')
Looks like you are trying to do his backwards.
Per-condition: You need to define emit(someStr, someDict) somewhere.
Your MainClass need to extend ClassA.
Assuming you have class_a.py and main_app.py
main_app.py
from class_a import ClassA
class MainApp(ClassA):
## now you can call any method from ClassA
self.emitter(someArg)
Note: you don't need to MainApp.MainApp(self.emitter) in ClassA because MainApp extends ClassA.