Bottle debug toolbar - python

Im trying to setup bottle debug toolbar but get the following error...
todo.py
import sqlite3
import bottle
from bottle_debugtoolbar import DebugToolbarPlugin
from bottle import route, run, debug, template, request, error, PasteServer
config = {'DEBUG_TB_ENABLED': True,
'DEBUG_TB_INTERCEPT_REDIRECTS':True
}
plugin = DebugToolbarPlugin(config)
bottle.install(plugin)
Error
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/bottle.py", line 862, in _handle
return route.call(**args)
File "/usr/local/lib/python2.7/dist-packages/bottle.py", line 1727, in wrapper
rv = callback(*a, **ka)
File "/usr/local/lib/python2.7/dist-packages/bottle_debugtoolbar/__init__.py", line 75, in wrapper
return self.process_response(content)
File "/usr/local/lib/python2.7/dist-packages/bottle_debugtoolbar/__init__.py", line 135, in process_response
and bottle.response.headers['content-type'].startswith('text/html')
File "/usr/local/lib/python2.7/dist-packages/bottle.py", line 1930, in __getitem__
def __getitem__(self, key): return self.dict[_hkey(key)][-1]
KeyError: 'Content-Type'

bottle-debugtoolbar makes an assumption that Content-type response header is set.
Just set the response content-type using bottle.response:
from bottle import response
...
#bottle.route('/')
def index():
response.headers['Content-Type'] = 'text/html; charset=UTF-8'
...
UPD:
Here's a simple working example:
import bottle
from bottle_debugtoolbar import DebugToolbarPlugin
from bottle import response
config = {
'DEBUG_TB_ENABLED': True,
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
}
plugin = DebugToolbarPlugin(config)
bottle.install(plugin)
#bottle.route('/')
def guestbook_index():
response.headers['Content-Type'] = 'text/html; charset=UTF-8'
return '<html><body>Hello, world</body></html>'
bottle.debug(True)
bottle.run(host='localhost', port=8082)
Hope that helps.

Related

Can't get Flask-Session to send session cookie (Python+React)

So basically the issue is that my flask server is not saving the sessions I create in one route to access in a separate route. The sessions in my /login works, it returns the refresh and access token, however, the same cannot be said about the /auth path. I believe that there is something wrong with my CORS which is blocking my session cookies, but I'm not necessarily sure.
This is my flask app.py code:
from flask import Flask, jsonify, request, url_for,session
from flask_cors import CORS, cross_origin
from google_auth_oauthlib.flow import Flow
from google.oauth2 import id_token
from flask_session import Session
import os,pathlib,requests
app = Flask(__name__)
app.config['CORS_HEADERS'] = 'Content-Type'
SESSION_TYPE = 'filesystem'
app.config.from_object(__name__)
CORS(app,supports_credentials=True)
Session(app)
GOOGLE_CLIENT_ID = "----------------------------"
client_secrets_file = os.path.join(pathlib.Path(__file__).parent, "client_secret.json")
os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'
#app.route('/login', methods=['POST'])
#cross_origin()
def login():
flow = Flow.from_client_secrets_file(
'client_secret.json',
scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'],
redirect_uri='http://localhost:3000',
)
flow.fetch_token(code=request.get_json()['code'])
credentials = flow.credentials
session['access_token'] = credentials.token
session['refresh_token'] = credentials.refresh_token
session.permanent = True
return jsonify({"access_token":session.get('access_token'),"refresh_token":session.get('refresh_token')})
#app.route('/auth', methods=['POST'])
#cross_origin()
def authenticate():
#This is just temp code to test if sessions work
return jsonify(session['access_token'])
#app.route('/refresh', methods=['POST'])
#cross_origin()
def refresh():
# check if refresh token is in session if not prompt user to login
# if user refresh token exists, contact google api to create new access token
# return either "403" relogin or "200" {new access token}
pass
if __name__ == '__main__':
app.run(debug=True)
This is my react code:
import axios from "axios";
import { useContext, useEffect, useState } from "react"
import { GlobalContext } from "../globalState/globalState";
import ErrorPage from "./errorPage";
const Authenticate = ({element}) => {
const [message, setMessage] = useState();
const {accessToken} = useContext(GlobalContext);
const checkValidToken = async() => {
//console.log(accessToken)
return await axios.post(
"http://localhost:5000/auth",{headers: {
'Content-Type': 'application/json'
},
withCredentials: true
}
).then((resp)=> {
console.log(resp)
})
}
useEffect(()=> {
// check if refresh token is a thing, if not post request localhost:5000/refresh
checkValidToken().then((resp)=> {
if(resp)
setMessage(element);
else
setMessage(<ErrorPage />)
})
},[])
return (<>{message}</>);
}
export default Authenticate;
This is the error I get when I make a /auth request.
Traceback (most recent call last):
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 2091, in call
return self.wsgi_app(environ, start_response)
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 2076, in wsgi_app
response = self.handle_exception(e)
File "C:\Program Files\Python39\Lib\site-packages\flask_cors\extension.py", line 165, in wrapped_function
return cors_after_request(app.make_response(f(args, **kwargs)))
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 2073, in wsgi_app
response = self.full_dispatch_request()
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 1518, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Program Files\Python39\Lib\site-packages\flask_cors\extension.py", line 165, in wrapped_function
return cors_after_request(app.make_response(f(args, kwargs)))
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 1516, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Program Files\Python39\Lib\site-packages\flask\app.py", line 1502, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(req.view_args)
File "C:\Program Files\Python39\Lib\site-packages\flask_cors\decorator.py", line 128, in wrapped_function
resp = make_response(f(*args, **kwargs))
File "C:\Users-------------------\Documents\GitHub\RememberMyProject\app\pythonxr\app.py", line 41, in authenticate
return jsonify(session['access_token'])
KeyError: 'access_token'
Try initializing your CORS with CORS(app, supports_credentials=True).
Check here.

