Source code for galaxy.webapps.galaxy.api.visualizations

"""
Visualizations resource control over the API.

NOTE!: this is a work in progress and functionality and data structures
may change often.
"""
import json
import logging

from six import string_types

from galaxy import (
    exceptions,
    util,
    web
)
from galaxy.managers.visualizations import (
    VisualizationManager,
    VisualizationSerializer
)
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.web import expose_api
from galaxy.web.base.controller import (
    BaseAPIController,
    SharableMixin,
    UsesVisualizationMixin
)

log = logging.getLogger(__name__)


[docs]class VisualizationsController(BaseAPIController, UsesVisualizationMixin, SharableMixin, UsesAnnotations): """ RESTful controller for interactions with visualizations. """
[docs] def __init__(self, app): super(VisualizationsController, self).__init__(app) self.manager = VisualizationManager(app) self.serializer = VisualizationSerializer(app)
[docs] @expose_api def index(self, trans, **kwargs): """ GET /api/visualizations: """ rval = [] user = trans.user # TODO: search for: title, made by user, creation time range, type (vis name), dbkey, etc. # TODO: limit, offset, order_by # TODO: deleted # this is the default search - user's vis, vis shared with user, published vis visualizations = self.get_visualizations_by_user(trans, user) visualizations += self.get_visualizations_shared_with_user(trans, user) visualizations += self.get_published_visualizations(trans, exclude_user=user) # TODO: the admin case - everything for visualization in visualizations: item = self.get_visualization_summary_dict(visualization) item = trans.security.encode_dict_ids(item) item['url'] = web.url_for('visualization', id=item['id']) rval.append(item) return rval
[docs] @expose_api def show(self, trans, id, **kwargs): """ GET /api/visualizations/{viz_id} """ # TODO: revisions should be a contents/nested controller like viz/xxx/r/xxx)? # the important thing is the config # TODO:?? /api/visualizations/registry -> json of registry.listings? visualization = self.get_visualization(trans, id, check_ownership=False, check_accessible=True) dictionary = trans.security.encode_dict_ids(self.get_visualization_dict(visualization)) dictionary['url'] = web.url_for(controller='visualization', action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug) dictionary['annotation'] = self.get_item_annotation_str(trans.sa_session, trans.user, visualization) # need to encode ids in revisions as well encoded_revisions = [] for revision in dictionary['revisions']: # NOTE: does not encode ids inside the configs encoded_revisions.append(trans.security.encode_id(revision)) dictionary['revisions'] = encoded_revisions dictionary['latest_revision'] = trans.security.encode_dict_ids(dictionary['latest_revision']) if trans.app.visualizations_registry: visualization = trans.app.visualizations_registry.get_plugin(dictionary['type']) dictionary['plugin'] = visualization.to_dict() return dictionary
[docs] @expose_api def create(self, trans, payload, **kwargs): """ POST /api/visualizations creates a new visualization using the given payload POST /api/visualizations?import_id={encoded_visualization_id} imports a copy of an existing visualization into the user's workspace """ rval = None if 'import_id' in payload: import_id = payload('import_id') visualization = self.import_visualization(trans, import_id, user=trans.user) else: payload = self._validate_and_parse_payload(payload) # must have a type (I've taken this to be the visualization name) if 'type' not in payload: raise exceptions.RequestParameterMissingException("key/value 'type' is required") vis_type = payload.pop('type', False) payload['save'] = True try: # generate defaults - this will err if given a weird key? visualization = self.create_visualization(trans, vis_type, **payload) except ValueError as val_err: raise exceptions.RequestParameterMissingException(str(val_err)) rval = {'id' : trans.security.encode_id(visualization.id)} return rval
[docs] @expose_api def update(self, trans, id, payload, **kwargs): """ PUT /api/visualizations/{encoded_visualization_id} """ rval = None payload = self._validate_and_parse_payload(payload) # there's a differentiation here between updating the visualiztion and creating a new revision # that needs to be handled clearly here # or alternately, using a different controller like PUT /api/visualizations/{id}/r/{id} # TODO: consider allowing direct alteration of revisions title (without a new revision) # only create a new revsion on a different config # only update owned visualizations visualization = self.get_visualization(trans, id, check_ownership=True) title = payload.get('title', visualization.latest_revision.title) dbkey = payload.get('dbkey', visualization.latest_revision.dbkey) config = payload.get('config', visualization.latest_revision.config) latest_config = visualization.latest_revision.config if((title != visualization.latest_revision.title) or (dbkey != visualization.latest_revision.dbkey) or (json.dumps(config) != json.dumps(latest_config))): revision = self.add_visualization_revision(trans, visualization, config, title, dbkey) rval = {'id' : id, 'revision' : revision.id} # allow updating vis title visualization.title = title trans.sa_session.flush() return rval
def _validate_and_parse_payload(self, payload): """ Validate and parse incomming data payload for a visualization. """ # This layer handles (most of the stricter idiot proofing): # - unknown/unallowed keys # - changing data keys from api key to attribute name # - protection against bad data form/type # - protection against malicious data content # all other conversions and processing (such as permissions, etc.) should happen down the line # keys listed here don't error when attempting to set, but fail silently # this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs valid_but_uneditable_keys = ( 'id', 'model_class' # TODO: fill out when we create to_dict, get_dict, whatevs ) # TODO: deleted # TODO: importable ValidationError = exceptions.RequestParameterInvalidException validated_payload = {} for key, val in payload.items(): # TODO: validate types in VALID_TYPES/registry names at the mixin/model level? if key == 'type': if not isinstance(val, string_types): raise ValidationError('%s must be a string or unicode: %s' % (key, str(type(val)))) val = util.sanitize_html.sanitize_html(val) elif key == 'config': if not isinstance(val, dict): raise ValidationError('%s must be a dictionary: %s' % (key, str(type(val)))) elif key == 'annotation': if not isinstance(val, string_types): raise ValidationError('%s must be a string or unicode: %s' % (key, str(type(val)))) val = util.sanitize_html.sanitize_html(val) # these are keys that actually only be *updated* at the revision level and not here # (they are still valid for create, tho) elif key == 'title': if not isinstance(val, string_types): raise ValidationError('%s must be a string or unicode: %s' % (key, str(type(val)))) val = util.sanitize_html.sanitize_html(val) elif key == 'slug': if not isinstance(val, string_types): raise ValidationError('%s must be a string: %s' % (key, str(type(val)))) val = util.sanitize_html.sanitize_html(val) elif key == 'dbkey': if not isinstance(val, string_types): raise ValidationError('%s must be a string or unicode: %s' % (key, str(type(val)))) val = util.sanitize_html.sanitize_html(val) elif key not in valid_but_uneditable_keys: continue # raise AttributeError( 'unknown key: %s' %( str( key ) ) ) validated_payload[key] = val return validated_payload