I have openapi that defines API with this securityDefinitions:
securityDefinitions:
APIKeyHeader:
type: apiKey
in: header
name: Authorization
security:
- APIKeyHeader: []
When I start the project I get this warning:
WARNING 2022-01-27 13:24:41,001 connexion.operations.secure security_decorator 142 : ... x-apikeyInfoFunc missing
And such error when I try to use API methods:
INFO 2022-01-27 13:56:15,256 connexion.api.security get_authorization_info 131 : ... No auth provided. Aborting with 401.
I found that I need to specify x-apikeyInfoFunc in securityDefinitions. I specified a function that I believe does authentication:
securityDefinitions:
APIKeyHeader:
type: apiKey
in: header
name: Authorization
x-apikeyInfoFunc: util.authentication_decorator.authenticate
security:
- APIKeyHeader: []
The function itself:
def authenticate(arg: Optional[Sequence[str]] = DEFAULT_SCOPE):
""" decorator to handle api key authentication """
def decorator(fun):
""" decorator that gets applied to the function """
def wrapper(*a, **kw):
""" function wrapper """
# pylint: disable=unused-argument
api_key = request.headers.get('Authorization')
if validate_scope(api_key, scopes):
# return fun(*a, **kw)
return fun()
LOGGER.debug('Invalid or missing API key in request')
return {'msg': 'Make sure you supply your API key with sufficient scope in the Authorization header'}, 403
return wrapper
if callable(arg):
scopes = DEFAULT_SCOPE
return decorator(arg)
scopes = arg
return decorator
The function is used as a decorator to authenticate every API method. When I start the project I don't get warning. But I get another error when I actually trying to use one of API method:
ERROR 2022-01-28 13:50:03,330 openapi_helper.app_helper log_exception 1891: Exception on /v1/jira/search_issues_by_tags [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 322, in wrapper
token_info = get_authorization_info(auth_funcs, request, required_scopes)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 127, in get_authorization_info
token_info = func(request, required_scopes)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 284, in wrapper
token_info = apikey_info_func(apikey, required_scopes=required_scopes)
TypeError: authenticate() got an unexpected keyword argument 'required_scopes'
I'm stuck on this point, don't have idea how to proceed. connexion 2.6.0 is used in this case.
According to Connexion docs, the x-apikeyInfoFunc function must have two parameters: apikey and required_scopes.
Example 1
Example 2
Related
I'm using a decorator to format api response and it works on all routes. But when I deploy it to kubernetes, it checks a health route that does not have a body argument and I think I get that error from there. Is there a way to accept a wrapper with no body argument. But maybe I'm mixing things up since all the arguments in the api endpoints and the decorator are called "body".
def api_response(function):
def wrapper(body):
response = function(body)
return jsonify({"data": response})
return wrapper
My health point with no body parameter
#api_response
def check():
return "API up and running"
Another API endpoint with a body parameter
#api_response
def detect(body):
text = body["text"]
return get_language(text)
The error
TypeError: wrapper() missing 1 required positional argument: 'body'
Traceback (most recent call last):
File "/root/.cache/pypoetry/virtualenvs/raven-9TtSrW0h-py3.9/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/root/.cache/pypoetry/virtualenvs/raven-9TtSrW0h-py3.9/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/root/.cache/pypoetry/virtualenvs/raven-9TtSrW0h-py3.9/lib/python3.9/site-packages/connexion/decorators/decorator.py", line 68, in wrapper
response = function(request)
File "/root/.cache/pypoetry/virtualenvs/raven-9TtSrW0h-py3.9/lib/python3.9/site-packages/connexion/decorators/uri_parsing.py", line 149, in wrapper
response = function(request)
File "/root/.cache/pypoetry/virtualenvs/raven-9TtSrW0h-py3.9/lib/python3.9/site-packages/connexion/decorators/parameter.py", line 115, in wrapper
return function(**kwargs)
First: A big thanks to the Authlib creator / other open source creators and supporters.
I would like to have Authlib 0.11 to return oauth tokens as JWT.
I tried to follow the documentation provided in Authlib website to create a JWT token generator with Authlib 0.11 https://docs.authlib.org/en/latest/flask/2/authorization-server.html#token.
Since, I am a novice user in this topic I still couldn't figure out the right way to pass my JWT token generator method to the config:OAUTH2_ACCESS_TOKEN_GENERATOR
Any help is appreciated.
Here is my dummy jwt token generator:
from authlib.jose import jwt
def gen_access_token(client, grant_type, user, scope):
log.debug('Not used yet in the JWT:: {} \n{} \n{} \n{}'.format( client, grant_type, user, scope))
header = {'alg': 'RS256'}
payload = {
'iss': 'http://127.0.0.1:5000/oauth/token',
'sub': 'test client',
'aud': 'profile'
}
try:
key = open('wf-app-server.key', 'r').read()
s = jwt.encode(header, payload, key)
claims = jwt.decode(s, open('wf-app-pub.pem', 'r').read())
except Exception as e:
log.debug('JWT exception', e)
log.debug("jwt encoded:{}\n decoded :{} \n header:{}".format(
s, claims, claims.header))
return s
OAUTH2_REFRESH_TOKEN_GENERATOR = True
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 874000,
'implicit': 3600,
'password': 600000,
'client_credentials': 600000
}
OAUTH2_ACCESS_TOKEN_GENERATOR = gen_access_token('bCsNV2Lo8hxD593Km84lWM5d', 'client_credentials', 'admin', 'profile')
-- output showing my JWT token generator works and the returned value can be decoded correctly --
2019-06-22 13:37:38,024 DEBUG gen_access_token (7) Not used yet in the JWT:: bCsNV2Lo8hxD593Km84lWM5d client_credentials admin profile
2019-06-22 13:37:38,052 DEBUG gen_access_token (21) jwt encoded:b'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjUwMDAvb2F1dGgvdG9rZW4iLCJzdWIiOiJ0ZXN0IGNsaWVudCIsImF1ZCI6InByb2ZpbGUifQ.BU5dSbPAFzoDDo4vathd6jlQVmDHaygEUh4GCwknCdbf4AVig3SgOW8JbITuPCKTf7qnxE8iJCWUOAd_wDCZwWKXdpisG6EGGmNpwZLAsDqL1CLgqTsRuGrc2kUfyMOHXfGXGkqsNROuPFV0-XYgxCQOz4LolNcB3Knvu1ApRcZyej8nAFXKxccDkLYyhldjRJwRehRZ4tMjDlbP4ghmEUFBF1Msx5Yzot26IK3ps4dfLnYVJr2dKUIPK75BzYR5kgUm3nkJRe4F0898j8tIMZwvKa2lKSypORDQXUxC3i8-x7A2vsVk7Jw3qcbZBarqstUEWITCZSVPYoHoF5l8iw'
decoded :{'iss': 'http://127.0.0.1:5000/oauth/token', 'sub': 'test client', 'aud': 'profile'} header:{'alg': 'RS256', 'typ': 'JWT'}
First, to test whether my oauth token request credentials are correct, I tried to request a oauth token with right client_credentials and the default token_generator from Authlib. With this I got the default oauth token.
Second, I updated the config with my token generator, then when I request an oauth token with the same client credentials, then I get the following error:
2019-06-22 13:40:56,700 DEBUG authenticate_client_secret_basic (65)
Authenticate bCsNV2Lo8hxD593Km84lWM5d via "client_secret_basic"
success
I created this custom debug line below to understand what the default access_token_generator() takes as input parameters. It is exactly take the same types - my input parameter types also match!
2019-06-22 13:40:56,701 DEBUG validate_token_request (67)
Validate token request of <OAuth2Client 2> client: <OAuth2Client 2>
type:<class 'website.models.OAuth2Client'> grant_type:
client_credentials type:<class 'str'> user: None type:<class
'NoneType'> scope: rs1secret type:<class 'str'>
2019-06-22 13:40:56,708 INFO _log (122) 127.0.0.1 - - [22/Jun/2019 13:40:56] "POST /oauth/token HTTP/1.1" 500 - Traceback (most recent call last): File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 2328, in __call__
return self.wsgi_app(environ, start_response) File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 2314, in wsgi_app
response = self.handle_exception(e) File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 1760, in handle_exception
reraise(exc_type, exc_value, tb) File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/_compat.py",
line 36, in reraise
raise value File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 2311, in wsgi_app
response = self.full_dispatch_request() File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 1834, in full_dispatch_request
rv = self.handle_user_exception(e) File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 1737, in handle_user_exception
reraise(exc_type, exc_value, tb) File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/_compat.py",
line 36, in reraise
raise value File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 1832, in full_dispatch_request
rv = self.dispatch_request() File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/flask/app.py",
line 1818, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args) File "/home/pksec/xx/oAuthProvider/website/routes.py",
line 193, in issue_token
return authorization.create_token_response() File "/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/authlib/oauth2/rfc6749/authorization_server.py",
line 186, in create_token_response
args = grant.create_token_response() File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/authlib/oauth2/rfc6749/grants/client_credentials.py",
line 104, in create_token_response
include_refresh_token=False, File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/authlib/oauth2/rfc6749/grants/base.py",
line 58, in generate_token
include_refresh_token=include_refresh_token, File "/home/pksec/.local/share/virtualenvs/oAuthProvider-n_KOMqPA/lib/python3.7/site-packages/authlib/oauth2/rfc6750/wrappers.py",
line 91, in __call__
access_token = self.access_token_generator(client, grant_type, user, scope) TypeError: 'NoneType' object is not callable
I know that I am doing something wrong when I pass the my gen_acc_token() method to the config - but couldn't exactly figure out what is wrong.
A small code snippet with that passes a sample gen_JWT_access_token() would be great.
I figure out finally the right way to pass my JWT token generator method to the config:OAUTH2_ACCESS_TOKEN_GENERATOR
Here is my dummy jwt token generator:
from authlib.jose import jwt
def gen_access_token(client, grant_type, user, scope):
log.debug('Not used yet in the JWT:: {} \n{} \n{} \n{}'.format( client, grant_type, user, scope))
header = {'alg': 'RS256'}
payload = {
'iss': 'http://127.0.0.1:5000/oauth/token',
'sub': 'test client',
'aud': 'profile'
}
try:
key = open('wf-app-server.key', 'r').read()
s = jwt.encode(header, payload, key)
claims = jwt.decode(s, open('wf-app-pub.pem', 'r').read())
except Exception as e:
log.debug('JWT exception', e)
log.debug("jwt encoded:{}\n decoded :{} \n header:{}".format(
s, claims, claims.header))
return s
OAUTH2_REFRESH_TOKEN_GENERATOR = True
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 874000,
'implicit': 3600,
'password': 600000,
'client_credentials': 600000
}
OAUTH2_ACCESS_TOKEN_GENERATOR = gen_access_token
Do not Pass the function parameters: Python NoneType object is not callable (beginner)
This is a beginner mistake! Follow your error output, you will find the solution!
This is how you should not pass your generator function:
OAUTH2_ACCESS_TOKEN_GENERATOR = gen_access_token('bCsNV2Lo8hxD593Km84lWM5d', 'client_credentials', 'admin', 'profile')
I am trying to access MeisterTask's API with Python and Flask, and no matter what I do, I seem to always get a 302 code in return from the API, although I can get an access token (or so I think). Here is the code I have so far (I tried reducing it, this is the smallest snippet I could get that replicates the error):
from flask import Flask, redirect, url_for, session, request, jsonify
from flask_oauthlib.client import OAuth
app = Flask(__name__)
app.debug = True
app.secret_key = "development"
oauth = OAuth(app)
meistertask = oauth.remote_app(
'meistertask',
consumer_key= "XXXXXX",
consumer_secret= "XXXXXX",
request_token_params={"scope" : "meistertask"},
base_url='https://www.meistertask.com/api',
request_token_url=None,
access_token_method='GET',
access_token_url='https://www.mindmeister.com/login/oauth2/token',
authorize_url='https://www.mindmeister.com/oauth2/authorize'
)
#app.route('/')
def index():
if 'meistertask_token' in session:
me = meistertask.get('user')
return jsonify(me.data)
return redirect(url_for('login'))
#app.route('/login')
def login():
return meistertask.authorize(callback=url_for('authorized', _external=True))
#app.route('/logout')
def logout():
session.pop('meistertask_token', None)
return redirect(url_for('index'))
#app.route('/login/authorized')
def authorized():
resp = meistertask.authorized_response()
print(resp.get('code'))
if resp is None or resp.get('code') is None:
return 'Access denied: reason=%s error=%s resp=%s' % (
request.args['error'],
request.args['error_description'],
resp
)
session['meistertask_token'] = (resp['code'], '')
return "Hello"
#meistertask.tokengetter
def get_meistertask_oauth_token():
return session.get('meistertask_token')
if __name__ == "__main__":
app.run()
And here is the traceback:
flask_oauthlib.client.OAuthException: Invalid response from meistertask
Traceback (most recent call last):
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2309, in __call__ return self.wsgi_app(environ, start_response)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2295, in wsgi_app response = self.handle_exception(e)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1741, in handle_exception reraise(exc_type, exc_value, tb)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\_compat.py", line 35, in reraise raise value
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2292, in wsgi_app response = self.full_dispatch_request()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request rv = self.handle_user_exception(e)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1718, in handle_user_exception reraise(exc_type, exc_value, tb)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\_compat.py", line 35, in reraise raise value
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request rv = self.dispatch_request()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1799, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args)
File "~\Documents\MeisterTaskServer\hello.py", line 49, in authorized resp = meistertask.authorized_response()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_oauthlib\client.py", line 707, in authorized_response data = self.handle_oauth2_response(args)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_oauthlib\client.py", line 692, in handle_oauth2_response
Things I have tried
Tried to modify the method to get the access token from GET to POST. The API clearly states that I should use GET, yet every other example I have seen on flask_oauthlib's GitHub uses POST (the examples are 3 years old, but some still work, namely the GitHub one). Either give the same result.
Tried doing it barebones, without any library. The resulting code was thrice as long and also had more problems.
Used Django instead of Flask. Never even managed to get the hello world example going, it was too much work, and also I have discovered the library flask_oauthlib.
Things worth mentioning
I derived this code from this here GitHub example
There is also code there I omitted in order to keep the snippet short, that establishes that the server should use SSL (as per the request from the API that the redirect_uri should use HTTPS)
The app manages to redirect me over at MeisterTask and asks for my permission. Once I grant it, it redirects to "https://127.0.0.1:5000/login/authorized?code=some_token" where I get the traceback. If I look with Chrome's debugging tools to the requests made and what I receive, I see that I get an 302 from the API, but I also get an access token.
I run Windows 10 with Python 3.7.0
So what's the deal? What's the next step here? I've run out of things to try. Thank you for taking the time to solve this!
I want to pass variables from a function to a class in a Flask app using session. This is my code:
#app.route('/login', methods=['POST'])
def login():
if not request.is_json:
return jsonify({"msg": "Missing JSON in request"}), 400
username = request.json.get('username', None)
password = request.json.get('password', None)
session['client_fname'] = request.json.get('Client First Name', None)
session['client_lname'] = request.json.get('Client Last Name', None)
... ...
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
class PrivateResource(Resource):
#app.route('/protected', methods=['GET'])
#jwt_required
def sendData():
return mysqldb.addUser("{}".format(session['client_fname']),"{}".format(session['client_lname']))
The variables I want to pass are session['client_fname'] and session['client_lname']. However, when I try to trigger sendData() using curl -X GET http://localhost:5000/protected -H "Authorization: Bearer JWTGOESHERE" I get:
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 2309, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask_restful/__init__.py", line 273, in error_router
return original_handler(e)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask_restful/__init__.py", line 273, in error_router
return original_handler(e)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask_jwt_extended/view_decorators.py", line 101, in wrapper
return fn(*args, **kwargs)
File "/Users/open/PycharmProjects/FlaskMiddleware/Flask-API-Middleware-V1/authenticate.py", line 48, in sendData
return mysqldb.addUser("{}".format(session['client_fname'],None),"{}".format(session['client_lname']))
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/werkzeug/local.py", line 377, in <lambda>
__getitem__ = lambda x, i: x._get_current_object()[i]
File "/Users/open/venv/FlaskMiddleware/lib/python2.7/site-packages/flask/sessions.py", line 83, in __getitem__
return super(SecureCookieSession, self).__getitem__(key)
KeyError: 'client_fname'
Whats wrong with the way I'm using session to pass the variables? How can I fix this?
UPDATE
If this is a cookie issue having to do with preserving the session, how could I achieve this? Now I'm creating a cookie file in curl -H "Content-Type: application/json" -X POST -d '{"username":"user1","password":"abc123","Client First Name":"SAUR","Client Last Name":"KRIS"}' http://localhost:5000/login -c cookies.txt and then trying curl -X GET http://localhost:5000/protected -H "Authorization: Bearer JWTGOESHERE" -b cookies.txt. Now I'm getting TypeError: The view function did not return a valid response. The function either returned None or ended without a return statement. and I'm not sure if this is progress or just plain wrong.
It seems that you're using flask_jwt_extended so you could bypass the problem by storing that data directly in the token.
# ...
access_token = create_access_token({
'first_name': request.json.get('Client First Name', None),
'last_name': request.json.get('Client Last Name', None)
})
And then retrieving it in the protected route:
# ...
user_adata = get_jwt_identity()
You are almost definitely not preserving state in between your requests. If you are using postman or curl make sure to save the session id cookie. If you are using requests use requests.session().
Your update looks like it's likely to be progress, but perhaps the mysqldb.addUser function is returning None.
The easiest way to determine if you're making progress would be to debug with a breakpoint at the line return mysqldb.addUser("{}".format(session['client_fname']),"{}".format(session['client_lname'])) (I usually use PyCharm for this, but most IDEs will have something for debug, or you can use pdb). A faster but messier option, add a print statement before the call to mysqldb.addUser, and another print statement afterwards printing the return value.
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