How to use application context to mock flask request

I am trying to properly unit test my Flask 1.1.2 routes, but do not really understand the documentation around mocking the application context.
Here is my class Launch.py:
from flask import Flask, request
from LaunchHelper import runController
#Create app
app = Flask(__name__)
#app.route('/write-database',methods=['POST'])
def writeDatabase():
return runController(request)
Here is my new TestLaunch.py after following the comment of #Felipe Emirem:
import unittest
from mock import patch, MagicMock
from flask import request #added
from Launch import app #added
class TestLaunch(unittest.TestCase):
#patch('Launch.runController')
def test_writeDatabase(self,runController):
resp = MagicMock()
runController.return_value = resp
with app.test_client() as c:
ret = c.post('/write-database')
#assert ret == resp
runController.assert_called_with(request)
if __name__ == '__main__':
unittest.main()
I can now assert that runController was called with flask.request by importing the app directly from Launch.py, which makes sense. But I am not sure how to confirm that the response is returning correctly.
Additionally I am now getting the following error:
ERROR in app: Exception on /write-database [POST]
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "C:\ProgramData\Anaconda3\lib\site-packages\flask\app.py", line 1953, in full_dispatch_request
return self.finalize_request(rv)
File "C:\ProgramData\Anaconda3\lib\site-packages\flask\app.py", line 1968, in finalize_request
response = self.make_response(rv)
File "C:\ProgramData\Anaconda3\lib\site-packages\flask\app.py", line 2117, in make_response
rv = self.response_class.force_type(rv, request.environ)
File "C:\ProgramData\Anaconda3\lib\site-packages\werkzeug\wrappers\base_response.py", line 269, in force_type
response = BaseResponse(*_run_wsgi_app(response, environ))
File "C:\ProgramData\Anaconda3\lib\site-packages\werkzeug\wrappers\base_response.py", line 26, in _run_wsgi_app
return app_iter, response[0], Headers(response[1])
IndexError: list index out of range
Even though the test is passing.
*Edit: Here is my old test class TestLaunch.py before I changed to use app.test_context():
import unittest
from mock import patch, MagicMock
from Launch import writeDatabase
class TestLaunch(unittest.TestCase):
#patch('Launch.runController')
#patch('Launch.request')
def test_writeDatabase(self,request,runController):
resp = MagicMock()
runController.return_value = resp
ret = writeDatabase()
assert ret == resp
runController.assert_called_with(request)
if __name__ == '__main__':
unittest.main()
I ran this on older versions of flask, and it worked fine, but now I am getting Working out of request context error due to the #patch('Launch.request') line. I have tried to read through other stackoverflow posts and the flask documentation, but I can't really find anything that applies to my current use case.
Ok, I figured it out. Just needed to use the test_request_context which I read about here: link.
Here is my new TestLaunch.py:
import unittest
from mock import patch, MagicMock
from flask import request
from Launch import app, writeDatabase
class TestLaunch(unittest.TestCase):
#patch('Launch.runController')
def test_writeDatabase(self,runController):
resp = MagicMock()
runController.return_value = resp
with app.test_request_context() as c:
ret = writeDatabase()
assert ret == resp
runController.assert_called_with(request)
if __name__ == '__main__':
unittest.main()
I am not testing the URL (which I wasn't originally anyway), but I am able to test that the flask request object is being handled properly.

How to debug patched method with unittest.mock

I have the following (simplified) FBV:
def check_existing_contacts(request):
if request.is_ajax and request.method == "GET":
print('Function called')
return mailgun_validate_email(request)
return JsonResponse({"error": "Incorrect AJAX / GET request."}, status=400)
I want to test that the mailgun_validate_email function is called:
class TestCheckExistingContacts(TestCase):
#patch('myapp.mailgun_validate_email')
def test_new_contact(self, mock):
client = Client()
client.get('/check/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertTrue(mock.called)
I am certain the test calls mailgun_validate_email as the print('Function called') displays in the console. However I get an assertion error that the mock.called is False.
Where am I going wrong / how can I debug this?
************ UPDATE *******************
When patching the function in the same module as the view, I get the following error:
class TestCheckExistingContacts(TestCase):
#patch('[path to views.py with check_existing_contacts].mailgun_validate_email')
def test_new_contact(self, mock):
client = Client()
client.get('/check/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertTrue(mock.called)
Results in:
Failure
Traceback (most recent call last):
File "\tests\test_utils.py", line 123, in test_new_contact
response = self.client.get('/validate/',
File "\.venv\lib\site-packages\django\test\client.py", line 518, in get
response = super().get(path, data=data, secure=secure, **extra)
File "\.venv\lib\site-packages\django\test\client.py", line 344, in get
return self.generic('GET', path, secure=secure, **{
File "\.venv\lib\site-packages\django\test\client.py", line 421, in generic
return self.request(**r)
File "\.venv\lib\site-packages\django\test\client.py", line 496, in request
raise exc_value
File "\.venv\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "\.venv\lib\site-packages\django\utils\deprecation.py", line 96, in __call__
response = self.process_response(request, response)
File "\.venv\lib\site-packages\django\contrib\sessions\middleware.py", line 45, in process_response
patch_vary_headers(response, ('Cookie',))
File "\.venv\lib\site-packages\django\utils\cache.py", line 267, in patch_vary_headers
vary_headers = cc_delim_re.split(response['Vary'])
TypeError: expected string or bytes-like object
If you did from myapp import mailgun_validate_email for check_existing_contacts, then you need to patch the reference in that module instead of myapp.
E.g. if the import is in myapp/views.py, then patch myapp.views.mailgun_validate_email.
The view needs to return an instance of HttpResponse or one of its subclasses, same for mailgun_validate_email since you directly return mailgun_validate_email(...).
# #patch('myapp.mailgun_validate_email') # Change this
#patch('myapp.views.mailgun_validate_email', return_value=JsonResponse({})) # to this

Internal Server Error, rather than raised AuthError response from Auth0

I have my flask-restful app.py which contains all of my main functions. I have created a server.py file as instructed from here: https://auth0.com/docs/quickstart/backend/python
In my app.py file from server i import AuthError and requires_auth. I have then put #requires_auth in front of my functions.
When I have a valid jwt, it works perfectly. When the jwt is not valid it fails. Failing is good, because the requests shouldn't work. But the response i get from my api is "Internal Server Error" rather than the detailed response in the raise AuthError section in the server.py file.
I get 2 errors:
Traceback (most recent call last):
File "C:\Users\ME\code\server.py", line 88, in decorated
issuer="https://"+AUTH0_DOMAIN+"/"
File "C:\Users\ME\lib\site-packages\jose\jwt.py", line 150, in decode
options=defaults)
File "C:\Users\ME\lib\site-packages\jose\jwt.py", line 457, in _validate_claims
_validate_exp(claims, leeway=leeway)
File "C:\Users\ME\lib\site-packages\jose\jwt.py", line 299, in _validate_exp
raise ExpiredSignatureError('Signature has expired.')
jose.exceptions.ExpiredSignatureError: Signature has expired.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\ME\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\ME\lib\site-packages\flask\app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "C:\Users\ME\lib\site-packages\flask_restful\__init__.py", line 480, in wrapper
resp = resource(*args, **kwargs)
File "C:\Users\ME\lib\site-packages\flask\views.py", line 88, in view
return self.dispatch_request(*args, **kwargs)
File "C:\Users\ME\lib\site-packages\flask_restful\__init__.py", line 595, in dispatch_request
resp = meth(*args, **kwargs)
File "C:\Users\ME\code\server.py", line 92, in decorated
"description": "token is expired"}, 401)
server.AuthError: ({'code': 'token_expired', 'description': 'token is expired'}, 401)
How do i get the AuthError as the response to the call, rather than just my Internal Server Error?
Thanks!
There is an issue with this specific tutorial in Auth0, it instructs you to include the error handler in auth.py:
#app.errorhandler(AuthError)
def handle_auth_error(ex):
response = jsonify(ex.error)
response.status_code = ex.status_code
return response
Instead, you have to include this handler in your app.py, where you actually use #requires_auth.
Notice that to do so, you need to add relevant imports:
from auth import AuthError
from flask import jsonify
Notice: To be able to import from auth.py you need to add an empty file __init__.py in the same directory.
Try setting app.config[“PROPAGATE_EXCEPTIONS”] = True
Uou could maybe use an errorhandler to explicitly catch those errors and return some explicit json based on them.

Unit testing Flask app - mocking global variables in the app

I've got a Flask app module (app.py) which looks like this
# imports
...
from flask import Flask, request, Response
...
# module-level vars, including `logger` and `APP`
...
logger = None
APP = None
...
def init():
"""
Initialisation of app resources, including `logger`
"""
...
APP = Flask(__name__)
...
logger = logging.getLogger()
...
...
try:
init()
except Exception as e:
logger.error(str(e))
#APP.route('/healthcheck', methods=['GET'])
def healthcheck():
"""
Healthcheck endpoint - just returns OK if the app
initialised OK.
"""
return 'OK'
#APP.route('/get_keys', method=['POST'])
def get_keys():
"""
Main endpoint - accepts a POST request from a client
containing either a CSV or JSON payload defining a set
of geographic locations, and then returns some "keys"
for these.
"""
try:
logger.info('Extracting payload')
# extract payload
logger.info('Processing for keys')
# do some stuff
...
...
except Exception as e:
logger.error("Error: {}.".format(str(e)))
# return response
I've got unit tests for the Flask app defined in a module AppTests in the tests subpackage.
# general imports including `unittest` etc.
# import app module as `app`
class AppTests(unittest.TestCase):
"""
Flask app tests
"""
#classmethod
def setUpClass(self):
app.APP.config['TESTING'] = True
app.APP.config['DEBUG'] = False
self.app = app.APP.test_client()
# define other resources needed for `self.app`
def test_healthcheck(self):
res = self.app.get(path='/healthcheck')
self.assertEqual(res.status_code, 200)
def test_get_keys__csv(self):
# define sample csv data in `data` variable
headers = {
'Accept-Encoding': 'identity,deflate,gzip,compress',
'Content-Type': 'text/csv; charset=utf-8',
'Content-Length': len(data)
}
res = self.app.post(path='/get_keys', headers=headers.items(), data=data)
self.assertEqual(res.status_code, 200)
The test for the healthcheck endpoint passes but the test for the get_keys endpoint fails.
$ python -m unittest -v AppTests.AppTests.test_get_keys__csv
test_get_keys__csv (AppTests.AppTests) ...
ERROR
======================================================================
ERROR: test_get_keys__csv (AppTests.AppTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "AppTests.py", line 105, in test_get_keys__csv
res = self.app.post(path='/get_keys', headers=headers.items(), data=data)
File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 801, in post
return self.open(*args, **kw)
File "/path/to/venv/lib/python2.7/site-packages/flask/testing.py", line 127, in open
follow_redirects=follow_redirects)
File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 764, in open
response = self.run_wsgi_app(environ, buffered=buffered)
File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 677, in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 884, in run_wsgi_app
app_rv = app(environ, start_response)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1994, in __call__
return self.wsgi_app(environ, start_response)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/path/to/app.py", line 205, in get_keys
logger.error("Error: {}.".format(str(e)))
AttributeError: 'NoneType' object has no attribute 'error'
----------------------------------------------------------------------
Ran 1 test in 0.036s
FAILED (errors=1)
It looks like the reference to the logger object in the get_keys endpoint in the app is null when I make the call to self.app.post('/get_keys, headers=headers.items(), data=data). Every call to logger.info is generating an exception in the endpoint, which is caught and logged, and that's what I am seeing when I run the endpoint test.
Is there a way to mock this, or some how bypass the use of logger from the tests module itself? I would rather not modify the endpoint method itself.
You could potentially mock out the logging import when you run test_get_keys__csv().
from unittest.mock import patch
#patch('path.to.app.logging') # Mock the logging import
def test_get_keys__csv(self, mock_logging):
# define sample csv data in `data` variable
headers = {
'Accept-Encoding': 'identity,deflate,gzip,compress',
'Content-Type': 'text/csv; charset=utf-8',
'Content-Length': len(data)
}
res = self.app.post(path='/get_keys', headers=headers.items(), data=data)
self.assertEqual(res.status_code, 200)
If you're using Python 2, mock is a separate install.
pip install mock
and then import with
from mock import patch
More info on mock: https://docs.python.org/3/library/unittest.mock.html

Categories