Last active
August 29, 2015 14:08
-
-
Save joshfriend/68e08c05c652518f60d0 to your computer and use it in GitHub Desktop.
Magicks to make Flask MethodViews more testable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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