mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Managers: add shorthand for adding a serializer view, back off of class-level url service, more testing
This commit is contained in:
@@ -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 )
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
+15
-21
@@ -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() )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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'
|
||||
])
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
Reference in New Issue
Block a user