Skip to content

Commit

Permalink
API, batch: update docs and test, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
carlfeberhard committed Jul 29, 2016
1 parent 15a686e commit ff26129
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 13 deletions.
54 changes: 42 additions & 12 deletions lib/galaxy/web/framework/middleware/batch.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
"""
Batch API middleware
Adds a single route to the installation that:
1. accepts a POST call containing a JSON array of 'http-like' JSON
dictionaries.
2. Each dictionary describes a single API call within the batch and is routed
back by the middleware to the application's `handle_request` as if it was
a seperate request.
3. Each response generated is combined into a final JSON list that is
returned from the POST call.
In this way, API calls can be kept properly atomic and the endpoint can compose
them into complex tasks using only one request.
..note: This batch system is primarily designed for use by the UI as these
types of batch operations *reduce the number of requests* for a given group of
API tasks. IOW, this ain't about batching jobs.
..warning: this endpoint is experimental is likely to change.
"""
import io
from urlparse import urlparse
Expand All @@ -14,12 +33,29 @@

class BatchMiddleware( object ):
"""
Adds a URL endpoint for processing batch API calls formatted as a JSON
array of JSON dictionaries. These dictionaries are in the form:
[
{
"url": "/api/histories",
"type": "POST",
"body": "{ \"name\": \"New History Name\" }"
},
...
]
where:
* `url` is the url for the API call to be made including any query string
* `type` is the HTTP method used (e.g. 'POST', 'PUT') - defaults to 'GET'
* `body` is the text body of the request (optional)
* `contentType` content-type request header (defaults to application/json)
"""
DEFAULT_CONFIG = {
'route' : '/api/batch',
'allowed_routes' : [
'^api\/users.*',
'^api\/histories.*',
'^api\/jobs.*',
]
}

Expand All @@ -39,13 +75,15 @@ def __call__( self, environ, start_response ):
return self.application( environ, start_response )

def process_batch_requests( self, batch_environ, start_response ):
"""
Loops through any provided JSON formatted 'requests', aggregates their
JSON responses, and wraps them in the batch call response.
"""
payload = self._read_post_payload( batch_environ )
requests = payload.get( 'batch', [] )

responses = []
for request in requests:
print '------------------------'
print request
if not self._is_allowed_route( request[ 'url' ] ):
responses.append( self._disallowed_route_response( request[ 'url' ] ) )
continue
Expand All @@ -71,10 +109,8 @@ def _read_post_payload( self, environ ):

def _is_allowed_route( self, route ):
if self.config.get( 'allowed_routes', None ):
print self.base_url
shortened_route = route.replace( self.base_url, '', 1 )
matches = [ re.match( allowed, shortened_route ) for allowed in self.config[ 'allowed_routes' ] ]
print matches
return any( matches )
return True

Expand All @@ -85,12 +121,6 @@ def _disallowed_route_response( self, route ):
'allowed' : self.config[ 'allowed_routes' ]
})

def _create_invalid_batch_response( self ):
return dict( status=403, headers=self._default_headers(), body={
"err_msg" : "Disallowed route used for batch operation",
"allowed" : self.allowed_routes,
})

def _build_request_environ( self, original_environ, request ):
"""
Given a request and the original environ used to call the batch, return
Expand All @@ -103,8 +133,7 @@ def _build_request_environ( self, original_environ, request ):
request_environ[ 'CONTENT_TYPE' ] = request.get( 'contentType', 'application/json' )
request_environ[ 'REQUEST_METHOD' ] = request.get( 'method', request.get( 'type', 'GET' ) )
url = '{0}://{1}{2}'.format( request_environ.get( 'wsgi.url_scheme' ),
request_environ.get( 'HTTP_HOST' ),
request[ 'url' ] )
request_environ.get( 'HTTP_HOST' ), request[ 'url' ] )
parsed = urlparse( url )
request_environ[ 'PATH_INFO' ] = parsed.path
request_environ[ 'QUERY_STRING' ] = parsed.query
Expand Down Expand Up @@ -137,6 +166,7 @@ def _proccess_batch_request( self, request, environ, start_response ):

def body_renderer( self, trans, body, environ, start_response ):
# this is a dummy renderer that does not call start_response
# See 'We have to re-create the handle request method...' in _process_batch_request above
return dict(
status=trans.response.status,
headers=trans.response.headers,
Expand Down
2 changes: 1 addition & 1 deletion test/api/test_api_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def test_404_route( self ):
def test_errors( self ):
batch = [
dict( url=self._with_key( '/api/histories/abc123' ) ),
dict( url=self._with_key( '/api/users/123' ), method='PUT' ),
dict( url=self._with_key( '/api/jobs' ), method='POST', body=json.dumps( dict( name='Wat' ) ) ),
]
response = self._post_batch( batch )
response = response.json()
Expand Down

0 comments on commit ff26129

Please sign in to comment.