3 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/d339a00374b9/ Changeset: d339a00374b9 User: martenson Date: 2014-03-05 21:07:42 Summary: library API improvements - DELETE and PATCH; addition of http error codes and exceptions; slight refator; small bugfix Affected #: 4 files diff -r 4cad859d032560a123f57eac78a81f852dc7531e -r d339a00374b9fea91e659256be9db20ed226e9fe lib/galaxy/exceptions/__init__.py --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -31,42 +31,68 @@ pass -class ItemAccessibilityException( MessageException ): - status_code = 403 - err_code = error_codes.USER_CANNOT_ACCESS_ITEM +class ObjectInvalid( Exception ): + """ Accessed object store ID is invalid """ + pass +# Please keep the exceptions ordered by status code -class ItemOwnershipException( MessageException ): - status_code = 403 - err_code = error_codes.USER_DOES_NOT_OWN_ITEM +class ActionInputError( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_INVALID_PARAMETER + def __init__( self, err_msg, type="error" ): + super( ActionInputError, self ).__init__( err_msg, type ) class DuplicatedSlugException( MessageException ): status_code = 400 err_code = error_codes.USER_SLUG_DUPLICATE - class ObjectAttributeInvalidException( MessageException ): status_code = 400 err_code = error_codes.USER_OBJECT_ATTRIBUTE_INVALID - class ObjectAttributeMissingException( MessageException ): status_code = 400 err_code = error_codes.USER_OBJECT_ATTRIBUTE_MISSING +class MalformedId( MessageException ): + status_code = 400 + err_code = error_codes.MALFORMED_ID -class ActionInputError( MessageException ): - def __init__( self, err_msg, type="error" ): - super( ActionInputError, self ).__init__( err_msg, type ) +class RequestParameterMissingException( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_MISSING_PARAMETER +class RequestParameterInvalidException( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_INVALID_PARAMETER + +class ItemAccessibilityException( MessageException ): + status_code = 403 + err_code = error_codes.USER_CANNOT_ACCESS_ITEM + +class ItemOwnershipException( MessageException ): + status_code = 403 + err_code = error_codes.USER_DOES_NOT_OWN_ITEM class ObjectNotFound( MessageException ): """ Accessed object was not found """ status_code = 404 err_code = error_codes.USER_OBJECT_NOT_FOUND +class Conflict( MessageException ): + status_code = 409 + err_code = error_codes.CONFLICT -class ObjectInvalid( Exception ): - """ Accessed object store ID is invalid """ - pass +class InconsistentDatabase ( MessageException ): + status_code = 500 + err_code = error_codes.INCONSISTENT_DATABASE + +class InternalServerError ( MessageException ): + status_code = 500 + err_code = error_codes.INTERNAL_SERVER_ERROR + +class NotImplemented ( MessageException ): + status_code = 501 + err_code = error_codes.NOT_IMPLEMENTED diff -r 4cad859d032560a123f57eac78a81f852dc7531e -r d339a00374b9fea91e659256be9db20ed226e9fe lib/galaxy/exceptions/error_codes.json --- a/lib/galaxy/exceptions/error_codes.json +++ b/lib/galaxy/exceptions/error_codes.json @@ -3,7 +3,7 @@ "name": "UNKNOWN", "code": 0, "message": "Unknown error occurred while processing request." - }, + }, { "name": "USER_CANNOT_RUN_AS", "code": 400001, @@ -35,6 +35,21 @@ "message": "Slug must be unique per user." }, { + "name": "USER_REQUEST_MISSING_PARAMETER", + "code": 400007, + "message": "Request is missing parameter required to complete desired action." + }, + { + "name": "USER_REQUEST_INVALID_PARAMETER", + "code": 400008, + "message": "Request contained invalid parameter, action could not be completed." + }, + { + "name": "MALFORMED_ID", + "code": 400009, + "message": "The id of the resource is malformed." + }, + { "name": "USER_NO_API_KEY", "code": 403001, "message": "API Authentication Required for this request" @@ -53,5 +68,25 @@ "name": "USER_OBJECT_NOT_FOUND", "code": 404001, "message": "No such object found." + }, + { + "name": "CONFLICT", + "code": 409001, + "message": "Database conflict prevented fulfilling the request." + }, + { + "name": "INTERNAL_SERVER_ERROR", + "code": 500001, + "message": "Internal server error." + }, + { + "name": "INCONSISTENT_DATABASE", + "code": 500001, + "message": "Inconsistent database prevented fulfilling the request." + }, + { + "name": "NOT_IMPLEMENTED", + "code": 501001, + "message": "Method is not implemented." } ] diff -r 4cad859d032560a123f57eac78a81f852dc7531e -r d339a00374b9fea91e659256be9db20ed226e9fe lib/galaxy/webapps/galaxy/api/libraries.py --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -2,10 +2,12 @@ API operations on a data library. """ from galaxy import util +from galaxy.util import string_as_bool from galaxy import web +from galaxy import exceptions +from galaxy.web import _future_expose_api as expose_api from galaxy.model.orm import and_, not_, or_ from galaxy.web.base.controller import BaseAPIController, url_for -from paste.httpexceptions import HTTPBadRequest, HTTPForbidden import logging log = logging.getLogger( __name__ ) @@ -13,7 +15,7 @@ class LibrariesController( BaseAPIController ): - @web.expose_api + @expose_api def index( self, trans, deleted='False', **kwd ): """ index( self, trans, deleted='False', **kwd ) @@ -22,11 +24,12 @@ * GET /api/libraries/deleted: Returns a list of summary data for ``deleted`` libraries. - :param deleted: if True, show only deleted libraries, if False, ``non-deleted`` + :param deleted: if True, show only ``deleted`` libraries, if False or nonpresent show only ``non-deleted`` :type deleted: boolean + :returns: list of dictionaries containing library information :rtype: list - :returns: list of dictionaries containing library information + .. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys` """ query = trans.sa_session.query( trans.app.model.Library ) @@ -50,13 +53,13 @@ libraries = [] for library in query: item = library.to_dict( view='element' ) - item['url'] = url_for( route, id=trans.security.encode_id( library.id ) ) - item['id'] = trans.security.encode_id( item['id'] ) - item['root_folder_id'] = 'F' + trans.security.encode_id( item['root_folder_id'] ) + item[ 'url' ] = url_for( route, id=trans.security.encode_id( library.id ) ) + item[ 'id' ] = trans.security.encode_id( item[ 'id' ] ) + item[ 'root_folder_id' ] = 'F' + trans.security.encode_id( item[ 'root_folder_id' ] ) libraries.append( item ) return libraries - @web.expose_api + @expose_api def show( self, trans, id, deleted='False', **kwd ): """ show( self, trans, id, deleted='False', **kwd ) @@ -70,50 +73,50 @@ :param deleted: if True, allow information on a ``deleted`` library :type deleted: boolean + :returns: detailed library information :rtype: dictionary - :returns: detailed library information + .. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys` """ library_id = id deleted = util.string_as_bool( deleted ) try: decoded_library_id = trans.security.decode_id( library_id ) - except TypeError: - raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id ) + except: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_library_id ) assert library.deleted == deleted except: library = None if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ): - raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id ) - item = library.to_dict( view='element' ) - item['contents_url'] = url_for( 'library_contents', library_id=library_id ) - return item + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) - @web.expose_api + @expose_api def create( self, trans, payload, **kwd ): """ create( self, trans, payload, **kwd ) * POST /api/libraries: Creates a new library. Only ``name`` parameter is required. + .. note:: Currently, only admin users can create libraries. - :param payload: (optional) dictionary structure containing:: - 'name': the new library's name - 'description': the new library's description - 'synopsis': the new library's synopsis + :param payload: dictionary structure containing:: + 'name': the new library's name (required) + 'description': the new library's description (optional) + 'synopsis': the new library's synopsis (optional) :type payload: dict + :returns: detailed library information :rtype: dict - :returns: a dictionary containing the encoded_id, name, description, synopsis, and 'show' url of the new library """ if not trans.user_is_admin(): - raise HTTPForbidden( detail='You are not authorized to create a new library.' ) + raise exceptions.ItemAccessibilityException( 'Only administrators can create libraries.' ) params = util.Params( payload ) name = util.restore_text( params.get( 'name', None ) ) if not name: - raise HTTPBadRequest( detail="Missing required parameter 'name'." ) + raise exceptions.RequestParameterMissingException( "Missing required parameter 'name'." ) description = util.restore_text( params.get( 'description', '' ) ) synopsis = util.restore_text( params.get( 'synopsis', '' ) ) if synopsis in [ 'None', None ]: @@ -123,63 +126,95 @@ library.root_folder = root_folder trans.sa_session.add_all( ( library, root_folder ) ) trans.sa_session.flush() - encoded_id = trans.security.encode_id( library.id ) - new_library = {} - new_library['url'] = url_for( 'library', id=encoded_id ) - new_library['name'] = name - new_library['description'] = description - new_library['synopsis'] = synopsis - new_library['id'] = encoded_id - new_library['root_folder_id'] = trans.security.encode_id( root_folder.id ) - return new_library + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) - def edit( self, trans, encoded_id, payload, **kwd ): + @expose_api + def update( self, trans, id, **kwd ): """ - * PUT /api/libraries/{encoded_id} + * PATCH /api/libraries/{encoded_id} Updates the library defined by an ``encoded_id`` with the data in the payload. - .. note:: Currently, only admin users can edit libraries. + .. note:: Currently, only admin users can update libraries. :param payload: (required) dictionary structure containing:: - 'name': new library's name + 'name': new library's name, cannot be empty 'description': new library's description 'synopsis': new library's synopsis :type payload: dict + :returns: detailed library information :rtype: dict - :returns: a dictionary containing the encoded_id, name, description, synopsis, and 'show' url of the updated library - """ - return "Not implemented yet" - - @web.expose_api - def delete( self, trans, id, **kwd ): - """ - delete( self, trans, id, **kwd ) - * DELETE /api/histories/{id} - mark the library with the given ``id`` as deleted - - .. note:: Currently, only admin users can delete libraries. - - :param id: the encoded id of the library to delete - :type id: str - - :rtype: dictionary - :returns: detailed library information - .. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys` """ if not trans.user_is_admin(): - raise HTTPForbidden( detail='You are not authorized to delete libraries.' ) + raise exceptions.ItemAccessibilityException( 'Only administrators can update libraries.' ) + try: decoded_id = trans.security.decode_id( id ) - except TypeError: - raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id ) + except: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) + library = None try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) except: library = None if not library: - raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id ) - library.deleted = True + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + + payload = kwd.get( 'payload', None ) + if payload: + name = payload.get( 'name', None ) + if name == '': + raise exceptions.RequestParameterMissingException( "Parameter 'name' of library is required. You cannot remove it." ) + library.name = name + if payload.get( 'description', None ) or payload.get( 'description', None ) == '': + library.description = payload.get( 'description', None ) + if payload.get( 'synopsis', None ) or payload.get( 'synopsis', None ) == '': + library.synopsis = payload.get( 'synopsis', None ) + else: + raise exceptions.RequestParameterMissingException( "You did not specify any payload." ) trans.sa_session.add( library ) trans.sa_session.flush() - return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id } ) + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) + + @expose_api + def delete( self, trans, id, **kwd ): + """ + delete( self, trans, id, **kwd ) + * DELETE /api/libraries/{id} + marks the library with the given ``id`` as `deleted` (or removes the `deleted` mark if the `undelete` param is true) + + .. note:: Currently, only admin users can un/delete libraries. + + :param id: the encoded id of the library to un/delete + :type id: str + + :param undelete: flag specifying whether the item should be deleted or undeleted, defaults to false: + :type undelete: bool + + :returns: detailed library information + :rtype: dictionary + + .. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys` + """ + undelete = string_as_bool( kwd.get( 'undelete', False ) ) + if not trans.user_is_admin(): + raise exceptions.ItemAccessibilityException( 'Only administrators can delete and undelete libraries.' ) + try: + decoded_id = trans.security.decode_id( id ) + except: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) + try: + library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) + except: + library = None + if not library: + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + + if undelete: + library.deleted = False + else: + library.deleted = True + + trans.sa_session.add( library ) + trans.sa_session.flush() + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) diff -r 4cad859d032560a123f57eac78a81f852dc7531e -r d339a00374b9fea91e659256be9db20ed226e9fe lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -135,7 +135,6 @@ path_prefix='/api/histories/:history_id/contents/:history_content_id' ) webapp.mapper.resource( 'dataset', 'datasets', path_prefix='/api' ) - webapp.mapper.resource_with_deleted( 'library', 'libraries', path_prefix='/api' ) webapp.mapper.resource( 'sample', 'samples', path_prefix='/api' ) webapp.mapper.resource( 'request', 'requests', path_prefix='/api' ) webapp.mapper.resource( 'form', 'forms', path_prefix='/api' ) @@ -206,6 +205,18 @@ # ===== LIBRARY API ===== # ======================= + webapp.mapper.connect( 'delete_library', + '/api/libraries/:id', + controller='libraries', + action='delete', + conditions=dict( method=[ "DELETE" ] ) ) + + webapp.mapper.connect( 'update_library', + '/api/libraries/:id', + controller='libraries', + action='update', + conditions=dict( method=[ "PATCH" ] ) ) + webapp.mapper.connect( 'show_lda_item', '/api/libraries/datasets/:id', controller='lda_datasets', https://bitbucket.org/galaxy/galaxy-central/commits/03dd71b129e5/ Changeset: 03dd71b129e5 User: martenson Date: 2014-03-06 16:49:36 Summary: API libraries exceptions improvement, unnecessary endpoint removal Affected #: 3 files diff -r d339a00374b9fea91e659256be9db20ed226e9fe -r 03dd71b129e5dd19b0687ef56f75a270816433b0 lib/galaxy/exceptions/error_codes.json --- a/lib/galaxy/exceptions/error_codes.json +++ b/lib/galaxy/exceptions/error_codes.json @@ -81,7 +81,7 @@ }, { "name": "INCONSISTENT_DATABASE", - "code": 500001, + "code": 500002, "message": "Inconsistent database prevented fulfilling the request." }, { diff -r d339a00374b9fea91e659256be9db20ed226e9fe -r 03dd71b129e5dd19b0687ef56f75a270816433b0 lib/galaxy/webapps/galaxy/api/libraries.py --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -82,12 +82,12 @@ deleted = util.string_as_bool( deleted ) try: decoded_library_id = trans.security.decode_id( library_id ) - except: + except Exception: raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_library_id ) assert library.deleted == deleted - except: + except Exception: library = None if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ): raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) @@ -150,12 +150,12 @@ try: decoded_id = trans.security.decode_id( id ) - except: + except Exception: raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) library = None try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) - except: + except Exception: library = None if not library: raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) @@ -201,11 +201,11 @@ raise exceptions.ItemAccessibilityException( 'Only administrators can delete and undelete libraries.' ) try: decoded_id = trans.security.decode_id( id ) - except: + except Exception: raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) - except: + except Exception: library = None if not library: raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) diff -r d339a00374b9fea91e659256be9db20ed226e9fe -r 03dd71b129e5dd19b0687ef56f75a270816433b0 lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -205,12 +205,6 @@ # ===== LIBRARY API ===== # ======================= - webapp.mapper.connect( 'delete_library', - '/api/libraries/:id', - controller='libraries', - action='delete', - conditions=dict( method=[ "DELETE" ] ) ) - webapp.mapper.connect( 'update_library', '/api/libraries/:id', controller='libraries', https://bitbucket.org/galaxy/galaxy-central/commits/8093e5f39efb/ Changeset: 8093e5f39efb User: martenson Date: 2014-03-06 17:07:40 Summary: Merged in martenson/galaxy-central-marten (pull request #344) library API improvements - DELETE and PATCH; addition to John's http error codes and exceptions; slight refator; small bugfix Affected #: 4 files diff -r 52948dd567c86e5c0ce83ff0c5e80d3fe175c8a1 -r 8093e5f39efb831da042a20fa2bb850470355d7a lib/galaxy/exceptions/__init__.py --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -31,42 +31,68 @@ pass -class ItemAccessibilityException( MessageException ): - status_code = 403 - err_code = error_codes.USER_CANNOT_ACCESS_ITEM +class ObjectInvalid( Exception ): + """ Accessed object store ID is invalid """ + pass +# Please keep the exceptions ordered by status code -class ItemOwnershipException( MessageException ): - status_code = 403 - err_code = error_codes.USER_DOES_NOT_OWN_ITEM +class ActionInputError( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_INVALID_PARAMETER + def __init__( self, err_msg, type="error" ): + super( ActionInputError, self ).__init__( err_msg, type ) class DuplicatedSlugException( MessageException ): status_code = 400 err_code = error_codes.USER_SLUG_DUPLICATE - class ObjectAttributeInvalidException( MessageException ): status_code = 400 err_code = error_codes.USER_OBJECT_ATTRIBUTE_INVALID - class ObjectAttributeMissingException( MessageException ): status_code = 400 err_code = error_codes.USER_OBJECT_ATTRIBUTE_MISSING +class MalformedId( MessageException ): + status_code = 400 + err_code = error_codes.MALFORMED_ID -class ActionInputError( MessageException ): - def __init__( self, err_msg, type="error" ): - super( ActionInputError, self ).__init__( err_msg, type ) +class RequestParameterMissingException( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_MISSING_PARAMETER +class RequestParameterInvalidException( MessageException ): + status_code = 400 + err_code = error_codes.USER_REQUEST_INVALID_PARAMETER + +class ItemAccessibilityException( MessageException ): + status_code = 403 + err_code = error_codes.USER_CANNOT_ACCESS_ITEM + +class ItemOwnershipException( MessageException ): + status_code = 403 + err_code = error_codes.USER_DOES_NOT_OWN_ITEM class ObjectNotFound( MessageException ): """ Accessed object was not found """ status_code = 404 err_code = error_codes.USER_OBJECT_NOT_FOUND +class Conflict( MessageException ): + status_code = 409 + err_code = error_codes.CONFLICT -class ObjectInvalid( Exception ): - """ Accessed object store ID is invalid """ - pass +class InconsistentDatabase ( MessageException ): + status_code = 500 + err_code = error_codes.INCONSISTENT_DATABASE + +class InternalServerError ( MessageException ): + status_code = 500 + err_code = error_codes.INTERNAL_SERVER_ERROR + +class NotImplemented ( MessageException ): + status_code = 501 + err_code = error_codes.NOT_IMPLEMENTED diff -r 52948dd567c86e5c0ce83ff0c5e80d3fe175c8a1 -r 8093e5f39efb831da042a20fa2bb850470355d7a lib/galaxy/exceptions/error_codes.json --- a/lib/galaxy/exceptions/error_codes.json +++ b/lib/galaxy/exceptions/error_codes.json @@ -3,7 +3,7 @@ "name": "UNKNOWN", "code": 0, "message": "Unknown error occurred while processing request." - }, + }, { "name": "USER_CANNOT_RUN_AS", "code": 400001, @@ -35,6 +35,21 @@ "message": "Slug must be unique per user." }, { + "name": "USER_REQUEST_MISSING_PARAMETER", + "code": 400007, + "message": "Request is missing parameter required to complete desired action." + }, + { + "name": "USER_REQUEST_INVALID_PARAMETER", + "code": 400008, + "message": "Request contained invalid parameter, action could not be completed." + }, + { + "name": "MALFORMED_ID", + "code": 400009, + "message": "The id of the resource is malformed." + }, + { "name": "USER_NO_API_KEY", "code": 403001, "message": "API Authentication Required for this request" @@ -53,5 +68,25 @@ "name": "USER_OBJECT_NOT_FOUND", "code": 404001, "message": "No such object found." + }, + { + "name": "CONFLICT", + "code": 409001, + "message": "Database conflict prevented fulfilling the request." + }, + { + "name": "INTERNAL_SERVER_ERROR", + "code": 500001, + "message": "Internal server error." + }, + { + "name": "INCONSISTENT_DATABASE", + "code": 500002, + "message": "Inconsistent database prevented fulfilling the request." + }, + { + "name": "NOT_IMPLEMENTED", + "code": 501001, + "message": "Method is not implemented." } ] diff -r 52948dd567c86e5c0ce83ff0c5e80d3fe175c8a1 -r 8093e5f39efb831da042a20fa2bb850470355d7a lib/galaxy/webapps/galaxy/api/libraries.py --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -2,10 +2,12 @@ API operations on a data library. """ from galaxy import util +from galaxy.util import string_as_bool from galaxy import web +from galaxy import exceptions +from galaxy.web import _future_expose_api as expose_api from galaxy.model.orm import and_, not_, or_ from galaxy.web.base.controller import BaseAPIController, url_for -from paste.httpexceptions import HTTPBadRequest, HTTPForbidden import logging log = logging.getLogger( __name__ ) @@ -13,7 +15,7 @@ class LibrariesController( BaseAPIController ): - @web.expose_api + @expose_api def index( self, trans, deleted='False', **kwd ): """ index( self, trans, deleted='False', **kwd ) @@ -22,11 +24,12 @@ * GET /api/libraries/deleted: Returns a list of summary data for ``deleted`` libraries. - :param deleted: if True, show only deleted libraries, if False, ``non-deleted`` + :param deleted: if True, show only ``deleted`` libraries, if False or nonpresent show only ``non-deleted`` :type deleted: boolean + :returns: list of dictionaries containing library information :rtype: list - :returns: list of dictionaries containing library information + .. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys` """ query = trans.sa_session.query( trans.app.model.Library ) @@ -50,13 +53,13 @@ libraries = [] for library in query: item = library.to_dict( view='element' ) - item['url'] = url_for( route, id=trans.security.encode_id( library.id ) ) - item['id'] = trans.security.encode_id( item['id'] ) - item['root_folder_id'] = 'F' + trans.security.encode_id( item['root_folder_id'] ) + item[ 'url' ] = url_for( route, id=trans.security.encode_id( library.id ) ) + item[ 'id' ] = trans.security.encode_id( item[ 'id' ] ) + item[ 'root_folder_id' ] = 'F' + trans.security.encode_id( item[ 'root_folder_id' ] ) libraries.append( item ) return libraries - @web.expose_api + @expose_api def show( self, trans, id, deleted='False', **kwd ): """ show( self, trans, id, deleted='False', **kwd ) @@ -70,50 +73,50 @@ :param deleted: if True, allow information on a ``deleted`` library :type deleted: boolean + :returns: detailed library information :rtype: dictionary - :returns: detailed library information + .. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys` """ library_id = id deleted = util.string_as_bool( deleted ) try: decoded_library_id = trans.security.decode_id( library_id ) - except TypeError: - raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id ) + except Exception: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_library_id ) assert library.deleted == deleted - except: + except Exception: library = None if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ): - raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id ) - item = library.to_dict( view='element' ) - item['contents_url'] = url_for( 'library_contents', library_id=library_id ) - return item + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) - @web.expose_api + @expose_api def create( self, trans, payload, **kwd ): """ create( self, trans, payload, **kwd ) * POST /api/libraries: Creates a new library. Only ``name`` parameter is required. + .. note:: Currently, only admin users can create libraries. - :param payload: (optional) dictionary structure containing:: - 'name': the new library's name - 'description': the new library's description - 'synopsis': the new library's synopsis + :param payload: dictionary structure containing:: + 'name': the new library's name (required) + 'description': the new library's description (optional) + 'synopsis': the new library's synopsis (optional) :type payload: dict + :returns: detailed library information :rtype: dict - :returns: a dictionary containing the encoded_id, name, description, synopsis, and 'show' url of the new library """ if not trans.user_is_admin(): - raise HTTPForbidden( detail='You are not authorized to create a new library.' ) + raise exceptions.ItemAccessibilityException( 'Only administrators can create libraries.' ) params = util.Params( payload ) name = util.restore_text( params.get( 'name', None ) ) if not name: - raise HTTPBadRequest( detail="Missing required parameter 'name'." ) + raise exceptions.RequestParameterMissingException( "Missing required parameter 'name'." ) description = util.restore_text( params.get( 'description', '' ) ) synopsis = util.restore_text( params.get( 'synopsis', '' ) ) if synopsis in [ 'None', None ]: @@ -123,63 +126,95 @@ library.root_folder = root_folder trans.sa_session.add_all( ( library, root_folder ) ) trans.sa_session.flush() - encoded_id = trans.security.encode_id( library.id ) - new_library = {} - new_library['url'] = url_for( 'library', id=encoded_id ) - new_library['name'] = name - new_library['description'] = description - new_library['synopsis'] = synopsis - new_library['id'] = encoded_id - new_library['root_folder_id'] = trans.security.encode_id( root_folder.id ) - return new_library + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) - def edit( self, trans, encoded_id, payload, **kwd ): + @expose_api + def update( self, trans, id, **kwd ): """ - * PUT /api/libraries/{encoded_id} + * PATCH /api/libraries/{encoded_id} Updates the library defined by an ``encoded_id`` with the data in the payload. - .. note:: Currently, only admin users can edit libraries. + .. note:: Currently, only admin users can update libraries. :param payload: (required) dictionary structure containing:: - 'name': new library's name + 'name': new library's name, cannot be empty 'description': new library's description 'synopsis': new library's synopsis :type payload: dict + :returns: detailed library information :rtype: dict - :returns: a dictionary containing the encoded_id, name, description, synopsis, and 'show' url of the updated library """ - return "Not implemented yet" + if not trans.user_is_admin(): + raise exceptions.ItemAccessibilityException( 'Only administrators can update libraries.' ) - @web.expose_api + try: + decoded_id = trans.security.decode_id( id ) + except Exception: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) + library = None + try: + library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) + except Exception: + library = None + if not library: + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + + payload = kwd.get( 'payload', None ) + if payload: + name = payload.get( 'name', None ) + if name == '': + raise exceptions.RequestParameterMissingException( "Parameter 'name' of library is required. You cannot remove it." ) + library.name = name + if payload.get( 'description', None ) or payload.get( 'description', None ) == '': + library.description = payload.get( 'description', None ) + if payload.get( 'synopsis', None ) or payload.get( 'synopsis', None ) == '': + library.synopsis = payload.get( 'synopsis', None ) + else: + raise exceptions.RequestParameterMissingException( "You did not specify any payload." ) + trans.sa_session.add( library ) + trans.sa_session.flush() + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) + + @expose_api def delete( self, trans, id, **kwd ): """ delete( self, trans, id, **kwd ) - * DELETE /api/histories/{id} - mark the library with the given ``id`` as deleted + * DELETE /api/libraries/{id} + marks the library with the given ``id`` as `deleted` (or removes the `deleted` mark if the `undelete` param is true) - .. note:: Currently, only admin users can delete libraries. + .. note:: Currently, only admin users can un/delete libraries. - :param id: the encoded id of the library to delete + :param id: the encoded id of the library to un/delete :type id: str + :param undelete: flag specifying whether the item should be deleted or undeleted, defaults to false: + :type undelete: bool + + :returns: detailed library information :rtype: dictionary - :returns: detailed library information + .. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys` """ + undelete = string_as_bool( kwd.get( 'undelete', False ) ) if not trans.user_is_admin(): - raise HTTPForbidden( detail='You are not authorized to delete libraries.' ) + raise exceptions.ItemAccessibilityException( 'Only administrators can delete and undelete libraries.' ) try: decoded_id = trans.security.decode_id( id ) - except TypeError: - raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id ) + except Exception: + raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id ) - except: + except Exception: library = None if not library: - raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id ) - library.deleted = True + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + + if undelete: + library.deleted = False + else: + library.deleted = True + trans.sa_session.add( library ) trans.sa_session.flush() - return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id } ) + return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) diff -r 52948dd567c86e5c0ce83ff0c5e80d3fe175c8a1 -r 8093e5f39efb831da042a20fa2bb850470355d7a lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -135,7 +135,6 @@ path_prefix='/api/histories/:history_id/contents/:history_content_id' ) webapp.mapper.resource( 'dataset', 'datasets', path_prefix='/api' ) - webapp.mapper.resource_with_deleted( 'library', 'libraries', path_prefix='/api' ) webapp.mapper.resource( 'sample', 'samples', path_prefix='/api' ) webapp.mapper.resource( 'request', 'requests', path_prefix='/api' ) webapp.mapper.resource( 'form', 'forms', path_prefix='/api' ) @@ -206,6 +205,12 @@ # ===== LIBRARY API ===== # ======================= + webapp.mapper.connect( 'update_library', + '/api/libraries/:id', + controller='libraries', + action='update', + conditions=dict( method=[ "PATCH" ] ) ) + webapp.mapper.connect( 'show_lda_item', '/api/libraries/datasets/:id', controller='lda_datasets', 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.