Skip to content

Commit

Permalink
Merge branch 'master' into surface-exception-messages
Browse files Browse the repository at this point in the history
  • Loading branch information
joshfriend committed Jun 5, 2015
2 parents 1ccec7d + d7a62be commit fb86f31
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 61 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Authors
A huge thanks to all of our contributors:


- Alec Reiter
- Alex Gaynor
- Alex M
- Alex Morken
Expand Down
20 changes: 7 additions & 13 deletions flask_restful/representations/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,21 @@
from json import dumps


# This dictionary contains any kwargs that are to be passed to the json.dumps
# function, used below.
settings = {}


def output_json(data, code, headers=None):
"""Makes a Flask response with a JSON encoded body"""

settings = current_app.config.get('RESTFUL_JSON', {})

# If we're in debug mode, and the indent is not set, we set it to a
# reasonable value here. Note that this won't override any existing value
# that was set. We also set the "sort_keys" value.
local_settings = settings.copy()
if current_app.debug:
local_settings.setdefault('indent', 4)
local_settings.setdefault('sort_keys', True)
settings.setdefault('indent', 4)
settings.setdefault('sort_keys', True)

# We also add a trailing newline to the dumped JSON if the indent value is
# set - this makes using `curl` on the command line much nicer.
dumped = dumps(data, **local_settings)
if 'indent' in local_settings:
dumped += '\n'
# always end the json dumps with a new line
# see https://github.com/mitsuhiko/flask/pull/1262
dumped = dumps(data, **settings) + "\n"

resp = make_response(dumped, code)
resp.headers.extend(headers or {})
Expand Down
16 changes: 0 additions & 16 deletions tests/conftest.py

This file was deleted.

108 changes: 76 additions & 32 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
from flask import Flask, Blueprint, redirect, views
from flask.signals import got_request_exception, signals_available
try:
from mock import Mock, patch
from mock import Mock
except:
# python3
from unittest.mock import Mock, patch
from unittest.mock import Mock
import flask
import werkzeug
from werkzeug.exceptions import HTTPException, Unauthorized, BadRequest, NotFound
from flask_restful.utils import http_status_message, unpack
import flask_restful
import flask_restful.fields
from flask_restful import OrderedDict
from json import dumps, loads
from json import dumps, loads, JSONEncoder
#noinspection PyUnresolvedReferences
from nose.tools import assert_equals, assert_true, assert_false # you need it for tests in form of continuations
import six
Expand Down Expand Up @@ -115,7 +115,7 @@ def test_handle_error_does_not_swallow_exceptions(self):
with app.test_request_context('/foo'):
resp = api.handle_error(exception)
self.assertEquals(resp.status_code, 400)
self.assertEquals(resp.get_data(), b'{"message": "x"}')
self.assertEquals(resp.get_data(), b'{"message": "x"}\n')


def test_marshal(self):
Expand Down Expand Up @@ -359,7 +359,9 @@ def test_handle_server_error(self):
with app.test_request_context("/foo"):
resp = api.handle_error(Exception())
self.assertEquals(resp.status_code, 500)
self.assertEquals(resp.data.decode(), dumps({"message": "Internal Server Error"}))
self.assertEquals(resp.data.decode(), dumps({
"message": "Internal Server Error"
}) + "\n")

def test_handle_error_with_code(self):
app = Flask(__name__)
Expand All @@ -372,7 +374,7 @@ def test_handle_error_with_code(self):
with app.test_request_context("/foo"):
resp = api.handle_error(exception)
self.assertEquals(resp.status_code, 500)
self.assertEquals(resp.data.decode(), dumps({"foo": "bar"}))
self.assertEquals(resp.data.decode(), dumps({"foo": "bar"}) + "\n")

def test_handle_auth(self):
app = Flask(__name__)
Expand All @@ -381,7 +383,7 @@ def test_handle_auth(self):
with app.test_request_context("/foo"):
resp = api.handle_error(Unauthorized())
self.assertEquals(resp.status_code, 401)
expected_data = dumps({'message': Unauthorized.description})
expected_data = dumps({'message': Unauthorized.description}) + "\n"
self.assertEquals(resp.data.decode(), expected_data)

self.assertTrue('WWW-Authenticate' in resp.headers)
Expand Down Expand Up @@ -453,7 +455,7 @@ def test_handle_error(self):
self.assertEquals(resp.status_code, 400)
self.assertEquals(resp.data.decode(), dumps({
'message': BadRequest.description,
}))
}) + "\n")

def test_handle_smart_errors(self):
app = Flask(__name__)
Expand All @@ -464,6 +466,13 @@ def test_handle_smart_errors(self):
api.add_resource(view, '/fee', endpoint='bir')
api.add_resource(view, '/fii', endpoint='ber')

