diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py index 3258e236a57..762cb4b9c91 100644 --- a/lib/galaxy/managers/base.py +++ b/lib/galaxy/managers/base.py @@ -29,6 +29,9 @@ import pkg_resources pkg_resources.require( "SQLAlchemy >= 0.4" ) import sqlalchemy +pkg_resources.require("Routes") +import routes + from galaxy import exceptions from galaxy import model from galaxy import web @@ -490,15 +493,17 @@ class ModelSerializer( object ): that should be called for those keys. E.g. { 'x' : lambda trans, item, key: item.x, ... } + Note: if a key to serialize is not listed in the Serializer.serializable_keyset + or serializers, it will not be returned. + To serialize call: my_serializer = MySerializer( app ) ... keys_to_serialize = [ 'id', 'name', 'attr1', 'attr2', ... ] item_dict = MySerializer.serialize( trans, my_item, keys_to_serialize ) - # if a key to serialize is not listed in the Serializer.serializable_keys or serializers, it will not be added """ #: 'service' to use for getting urls - use class var to allow overriding when testing - url_for = web.url_for + #url_service = def __init__( self, app ): """ @@ -506,14 +511,17 @@ class ModelSerializer( object ): """ self.app = app - # a map of dictionary keys to the functions (often lambdas) that create the values for those keys - self.serializers = {} # a list of valid serializable keys that can use the default (string) serializer # this allows us to: 'mention' the key without adding the default serializer - # NOTE: if a key is requested that is in neither serializable_keys or serializers, it is not returned - self.serializable_keys = [] + # TODO: we may want to eventually error if a key is requested + # that is in neither serializable_keyset or serializers + self.serializable_keyset = set([]) + # a map of dictionary keys to the functions (often lambdas) that create the values for those keys + self.serializers = {} # add subclass serializers defined there self.add_serializers() + # update the keyset by the serializers (removing the responsibility from subclasses) + self.serializable_keyset.update( self.serializers.keys() ) # views are collections of serializable attributes (a named array of keys) # inspired by model.dict_{view}_visible_keys @@ -528,6 +536,19 @@ class ModelSerializer( object ): # to be overridden in subclasses pass + def add_view( self, view_name, key_list, include_keys_from=None ): + """ + Add the list of serializable attributes `key_list` to the serializer's + view dictionary under the key `view_name`. + + If `include_keys_from` is a proper view name, extend `key_list` by + the list in that view. + """ + key_list += self.views.get( include_keys_from, [] ) + self.views[ view_name ] = key_list + self.serializable_keyset.update( key_list ) + return key_list + def serialize( self, trans, item, keys ): """ Serialize the model `item` to a dictionary. @@ -542,7 +563,7 @@ class ModelSerializer( object ): # check both serializers and serializable keys if key in self.serializers: returned[ key ] = self.serializers[ key ]( trans, item, key ) - elif key in self.serializable_keys: + elif key in self.serializable_keyset: returned[ key ] = self.default_serializer( trans, item, key ) # ignore bad/unreg keys return returned @@ -569,6 +590,10 @@ class ModelSerializer( object ): id = getattr( item, key ) return self.app.security.encode_id( id ) if id is not None else None + @staticmethod + def url_for( *args, **kwargs ): + return routes.url_for( *args, **kwargs ) + # serializing to a view where a view is a predefied list of keys to serialize def serialize_to_view( self, trans, item, view=None, keys=None, default_view=None ): """ @@ -628,7 +653,7 @@ class ModelDeserializer( object ): self.app = app self.deserializers = {} - self.deserializable_keys = [] + self.deserializable_keyset = set([]) self.add_deserializers() # a sub object that can validate incoming values self.validate = ModelValidator( self.app ) diff --git a/lib/galaxy/managers/datasets.py b/lib/galaxy/managers/datasets.py index 35c642d0996..1df9d2d0683 100644 --- a/lib/galaxy/managers/datasets.py +++ b/lib/galaxy/managers/datasets.py @@ -192,8 +192,8 @@ class DatasetSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin def __init__( self, app ): super( DatasetSerializer, self ).__init__( app ) - # most of these views build/add to the previous view - summary_view = [ + self.default_view = 'summary' + self.add_view( 'summary', [ 'id', 'create_time', 'update_time', 'state', @@ -203,19 +203,13 @@ class DatasetSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin #'extra_files_path', 'file_size', 'total_size', 'uuid', - ] + ]) # could do visualizations and/or display_apps - self.serializable_keys = summary_view + [ - ] - self.views = { - 'summary' : summary_view, - } - self.default_view = 'summary' - def add_serializers( self ): super( DatasetSerializer, self ).add_serializers() deletable.PurgableSerializerMixin.add_serializers( self ) + self.serializers.update({ 'id' : self.serialize_id, 'create_time' : self.serialize_date, diff --git a/lib/galaxy/managers/deletable.py b/lib/galaxy/managers/deletable.py index eb7e4fa8786..4c17132ef70 100644 --- a/lib/galaxy/managers/deletable.py +++ b/lib/galaxy/managers/deletable.py @@ -35,7 +35,7 @@ class DeletableManagerMixin( object ): class DeletableSerializerMixin( object ): def add_serializers( self ): - pass + self.serializable_keyset.add( 'deleted' ) # TODO: these are of questionable value if we don't want to enable users to delete/purge via update @@ -87,6 +87,7 @@ class PurgableSerializerMixin( DeletableSerializerMixin ): def add_serializers( self ): DeletableSerializerMixin.add_serializers( self ) + self.serializable_keyset.add( 'purged' ) class PurgableDeserializerMixin( DeletableDeserializerMixin ): diff --git a/lib/galaxy/managers/hdas.py b/lib/galaxy/managers/hdas.py index 0eaa05dc63e..483cc77c042 100644 --- a/lib/galaxy/managers/hdas.py +++ b/lib/galaxy/managers/hdas.py @@ -315,8 +315,8 @@ class HDASerializer( datasets.DatasetAssociationSerializer, super( HDASerializer, self ).__init__( app ) self.hda_manager = HDAManager( app ) - # most of these views build/add to the previous view - summary_view = [ + self.default_view = 'summary' + self.add_view( 'summary', [ 'id', 'name', 'history_id', 'hid', # why include if model_class is there? @@ -325,12 +325,8 @@ class HDASerializer( datasets.DatasetAssociationSerializer, 'state', 'extension', 'deleted', 'purged', 'visible', 'resubmitted', 'type', 'url' - ] - inaccessible = [ - 'id', 'name', 'history_id', 'hid', 'history_content_type', - 'state', 'deleted', 'visible' - ] - detailed_view = summary_view + [ + ]) + self.add_view( 'detailed', [ 'model_class', 'history_id', 'hid', # why include if model_class is there? @@ -362,19 +358,17 @@ class HDASerializer( datasets.DatasetAssociationSerializer, 'annotation', 'tags', 'api_type' - ] - extended_view = detailed_view + [ - 'tool_version', 'parent_id', 'designation', - ] + ], include_keys_from='summary' ) - self.serializable_keys = extended_view + [ - ] - self.views = { - 'summary' : summary_view, - 'detailed' : detailed_view, - 'extended' : extended_view, - } - self.default_view = 'summary' + self.add_view( 'extended', [ + 'tool_version', 'parent_id', 'designation', + ], include_keys_from='detailed' ) + + # keyset returned to create show a dataset where the owner has no access + self.add_view( 'inaccessible', [ + 'id', 'name', 'history_id', 'hid', 'history_content_type', + 'state', 'deleted', 'visible' + ]) def add_serializers( self ): super( HDASerializer, self ).add_serializers() @@ -597,4 +591,4 @@ class HDADeserializer( datasets.DatasetAssociationDeserializer, 'importable' : self.deserialize_bool, }) - self.deserializable_keys.extend( self.deserializers.keys() ) + self.deserializable_keyset.update( self.deserializers.keys() ) diff --git a/lib/galaxy/managers/histories.py b/lib/galaxy/managers/histories.py index 4e53e1010ab..6e1278dc4f6 100644 --- a/lib/galaxy/managers/histories.py +++ b/lib/galaxy/managers/histories.py @@ -249,12 +249,13 @@ class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSer def __init__( self, app ): super( HistorySerializer, self ).__init__( app ) - self.history_manager = HistoryManager( app ) + self.history_manager = HistoryManager( app ) self.hda_manager = hdas.HDAManager( app ) self.hda_serializer = hdas.HDASerializer( app ) - summary_view = [ + self.default_view = 'summary' + self.add_view( 'summary', [ 'id', 'model_class', 'name', @@ -266,9 +267,8 @@ class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSer 'published', 'annotation', 'tags', - ] - # in the Historys' case, each of these views includes the keys from the previous - detailed_view = summary_view + [ + ]) + self.add_view( 'detailed', [ 'contents_url', #'hdas', 'empty', @@ -281,16 +281,8 @@ class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSer 'state', 'state_details', 'state_ids', - ] - extended_view = detailed_view + [ - ] - self.serializable_keys = extended_view + [] - self.views = { - 'summary' : summary_view, - 'detailed' : detailed_view, - 'extended' : extended_view, - } - self.default_view = 'summary' + # in the Historys' case, each of these views includes the keys from the previous + ], include_keys_from='summary' ) #assumes: outgoing to json.dumps and sanitized def add_serializers( self ): @@ -300,19 +292,19 @@ class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSer self.serializers.update({ 'model_class' : lambda *a: 'History', 'id' : self.serialize_id, - 'count' : lambda trans, item, key: len( item.datasets ), 'create_time' : self.serialize_date, 'update_time' : self.serialize_date, 'size' : lambda t, i, k: int( i.get_disk_size() ), 'nice_size' : lambda t, i, k: i.get_disk_size( nice_size=True ), + 'state' : lambda t, i, k: self.history_manager.get_history_state( t, i ), 'url' : lambda t, i, k: self.url_for( 'history', id=t.security.encode_id( i.id ) ), 'contents_url' : lambda t, i, k: self.url_for( 'history_contents', history_id=t.security.encode_id( i.id ) ), - 'empty' : lambda t, i, k: len( i.datasets ) <= 0, + 'empty' : lambda t, i, k: len( i.datasets ) <= 0, + 'count' : lambda trans, item, key: len( item.datasets ), 'hdas' : lambda t, i, k: [ t.security.encode_id( hda.id ) for hda in i.datasets ], - 'state' : lambda t, i, k: self.history_manager.get_history_state( t, i ), 'state_details' : lambda t, i, k: self.history_manager.get_state_counts( t, i ), 'state_ids' : lambda t, i, k: self.history_manager.get_state_ids( t, i ), 'contents' : self.serialize_contents diff --git a/lib/galaxy/managers/pages.py b/lib/galaxy/managers/pages.py index 4698a4306ba..373007f581e 100644 --- a/lib/galaxy/managers/pages.py +++ b/lib/galaxy/managers/pages.py @@ -46,17 +46,9 @@ class PageSerializer( sharable.SharableModelSerializer ): super( PageSerializer, self ).__init__( app ) self.page_manager = PageManager( app ) - summary_view = [ - ] - # in the Pages' case, each of these views includes the keys from the previous - detailed_view = summary_view + [ - ] - self.serializable_keys = detailed_view + [] - self.views = { - 'summary': summary_view, - 'detailed': detailed_view - } self.default_view = 'summary' + self.add_view( 'summary', [] ) + self.add_view( 'detailed', [] ) def add_serializers( self ): super( PageSerializer, self ).add_serializers() @@ -79,4 +71,4 @@ class PageDeserializer( sharable.SharableModelDeserializer ): super( PageDeserializer, self ).add_deserializers() self.deserializers.update({ }) - self.deserializable_keys = self.deserializers.keys() + self.deserializable_keyset.update( self.deserializers.keys() ) diff --git a/lib/galaxy/managers/sharable.py b/lib/galaxy/managers/sharable.py index eb56a6e033a..96b1cc68117 100644 --- a/lib/galaxy/managers/sharable.py +++ b/lib/galaxy/managers/sharable.py @@ -319,7 +319,7 @@ class SharableModelSerializer( base.ModelSerializer, 'user_id' : self.serialize_id, 'username_and_slug' : self.serialize_username_and_slug }) - self.serializable_keys.extend([ + self.serializable_keyset.update([ 'importable', 'published', 'slug' ]) diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 87383d62df8..7f09551bf28 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -202,28 +202,26 @@ class UserSerializer( base.ModelSerializer ): """ super( UserSerializer, self ).__init__() - summary_view = [ - 'id', 'email', 'username' - ] - # in the Historys' case, each of these views includes the keys from the previous - detailed_view = summary_view + [ - 'update_time', 'create_time', - 'total_disk_usage', 'nice_total_disk_usage', - 'deleted', 'purged', - 'active' - ] - extended_view = detailed_view + [ - #'preferences', - #'tags', # all tags - #'annotations' # all annotations - ] - self.serializable_keys = extended_view - self.views = { - 'summary' : summary_view, - 'detailed' : detailed_view, - 'extended' : extended_view, - } self.default_view = 'summary' + self.add_view( 'summary', [ + 'id', 'email', 'username' + ]) + self.add_view( 'detailed', [ + 'update_time', + 'create_time', + 'total_disk_usage', + 'nice_total_disk_usage', + 'deleted', + 'purged', + 'active' + ], include_keys_from='summary' ) + #self.add_view( 'summary', [ + # 'preferences', + # # all tags + # 'tags', + # # all annotations + # 'annotations' + #], include_keys_from='detailed' ) def add_serializers( self ): self.serializers.update({ diff --git a/lib/galaxy/managers/visualizations.py b/lib/galaxy/managers/visualizations.py index ad2f39133d1..7bd6934b785 100644 --- a/lib/galaxy/managers/visualizations.py +++ b/lib/galaxy/managers/visualizations.py @@ -48,17 +48,9 @@ class VisualizationSerializer( sharable.SharableModelSerializer ): super( VisualizationSerializer, self ).__init__( app ) self.visualizations_manager = VisualizationManager( app ) - summary_view = [ - ] - # in the Visualizations' case, each of these views includes the keys from the previous - detailed_view = summary_view + [ - ] - self.serializable_keys = detailed_view + [] - self.views = { - 'summary' : summary_view, - 'detailed' : detailed_view - } self.default_view = 'summary' + self.add_view( 'summary', [] ) + self.add_view( 'detailed', [] ) def add_serializers( self ): super( VisualizationSerializer, self ).add_serializers() @@ -81,4 +73,4 @@ class VisualizationDeserializer( sharable.SharableModelDeserializer ): super( VisualizationDeserializer, self ).add_deserializers() self.deserializers.update({ }) - self.deserializable_keys = self.deserializers.keys() + self.deserializable_keyset.update( self.deserializers.keys() ) diff --git a/test/unit/managers/test_HistoryManager.py b/test/unit/managers/test_HistoryManager.py index cf1d473781a..81d1cfd6a34 100644 --- a/test/unit/managers/test_HistoryManager.py +++ b/test/unit/managers/test_HistoryManager.py @@ -339,17 +339,13 @@ class HistorySerializerTestCase( BaseTestCase ): detailed_view = self.history_serializer.serialize_to_view( self.trans, history1, view='detailed' ) self.assertHasKeys( detailed_view, self.history_serializer.views[ 'detailed' ] ) - self.log( 'should have a extended view' ) - extended_view = self.history_serializer.serialize_to_view( self.trans, history1, view='extended' ) - self.assertHasKeys( extended_view, self.history_serializer.views[ 'extended' ] ) - self.log( 'should have the summary view as default view' ) default_view = self.history_serializer.serialize_to_view( self.trans, history1, default_view='summary' ) self.assertHasKeys( summary_view, self.history_serializer.views[ 'summary' ] ) self.log( 'should have a serializer for all serializable keys' ) need_no_serializers = ( basestring, bool, type( None ) ) - for key in self.history_serializer.serializable_keys: + for key in self.history_serializer.serializable_keyset: instantiated_attribute = getattr( history1, key, None ) if not ( ( key in self.history_serializer.serializers ) or ( isinstance( instantiated_attribute, need_no_serializers ) ) ): @@ -372,24 +368,61 @@ class HistorySerializerTestCase( BaseTestCase ): keys=[ 'state_ids', 'user_id' ] ) self.assertHasKeys( serialized, [ 'state_ids', 'user_id' ] ) - def test_serializers( self ): - # size - # nice size - pass + def test_sharable( self ): + user2 = self.user_mgr.create( self.trans, **user2_data ) + history1 = self.history_mgr.create( self.trans, name='history1', user=user2 ) + + self.log( 'should have a serializer for all SharableModel keys' ) + sharable_attrs = [ 'user_id', 'username_and_slug', 'importable', 'published', 'slug' ] + serialized = self.history_serializer.serialize( self.trans, history1, sharable_attrs ) + self.assertHasKeys( serialized, sharable_attrs ) + + def test_purgable( self ): + user2 = self.user_mgr.create( self.trans, **user2_data ) + history1 = self.history_mgr.create( self.trans, name='history1', user=user2 ) + + self.log( 'deleted and purged should be returned in their default states' ) + keys = [ 'deleted', 'purged' ] + serialized = self.history_serializer.serialize( self.trans, history1, keys ) + self.assertEqual( serialized[ 'deleted' ], False ) + self.assertEqual( serialized[ 'purged' ], False ) + + self.log( 'deleted and purged should return their current state' ) + self.history_mgr.delete( self.trans, history1 ) + serialized = self.history_serializer.serialize( self.trans, history1, keys ) + self.assertEqual( serialized[ 'deleted' ], True ) + self.assertEqual( serialized[ 'purged' ], False ) + + self.history_mgr.purge( self.trans, history1 ) + serialized = self.history_serializer.serialize( self.trans, history1, keys ) + self.assertEqual( serialized[ 'deleted' ], True ) + self.assertEqual( serialized[ 'purged' ], True ) + + #pprint.pprint( self.history_serializer.serialize( self.trans, history1, [ 'contents' ] ) ) + + def test_history_serializers( self ): + user2 = self.user_mgr.create( self.trans, **user2_data ) + history1 = self.history_mgr.create( self.trans, name='history1', user=user2 ) + + serialized = self.history_serializer.serialize( self.trans, history1, [ 'size', 'nice_size' ]) + self.assertIsInstance( serialized[ 'size' ], int ) + self.assertIsInstance( serialized[ 'nice_size' ], basestring ) def test_contents( self ): user2 = self.user_mgr.create( self.trans, **user2_data ) history1 = self.history_mgr.create( self.trans, name='history1', user=user2 ) self.log( 'a history with no contents should be properly reflected in empty, etc.' ) - keys = [ 'empty', 'count', 'state_ids', 'state_details', 'state' ] + keys = [ 'empty', 'count', 'state_ids', 'state_details', 'state', 'hdas' ] serialized = self.history_serializer.serialize( self.trans, history1, keys ) self.assertEqual( serialized[ 'state' ], 'new' ) self.assertEqual( serialized[ 'empty' ], True ) self.assertEqual( serialized[ 'count' ], 0 ) self.assertEqual( sum( serialized[ 'state_details' ].values() ), 0 ) self.assertEqual( serialized[ 'state_ids' ][ 'ok' ], [] ) + self.assertIsInstance( serialized[ 'hdas' ], list ) + self.log( 'a history with contents should be properly reflected in empty, etc.' ) hda1 = self.hda_mgr.create( self.trans, history=history1, hid=1 ) self.hda_mgr.update( self.trans, hda1, dict( state='ok' ) ) @@ -399,8 +432,9 @@ class HistorySerializerTestCase( BaseTestCase ): self.assertEqual( serialized[ 'count' ], 1 ) self.assertEqual( serialized[ 'state_details' ][ 'ok' ], 1 ) self.assertIsInstance( serialized[ 'state_ids' ][ 'ok' ], list ) + self.assertIsInstance( serialized[ 'hdas' ], list ) + self.assertIsInstance( serialized[ 'hdas' ][0], basestring ) - #pprint.pprint( self.history_serializer.serialize( self.trans, history1, [ 'contents' ] ) ) # ============================================================================= class HistoryDeserializerTestCase( BaseTestCase ):