Skip to content

Instantly share code, notes, and snippets.

@joshfriend
Last active August 29, 2015 14:08
Show Gist options
  • Save joshfriend/68e08c05c652518f60d0 to your computer and use it in GitHub Desktop.
Save joshfriend/68e08c05c652518f60d0 to your computer and use it in GitHub Desktop.
Magicks to make Flask MethodViews more testable
class Resource(MethodView):
"""
Represents an abstract RESTful resource. Concrete resources should
extend from this class and expose methods for each supported HTTP
method. If a resource is invoked with an unsupported HTTP method,
the API will return a response with status 405 Method Not Allowed.
Otherwise the appropriate method is called and passed all arguments
from the url rule used when adding the resource to an Api instance. See
:meth:`~flask.ext.restful.Api.add_resource` for details.
"""
representations = None
method_decorators = []
def dispatch_request(self, *args, **kwargs):
# Taken from flask
#noinspection PyUnresolvedReferences
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None)
assert meth is not None, 'Unimplemented method %r' % request.method
meth = inject_globals(meth)
for decorator in self.method_decorators:
meth = decorator(meth)
resp = meth(*args, **kwargs)
if isinstance(resp, ResponseBase): # There may be a better way to test
return resp
representations = self.representations or {}
#noinspection PyUnresolvedReferences
for mediatype in self.mediatypes():
if mediatype in representations:
data, code, headers = unpack(resp)
resp = representations[mediatype](data, code, headers)
resp.headers['Content-Type'] = mediatype
return resp
return resp
def inject_globals(func):
"""Pass the Flask global proxy objects into a view function.
View functions should then rely on using the Flask globals passed by the
injector decorator. This will make Flask view functions more testable
since the tests can just create simple `werkzeug.wrappers.Response`
instances and pass them instead of having to go through the full WSGI
environment/routing used by `app.test_client`.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
flask_globals = {
'g': g,
'app': current_app,
'request': request,
'session': session,
}
kwargs.update(flask_globals)
return func(*args, **kwargs)
return wrapper
#!/usr/bin/env python
from flask import abort
from resource import Resource
from models import User
class UserResource(Resource):
def get(self, user_id=0, **kwargs):
user = User.get_by_username(user_id)
if not user:
abort(404)
return user.to_json()
def post(self, user_id=0, **kwargs):
request = kwargs['request']
user = User.get_by_username(user_id)
if not user:
abort(404)
user.update(**request.args)
return user.to_json()
def delete(self, user_id=0, **kwargs):
user = User.get_by_username(user_id)
if not user:
abort(404)
user.delete()
return {'message': 'deleted', 'status': 204}, 204
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment