[galaxy-commits] commit/galaxy-central: natefoo: API enhancements: Rename the 'contents' controller to 'library_contents', move (some) sanity and security checks to galaxy.web.api.util, add some enhancements to the histories code and some sample scripts for histories.
Bitbucket
commits-noreply at bitbucket.org
Fri Aug 26 13:55:17 EDT 2011
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/b35bf6a8c11f/
changeset: b35bf6a8c11f
user: natefoo
date: 2011-08-26 19:55:08
summary: API enhancements: Rename the 'contents' controller to 'library_contents', move (some) sanity and security checks to galaxy.web.api.util, add some enhancements to the histories code and some sample scripts for histories.
affected #: 9 files (2.6 KB)
--- a/lib/galaxy/web/api/contents.py Fri Aug 26 10:39:34 2011 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-"""
-API operations on the contents of a library.
-"""
-import logging, os, string, shutil, urllib, re, socket
-from cgi import escape, FieldStorage
-from galaxy import util, datatypes, jobs, web, util
-from galaxy.web.base.controller import *
-from galaxy.util.sanitize_html import sanitize_html
-from galaxy.model.orm import *
-
-log = logging.getLogger( __name__ )
-
-class ContentsController( BaseController ):
-
- @web.expose_api
- def index( self, trans, library_id, **kwd ):
- """
- GET /api/libraries/{encoded_library_id}/contents
- Displays a collection (list) of library contents (files and folders).
- """
- rval = []
- current_user_roles = trans.get_current_user_roles()
- def traverse( folder ):
- admin = trans.user_is_admin()
- rval = []
- for subfolder in folder.active_folders:
- if not admin:
- can_access, folder_ids = trans.app.security_agent.check_folder_contents( trans.user, current_user_roles, subfolder )
- if (admin or can_access) and not subfolder.deleted:
- subfolder.api_path = folder.api_path + '/' + subfolder.name
- subfolder.api_type = 'folder'
- rval.append( subfolder )
- rval.extend( traverse( subfolder ) )
- for ld in folder.datasets:
- if not admin:
- can_access = trans.app.security_agent.can_access_dataset( current_user_roles, ld.library_dataset_dataset_association.dataset )
- if (admin or can_access) and not ld.deleted:
- ld.api_path = folder.api_path + '/' + ld.name
- ld.api_type = 'file'
- rval.append( ld )
- return rval
- try:
- decoded_library_id = trans.security.decode_id( library_id )
- except TypeError:
- trans.response.status = 400
- return "Malformed library id ( %s ) specified, unable to decode." % str( library_id )
- try:
- library = trans.sa_session.query( trans.app.model.Library ).get( decoded_library_id )
- except:
- library = None
- if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( current_user_roles, library ) ):
- trans.response.status = 400
- return "Invalid library id ( %s ) specified." % str( library_id )
- encoded_id = trans.security.encode_id( 'folder.%s' % library.root_folder.id )
- rval.append( dict( id = encoded_id,
- type = 'folder',
- name = '/',
- url = url_for( 'library_content', library_id=library_id, id=encoded_id ) ) )
- library.root_folder.api_path = ''
- for content in traverse( library.root_folder ):
- encoded_id = trans.security.encode_id( '%s.%s' % ( content.api_type, content.id ) )
- rval.append( dict( id = encoded_id,
- type = content.api_type,
- name = content.api_path,
- url = url_for( 'library_content', library_id=library_id, id=encoded_id, ) ) )
- return rval
-
- @web.expose_api
- def show( self, trans, id, library_id, **kwd ):
- """
- GET /api/libraries/{encoded_library_id}/contents/{encoded_content_type_and_id}
- Displays information about a library content (file or folder).
- """
- content_id = id
- try:
- decoded_type_and_id = trans.security.decode_string_id( content_id )
- content_type, decoded_content_id = decoded_type_and_id.split( '.' )
- except:
- trans.response.status = 400
- return "Malformed content id ( %s ) specified, unable to decode." % str( content_id )
- if content_type == 'folder':
- model_class = trans.app.model.LibraryFolder
- elif content_type == 'file':
- model_class = trans.app.model.LibraryDataset
- else:
- trans.response.status = 400
- return "Invalid type ( %s ) specified." % str( content_type )
- try:
- content = trans.sa_session.query( model_class ).get( decoded_content_id )
- except:
- content = None
- if not content or ( not trans.user_is_admin() and not trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), content, trans.user ) ):
- trans.response.status = 400
- return "Invalid %s id ( %s ) specified." % ( content_type, str( content_id ) )
- return content.get_api_value( view='element' )
-
- @web.expose_api
- def create( self, trans, library_id, payload, **kwd ):
- """
- POST /api/libraries/{encoded_library_id}/contents
- Creates a new library content item (file or folder).
- """
- create_type = None
- if 'create_type' not in payload:
- trans.response.status = 400
- return "Missing required 'create_type' parameter. Please consult the API documentation for help."
- else:
- create_type = payload.pop( 'create_type' )
- if create_type not in ( 'file', 'folder' ):
- trans.response.status = 400
- return "Invalid value for 'create_type' parameter ( %s ) specified. Please consult the API documentation for help." % create_type
- try:
- content_id = str( payload.pop( 'folder_id' ) )
- decoded_type_and_id = trans.security.decode_string_id( content_id )
- parent_type, decoded_parent_id = decoded_type_and_id.split( '.' )
- assert parent_type in ( 'folder', 'file' )
- except:
- trans.response.status = 400
- return "Malformed parent id ( %s ) specified, unable to decode." % content_id
- # "content" can be either a folder or a file, but the parent of new contents can only be folders.
- if parent_type == 'file':
- trans.response.status = 400
- try:
- # With admins or people who can access the dataset provided as the parent, be descriptive.
- dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( decoded_parent_id ).library_dataset_dataset_association.dataset
- assert trans.user_is_admin() or trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), dataset )
- return "The parent id ( %s ) points to a file, not a folder." % content_id
- except:
- # If you can't access the parent we don't want to reveal its existence.
- return "Invalid parent folder id ( %s ) specified." % content_id
- # The rest of the security happens in the library_common controller.
- folder_id = trans.security.encode_id( decoded_parent_id )
- # Now create the desired content object, either file or folder.
- if create_type == 'file':
- status, output = trans.webapp.controllers['library_common'].upload_library_dataset( trans, 'api', library_id, folder_id, **payload )
- elif create_type == 'folder':
- status, output = trans.webapp.controllers['library_common'].create_folder( trans, 'api', folder_id, library_id, **payload )
- if status != 200:
- trans.response.status = status
- # We don't want to reveal the encoded folder_id since it's invalid
- # in the API context. Instead, return the content_id originally
- # supplied by the client.
- output = output.replace( folder_id, content_id )
- return output
- else:
- rval = []
- for k, v in output.items():
- if type( v ) == trans.app.model.LibraryDatasetDatasetAssociation:
- v = v.library_dataset
- encoded_id = trans.security.encode_id( create_type + '.' + str( v.id ) )
- rval.append( dict( id = encoded_id,
- name = v.name,
- url = url_for( 'library_content', library_id=library_id, id=encoded_id ) ) )
- return rval
--- a/lib/galaxy/web/api/histories.py Fri Aug 26 10:39:34 2011 -0400
+++ b/lib/galaxy/web/api/histories.py Fri Aug 26 13:55:08 2011 -0400
@@ -7,9 +7,9 @@
from galaxy.web.base.controller import *
from galaxy.util.sanitize_html import sanitize_html
from galaxy.model.orm import *
-from galaxy.model import Dataset
import galaxy.datatypes
from galaxy.util.bunch import Bunch
+from galaxy.web.api.util import *
log = logging.getLogger( __name__ )
@@ -21,21 +21,26 @@
GET /api/histories
Displays a collection (list) of histories.
"""
+ rval = []
+
try:
query = trans.sa_session.query( trans.app.model.History ).filter_by( user=trans.user, deleted=False ).order_by(
desc(trans.app.model.History.table.c.update_time)).all()
except Exception, e:
- log.debug("Error in history API: %s" % str(e))
+ rval = "Error in history API"
+ log.error( rval + ": %s" % str(e) )
+ trans.response.status = 500
- rval = []
- try:
- for history in query:
- item = history.get_api_value(value_mapper={'id':trans.security.encode_id})
- item['url'] = url_for( 'history', id=trans.security.encode_id( history.id ) )
- # item['id'] = trans.security.encode_id( item['id'] )
- rval.append( item )
- except Exception, e:
- log.debug("Error in history API at constructing return list: %s" % str(e))
+ if not rval:
+ try:
+ for history in query:
+ item = history.get_api_value(value_mapper={'id':trans.security.encode_id})
+ item['url'] = url_for( 'history', id=trans.security.encode_id( history.id ) )
+ rval.append( item )
+ except Exception, e:
+ rval = "Error in history API at constructing return list"
+ log.error( rval + ": %s" % str(e) )
+ trans.response.status = 500
return rval
@web.expose_api
@@ -49,42 +54,29 @@
def traverse( datasets ):
rval = {}
- states = Dataset.states
+ states = trans.app.model.Dataset.states
for key, state in states.items():
rval[state] = 0
- #log.debug("History API: Init rval %s" % rval)
for dataset in datasets:
item = dataset.get_api_value( view='element' )
- #log.debug("History API: Set rval %s" % item['state'])
if not item['deleted']:
rval[item['state']] = rval[item['state']] + 1
- return rval
+ return rval
try:
- decoded_history_id = trans.security.decode_id( history_id )
- except TypeError:
- trans.response.status = 400
- return "Malformed history id ( %s ) specified, unable to decode." % str( history_id )
- try:
- history = trans.sa_session.query(trans.app.model.History).get(decoded_history_id)
- if history.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=history).count() == 0:
- trans.response.status = 400
- return("History is not owned by or shared with current user")
- except:
- trans.response.status = 400
- return "That history does not exist."
+ history = get_history_for_access( trans, history_id )
+ except Exception, e:
+ return str( e )
try:
item = history.get_api_value(view='element', value_mapper={'id':trans.security.encode_id})
num_sets = len( [hda.id for hda in history.datasets if not hda.deleted] )
- states = Dataset.states
+ states = trans.app.model.Dataset.states
state = states.ERROR
- if num_sets == 0:
+ if num_sets == 0:
state = states.NEW
else:
summary = traverse(history.datasets)
- #log.debug("History API: Status summary %s" % summary)
if summary[states.ERROR] > 0 or summary[states.FAILED_METADATA] > 0:
state = states.ERROR
elif summary[states.RUNNING] > 0 or summary[states.SETTING_METADATA] > 0:
@@ -93,13 +85,13 @@
state = states.QUEUED
elif summary[states.OK] == num_sets:
state = states.OK
- #item['user'] = item['user'].username
item['contents_url'] = url_for( 'history_contents', history_id=history_id )
- #item['datasets'] = len( item['datasets'] )
item['state'] = state
- #log.debug("History API: State %s for %d datasets" % (state, num_sets))
+ item['state_details'] = summary
except Exception, e:
- log.debug("Error in history API at showing history detail: %s" % str(e))
+ item = "Error in history API at showing history detail"
+ log.error(item + ": %s" % str(e))
+ trans.response.status = 500
return item
@web.expose_api
@@ -109,13 +101,16 @@
Creates a new history.
"""
params = util.Params( payload )
- hist_name = util.restore_text( params.get( 'name', None ) )
+ hist_name = None
+ if payload.get( 'name', None ):
+ hist_name = util.restore_text( payload['name'] )
new_history = trans.app.model.History( user=trans.user, name=hist_name )
trans.sa_session.add( new_history )
trans.sa_session.flush()
item = new_history.get_api_value(view='element', value_mapper={'id':trans.security.encode_id})
return item
+
@web.expose_api
def delete( self, trans, id, **kwd ):
"""
@@ -123,33 +118,31 @@
Deletes a history
"""
history_id = id
- params = util.Params( kwd )
+ # a request body is optional here
+ purge = False
+ if kwd.get( 'payload', None ):
+ purge = util.string_as_bool( kwd['payload'].get( 'purge', False ) )
try:
- decoded_history_id = trans.security.decode_id( history_id )
- except TypeError:
- trans.response.status = 400
- return "Malformed history id ( %s ) specified, unable to decode." % str( history_id )
- try:
- history = trans.sa_session.query(trans.app.model.History).get(decoded_history_id)
- if history.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=history).count() == 0:
- trans.response.status = 400
- return("History is not owned by or shared with current user")
- except:
- trans.response.status = 400
- return "That history does not exist."
+ history = get_history_for_modification( trans, history_id )
+ except Exception, e:
+ return str( e )
+
history.deleted = True
- # If deleting the current history, make a new current.
- if history == trans.get_history():
- trans.new_history()
- if trans.app.config.allow_user_dataset_purge:
+ if purge and trans.app.config.allow_user_dataset_purge:
for hda in history.datasets:
+ if hda.purged:
+ continue
hda.purged = True
trans.sa_session.add( hda )
+ trans.sa_session.flush()
if hda.dataset.user_can_purge:
try:
hda.dataset.full_delete()
trans.sa_session.add( hda.dataset )
except:
- trans.sa_session.flush()
+ pass
+ trans.sa_session.flush()
+
+ trans.sa_session.flush()
+ return 'OK'
--- a/lib/galaxy/web/api/history_contents.py Fri Aug 26 10:39:34 2011 -0400
+++ b/lib/galaxy/web/api/history_contents.py Fri Aug 26 13:55:08 2011 -0400
@@ -7,6 +7,7 @@
from galaxy.web.base.controller import *
from galaxy.util.sanitize_html import sanitize_html
from galaxy.model.orm import *
+from galaxy.web.api.util import *
import pkg_resources
pkg_resources.require( "Routes" )
@@ -23,63 +24,36 @@
Displays a collection (list) of history contents
"""
try:
- decoded_history_id = trans.security.decode_id( history_id )
- except TypeError:
- trans.response.status = 400
- return "Malformed history id ( %s ) specified, unable to decode." % str( history_id )
- try:
- history = trans.sa_session.query(trans.app.model.History).get(decoded_history_id)
- if history.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=history).count() == 0:
- trans.response.status = 400
- return("History is not owned by or shared with current user")
- except:
- trans.response.status = 400
- return "That history does not exist."
+ history = get_history_for_access( trans, history_id )
+ except Exception, e:
+ return str( e )
rval = []
try:
for dataset in history.datasets:
api_type = "file"
encoded_id = trans.security.encode_id( '%s.%s' % (api_type, dataset.id) )
- #log.debug("History dataset %s" % str(encoded_id))
rval.append( dict( id = encoded_id,
type = api_type,
name = dataset.name,
url = url_for( 'history_content', history_id=history_id, id=encoded_id, ) ) )
except Exception, e:
- log.debug("Error in history API at listing contents: %s" % str(e))
+ rval = "Error in history API at listing contents"
+ log.error( rval + ": %s" % str(e) )
+ trans.response.status = 500
return rval
@web.expose_api
def show( self, trans, id, history_id, **kwd ):
"""
GET /api/histories/{encoded_history_id}/contents/{encoded_content_type_and_id}
- Displays information about a history content dataset.
+ Displays information about a history content (dataset).
"""
- #log.debug("Entering Content API for history dataset with %s" % str(history_id))
try:
- content_id = id
- try:
- decoded_type_and_id = trans.security.decode_string_id( content_id )
- content_type, decoded_content_id = decoded_type_and_id.split( '.' )
- except:
- trans.response.status = 400
- return "Malformed content id ( %s ) specified, unable to decode." % str( content_id )
- if content_type == 'file':
- model_class = trans.app.model.HistoryDatasetAssociation
- else:
- trans.response.status = 400
- return "Invalid type ( %s ) specified." % str( content_type )
- try:
- content = trans.sa_session.query( model_class ).get( decoded_content_id )
- except:
- trans.response.status = 400
- return "Invalid %s id ( %s ) specified." % ( content_type, str( content_id ) )
- if content.history.user != trans.user and not trans.user_is_admin():
- if trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=history).count() == 0:
- trans.response.status = 400
- return("History is not owned by or shared with current user")
+ content = get_history_content_for_access( trans, content_id )
+ except Exception, e:
+ return str( e )
+ try:
item = content.get_api_value( view='element' )
if not item['deleted']:
# Problem: Method url_for cannot use the dataset controller
@@ -87,23 +61,41 @@
url = routes.URLGenerator(trans.webapp.mapper, trans.environ)
# http://routes.groovie.org/generating.html
# url_for is being phased out, so new applications should use url
- item['download_url'] = url(controller='dataset', action='display', dataset_id=trans.security.encode_id(decoded_content_id), to_ext=content.ext)
+ item['download_url'] = url(controller='dataset', action='display', dataset_id=trans.security.encode_id(content.id), to_ext=content.ext)
except Exception, e:
- log.debug("Error in history API at listing dataset: %s" % str(e))
+ item = "Error in history API at listing dataset"
+ log.error( item + ": %s" % str(e) )
+ trans.response.status = 500
return item
@web.expose_api
def create( self, trans, history_id, payload, **kwd ):
"""
POST /api/libraries/{encoded_history_id}/contents
- Creates a new history content item. """
+ Creates a new history content item (file, aka HistoryDatasetAssociation).
+ """
params = util.Params( payload )
- history_id = util.restore_text( params.get( 'history_id', None ) )
- ldda_id = util.restore_text( params.get( 'ldda_id', None ) )
- add_to_history = True
- decoded_history_id = trans.security.decode_id( history_id )
- ld_t, ld_id = trans.security.decode_string_id(ldda_id).split('.')
- history = trans.sa_session.query(trans.app.model.History).get(decoded_history_id)
- ldda = trans.sa_session.query(self.app.model.LibraryDatasetDatasetAssociation).get(ld_id)
- hda = ldda.to_history_dataset_association(history, add_to_history=add_to_history)
- history.add_dataset(hda)
+ from_ld_id = payload.get( 'from_ld_id', None )
+
+ try:
+ history = get_history_for_modification( trans, history_id )
+ except Exception, e:
+ return str( e )
+
+ if from_ld_id:
+ try:
+ ld = get_library_content_for_access( trans, from_ld_id )
+ assert type( ld ) is trans.app.model.LibraryDataset, "Library content id ( %s ) is not a dataset" % from_ld_id
+ except AssertionError, e:
+ trans.response.status = 400
+ return str( e )
+ except Exception, e:
+ return str( e )
+ hda = ld.library_dataset_dataset_association.to_history_dataset_association( history, add_to_history=True )
+ history.add_dataset( hda )
+ trans.sa_session.flush()
+ return hda.get_api_value()
+ else:
+ # TODO: implement other "upload" methods here.
+ trans.response.status = 403
+ return "Not implemented."
--- a/lib/galaxy/web/buildapp.py Fri Aug 26 10:39:34 2011 -0400
+++ b/lib/galaxy/web/buildapp.py Fri Aug 26 13:55:08 2011 -0400
@@ -108,7 +108,7 @@
add_api_controllers( webapp, app )
webapp.api_mapper.resource( 'content',
'contents',
- controller='contents',
+ controller='library_contents',
name_prefix='library_',
path_prefix='/api/libraries/:library_id',
parent_resources=dict( member_name='library', collection_name='libraries' ) )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
More information about the galaxy-commits
mailing list