with app.test_request_context("/faaaaa"):
resp = api.handle_error(NotFound())
self.assertEquals(resp.status_code, 404)
self.assertEquals(resp.data.decode(), dumps({
"message": NotFound.description,
}) + "\n")

with app.test_request_context("/fOo"):
resp = api.handle_error(NotFound())
self.assertEquals(resp.status_code, 404)
Expand All @@ -476,7 +485,7 @@ def test_handle_smart_errors(self):
self.assertEquals(resp.status_code, 404)
self.assertEquals(resp.data.decode(), dumps({
"message": NotFound.description
}))
}) + "\n")

def test_error_router_falls_back_to_original(self):
"""Verify that if an exception occurs in the Flask-RESTful error handler,
Expand Down Expand Up @@ -576,9 +585,9 @@ def get(self):

with app.test_client() as client:
foo1 = client.get('/foo')
self.assertEquals(foo1.data, b'"foo1"')
self.assertEquals(foo1.data, b'"foo1"\n')
foo2 = client.get('/foo/toto')
self.assertEquals(foo2.data, b'"foo1"')
self.assertEquals(foo2.data, b'"foo1"\n')

def test_add_resource(self):
app = Mock(flask.Flask)
Expand Down Expand Up @@ -632,7 +641,7 @@ def get(self):

with app.test_client() as client:
foo = client.get('/foo')
self.assertEquals(foo.data, b'"wonderful slurm"')
self.assertEquals(foo.data, b'"wonderful slurm"\n')

def test_output_unpack(self):

Expand All @@ -646,7 +655,7 @@ def make_empty_response():
wrapper = api.output(make_empty_response)
resp = wrapper()
self.assertEquals(resp.status_code, 200)
self.assertEquals(resp.data.decode(), '{"foo": "bar"}')
self.assertEquals(resp.data.decode(), '{"foo": "bar"}\n')

def test_output_func(self):

Expand Down Expand Up @@ -800,34 +809,69 @@ def get(self):
# Assert our trailing newline.
self.assertTrue(foo.data.endswith(b'\n'))

def test_will_pass_options_to_json(self):
def test_read_json_settings_from_config(self):
class TestConfig(object):
RESTFUL_JSON = {'indent': 2,
'sort_keys': True,
'separators': (', ', ': ')}

app = Flask(__name__)
app.config.from_object(TestConfig)
api = flask_restful.Api(app)

class Foo1(flask_restful.Resource):
class Foo(flask_restful.Resource):
def get(self):
return {'foo': 'bar'}
return {'foo': 'bar', 'baz': 'qux'}

api.add_resource(Foo1, '/foo', endpoint='bar')
api.add_resource(Foo, '/foo')

with app.test_client() as client:
data = client.get('/foo').data

expected = b'{\n "baz": "qux", \n "foo": "bar"\n}\n'

self.assertEquals(data, expected)


def test_use_custom_jsonencoder(self):
class CabageEncoder(JSONEncoder):
def default(self, obj):
return 'cabbage'

# We patch the representations module here, with two things:
# 1. Set the settings dict() with some value
# 2. Patch the json.dumps function in the module with a Mock object.
class TestConfig(object):
RESTFUL_JSON = {'cls': CabageEncoder}

from flask_restful.representations import json as json_rep
json_dumps_mock = Mock(return_value='bar')
new_settings = {'indent': 123}
app = Flask(__name__)
app.config.from_object(TestConfig)
api = flask_restful.Api(app)

class Cabbage(flask_restful.Resource):
def get(self):
return {'frob': object()}

api.add_resource(Cabbage, '/cabbage')

with patch.multiple(json_rep, dumps=json_dumps_mock,
settings=new_settings):
with app.test_client() as client:
client.get('/foo')
with app.test_client() as client:
data = client.get('/cabbage').data

expected = b'{"frob": "cabbage"}\n'
self.assertEquals(data, expected)

def test_json_with_no_settings(self):
app = Flask(__name__)
api = flask_restful.Api(app)

class Foo(flask_restful.Resource):
def get(self):
return {'foo': 'bar'}

api.add_resource(Foo, '/foo')

with app.test_client() as client:
data = client.get('/foo').data

# Assert that the function was called with the above settings.
data, kwargs = json_dumps_mock.call_args
self.assertTrue(json_dumps_mock.called)
self.assertEqual(123, kwargs['indent'])
expected = b'{"foo": "bar"}\n'
self.assertEquals(data, expected)

def test_redirect(self):
app = Flask(__name__)
Expand Down Expand Up @@ -858,7 +902,7 @@ def get(self):
app = app.test_client()
resp = app.get('/api')
self.assertEquals(resp.status_code, 200)
self.assertEquals(resp.data.decode('utf-8'), '{"foo": 3.0}')
self.assertEquals(resp.data.decode('utf-8'), '{"foo": 3.0}\n')

def test_custom_error_message(self):
errors = {
Expand Down

0 comments on commit fb86f31

Please sign in to comment.