Warning
This document is for an old release of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.
Source code for galaxy.visualization.plugins.plugin
"""
Visualization plugins: instantiate/deserialize data and models
from a query string and render a webpage based on those data.
"""
import copy
import logging
import os
from typing import Any, Dict
import mako.lookup
from galaxy.managers import api_keys
from galaxy.visualization.plugins import (
interactive_environments,
resource_parser,
utils
)
from galaxy.web import url_for
log = logging.getLogger(__name__)
[docs]class ServesTemplatesPluginMixin:
"""
An object that renders (mako) template files from the server.
"""
path: str
#: default number of templates to search for plugin template lookup
DEFAULT_TEMPLATE_COLLECTION_SIZE = 10
def _set_up_template_plugin(self, template_cache_dir, additional_template_paths=None, **kwargs):
"""
Detect and set up template paths if the plugin serves templates.
"""
self.serves_templates = False
if self._is_template_plugin():
self.template_path = self._build_template_path()
self.template_lookup = self._build_template_lookup(template_cache_dir,
additional_template_paths=additional_template_paths)
self.serves_templates = True
return self.serves_templates
def _is_template_plugin(self):
return os.path.isdir(self._build_template_path())
def _build_template_path(self):
return os.path.join(self.path, 'templates')
def _build_template_lookup(self, template_cache_dir, additional_template_paths=None,
collection_size=DEFAULT_TEMPLATE_COLLECTION_SIZE):
"""
Build a mako template filename lookup for the plugin.
"""
template_lookup_paths = self.template_path
if additional_template_paths:
template_lookup_paths = [template_lookup_paths] + additional_template_paths
return mako.lookup.TemplateLookup(
directories=template_lookup_paths,
module_directory=template_cache_dir,
collection_size=collection_size)
[docs]class VisualizationPlugin(ServesTemplatesPluginMixin):
"""
A plugin that instantiates resources, serves static files, and uses mako
templates to render web pages.
"""
[docs] def __init__(self, app, path, name, config, context=None, **kwargs):
context = context or {}
self.app = app
self.path = path
self.name = name
self.config = config
base_url = context.get('base_url', '')
self.base_url = '/'.join((base_url, self.name)) if base_url else self.name
self.static_path = self._get_static_path(self.path)
if self.static_path and os.path.exists(os.path.join(self.static_path, 'logo.png')):
self.config['logo'] = f"{self.static_path}/logo.png"
template_cache_dir = context.get('template_cache_dir', None)
additional_template_paths = context.get('additional_template_paths', [])
self._set_up_template_plugin(template_cache_dir, additional_template_paths=additional_template_paths)
self.resource_parser = resource_parser.ResourceParser(app)
[docs] def render(self, trans=None, embedded=None, **kwargs):
"""
Render and return the text of the non-saved plugin webpage/fragment.
"""
# not saved - no existing config
# set up render vars based on plugin.config and kwargs
render_vars = self._build_render_vars({}, trans=trans, **kwargs)
return self._render(render_vars, trans=trans, embedded=embedded)
[docs] def render_saved(self, visualization, trans=None, embedded=None, **kwargs):
"""
Render and return the text of the plugin webpage/fragment using the
config/data of a saved visualization.
"""
config: Dict[str, Any] = self._get_saved_visualization_config(visualization, **kwargs)
# pass the saved visualization config for parsing into render vars
render_vars = self._build_render_vars(config, trans=trans, **kwargs)
# update any values that were loaded from the saved Visualization
render_vars.update(dict(
title=visualization.latest_revision.title,
saved_visualization=visualization,
visualization_id=trans.security.encode_id(visualization.id),
))
return self._render(render_vars, trans=trans, embedded=embedded)
[docs] def to_dict(self):
return {
'name': self.name,
'html': self.config.get('name'),
'description': self.config.get('description'),
'logo': self.config.get('logo'),
'title': self.config.get('title'),
'target': self.config.get('render_target', 'galaxy_main'),
'embeddable': self.config.get('embeddable'),
'entry_point': self.config.get('entry_point'),
'settings': self.config.get('settings'),
'groups': self.config.get('groups'),
'specs': self.config.get('specs'),
'href': self._get_url()
}
def _get_url(self):
if self.name in self.app.visualizations_registry.BUILT_IN_VISUALIZATIONS:
return url_for(controller='visualization', action=self.name)
return url_for('visualization_plugin', visualization_name=self.name)
def _get_static_path(self, path):
if '/config/' in path:
match = path.split('/config/')[-1]
return os.path.join('./static', match, 'static')
else:
log.debug(f'Visualization has no static path: {path}.')
def _get_saved_visualization_config(self, visualization, revision=None, **kwargs) -> Dict[str, Any]:
"""
Return the config of a saved visualization and revision.
If no revision given, default to latest revision.
"""
# TODO: allow loading a specific revision - should be part of UsesVisualization
return copy.copy(visualization.latest_revision.config)
# ---- non-public
def _build_render_vars(self, config: Dict[str, Any], trans=None, **kwargs) -> Dict[str, Any]:
"""
Build all the variables that will be passed into the renderer.
"""
render_vars: Dict[str, Any] = {}
# Meta variables passed to the template/renderer to describe the visualization being rendered.
render_vars.update(
visualization_name=self.name,
visualization_display_name=self.config['name'],
title=kwargs.get('title', None),
saved_visualization=None,
visualization_id=None,
visualization_plugin=self.to_dict(),
# NOTE: passing *unparsed* kwargs as query
query=kwargs,
)
# config based on existing or kwargs
render_config = self._build_config(config, trans=trans, **kwargs)
render_vars['config'] = render_config
# further parse config to resources (models, etc.) used in template based on registry config
resources = self._config_to_resources(trans, render_config)
render_vars.update(resources)
return render_vars
def _build_config(self, config, trans=None, **kwargs) -> utils.OpenObject:
"""
Build the configuration for this new/saved visualization by combining
any existing config and the kwargs (gen. from the url query).
"""
# first, pull from any existing config
if config:
config = copy.copy(config)
else:
config = {}
# then, overwrite with keys/values from kwargs (gen. a query string)
config_from_kwargs = self._kwargs_to_config(trans, kwargs)
config.update(config_from_kwargs)
# to object format for easier querying
config = utils.OpenObject(**config)
return config
# TODO: the difference between config & resources is unclear in this section - is it needed?
def _kwargs_to_config(self, trans, kwargs):
"""
Given a kwargs dict (gen. a query string dict from a controller action), parse
and return any key/value pairs found in the plugin's `params` section.
"""
expected_params = self.config.get('params', {})
config = self.resource_parser.parse_config(trans, expected_params, kwargs)
return config
def _config_to_resources(self, trans, config):
"""
Instantiate/deserialize the resources (HDAs, LDDAs, etc.) given in a
visualization config into models/variables a visualization renderer can use.
"""
expected_params = self.config.get('params', {})
param_modifiers = self.config.get('param_modifiers', {})
resources = self.resource_parser.parse_parameter_dictionary(trans, expected_params, config, param_modifiers)
return resources
def _render(self, render_vars, trans=None, embedded=None, **kwargs):
"""
Render the visualization via Mako and the plugin's template file.
"""
render_vars['embedded'] = self._parse_embedded(embedded)
# NOTE: (mako specific) vars is a dictionary for shared data in the template
# this feels hacky to me but it's what mako recommends:
# http://docs.makotemplates.org/en/latest/runtime.html
render_vars.update(vars={})
template_filename = self.config['entry_point']['file']
return trans.fill_template(template_filename, template_lookup=self.template_lookup, **render_vars)
def _parse_embedded(self, embedded):
"""
Parse information on dimensions, readonly, etc. from the embedded query val.
"""
# as is for now
return embedded
[docs]class InteractiveEnvironmentPlugin(VisualizationPlugin):
"""
Serves web-based REPLs such as Jupyter and RStudio.
"""
INTENV_REQUEST_FACTORY = interactive_environments.InteractiveEnvironmentRequest
[docs] def __init__(self, app, path, name, config, context=None, **kwargs):
# TODO: this is a hack until we can get int envs seperated from the vis reg and into their own framework
context['base_url'] = 'interactive_environments'
super().__init__(app, path, name, config, context=context, **kwargs)
def _error_template(self, trans):
return trans.fill_template('message.mako',
message='Loading the interactive environment failed, please contact the {admin_tag} for assistance'.format(
admin_tag='<a href="mailto:{admin_mail}">Galaxy administrator</a>'.format(
admin_mail=trans.app.config.error_email_to)
if trans.app.config.error_email_to else 'Galaxy administrator'),
status='error')
def _render(self, render_vars, trans=None, embedded=None, **kwargs):
"""
Override to add interactive environment specific template vars.
"""
render_vars['embedded'] = self._parse_embedded(embedded)
# NOTE: (mako specific) vars is a dictionary for shared data in the template
# this feels hacky to me but it's what mako recommends:
# http://docs.makotemplates.org/en/latest/runtime.html
render_vars.update(vars={})
# No longer needed but being left around for a few releases as jupyter-galaxy
# as an external visualization plugin is deprecated in favor of core interactive
# environment plugin.
if 'get_api_key' not in render_vars:
def get_api_key():
return api_keys.ApiKeyManager(trans.app).get_or_create_api_key(trans.user)
render_vars['get_api_key'] = get_api_key
if 'plugin_path' not in render_vars:
render_vars['plugin_path'] = os.path.abspath(self.path)
if self.config.get('plugin_type', 'visualization') == "interactive_environment":
try:
request = self.INTENV_REQUEST_FACTORY(trans, self)
except Exception:
log.exception("IE plugin request handling failed")
return self._error_template(trans)
render_vars["ie_request"] = request
template_filename = self.config['entry_point']['file']
try:
return trans.fill_template(template_filename, template_lookup=self.template_lookup, **render_vars)
except Exception:
log.exception("IE plugin template fill failed")
return self._error_template(trans)
def _get_url(self):
return url_for('interactive_environment_plugin', visualization_name=self.name)
[docs]class ScriptVisualizationPlugin(VisualizationPlugin):
"""
A visualization plugin that starts by loading a single (js) script.
The script is loaded into a pre-defined mako template:
`config/plugins/visualizations/common/templates/script_entry_point.mako`
"""
MAKO_TEMPLATE = 'script_entry_point.mako'
def _is_template_plugin(self):
"""
Override to always yield true since this plugin type always uses the
pre-determined mako template.
"""
return True
def _render(self, render_vars, trans=None, embedded=None, **kwargs):
"""
Override to add script attributes and point mako at the script entry point
template.
"""
render_vars['embedded'] = self._parse_embedded(embedded)
render_vars['static_url'] = url_for(f'/{self.static_path}/')
render_vars.update(vars={})
render_vars.update({
"script_attributes": self.config['entry_point']['attr']
})
template_filename = os.path.join(self.MAKO_TEMPLATE)
return trans.fill_template(template_filename, template_lookup=self.template_lookup, **render_vars)
[docs]class ChartVisualizationPlugin(ScriptVisualizationPlugin):
MAKO_TEMPLATE = 'chart_entry_point.mako'
[docs]class StaticFileVisualizationPlugin(VisualizationPlugin):
"""
A visualization plugin that starts by loading a static html file defined in
the visualization's config file.
"""
# TODO: these are not embeddable by their nature - update config
# TODO: should do render/render_saved here since most of the calc done there is unneeded in this case
def _render(self, render_vars, trans=None, embedded=None, **kwargs):
"""
Render the static file simply by reading and returning it.
"""
render_vars['embedded'] = self._parse_embedded(embedded)
render_vars.update(vars={})
static_file_path = self.config['entry_point']['file']
static_file_path = os.path.join(self.path, static_file_path)
with open(static_file_path) as outfile:
return outfile.read()