Warning

This document is for an in-development version 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 tool_shed.webapp.buildapp

"""
Provides factory methods to assemble the Galaxy web application
"""
import atexit
import logging
import os
from inspect import isclass

import routes
from paste import httpexceptions
from routes.middleware import RoutesMiddleware
from six.moves.urllib.parse import parse_qs

import galaxy.webapps.base.webapp
import tool_shed.webapp.model
import tool_shed.webapp.model.mapping
from galaxy import util
from galaxy.util import asbool
from galaxy.util.properties import load_app_properties
from galaxy.webapps.util import wrap_if_allowed

log = logging.getLogger(__name__)


[docs]class CommunityWebApplication(galaxy.webapps.base.webapp.WebApplication): pass
[docs]def add_ui_controllers(webapp, app): """ Search for controllers in the 'galaxy.webapps.controllers' module and add them to the webapp. """ from galaxy.webapps.base.controller import BaseUIController import tool_shed.webapp.controllers controller_dir = tool_shed.webapp.controllers.__path__[0] for fname in os.listdir(controller_dir): if not fname.startswith("_") and fname.endswith(".py"): name = fname[:-3] module_name = "tool_shed.webapp.controllers." + name module = __import__(module_name) for comp in module_name.split(".")[1:]: module = getattr(module, comp) # Look for a controller inside the modules for key in dir(module): T = getattr(module, key) if isclass(T) and T is not BaseUIController and issubclass(T, BaseUIController): webapp.add_ui_controller(name, T(app))
[docs]def app_factory(global_conf, load_app_kwds={}, **kwargs): """Return a wsgi application serving the root object""" # Create the Galaxy tool shed application unless passed in kwargs = load_app_properties( kwds=kwargs, config_prefix='TOOL_SHED_CONFIG_', **load_app_kwds ) if 'app' in kwargs: app = kwargs.pop('app') else: try: from tool_shed.webapp.app import UniverseApplication app = UniverseApplication(global_conf=global_conf, **kwargs) except Exception: import traceback import sys traceback.print_exc() sys.exit(1) atexit.register(app.shutdown) # Create the universe WSGI application webapp = CommunityWebApplication(app, session_cookie='galaxycommunitysession', name="tool_shed") add_ui_controllers(webapp, app) webapp.add_route('/view/{owner}', controller='repository', action='sharable_owner') webapp.add_route('/view/{owner}/{name}', controller='repository', action='sharable_repository') webapp.add_route('/view/{owner}/{name}/{changeset_revision}', controller='repository', action='sharable_repository_revision') # Handle displaying tool help images and README file images for tools contained in repositories. webapp.add_route('/repository/static/images/{repository_id}/{image_file:.+?}', controller='repository', action='display_image_in_repository', repository_id=None, image_file=None) webapp.add_route('/{controller}/{action}', action='index') webapp.add_route('/{action}', controller='repository', action='index') # Enable 'hg clone' functionality on repos by letting hgwebapp handle the request webapp.add_route('/repos/*path_info', controller='hg', action='handle_request', path_info='/') # Add the web API. # A good resource for RESTful services - https://routes.readthedocs.io/en/latest/restful.html webapp.add_api_controllers('tool_shed.webapp.api', app) webapp.mapper.connect('api_key_retrieval', '/api/authenticate/baseauth/', controller='authenticate', action='get_tool_shed_api_key', conditions=dict(method=["GET"])) webapp.mapper.connect('group', '/api/groups/', controller='groups', action='index', conditions=dict(method=["GET"])) webapp.mapper.connect('group', '/api/groups/', controller='groups', action='create', conditions=dict(method=["POST"])) webapp.mapper.connect('group', '/api/groups/{encoded_id}', controller='groups', action='show', conditions=dict(method=["GET"])) webapp.mapper.resource('category', 'categories', controller='categories', name_prefix='category_', path_prefix='/api', parent_resources=dict(member_name='category', collection_name='categories')) webapp.mapper.connect('repositories_in_category', '/api/categories/{category_id}/repositories', controller='categories', action='get_repositories', conditions=dict(method=["GET"])) webapp.mapper.connect('show_updates_for_repository', '/api/repositories/updates', controller='repositories', action='updates', conditions=dict(method=["GET"])) webapp.mapper.resource('repository', 'repositories', controller='repositories', collection={'add_repository_registry_entry': 'POST', 'get_repository_revision_install_info': 'GET', 'get_ordered_installable_revisions': 'GET', 'get_installable_revisions': 'GET', 'remove_repository_registry_entry': 'POST', 'repository_ids_for_setting_metadata': 'GET', 'reset_metadata_on_repositories': 'POST', 'reset_metadata_on_repository': 'POST'}, name_prefix='repository_', path_prefix='/api', parent_resources=dict(member_name='repository', collection_name='repositories')) webapp.mapper.resource('repository_revision', 'repository_revisions', member={'repository_dependencies': 'GET', 'export': 'POST'}, controller='repository_revisions', name_prefix='repository_revision_', path_prefix='/api', parent_resources=dict(member_name='repository_revision', collection_name='repository_revisions')) webapp.mapper.resource('user', 'users', controller='users', name_prefix='user_', path_prefix='/api', parent_resources=dict(member_name='user', collection_name='users')) webapp.mapper.connect('update_repository', '/api/repositories/{id}', controller='repositories', action='update', conditions=dict(method=["PATCH", "PUT"])) webapp.mapper.connect('repository_create_changeset_revision', '/api/repositories/{id}/changeset_revision', controller='repositories', action='create_changeset_revision', conditions=dict(method=["POST"])) webapp.mapper.connect('repository_get_metadata', '/api/repositories/{id}/metadata', controller='repositories', action='metadata', conditions=dict(method=["GET"])) webapp.mapper.connect('repository_show_tools', '/api/repositories/{id}/{changeset}/show_tools', controller='repositories', action='show_tools', conditions=dict(method=["GET"])) webapp.mapper.connect('create_repository', '/api/repositories', controller='repositories', action='create', conditions=dict(method=["POST"])) webapp.mapper.connect('tools', '/api/tools', controller='tools', action='index', conditions=dict(method=["GET"])) webapp.mapper.connect('json', '/api/tools/json', controller='tools', action='json', conditions=dict(method=["GET"])) webapp.mapper.connect("version", "/api/version", controller="configuration", action="version", conditions=dict(method=["GET"])) webapp.finalize_config() # Wrap the webapp in some useful middleware if kwargs.get('middleware', True): webapp = wrap_in_middleware(webapp, global_conf, app.application_stack, **kwargs) if asbool(kwargs.get('static_enabled', True)): webapp = wrap_if_allowed(webapp, app.application_stack, wrap_in_static, args=(global_conf,), kwargs=kwargs) # Close any pooled database connections before forking try: tool_shed.webapp.model.mapping.metadata.bind.dispose() except Exception: log.exception("Unable to dispose of pooled tool_shed model database connections.") # Return return webapp
[docs]def wrap_in_middleware(app, global_conf, application_stack, **local_conf): """Based on the configuration wrap `app` in a set of common and useful middleware.""" stack = application_stack # Merge the global and local configurations conf = global_conf.copy() conf.update(local_conf) debug = asbool(conf.get('debug', False)) # First put into place httpexceptions, which must be most closely # wrapped around the application (it can interact poorly with # other middleware): app = wrap_if_allowed(app, stack, httpexceptions.make_middleware, name='paste.httpexceptions', args=(conf,)) # Create a separate mapper for redirects to prevent conflicts. redirect_mapper = routes.Mapper() redirect_mapper = _map_redirects(redirect_mapper) # Load the Routes middleware which we use for redirecting app = wrap_if_allowed(app, stack, RoutesMiddleware, args=(redirect_mapper,)) # If we're using remote_user authentication, add middleware that # protects Galaxy from improperly configured authentication in the # upstream server if asbool(conf.get('use_remote_user', False)): from tool_shed.webapp.framework.middleware.remoteuser import RemoteUser app = wrap_if_allowed(app, stack, RemoteUser, kwargs=dict( maildomain=conf.get('remote_user_maildomain', None), display_servers=util.listify(conf.get('display_servers', '')), admin_users=conf.get('admin_users', '').split(','), remote_user_header=conf.get('remote_user_header', 'HTTP_REMOTE_USER'), remote_user_secret_header=conf.get('remote_user_secret', None), normalize_remote_user_email=conf.get('normalize_remote_user_email', False))) # The recursive middleware allows for including requests in other # requests or forwarding of requests, all on the server side. if asbool(conf.get('use_recursive', True)): from paste import recursive app = wrap_if_allowed(app, stack, recursive.RecursiveMiddleware, args=(conf,)) # Transaction logging (apache access.log style) if asbool(conf.get('use_translogger', True)): from paste.translogger import TransLogger app = wrap_if_allowed(app, stack, TransLogger) # If sentry logging is enabled, log here before propogating up to # the error middleware # TODO sentry config is duplicated between tool_shed/galaxy, refactor this. sentry_dsn = conf.get('sentry_dsn', None) if sentry_dsn: from galaxy.web.framework.middleware.sentry import Sentry app = wrap_if_allowed(app, stack, Sentry, args=(sentry_dsn,)) # X-Forwarded-Host handling from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware app = wrap_if_allowed(app, stack, XForwardedHostMiddleware) # Various debug middleware that can only be turned on if the debug # flag is set, either because they are insecure or greatly hurt # performance. if debug: # Middleware to check for WSGI compliance if asbool(conf.get('use_lint', True)): from paste import lint app = wrap_if_allowed(app, stack, lint.make_middleware, name='paste.lint', args=(conf,)) # Middleware to run the python profiler on each request if asbool(conf.get('use_profile', False)): from paste.debug import profile app = wrap_if_allowed(app, stack, profile.ProfileMiddleware, args=(conf,)) # Error middleware import galaxy.web.framework.middleware.error app = wrap_if_allowed(app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,)) return app
[docs]def wrap_in_static(app, global_conf, **local_conf): urlmap, _ = galaxy.webapps.base.webapp.build_url_map(app, global_conf, local_conf) return urlmap
def _map_redirects(mapper): """ Add redirect to the Routes mapper and forward the received query string. Subsequently when the redirect is triggered in Routes middleware the request will not even reach the webapp. """ def forward_qs(environ, result): qs_dict = parse_qs(environ['QUERY_STRING']) for qs in qs_dict: result[qs] = qs_dict[qs] return True mapper.redirect("/repository/status_for_installed_repository", "/api/repositories/updates/", _redirect_code="301 Moved Permanently", conditions=dict(function=forward_qs)) return mapper
[docs]def uwsgi_app(): return galaxy.webapps.base.webapp.build_native_uwsgi_app(app_factory, "tool_shed")