Merge pull request #2937 from carlfeberhard/jsonp

Allow JSONP to be returned from API endpoints
This commit is contained in:
Martin Cech
2016-09-20 11:43:43 -04:00
committed by GitHub
+56 -17
View File
@@ -15,6 +15,8 @@ from galaxy.web.framework import url_for
log = logging.getLogger( __name__ )
JSON_CONTENT_TYPE = "application/json"
JSONP_CONTENT_TYPE = "application/javascript"
JSONP_CALLBACK_KEY = 'callback'
def error( message ):
@@ -36,15 +38,22 @@ def expose( func ):
return func
def json( func, **json_kwargs ):
def json( func, pretty=False ):
"""
Format the response as JSON and set the response content type to
JSON_CONTENT_TYPE.
"""
@wraps(func)
def call_and_format( self, trans, *args, **kwargs ):
trans.response.set_content_type( JSON_CONTENT_TYPE )
return safe_dumps( func( self, trans, *args, **kwargs ), **json_kwargs )
# pull out any callback argument to the api endpoint and set the content type to json or javascript
jsonp_callback = kwargs.pop( JSONP_CALLBACK_KEY, None )
if jsonp_callback:
trans.response.set_content_type( JSONP_CONTENT_TYPE )
else:
trans.response.set_content_type( JSON_CONTENT_TYPE )
rval = func( self, trans, *args, **kwargs )
return _format_return_as_json( rval, jsonp_callback, pretty=( pretty or trans.debug ) )
if not hasattr( func, '_orig' ):
call_and_format._orig = func
return expose( _save_orig_fn( call_and_format, func ) )
@@ -54,7 +63,7 @@ def json_pretty( func ):
"""
Indent and sort returned JSON.
"""
return json( func, indent=4, sort_keys=True )
return json( func, pretty=True )
def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
@@ -113,9 +122,17 @@ def expose_api( func, to_json=True, user_required=True ):
error_status = '400 Bad Request'
error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
return error
trans.response.set_content_type( "application/json" )
# pull out any callback argument to the api endpoint and set the content type to json or javascript
jsonp_callback = kwargs.pop( JSONP_CALLBACK_KEY, None )
if jsonp_callback:
trans.response.set_content_type( JSONP_CONTENT_TYPE )
else:
trans.response.set_content_type( JSON_CONTENT_TYPE )
# send 'do not cache' headers to handle IE's caching of ajax get responses
trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
# Perform api_run_as processing, possibly changing identity
if 'payload' in kwargs and 'run_as' in kwargs['payload']:
if not trans.user_can_do_run_as():
@@ -135,10 +152,8 @@ def expose_api( func, to_json=True, user_required=True ):
return "That user does not exist."
try:
rval = func( self, trans, *args, **kwargs)
if to_json and trans.debug:
rval = safe_dumps( rval, indent=4, sort_keys=True )
elif to_json:
rval = safe_dumps( rval )
if to_json:
rval = _format_return_as_json( rval, jsonp_callback, pretty=trans.debug )
return rval
except paste.httpexceptions.HTTPException:
raise # handled
@@ -206,7 +221,7 @@ def expose_api_anonymous( func, to_json=True ):
# ----------------------------------------------------------------------------- (new) api decorators
# TODO: rename as expose_api and make default.
def _future_expose_api( func, to_json=True, user_required=True, user_or_session_required=True ):
def _future_expose_api( func, to_json=True, user_required=True, user_or_session_required=True, handle_jsonp=True ):
"""
Expose this function via the API.
"""
@@ -233,7 +248,13 @@ def _future_expose_api( func, to_json=True, user_required=True, user_or_session_
error_code = error_codes.USER_INVALID_JSON
return __api_error_response( trans, status_code=400, err_code=error_code )
trans.response.set_content_type( JSON_CONTENT_TYPE )
# pull out any callback argument to the api endpoint and set the content type to json or javascript
# TODO: use handle_jsonp to NOT overwrite existing tool_shed JSONP
jsonp_callback = kwargs.pop( JSONP_CALLBACK_KEY, None ) if handle_jsonp else None
if jsonp_callback:
trans.response.set_content_type( JSONP_CONTENT_TYPE )
else:
trans.response.set_content_type( JSON_CONTENT_TYPE )
# send 'do not cache' headers to handle IE's caching of ajax get responses
trans.response.headers[ 'Cache-Control' ] = "max-age=0,no-cache,no-store"
@@ -258,11 +279,9 @@ def _future_expose_api( func, to_json=True, user_required=True, user_or_session_
error_code = error_codes.USER_INVALID_RUN_AS
return __api_error_response( trans, err_code=error_code, status_code=400 )
try:
rval = func( self, trans, *args, **kwargs)
if to_json and trans.debug:
rval = safe_dumps( rval, indent=4, sort_keys=True )
elif to_json:
rval = safe_dumps( rval )
rval = func( self, trans, *args, **kwargs )
if to_json:
rval = _format_return_as_json( rval, jsonp_callback, pretty=trans.debug )
return rval
except MessageException as e:
traceback_string = format_exc()
@@ -288,6 +307,19 @@ def _future_expose_api( func, to_json=True, user_required=True, user_or_session_
return decorator
def _format_return_as_json( rval, jsonp_callback=None, pretty=False ):
"""
Formats a return value as JSON or JSONP if `jsonp_callback` is present.
Use `pretty=True` to return pretty printed json.
"""
dumps_kwargs = dict( indent=4, sort_keys=True ) if pretty else {}
json = safe_dumps( rval, **dumps_kwargs )
if jsonp_callback:
json = "{}({});".format( jsonp_callback, json )
return json
def __api_error_message( trans, **kwds ):
exception = kwds.get( "exception", None )
if exception:
@@ -362,4 +394,11 @@ def _future_expose_api_raw_anonymous( func ):
def _future_expose_api_raw_anonymous_and_sessionless( func ):
return _future_expose_api( func, to_json=False, user_required=False, user_or_session_required=False )
# TODO: tool_shed api implemented JSONP first on a method-by-method basis, don't overwrite that for now
return _future_expose_api(
func,
to_json=False,
user_required=False,
user_or_session_required=False,
handle_jsonp=False
)