Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ def __init__(self, app, rule, method, callback,
#: decorator are stored in this dictionary. Used for route-specific
#: plugin configuration and meta-data.
self.config = app.config._make_overlay()
if 'config' in config and isinstance(config['config'], dict):
self.config.load_dict(config.pop('config'))
self.config.load_dict(config)

@cached_property
Expand Down Expand Up @@ -1962,21 +1964,27 @@ def __init__(self, json_dumps=json_dumps):
def setup(self, app):
app.config._define('json.enable', default=True, validate=bool,
help="Enable or disable automatic dict->json filter.")
app.config._define('json.ascii', default=False, validate=bool,
help="Use only 7-bit ASCII characters in output.")
app.config._define('json.indent', default=True, validate=bool,
help="Add whitespace to make json more readable.")
app.config._define('json.dump_func', default=None,
help="If defined, use this function to transform"
" dict into json. The other options no longer"
" apply.")
" dict into json.")
if self.json_dumps is not json_dumps:
app.config['json.enable'] = True
if not app.config.get('json.dump_func'):
app.config['json.dump_func'] = self.json_dumps

def apply(self, callback, route):
dumps = self.json_dumps
if not self.json_dumps: return callback
dumps = route.config.get('json.dump_func') or self.json_dumps
if not dumps: return callback

@functools.wraps(callback)
def wrapper(*a, **ka):
if route.config.get('json.disable') or route.config.get('autojson') is False or not route.config.get('json.enable', True):
if self.json_dumps is json_dumps:
return callback(*a, **ka)

dumps = route.config.get('json.dump_func') or self.json_dumps
if not dumps: return callback(*a, **ka)

try:
rv = callback(*a, **ka)
except HTTPResponse as resp:
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ of keys and values with namespaced keys::

# Load an entire dict structure
app.config.load_dict({
'autojson': False,
'json.enable': False,
'sqlite': { 'db': ':memory:' },
'myapp': {
'param': 'value',
Expand Down
4 changes: 4 additions & 0 deletions test/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ def test_setattr(self):
del app.test
app.test = 6
self.assertEqual(6, app.test)

def test_autojson_kwarg(self):
app = Bottle(autojson=False)
self.assertFalse(app.config['json.enable'])
51 changes: 51 additions & 0 deletions test/test_outputfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,57 @@ def test_json_HTTPError(self):
self.assertBody(bottle.json_dumps({'a': 1}))
self.assertHeader('Content-Type','application/json')

def test_json_disabled_via_config(self):
self.app.config['json.enable'] = False
self.app.route('/')(lambda: {'a': 1})
self.assertBody('a')
self.assertHeader('Content-Type', 'text/html; charset=UTF-8')

def test_json_disabled_via_json_disable(self):
self.app.config['json.disable'] = True
self.app.route('/')(lambda: {'a': 1})
self.assertBody('a')
self.assertHeader('Content-Type', 'text/html; charset=UTF-8')

def test_json_disabled_via_route_config(self):
self.app.route('/json')(lambda: {'a': 1})
self.app.route('/raw', config={'json.enable': False})(lambda: {'a': 1})
self.app.route('/disabled', config={'json.disable': True})(lambda: {'a': 1})
self.assertBody(bottle.json_dumps({'a': 1}), '/json')
self.assertHeader('Content-Type', 'application/json', '/json')
self.assertBody('a', '/raw')
self.assertHeader('Content-Type', 'text/html; charset=UTF-8', '/raw')
self.assertBody('a', '/disabled')
self.assertHeader('Content-Type', 'text/html; charset=UTF-8', '/disabled')

def test_json_enable_dynamic_toggle(self):
self.app.config['json.enable'] = False
self.app.route('/')(lambda: {'a': 1})
self.assertBody('a')
self.app.config['json.enable'] = True
self.assertBody(bottle.json_dumps({'a': 1}))

def test_replacement_json_plugin_after_autojson_false(self):
app = bottle.Bottle(autojson=False)
app.install(bottle.JSONPlugin(lambda d: 'replacement'))
app.route('/')(lambda: {'a': 1})
env = {'PATH_INFO': '/', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'wsgi.url_scheme': 'http', 'wsgi.input': None, 'wsgi.errors': None}
headers = []
body = list(app(env, lambda s, h, e=None: headers.extend(h)))
self.assertEqual(body, [b'replacement'])

def test_json_custom_dump_func(self):
self.app.config['json.dump_func'] = lambda x: 'custom_json'
self.app.route('/')(lambda: {'a': 1})
self.assertBody('custom_json')
self.assertHeader('Content-Type', 'application/json')

def test_json_route_custom_dump_func(self):
self.app.route('/default')(lambda: {'a': 1})
self.app.route('/custom', config={'json.dump_func': lambda x: 'route_custom'})(lambda: {'a': 1})
self.assertBody(bottle.json_dumps({'a': 1}), '/default')
self.assertBody('route_custom', '/custom')

def test_generator_callback(self):
@self.app.route('/')
def test():
Expand Down