Managers: add shorthand for adding a serializer view, back off of class-level url service, more testing

This commit is contained in:
Carl Eberhard
2015-02-02 13:33:06 -05:00
parent 04bd3c77c1
commit 66c86c9719
10 changed files with 135 additions and 113 deletions
+33 -8
View File
@@ -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 )
+4 -10
View File
@@ -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,
+2 -1
View File
@@ -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
View File
@@ -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() )
+10 -18
View File
@@ -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
+3 -11
View File
@@ -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() )
+1 -1
View File
@@ -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'
])
+19 -21
View File
@@ -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({
+3 -11
View File
@@ -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() )
+45 -11
View File
@@ -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 ):