Rebuild client and merge from upstream.

This commit is contained in:
Dannon Baker
2017-03-22 10:25:11 -04:00
89 changed files with 1363 additions and 947 deletions
+2
View File
@@ -47,6 +47,8 @@ lib/galaxy/tours/
lib/galaxy/util/
lib/galaxy/visualization/
lib/galaxy/web/buildapp.py
lib/galaxy/web/framework/base.py
lib/galaxy/web/framework/decorators.py
lib/galaxy/web/framework/__init__.py
lib/galaxy/web/framework/middleware/error.py
lib/galaxy/web/framework/middleware/static.py
+1 -1
View File
@@ -1,4 +1,4 @@
.. figure:: https://wiki.galaxyproject.org/Images/GalaxyLogo?action=AttachFile&do=get&target=galaxy_project_logo.jpg
.. figure:: https://galaxyproject.org/images/galaxy-logos/galaxy_project_logo.jpg
:alt: Galaxy Logo
The latest information about Galaxy is available via `https://galaxyproject.org/ <https://galaxyproject.org/>`__
@@ -0,0 +1,106 @@
define([
], function( ){
/* For presentation-related functionality shared across collection creators.
Particularily overlapping functionality related to name processing and help.
*/
var CollectionCreatorMixin = {
/** add (or clear if clear is truthy) a validation warning to the DOM element described in what */
_validationWarning : function( what, clear ){
var VALIDATION_CLASS = 'validation-warning';
if( what === 'name' ){
what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );
this.$( '.collection-name' ).focus().select();
}
if( clear ){
what = what || this.$( '.' + VALIDATION_CLASS );
what.removeClass( VALIDATION_CLASS );
} else {
what.addClass( VALIDATION_CLASS );
}
},
// ........................................................................ footer
/** handle a collection name change */
_changeName : function( ev ){
this._validationWarning( 'name', !!this._getName() );
},
/** check for enter key press when in the collection name and submit */
_nameCheckForEnter : function( ev ){
if( ev.keyCode === 13 && !this.blocking ){
this._clickCreate();
}
},
/** get the current collection name */
_getName : function(){
return _.escape( this.$( '.collection-name' ).val() );
},
// ........................................................................ header
/** expand help */
_clickMoreHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).addClass( 'expanded' );
this.$( '.more-help' ).hide();
},
/** collapse help */
_clickLessHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).removeClass( 'expanded' );
this.$( '.more-help' ).show();
},
/** toggle help */
_toggleHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).toggleClass( 'expanded' );
this.$( '.more-help' ).toggle();
},
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */
_showAlert : function( message, alertClass ){
alertClass = alertClass || 'alert-danger';
this.$( '.main-help' ).hide();
this.$( '.header .alert' )
.attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
.find( '.alert-message' ).html( message );
},
/** hide the alerts at the top */
_hideAlert : function( message ){
this.$( '.main-help' ).show();
this.$( '.header .alert' ).hide();
},
_cancelCreate: function( ev ){
if( typeof this.oncancel === 'function' ){
this.oncancel.call( this );
}
},
/** attempt to create the current collection */
_clickCreate : function( ev ){
var name = this._getName();
if( !name ){
this._validationWarning( 'name' );
} else if( !this.blocking ){
this.createList( name );
}
},
_creatorTemplates: {
main : _.template([
'<div class="header flex-row no-flex"></div>',
'<div class="middle flex-row flex-row-container"></div>',
'<div class="footer flex-row no-flex"></div>'
].join('')),
}
}
//==============================================================================
return {
CollectionCreatorMixin: CollectionCreatorMixin
};
});
@@ -3,15 +3,17 @@ define([
"mvc/history/hdca-model",
"mvc/dataset/states",
"mvc/base-mvc",
"mvc/collection/base-creator",
"mvc/ui/ui-modal",
"utils/natural-sort",
"utils/localization",
"ui/hoverhighlight"
], function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){
], function( HDCA, STATES, BASE_MVC, baseCreator, UI_MODAL, naturalSort, _l ){
'use strict';
var logNamespace = 'collections';
/*==============================================================================
TODO:
use proper Element model and not just json
@@ -151,7 +153,7 @@ var DatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin
// ============================================================================
/** An interface for building collections.
*/
var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend( baseCreator.CollectionCreatorMixin ).extend({
_logNamespace : logNamespace,
/** the class used to display individual elements */
@@ -367,21 +369,6 @@ var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).exten
this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );
},
/** add (or clear if clear is truthy) a validation warning to the DOM element described in what */
_validationWarning : function( what, clear ){
var VALIDATION_CLASS = 'validation-warning';
if( what === 'name' ){
what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );
this.$( '.collection-name' ).focus().select();
}
if( clear ){
what = what || this.$( '.' + VALIDATION_CLASS );
what.removeClass( VALIDATION_CLASS );
} else {
what.addClass( VALIDATION_CLASS );
}
},
_disableNameAndCreate : function( disable ){
disable = !_.isUndefined( disable )? disable : true;
if( disable ){
@@ -624,48 +611,10 @@ var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).exten
// footer
'change .collection-name' : '_changeName',
'keydown .collection-name' : '_nameCheckForEnter',
'click .cancel-create' : function( ev ){
if( typeof this.oncancel === 'function' ){
this.oncancel.call( this );
}
},
'click .cancel-create' : '_cancelCreate',
'click .create-collection' : '_clickCreate'//,
},
// ........................................................................ header
/** expand help */
_clickMoreHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).addClass( 'expanded' );
this.$( '.more-help' ).hide();
},
/** collapse help */
_clickLessHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).removeClass( 'expanded' );
this.$( '.more-help' ).show();
},
/** toggle help */
_toggleHelp : function( ev ){
ev.stopPropagation();
this.$( '.main-help' ).toggleClass( 'expanded' );
this.$( '.more-help' ).toggle();
},
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */
_showAlert : function( message, alertClass ){
alertClass = alertClass || 'alert-danger';
this.$( '.main-help' ).hide();
this.$( '.header .alert' )
.attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
.find( '.alert-message' ).html( message );
},
/** hide the alerts at the top */
_hideAlert : function( message ){
this.$( '.main-help' ).show();
this.$( '.header .alert' ).hide();
},
// ........................................................................ elements
/** reset all data to the initial state */
reset : function(){
@@ -789,46 +738,12 @@ var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).exten
this.$dragging = null;
},
// ........................................................................ footer
/** handle a collection name change */
_changeName : function( ev ){
this._validationWarning( 'name', !!this._getName() );
},
/** check for enter key press when in the collection name and submit */
_nameCheckForEnter : function( ev ){
if( ev.keyCode === 13 && !this.blocking ){
this._clickCreate();
}
},
/** get the current collection name */
_getName : function(){
return _.escape( this.$( '.collection-name' ).val() );
},
/** attempt to create the current collection */
_clickCreate : function( ev ){
var name = this._getName();
if( !name ){
this._validationWarning( 'name' );
} else if( !this.blocking ){
this.createList( name );
}
},
// ------------------------------------------------------------------------ templates
//TODO: move to require text plugin and load these as text
//TODO: underscore currently unnecc. bc no vars are used
//TODO: better way of localizing text-nodes in long strings
/** underscore template fns attached to class */
templates : {
/** the skeleton */
main : _.template([
'<div class="header flex-row no-flex"></div>',
'<div class="middle flex-row flex-row-container"></div>',
'<div class="footer flex-row no-flex"></div>'
].join('')),
templates : _.extend({}, baseCreator.CollectionCreatorMixin._creatorTemplates, {
/** the header (not including help text) */
header : _.template([
@@ -974,7 +889,7 @@ var ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).exten
'</div>',
'</div>'
].join('')),
},
}),
// ------------------------------------------------------------------------ misc
/** string rep */
@@ -1,11 +1,11 @@
define([
"utils/levenshtein",
"utils/natural-sort",
"mvc/collection/list-collection-creator",
"mvc/collection/base-creator",
"mvc/base-mvc",
"utils/localization",
"ui/hoverhighlight"
], function( levenshteinDistance, naturalSort, LIST_COLLECTION_CREATOR, baseMVC, _l ){
], function( levenshteinDistance, naturalSort, baseCreator, baseMVC, _l ){
'use strict';
@@ -190,7 +190,7 @@ function autoPairFnBuilder( options ){
// ============================================================================
/** An interface for building collections of paired datasets.
*/
var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend( baseCreator.CollectionCreatorMixin ).extend({
_logNamespace : logNamespace,
className: 'list-of-pairs-collection-creator collection-creator flex-row-container',
@@ -942,6 +942,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
// header
'click .more-help' : '_clickMoreHelp',
'click .less-help' : '_clickLessHelp',
'click .main-help' : '_toggleHelp',
'click .header .alert button' : '_hideAlert',
'click .forward-column .column-title' : '_clickShowOnlyUnpaired',
'click .reverse-column .column-title' : '_clickShowOnlyUnpaired',
@@ -981,39 +982,10 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
'change .remove-extensions' : function( ev ){ this.toggleExtensions(); },
'change .collection-name' : '_changeName',
'keydown .collection-name' : '_nameCheckForEnter',
'click .cancel-create' : function( ev ){
if( typeof this.oncancel === 'function' ){
this.oncancel.call( this );
}
},
'click .cancel-create' : '_cancelCreate',
'click .create-collection' : '_clickCreate'//,
},
// ........................................................................ header
/** expand help */
_clickMoreHelp : function( ev ){
this.$( '.main-help' ).addClass( 'expanded' );
this.$( '.more-help' ).hide();
},
/** collapse help */
_clickLessHelp : function( ev ){
this.$( '.main-help' ).removeClass( 'expanded' );
this.$( '.more-help' ).show();
},
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/
_showAlert : function( message, alertClass ){
alertClass = alertClass || 'alert-danger';
this.$( '.main-help' ).hide();
this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
.find( '.alert-message' ).html( message );
},
/** hide the alerts at the top */
_hideAlert : function( message ){
this.$( '.main-help' ).show();
this.$( '.header .alert' ).hide();
},
/** toggle between showing only unpaired and split view */
_clickShowOnlyUnpaired : function( ev ){
//this.debug( 'click unpaired', ev.currentTarget );
@@ -1419,33 +1391,6 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
creator._renderFooter();
},
/** handle a collection name change */
_changeName : function( ev ){
this._validationWarning( 'name', !!this._getName() );
},
/** check for enter key press when in the collection name and submit */
_nameCheckForEnter : function( ev ){
if( ev.keyCode === 13 && !this.blocking ){
this._clickCreate();
}
},
/** get the current collection name */
_getName : function(){
return _.escape( this.$( '.collection-name' ).val() );
},
/** attempt to create the current collection */
_clickCreate : function( ev ){
var name = this._getName();
if( !name ){
this._validationWarning( 'name' );
} else if( !this.blocking ){
this.createList();
}
},
// ------------------------------------------------------------------------ misc
/** debug a dataset list */
_printList : function( list ){
@@ -1473,14 +1418,7 @@ var PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).exte
//TODO: underscore currently unnecc. bc no vars are used
//TODO: better way of localizing text-nodes in long strings
/** underscore template fns attached to class */
PairedCollectionCreator.templates = PairedCollectionCreator.templates || {
/** the skeleton */
main : _.template([
'<div class="header flex-row no-flex"></div>',
'<div class="middle flex-row flex-row-container"></div>',
'<div class="footer flex-row no-flex">'
].join('')),
PairedCollectionCreator.templates = _.extend(PairedCollectionCreator.templates || {}, baseCreator.CollectionCreatorMixin._creatorTemplates, {
/** the header (not including help text) */
header : _.template([
@@ -1665,7 +1603,7 @@ PairedCollectionCreator.templates = PairedCollectionCreator.templates || {
'(Note: you do not have to pair all unpaired datasets to finish.)'
].join( '' )), '</p>'
].join(''))
};
});
//=============================================================================
@@ -729,27 +729,34 @@ var FolderToolbarView = Backbone.View.extend({
*/
addAllDatasetsFromHistory : function (){
var checked_hdas = this.modal.$el.find( '#selected_history_content' ).find( ':checked' );
var history_dataset_ids = [];
var hdas_to_add = [];
var history_item_ids = []; // can be hda or hdca
var history_item_types = [];
var items_to_add = [];
if ( checked_hdas.length < 1 ){
mod_toastr.info( 'You must select some datasets first.' );
} else {
this.modal.disableButton( 'Add' );
checked_hdas.each(function(){
var hid = $( this.parentElement ).data( 'id' );
if ( hid ) {
history_dataset_ids.push( hid );
}
if ( hid ) {
var item_type = $( this.parentElement ).data( 'name' );
history_item_ids.push( hid );
history_item_types.push( item_type );
}
});
for ( var i = history_dataset_ids.length - 1; i >= 0; i-- ) {
history_dataset_id = history_dataset_ids[i];
for ( var i = history_item_ids.length - 1; i >= 0; i-- ) {
history_item_id = history_item_ids[i];
var folder_item = new mod_library_model.Item();
folder_item.url = Galaxy.root + 'api/folders/' + this.options.id + '/contents';
folder_item.set( { 'from_hda_id':history_dataset_id } );
hdas_to_add.push( folder_item );
if (history_item_types[i] === 'collection') {
folder_item.set({'from_hdca_id': history_item_id});
} else {
folder_item.set({'from_hda_id': history_item_id});
}
items_to_add.push(folder_item);
}
this.initChainCallControl( { length: hdas_to_add.length, action: 'adding_datasets' } );
this.chainCallAddingHdas( hdas_to_add );
this.initChainCallControl( { length: items_to_add.length, action: 'adding_datasets' } );
this.chainCallAddingHdas( items_to_add );
}
},
@@ -1387,9 +1394,26 @@ var FolderToolbarView = Backbone.View.extend({
'<strong>Choose the datasets to import:</strong>',
'<ul>',
'<% _.each(history_contents, function(history_item) { %>',
'<li data-id="<%= _.escape(history_item.get("id")) %>">',
'<input style="margin: 0;" type="checkbox"> <%= _.escape(history_item.get("hid")) %>: <%= _.escape(history_item.get("name")) %>',
'</li>',
'<% if (history_item.get("deleted") != true ) { %>',
'<% if (history_item.get("type") === "collection") { %>',
'<% var collection_type = history_item.get("collection_type") %>',
'<% if (collection_type === "list") { %>',
'<li data-id="<%= _.escape(history_item.get("id")) %>" data-name="<%= _.escape(history_item.get("type")) %>">',
'<input style="margin: 0;" type="checkbox"> <%= _.escape(history_item.get("hid")) %>: <%= _.escape(history_item.get("name")) %> (Dataset Collection)',
'</li>',
'<% } else { %>',
'<li><input style="margin: 0;" type="checkbox" onclick="return false;" disabled="disabled">',
'<span title="You can convert this collection into a collection of type list using the Collection Tools">',
' <%= _.escape(history_item.get("hid")) %>: <%= _.escape(history_item.get("name")) %> (Dataset Collection of type <%= _.escape(collection_type) %> not supported.)',
'</span>',
'</li>',
'<% } %>',
'<% } else if (history_item.get("visible") === true && history_item.get("state") === "ok") { %>',
'<li data-id="<%= _.escape(history_item.get("id")) %>" data-name="<%= _.escape(history_item.get("type")) %>">',
'<input style="margin: 0;" type="checkbox"> <%= _.escape(history_item.get("hid")) %>: <%= _.escape(history_item.get("name")) %>',
'</li>',
'<% } %>',
'<% } %>',
'<% }); %>',
'</ul>'
].join(''));
+1 -1
View File
@@ -482,7 +482,7 @@
<datatype extension="stockholm" type="galaxy.datatypes.msa:Stockholm_1_0" display_in_upload="true" />
<datatype extension="xmfa" type="galaxy.datatypes.msa:MauveXmfa" display_in_upload="true" />
<datatype extension="cel" type="galaxy.datatypes.binary:Cel" display_in_upload="true" />
<datatype extension="RData" type="galaxy.datatypes.binary:RData" display_in_upload="true" description="Stored data from an R session"/>
<datatype extension="rdata" type="galaxy.datatypes.binary:RData" display_in_upload="true" description="Stored data from an R session"/>
<datatype extension="oxlicg" type="galaxy.datatypes.binary:OxliCountGraph" mimetype="application/octet-stream" display_in_upload="true"/>
<datatype extension="oxling" type="galaxy.datatypes.binary:OxliNodeGraph" mimetype="application/octet-stream" display_in_upload="true"/>
<datatype extension="oxlits" type="galaxy.datatypes.binary:OxliTagSet" mimetype="application/octet-stream" display_in_upload="true"/>
@@ -0,0 +1,38 @@
name: tool_list
type:
- masthead
activate: true
icon: fa-list
tooltip: Generate tool list
function: >
setTimeout(function() {
alert('The tool file will be created and opened in a new window. Please ' +
'be patient, this might take a moment.');
}, 1);
$.getJSON("/api/webhooks/tool_list/get_data", function(data) {
var popup = window.open('tool_list.html');
var html = '<!DOCTYPE html><body><h2>' +
'Create a Docker flavour of this instance:</h2>' +
'<table style="width:100%"><tr><td width="50%">The textarea on ' +
'the right shows the list of installed tools on this Galaxy instance.' +
'You can use it to build a custom Galaxy flavour based on the same ' +
'tools. Keep in mind that this list only contains tools which can be ' +
'installed from a toolshed. <br><br> 1. Save this list as a <i>.yaml</i> ' +
'file and edit it according to your needs. You might want to add or ' +
'remove tools.<br>2. Follow ' +
'<a href="https://github.com/bgruening/docker-galaxy-stable' +
'#Extending-the-Docker-Image">this guide</a> on how to create a new ' +
'Galaxy Docker flavour. Dont forget to add and install the tool list ' +
'with <br><br><b>ADD my_tool_list.yml $GALAXY_ROOT/my_tool_list.yml' +
'</b> <br><br> and <br><br><b>' +
'RUN install-tools $GALAXY_ROOT/my_tool_list.yml</b></td>' +
'<td width="50%"><textarea rows="30" cols="78">' +
data.yaml +
'</textarea></td></tr></table></body>';
popup.document.write(html);
});
@@ -0,0 +1,39 @@
import yaml
def main(trans, webhook):
data = {}
data['tools'] = []
unique_tools = []
tools = trans.app.toolbox.tools()
for tool in tools:
try:
ts_data = tool[1].tool_shed_repository.to_dict()
panel = tool[1].get_panel_section()
except AttributeError:
continue
if (ts_data['name'] + ts_data['installed_changeset_revision'] not in
unique_tools):
unique_tools.append(
ts_data['name'] + ts_data['installed_changeset_revision']
)
data['tools'].append({
'name': ts_data['name'],
'owner': ts_data['owner'],
'tool_panel_section_label': panel[1],
'tool_shed_url': ts_data['tool_shed'],
'install_tool_dependencies': True,
'install_repository_dependencies': True,
'install_resolver_dependencies': True,
'revisions': [ts_data['installed_changeset_revision']]
})
if panel[0]:
data['tools'][-1]['tool_panel_section_id'] = panel[0]
return {'yaml': yaml.safe_dump(data, default_flow_style=False)}
+4 -3
View File
@@ -23,9 +23,9 @@ from galaxy.openid.providers import OpenIDProviders
from galaxy.tools.data_manager.manager import DataManagers
from galaxy.jobs import metrics as job_metrics
from galaxy.web.proxy import ProxyManager
from galaxy.web.stack import application_stack_instance
from galaxy.queue_worker import GalaxyQueueWorker
from galaxy.util import heartbeat
from galaxy.util.postfork import register_postfork_function
from tool_shed.galaxy_install import update_repository_manager
@@ -44,6 +44,7 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
log.debug( "python path is: %s", ", ".join( sys.path ) )
self.name = 'galaxy'
self.new_installation = False
self.application_stack = application_stack_instance()
# Read config file and check for errors
self.config = config.Configuration( **kwargs )
self.config.check()
@@ -151,7 +152,7 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
fname=self.config.heartbeat_log
)
self.heartbeat.daemon = True
register_postfork_function(self.heartbeat.start)
self.application_stack.register_postfork_function(self.heartbeat.start)
self.sentry_client = None
if self.config.sentry_dsn:
@@ -159,7 +160,7 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
import raven
self.sentry_client = raven.Client(self.config.sentry_dsn)
register_postfork_function(postfork_sentry_client)
self.application_stack.register_postfork_function(postfork_sentry_client)
# Transfer manager client
if self.config.get_bool( 'enable_beta_job_managers', False ):
+1 -1
View File
@@ -25,8 +25,8 @@ from galaxy.exceptions import ConfigurationError
from galaxy.util import listify
from galaxy.util import string_as_bool
from galaxy.util.dbkeys import GenomeBuilds
from galaxy.util.postfork import register_postfork_function
from galaxy.web.formatting import expand_pretty_datetime_format
from galaxy.web.stack import register_postfork_function
from .version import VERSION_MAJOR
log = logging.getLogger( __name__ )
+1 -1
View File
@@ -1148,7 +1148,7 @@ Binary.register_sniffable_binary_format('sra', 'sra', Sra)
class RData( Binary ):
"""Generic R Data file datatype implementation"""
file_ext = 'RData'
file_ext = 'rdata'
def sniff( self, filename ):
rdata_header = b'RDX2\nX\n'
+8 -5
View File
@@ -131,7 +131,9 @@ class TagManager( object ):
lc_name = name.lower()
# Get or create item-tag association.
item_tag_assoc = self._get_item_tag_assoc( user, item, lc_name )
if not item_tag_assoc:
# If the association does not exist, or if it has a different value, add another.
# We do allow multiple associations with different values.
if not item_tag_assoc or (item_tag_assoc and item_tag_assoc.value != value):
# Create item-tag association.
# Create tag; if None, skip the tag (and log error).
tag = self._get_or_create_tag( lc_name )
@@ -159,7 +161,7 @@ class TagManager( object ):
# Parse tags.
parsed_tags = self.parse_tags( tags_str )
# Apply each tag.
for name, value in parsed_tags.items():
for name, value in parsed_tags:
self.apply_item_tag( user, item, name, value )
def get_tags_str( self, tags ):
@@ -231,7 +233,7 @@ class TagManager( object ):
def parse_tags( self, tag_str ):
"""
Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well.
Return value is a dictionary where tag-names are keys.
Return value is a list of (tag_name, tag_value) tuples.
"""
# Gracefully handle None.
if not tag_str:
@@ -240,12 +242,13 @@ class TagManager( object ):
reg_exp = re.compile( '[' + self.tag_separators + ']' )
raw_tags = reg_exp.split( tag_str )
# Extract name-value pairs.
name_value_pairs = dict()
name_value_pairs = []
for raw_tag in raw_tags:
nv_pair = self._get_name_value_pair( raw_tag )
scrubbed_name = self._scrub_tag_name( nv_pair[0] )
scrubbed_value = self._scrub_tag_value( nv_pair[1] )
name_value_pairs[scrubbed_name] = scrubbed_value
# Append tag_name, tag_value tuple -- TODO use NamedTuple
name_value_pairs.append(( scrubbed_name, scrubbed_value ))
return name_value_pairs
def _scrub_tag_value( self, value ):
+3 -3
View File
@@ -2381,8 +2381,8 @@ class HistoryDatasetAssociation( DatasetInstance, Dictifiable, UsesAnnotations,
def copy_attributes( self, new_dataset ):
new_dataset.hid = self.hid
def to_library_dataset_dataset_association( self, trans, target_folder,
replace_dataset=None, parent_id=None, user=None, roles=None, ldda_message='' ):
def to_library_dataset_dataset_association( self, trans, target_folder, replace_dataset=None,
parent_id=None, user=None, roles=None, ldda_message='', element_identifier=None ):
"""
Copy this HDA to a library optionally replacing an existing LDDA.
"""
@@ -2400,7 +2400,7 @@ class HistoryDatasetAssociation( DatasetInstance, Dictifiable, UsesAnnotations,
if not user:
# This should never happen since users must be authenticated to upload to a data library
user = self.history.user
ldda = LibraryDatasetDatasetAssociation( name=self.name,
ldda = LibraryDatasetDatasetAssociation( name=element_identifier or self.name,
info=self.info,
blurb=self.blurb,
peek=self.peek,
+4 -2
View File
@@ -51,8 +51,9 @@ def send_control_task(app, task, noop_self=False, kwargs={}):
try:
c = Connection(app.config.amqp_internal_connection)
with producers[c].acquire(block=True) as producer:
control_queues = galaxy.queues.all_control_queues_for_declare(app.config, app.application_stack)
producer.publish(payload, exchange=galaxy.queues.galaxy_exchange,
declare=[galaxy.queues.galaxy_exchange] + galaxy.queues.all_control_queues_for_declare(app.config),
declare=[galaxy.queues.galaxy_exchange] + control_queues,
routing_key='control')
except Exception:
# This is likely connection refused.
@@ -86,6 +87,7 @@ def reload_tool(app, **kwargs):
def reload_toolbox(app, **kwargs):
log.debug("Executing toolbox reload on '%s'", app.config.server_name)
reload_count = app.toolbox._reload_count
app.tool_cache.cleanup()
app.toolbox = _get_new_toolbox(app)
app.toolbox._reload_count = reload_count + 1
@@ -203,7 +205,7 @@ class GalaxyQueueWorker(ConsumerMixin, threading.Thread):
# Default to figuring out which control queue to use based on the app config.
queue = galaxy.queues.control_queue_from_config(app.config)
self.task_mapping = task_mapping
self.declare_queues = galaxy.queues.all_control_queues_for_declare(app.config)
self.declare_queues = galaxy.queues.all_control_queues_for_declare(app.config, app.application_stack)
# TODO we may want to purge the queue at the start to avoid executing
# stale 'reload_tool', etc messages. This can happen if, say, a web
# process goes down and messages get sent before it comes back up.
+3 -8
View File
@@ -4,26 +4,21 @@ All message queues used by Galaxy
"""
from galaxy.util.postfork import process_is_uwsgi
from kombu import Exchange, Queue, Connection
ALL_CONTROL = "control.*"
galaxy_exchange = Exchange('galaxy_core_exchange', type='topic')
def all_control_queues_for_declare(config):
def all_control_queues_for_declare(config, application_stack):
"""
For in-memory routing (used by sqlalchemy-based transports), we need to be able to
build the entire routing table in producers.
Refactor later to actually persist this somewhere instead of building it repeatedly.
"""
possible_uwsgi_queues = []
if process_is_uwsgi:
import uwsgi
possible_uwsgi_queues = [Queue("control.%s.%s" % (config.server_name.split('.')[0], wkr['id']), galaxy_exchange, routing_key='control') for wkr in uwsgi.workers()]
return possible_uwsgi_queues + [Queue('control.%s' % q, galaxy_exchange, routing_key='control') for q in config.server_names]
possible_stack_queues = [Queue("control.%s.%s" % (config.server_name.split('.')[0], wkr['id']), galaxy_exchange, routing_key='control') for wkr in application_stack.workers()]
return possible_stack_queues + [Queue('control.%s' % q, galaxy_exchange, routing_key='control') for q in config.server_names]
def control_queue_from_config(config):
-3
View File
@@ -1,3 +0,0 @@
"""
Galaxy tagging classes and methods.
"""
View File
+13 -1
View File
@@ -42,6 +42,7 @@ from galaxy.tools.deps import (
CachedDependencyManager,
views
)
from galaxy.tools.fetcher import ToolLocationFetcher
from galaxy.tools.parameters import (
check_param,
params_from_strings,
@@ -124,6 +125,12 @@ GALAXY_LIB_TOOLS_UNVERSIONED = [
"maf_limit_size1",
"maf_by_block_number1",
"wiggle2simple1",
# Converters
"CONVERTER_fastq_to_fqtoc0",
"CONVERTER_gff_to_interval_index_0",
"CONVERTER_maf_to_fasta_0",
"CONVERTER_maf_to_interval_0",
"CONVERTER_wiggle_to_interval_0",
# Tools improperly migrated to the tool shed (devteam)
"lastz_wrapper_2",
"qualityFilter",
@@ -181,6 +188,7 @@ class ToolBox( BaseGalaxyToolBox ):
def __init__( self, config_filenames, tool_root_dir, app, tool_conf_watcher=None ):
self._reload_count = 0
self.tool_location_fetcher = ToolLocationFetcher()
super( ToolBox, self ).__init__(
config_filenames=config_filenames,
tool_root_dir=tool_root_dir,
@@ -216,7 +224,11 @@ class ToolBox( BaseGalaxyToolBox ):
def create_tool( self, config_file, repository_id=None, guid=None, **kwds ):
try:
tool_source = get_tool_source( config_file, enable_beta_formats=getattr( self.app.config, "enable_beta_tool_formats", False ) )
tool_source = get_tool_source(
config_file,
enable_beta_formats=getattr( self.app.config, "enable_beta_tool_formats", False ),
tool_location_fetcher=self.tool_location_fetcher,
)
except Exception as e:
# capture and log parsing errors
global_tool_errors.add_error(config_file, "Tool XML parsing", e)
+10
View File
@@ -17,6 +17,7 @@ try:
workflow,
job,
process,
pathmapper,
)
except (ImportError, SyntaxError):
# Drop SyntaxError once cwltool supports Python 3
@@ -24,6 +25,7 @@ except (ImportError, SyntaxError):
workflow = None
job = None
process = None
pathmapper = None
try:
from cwltool import load_tool
@@ -41,6 +43,12 @@ except (ImportError, SyntaxError):
# Drop SyntaxError once schema_salad supports Python 3
schema_salad = None
try:
from schema_salad import ref_resolver
except (ImportError, SyntaxError):
ref_resolver = None
needs_shell_quoting = re.compile(r"""(^$|[\s|&;()<>\'"$@])""").search
@@ -66,9 +74,11 @@ def ensure_cwltool_available():
__all__ = (
'main',
'ref_resolver',
'load_tool',
'workflow',
'process',
'pathmapper',
'ensure_cwltool_available',
'schema_salad',
'shellescape',
+30 -14
View File
@@ -18,11 +18,10 @@ from galaxy.util.odict import odict
from .cwltool_deps import (
ensure_cwltool_available,
main,
workflow,
process,
)
from .schema import schema_loader
from .schema import non_strict_schema_loader, schema_loader
log = logging.getLogger(__name__)
@@ -42,36 +41,36 @@ SUPPORTED_WORKFLOW_REQUIREMENTS = SUPPORTED_TOOL_REQUIREMENTS + [
]
def tool_proxy(tool_path):
def tool_proxy(tool_path, strict_cwl_validation=True):
""" Provide a proxy object to cwltool data structures to just
grab relevant data.
"""
ensure_cwltool_available()
tool = to_cwl_tool_object(tool_path)
tool = to_cwl_tool_object(tool_path, strict_cwl_validation=strict_cwl_validation)
return tool
def workflow_proxy(workflow_path):
def workflow_proxy(workflow_path, strict_cwl_validation=True):
ensure_cwltool_available()
workflow = to_cwl_workflow_object(workflow_path)
workflow = to_cwl_workflow_object(workflow_path, strict_cwl_validation=strict_cwl_validation)
return workflow
def load_job_proxy(job_directory):
def load_job_proxy(job_directory, strict_cwl_validation=True):
ensure_cwltool_available()
job_objects_path = os.path.join(job_directory, JOB_JSON_FILE)
job_objects = json.load(open(job_objects_path, "r"))
tool_path = job_objects["tool_path"]
job_inputs = job_objects["job_inputs"]
output_dict = job_objects["output_dict"]
cwl_tool = tool_proxy(tool_path)
cwl_tool = tool_proxy(tool_path, strict_cwl_validation=strict_cwl_validation)
cwl_job = cwl_tool.job_proxy(job_inputs, output_dict, job_directory=job_directory)
return cwl_job
def to_cwl_tool_object(tool_path):
def to_cwl_tool_object(tool_path, strict_cwl_validation=True):
proxy_class = None
cwl_tool = schema_loader.tool(path=tool_path)
cwl_tool = _schema_loader(strict_cwl_validation).tool(path=tool_path)
if isinstance(cwl_tool, int):
raise Exception("Failed to load tool.")
@@ -93,10 +92,9 @@ def to_cwl_tool_object(tool_path):
return proxy
def to_cwl_workflow_object(workflow_path):
def to_cwl_workflow_object(workflow_path, strict_cwl_validation=None):
proxy_class = WorkflowProxy
make_tool = workflow.defaultMakeTool
cwl_workflow = main.load_tool(workflow_path, False, False, make_tool, False)
cwl_workflow = _schema_loader(strict_cwl_validation).tool(path=workflow_path)
raw_workflow = cwl_workflow.tool
check_requirements(raw_workflow, tool=False)
@@ -104,6 +102,11 @@ def to_cwl_workflow_object(workflow_path):
return proxy
def _schema_loader(strict_cwl_validation):
target_schema_loader = schema_loader if strict_cwl_validation else non_strict_schema_loader
return target_schema_loader
def check_requirements(rec, tool=True):
if isinstance(rec, dict):
if "requirements" in rec:
@@ -237,14 +240,21 @@ class JobProxy(object):
def _ensure_cwl_job_initialized(self):
if self._cwl_job is None:
self._cwl_job = next(self._tool_proxy._tool.job(
self._input_dict,
self._output_callback,
basedir=self._job_directory,
select_resources=self._select_resources,
use_container=False
))
self._is_command_line_job = hasattr(self._cwl_job, "command_line")
def _select_resources(self, request):
new_request = request.copy()
new_request["cores"] = "$GALAXY_SLOTS"
return new_request
@property
def command_line(self):
if self.is_command_line_job:
@@ -324,6 +334,12 @@ class JobProxy(object):
safe_makedirs(secondary_files_dir)
return secondary_files_dir
def stage_files(self):
cwl_job = self.cwl_job()
if hasattr(cwl_job, "pathmapper"):
process.stageFiles(self.cwl_job().pathmapper, os.symlink, ignoreWritable=True)
# else: expression tools do not have a path mapper.
@staticmethod
def _job_file(job_directory):
return os.path.join(job_directory, JOB_JSON_FILE)
+4 -5
View File
@@ -19,6 +19,7 @@ GALAXY_TO_CWL_TYPES = {
'float': 'float',
'data': 'File',
'boolean': 'boolean',
'text': 'text'
}
@@ -56,7 +57,8 @@ def to_cwl_job(tool, param_dict, local_working_directory):
os.symlink(secondary_file_path, new_input_path + secondary_file_name)
path = new_input_path
return {"path": path, "class": "File"}
return {"location": path,
"class": "File"}
elif cwl_type == "integer":
return int(str(param_dict_value))
elif cwl_type == "long":
@@ -67,7 +69,7 @@ def to_cwl_job(tool, param_dict, local_working_directory):
return float(str(param_dict_value))
elif cwl_type == "boolean":
return string_as_bool(param_dict_value)
elif cwl_type == "string":
elif cwl_type == "text":
return str(param_dict_value)
elif cwl_type == "json":
raw_value = param_dict_value.value
@@ -94,9 +96,6 @@ def to_cwl_job(tool, param_dict, local_working_directory):
else:
input_json[input_name] = simple_value(input, param_dict[input_name])
input_json["allocatedResources"] = {
"cpu": "$GALAXY_SLOTS",
}
return input_json
+9 -5
View File
@@ -2,6 +2,7 @@ import json
import os
import shutil
from .cwltool_deps import ref_resolver
from .parser import (
JOB_JSON_FILE,
load_job_proxy,
@@ -17,13 +18,16 @@ def handle_outputs(job_directory=None):
if not os.path.exists(cwl_job_file):
# Not a CWL job, just continue
return
job_proxy = load_job_proxy(job_directory)
# So we only need to do strict validation when the tool was loaded,
# no reason to do it again during job execution - so this shortcut
# allows us to not need Galaxy's full configuration on job nodes.
job_proxy = load_job_proxy(job_directory, strict_cwl_validation=False)
tool_working_directory = os.path.join(job_directory, "working")
outputs = job_proxy.collect_outputs(tool_working_directory)
for output_name, output in outputs.items():
target_path = job_proxy.output_path( output_name )
if isinstance(output, dict) and "path" in output:
output_path = output["path"]
target_path = job_proxy.output_path(output_name)
if isinstance(output, dict) and "location" in output:
output_path = ref_resolver.uri_file_path(output["location"])
if output["class"] != "File":
open("galaxy.json", "w").write(json.dump({
"dataset_id": job_proxy.output_id(output_name),
@@ -33,7 +37,7 @@ def handle_outputs(job_directory=None):
shutil.move(output_path, target_path)
for secondary_file in output.get("secondaryFiles", []):
# TODO: handle nested files...
secondary_file_path = secondary_file["path"]
secondary_file_path = ref_resolver.uri_file_path(secondary_file["location"])
assert secondary_file_path.startswith(output_path)
secondary_file_name = secondary_file_path[len(output_path):]
secondary_files_dir = job_proxy.output_secondary_files_dir(
+3 -5
View File
@@ -24,11 +24,8 @@ class SchemaLoader(object):
@property
def raw_document_loader(self):
if self._raw_document_loader is None:
ensure_cwltool_available()
self._raw_document_loader = schema_salad.ref_resolver.Loader({"cwl": "https://w3id.org/cwl/cwl#", "id": "@id"})
return self._raw_document_loader
ensure_cwltool_available()
return schema_salad.ref_resolver.Loader({"cwl": "https://w3id.org/cwl/cwl#", "id": "@id"})
def raw_process_reference(self, path):
uri = "file://" + os.path.abspath(path)
@@ -71,3 +68,4 @@ class SchemaLoader(object):
schema_loader = SchemaLoader()
non_strict_schema_loader = SchemaLoader(strict=False)
+54 -11
View File
@@ -30,6 +30,10 @@ VERSIONED_ENV_DIR_NAME = re.compile(r"__(.*)@(.*)")
UNVERSIONED_ENV_DIR_NAME = re.compile(r"__(.*)@_uv_")
USE_PATH_EXEC_DEFAULT = False
CONDA_VERSION = "4.2.13"
# 2.1.5 is incompatible with 4.2 Conda because of
# https://github.com/conda/conda-build/pull/1766
CONDA_BUILD_VERSION = "2.1.4"
USE_LOCAL_DEFAULT = False
def conda_link():
@@ -45,7 +49,16 @@ def find_conda_prefix(conda_prefix=None):
for Miniconda installs.
"""
if conda_prefix is None:
return os.path.join(os.path.expanduser("~"), "miniconda2")
home = os.path.expanduser("~")
miniconda_2_dest = os.path.join(home, "miniconda2")
miniconda_3_dest = os.path.join(home, "miniconda3")
# Prefer miniconda3 install if both available
if os.path.exists(miniconda_3_dest):
return miniconda_3_dest
elif os.path.exists(miniconda_2_dest):
return miniconda_2_dest
else:
return miniconda_3_dest
return conda_prefix
@@ -54,7 +67,8 @@ class CondaContext(installable.InstallableContext):
def __init__(self, conda_prefix=None, conda_exec=None,
shell_exec=None, debug=False, ensure_channels='',
condarc_override=None, use_path_exec=USE_PATH_EXEC_DEFAULT, copy_dependencies=False):
condarc_override=None, use_path_exec=USE_PATH_EXEC_DEFAULT,
copy_dependencies=False, use_local=USE_LOCAL_DEFAULT):
self.condarc_override = condarc_override
if not conda_exec and use_path_exec:
conda_exec = commands.which("conda")
@@ -84,18 +98,27 @@ class CondaContext(installable.InstallableContext):
self.ensured_channels = False
self._conda_version = None
self._miniconda_version = None
self._conda_build_available = None
self.use_local = use_local
@property
def conda_version(self):
if self._conda_version is None:
self._guess_conda_version()
self._guess_conda_properties()
return self._conda_version
def _guess_conda_version(self):
@property
def conda_build_available(self):
if self._conda_build_available is None:
self._guess_conda_properties()
return self._conda_build_available
def _guess_conda_properties(self):
conda_meta_path = self._conda_meta_path
# Perhaps we should call "conda info --json" and parse it but for now we are going
# to assume the default.
conda_version = LooseVersion(CONDA_VERSION)
conda_build_available = False
miniconda_version = "3"
if os.path.exists(conda_meta_path):
@@ -110,9 +133,12 @@ class CondaContext(installable.InstallableContext):
conda_version = LooseVersion(version)
if package == "python" and version.startswith("2"):
miniconda_version = "2"
if package == "conda-build":
conda_build_available = True
self._conda_version = conda_version
self._miniconda_version = miniconda_version
self._conda_build_available = conda_build_available
@property
def _conda_meta_path(self):
@@ -135,6 +161,14 @@ class CondaContext(installable.InstallableContext):
if changed:
self.save_condarc(conda_conf)
def ensure_conda_build_installed_if_needed(self):
if self.use_local and not self.conda_build_available:
conda_targets = [CondaTarget("conda-build", version=CONDA_BUILD_VERSION)]
# Cannot use --use-local during installation fo conda-build.
return install_conda_targets(conda_targets, env_name=None, conda_context=self, allow_local=False)
else:
return 0
def conda_info(self):
if self.conda_exec is not None:
info_out = commands.execute([self.conda_exec, "info", "--json"])
@@ -229,10 +263,12 @@ class CondaContext(installable.InstallableContext):
finally:
shutil.rmtree(env['HOME'], ignore_errors=True)
def exec_create(self, args):
def exec_create(self, args, allow_local=True):
create_base_args = [
"-y"
]
if allow_local and self.use_local:
create_base_args.extend(["--use-local"])
create_base_args.extend(args)
return self.exec_command("create", create_base_args)
@@ -246,10 +282,12 @@ class CondaContext(installable.InstallableContext):
remove_base_args.extend(args)
return self.exec_command("env", remove_base_args)
def exec_install(self, args):
def exec_install(self, args, allow_local=True):
install_base_args = [
"-y"
]
if allow_local and self.use_local:
install_base_args.extend(["--use-local"])
install_base_args.extend(args)
return self.exec_command("install", install_base_args)
@@ -390,13 +428,18 @@ def hash_conda_packages(conda_packages, conda_target=None):
# shell makes sense for planemo, in Galaxy this should just execute
# these commands as Python
def install_conda(conda_context=None):
def install_conda(conda_context=None, force_conda_build=False):
conda_context = _ensure_conda_context(conda_context)
f, script_path = tempfile.mkstemp(suffix=".sh", prefix="conda_install")
os.close(f)
download_cmd = " ".join(commands.download_command(conda_link(), to=script_path, quote_url=True))
install_cmd = "bash '%s' -b -p '%s'" % (script_path, conda_context.conda_prefix)
fix_version_cmd = "%s install -y -q conda=%s " % (os.path.join(conda_context.conda_prefix, 'bin/conda'), CONDA_VERSION)
package_targets = [
"conda=%s" % CONDA_VERSION,
]
if force_conda_build or conda_context.use_local:
package_targets.append("conda-build=%s" % CONDA_BUILD_VERSION)
fix_version_cmd = "%s install -y -q %s " % (os.path.join(conda_context.conda_prefix, 'bin/conda'), " ".join(package_targets))
full_command = "%s && %s && %s" % (download_cmd, install_cmd, fix_version_cmd)
try:
log.info("Installing Conda, this may take several minutes.")
@@ -406,7 +449,7 @@ def install_conda(conda_context=None):
os.remove(script_path)
def install_conda_targets(conda_targets, env_name=None, conda_context=None):
def install_conda_targets(conda_targets, env_name=None, conda_context=None, allow_local=True):
conda_context = _ensure_conda_context(conda_context)
conda_context.ensure_channels_configured()
if env_name is not None:
@@ -415,9 +458,9 @@ def install_conda_targets(conda_targets, env_name=None, conda_context=None):
]
for conda_target in conda_targets:
create_args.append(conda_target.package_specifier)
return conda_context.exec_create(create_args)
return conda_context.exec_create(create_args, allow_local=allow_local)
else:
return conda_context.exec_install([t.package_specifier for t in conda_targets])
return conda_context.exec_install([t.package_specifier for t in conda_targets], allow_local=allow_local)
def install_conda_target(conda_target, conda_context=None, skip_environment=False):
@@ -11,6 +11,7 @@ from galaxy.util.dictifiable import Dictifiable
@six.add_metaclass(ABCMeta)
@six.python_2_unicode_compatible
class ContainerResolver(Dictifiable, object):
"""Description of a technique for resolving container images for tool execution."""
@@ -49,3 +50,6 @@ class ContainerResolver(Dictifiable, object):
def _container_type_enabled(self, container_description, enabled_container_types):
"""Return a boolean indicating if the specified container type is enabled."""
return container_description.type in enabled_container_types
def __str__(self):
return "%s[]" % self.__class__.__name__
@@ -3,6 +3,8 @@
import collections
import logging
import six
from ..container_resolvers import (
ContainerResolver,
)
@@ -102,6 +104,7 @@ def cached_container_description(targets, namespace):
return container
@six.python_2_unicode_compatible
class CachedMulledContainerResolver(ContainerResolver):
resolver_type = "cached_mulled"
@@ -114,7 +117,11 @@ class CachedMulledContainerResolver(ContainerResolver):
targets = mulled_targets(tool_info)
return cached_container_description(targets, self.namespace)
def __str__(self):
return "CachedMulledContainerResolver[namespace=%s]" % self.namespace
@six.python_2_unicode_compatible
class MulledContainerResolver(ContainerResolver):
"""Look for mulled images matching tool dependencies."""
@@ -160,7 +167,11 @@ class MulledContainerResolver(ContainerResolver):
type="docker",
)
def __str__(self):
return "MulledContainerResolver[namespace=%s]" % self.namespace
@six.python_2_unicode_compatible
class BuildMulledContainerResolver(ContainerResolver):
"""Look for mulled images matching tool dependencies."""
@@ -195,6 +206,9 @@ class BuildMulledContainerResolver(ContainerResolver):
self.enabled = ensure_installed(involucro_context, self.auto_init)
return involucro_context
def __str__(self):
return "BuildContainerResolver[namespace=%s]" % self.namespace
def mulled_targets(tool_info):
return requirements_to_mulled_targets(tool_info.requirements)
+35 -5
View File
@@ -40,19 +40,49 @@ for i = 1, #test_binds_table do
table.insert(test_bind_args, test_binds_table[i])
end
local conda_image = VAR.CONDA_IMAGE
if conda_image == '' then
conda_image = 'continuumio/miniconda:latest'
end
local destination_base_image = VAR.DEST_BASE_IMAGE
if destination_base_image == '' then
destination_base_image = 'bgruening/busybox-bash:0.1'
end
local verbose = VAR.VERBOSE
if verbose == '' then
verbose = '--quiet'
else
verbose = '--verbose'
end
local preinstall = VAR.PREINSTALL
if preinstall ~= '' then
preinstall = preinstall .. ' && '
end
local postinstall = VAR.POSTINSTALL
if postinstall ~= '' then
postinstall = '&&' .. postinstall
end
inv.task('build')
.using('continuumio/miniconda:latest')
.using(conda_image)
.withHostConfig({binds = {"build:/data"}})
.run('rm', '-rf', '/data/dist')
.using('continuumio/miniconda:latest')
.using(conda_image)
.withHostConfig({binds = bind_args})
.run('/bin/sh', '-c', 'conda install '
.run('/bin/sh', '-c', preinstall
.. 'conda install '
.. channel_args .. ' '
.. target_args
.. ' -p /usr/local --copy --yes --quiet')
.. ' -p /usr/local --copy --yes '
.. verbose
.. postinstall)
.wrap('build/dist')
.at('/usr/local')
.inImage('bgruening/busybox-bash:0.1')
.inImage(destination_base_image)
.as(repo)
if VAR.TEST_BINDS == '' then
+15 -3
View File
@@ -116,7 +116,7 @@ def mull_targets(
command="build", channels=DEFAULT_CHANNELS, namespace="mulled",
test='true', test_files=None, image_build=None, name_override=None,
repository_template=DEFAULT_REPOSITORY_TEMPLATE, dry_run=False,
binds=DEFAULT_BINDS
conda_version=None, verbose=False, binds=DEFAULT_BINDS
):
targets = list(targets)
if involucro_context is None:
@@ -143,8 +143,13 @@ def mull_targets(
'-set', "TARGETS='%s'" % target_str,
'-set', "REPO='%s'" % repo,
'-set', "BINDS='%s'" % bind_str,
command,
]
if verbose:
involucro_args.extend(["-set", "VERBOSE='1'"])
if conda_version is not None:
verbose = "--verbose" if verbose else "--quiet"
involucro_args.extend(["-set", "PREINSTALL='conda install %s --yes conda=%s'" % (verbose, conda_version)])
involucro_args.append(command)
if test_files:
test_bind = []
for test_file in test_files:
@@ -165,7 +170,8 @@ def mull_targets(
def context_from_args(args):
return InvolucroContext(involucro_bin=args.involucro_path)
verbose = "2" if not args.verbose else "3"
return InvolucroContext(involucro_bin=args.involucro_path, verbose=verbose)
class InvolucroContext(installable.InstallableContext):
@@ -220,6 +226,8 @@ def add_build_arguments(parser):
help="Rebuild package even if already published.")
parser.add_argument('--dry-run', dest='dry_run', action="store_true",
help='Just print commands instead of executing them.')
parser.add_argument('--verbose', dest='verbose', action="store_true",
help='Cause process to be verbose.')
parser.add_argument('-n', '--namespace', dest='namespace', default="mulled",
help='quay.io namespace.')
parser.add_argument('-r', '--repository_template', dest='repository_template', default=DEFAULT_REPOSITORY_TEMPLATE,
@@ -228,6 +236,8 @@ def add_build_arguments(parser):
help='Target conda channel')
parser.add_argument('--extra-channels', dest='extra_channels', default=",".join(DEFAULT_EXTRA_CHANNELS),
help='Dependent conda channels.')
parser.add_argument('--conda-version', dest="conda_version", default=None,
help="Change to specified version of Conda before installing packages.")
def add_single_image_arguments(parser):
@@ -277,6 +287,8 @@ def args_to_mull_targets_kwds(args):
kwds["command"] = args.command
if hasattr(args, "repository_template"):
kwds["repository_template"] = args.repository_template
if hasattr(args, "conda_version"):
kwds["conda_version"] = args.conda_version
kwds["involucro_context"] = context_from_args(args)
+4
View File
@@ -151,6 +151,7 @@ DEFAULT_CONTAINER_RESOLVE_DEPENDENCIES = False
DEFAULT_CONTAINER_SHELL = "/bin/sh" # Galaxy assumes bash, but containers are usually thinner.
@six.python_2_unicode_compatible
class ContainerDescription( object ):
def __init__(
@@ -186,6 +187,9 @@ class ContainerDescription( object ):
shell=shell,
)
def __str__(self):
return "ContainerDescription[identifier=%s,type=%s]" % (self.identifier, self.type)
def parse_requirements_from_dict( root_dict ):
requirements = root_dict.get("requirements", [])
+6 -1
View File
@@ -55,6 +55,7 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
'auto_install': False,
'auto_init': True,
'copy_dependencies': False,
'use_local': False,
}
_specification_pattern = re.compile(r"https\:\/\/anaconda.org\/\w+\/\w+")
@@ -84,6 +85,7 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
)
copy_dependencies = _string_as_bool(get_option("copy_dependencies"))
use_local = _string_as_bool(get_option("use_local"))
conda_exec = get_option("exec")
debug = _string_as_bool(get_option("debug"))
ensure_channels = get_option("ensure_channels")
@@ -102,7 +104,8 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
ensure_channels=ensure_channels,
condarc_override=condarc_override,
use_path_exec=use_path_exec,
copy_dependencies=copy_dependencies
copy_dependencies=copy_dependencies,
use_local=use_local,
)
self.ensure_channels = ensure_channels
@@ -111,6 +114,8 @@ class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, Li
self.auto_init = _string_as_bool(get_option("auto_init"))
self.conda_context = conda_context
self.disabled = not galaxy.tools.deps.installable.ensure_installed(conda_context, install_conda, self.auto_init)
if self.auto_init and not self.disabled:
self.conda_context.ensure_conda_build_installed_if_needed()
self.auto_install = auto_install
self.copy_dependencies = copy_dependencies
+25
View File
@@ -0,0 +1,25 @@
from galaxy.util import plugin_config
class ToolLocationFetcher(object):
def __init__(self):
self.resolver_classes = self.__resolvers_dict()
def __resolvers_dict( self ):
import galaxy.tools.locations
return plugin_config.plugins_dict(galaxy.tools.locations, 'scheme')
def to_tool_path(self, path_or_uri_like, **kwds):
if "://" not in path_or_uri_like:
path = path_or_uri_like
else:
uri_like = path_or_uri_like
if ":" not in path_or_uri_like:
raise Exception("Invalid URI passed to get_tool_source")
scheme, rest = uri_like.split(":", 2)
if scheme not in self.resolver_classes:
raise Exception("Unknown tool scheme [%s] for URI [%s]" % (scheme, uri_like))
path = self.resolver_classes[scheme]().get_tool_source_path(uri_like)
return path
@@ -34,12 +34,13 @@
</output_collection>
</test>
</tests>
<help>
This tool takes a list dataset collection and filters out the failed
datasets from it. This is useful for continuing a multi-sample analysis
when one of more of the samples fails at some point.
<help><![CDATA[
This tool will create new history datasets from your collection
but your quota usage will not increase.
</help>
This tool takes a list dataset collection and filters out datasets in the failed state. This is useful for continuing a multi-sample analysis when one of more of the samples fails at some point.
.. class:: infomark
This tool will create new history datasets from your collection but your quota usage will not increase.
]]></help>
</tool>
+8 -8
View File
@@ -44,13 +44,13 @@
</output_collection>
</test>
</tests>
<help>
This tool takes nested collections such as a list of lists or a list of
dataset pairs - and produces a flat list from the inputs. The collection
identifiers are merged together to create new collection identifiers in
the flattened result.
<help><![CDATA[
This tool will create new history datasets from your collection
but your quota usage will not increase.
</help>
This tool takes nested collections such as a list of lists or a list of dataset pairs - and produces a flat list from the inputs. The collection identifiers are merged together to create new collection identifiers in the flattened result.
.. class:: infomark
This tool will create new history datasets from your collection but your quota usage will not increase.
]]></help>
</tool>
+25 -2
View File
@@ -119,7 +119,7 @@ def is_tool_load_error(obj):
return obj is TOOL_LOAD_ERROR
def looks_like_a_tool(path, invalid_names=[], enable_beta_formats=False):
def looks_like_a_tool(path_or_uri_like, invalid_names=[], enable_beta_formats=False):
"""Quick check to see if a file looks like it may be a tool file.
Whether true in a strict sense or not, lets say the intention and
@@ -130,6 +130,11 @@ def looks_like_a_tool(path, invalid_names=[], enable_beta_formats=False):
invalid_names may be supplied in the context of the tool shed to quickly
rule common tool shed XML files.
"""
path = resolved_path(path_or_uri_like)
if path is UNRESOLVED_URI:
# Assume the path maps to a real tool.
return True
looks = False
if os.path.basename(path) in invalid_names:
@@ -222,7 +227,12 @@ def looks_like_a_tool_cwl(path):
return looks_like_a_cwl_artifact(path, classes=["CommandLineTool", "ExpressionTool"])
def _find_tool_files(path, recursive, enable_beta_formats):
def _find_tool_files(path_or_uri_like, recursive, enable_beta_formats):
path = resolved_path(path_or_uri_like)
if path is UNRESOLVED_URI:
# Pass the URI through and assume it maps to a real tool.
return [path_or_uri_like]
is_file = not os.path.isdir(path)
if not os.path.exists(path):
raise Exception(PATH_DOES_NOT_EXIST_ERROR)
@@ -261,6 +271,19 @@ def _find_files(directory, pattern='*'):
return matches
UNRESOLVED_URI = object()
def resolved_path(path_or_uri_like):
"""If this is a simple file path, return the path else UNRESOLVED_URI."""
if "://" not in path_or_uri_like:
return path_or_uri_like
elif path_or_uri_like.startswith("file://"):
return path_or_uri_like[len("file://"):]
else:
return UNRESOLVED_URI
BETA_TOOL_CHECKERS = {
'yaml': looks_like_a_tool_yaml,
'cwl': looks_like_a_tool_cwl,
+29
View File
@@ -0,0 +1,29 @@
import os
import tempfile
from abc import (
ABCMeta,
abstractmethod,
abstractproperty,
)
import six
@six.add_metaclass(ABCMeta)
class ToolLocationResolver(object):
"""Parse a URI-like string and return a ToolSource object."""
@abstractproperty
def scheme(self):
"""Short label for the type of location resolver and URI scheme."""
@abstractmethod
def get_tool_source_path(self, uri_like):
"""Return a local path for the uri_like string."""
def _temp_path(self, uri_like):
"""Create an abstraction for this so we can configure and cache later."""
handle, filename = tempfile.mkstemp(suffix=uri_like.split("/")[-1])
os.close(handle)
return filename
+69
View File
@@ -0,0 +1,69 @@
try:
import requests
except ImportError:
requests = None
import yaml
from six.moves.urllib.parse import quote
from ..locations import (
ToolLocationResolver,
)
class DockStoreResolver(ToolLocationResolver):
scheme = "dockstore"
def get_tool_source_path(self, uri_like):
assert uri_like.startswith("dockstore://")
tool_id = uri_like[len("dockstore://"):]
if ":" in tool_id:
tool_id, version = tool_id.split(":", 1)
else:
tool_id, version = tool_id, "latest"
tmp_path = self._temp_path(uri_like + ".cwl")
cwl_str = _Ga4ghToolClient().get_tool_cwl(tool_id, version=version, as_string=True)
with open(tmp_path, "wb") as f:
f.write(cwl_str)
return tmp_path
class _Ga4ghToolClient(object):
def __init__(self, base_url="https://www.dockstore.org:8443/api"):
self.base_url = base_url
def get_tools(self):
return self._requests.get("%s/ga4gh/v1/tools" % self.base_url)
def get_tool(self, tool_id):
url = "%s/ga4gh/v1/tools/%s" % (self.base_url, quote(tool_id, safe=''))
return self._requests.get(url)
def get_tool_version(self, tool_id, version="latest"):
url = "%s/ga4gh/v1/tools/%s/versions/%s" % (self.base_url, quote(tool_id, safe=''), version)
return self._requests.get(url)
def get_tool_descriptor(self, tool_id, version="latest", tool_type="CWL"):
url = "%s/ga4gh/v1/tools/%s/versions/%s/%s/descriptor" % (self.base_url, quote(tool_id, safe=''), version, tool_type)
return self._requests.get(url)
def get_tool_cwl(self, tool_id, version="latest", as_string=False):
tool_type = "CWL"
url = "%s/ga4gh/v1/tools/%s/versions/%s/%s/descriptor" % (self.base_url, quote(tool_id, safe=''), version, tool_type)
descriptor_response = self._requests.get(url)
descriptor_str = descriptor_response.json()["descriptor"]
if as_string:
return descriptor_str
else:
return yaml.load(descriptor_str)
@property
def _requests(self):
if requests is None:
raise Exception("requests Python library needs to be installed use GA4GH APIs")
return requests
__all__ = ("DockStoreResolver",)
+12
View File
@@ -0,0 +1,12 @@
from ..locations import (
ToolLocationResolver,
)
class HttpToolResolver(ToolLocationResolver):
scheme = "file"
def get_tool_source_path(self, uri_like):
assert uri_like.startswith("file://")
return uri_like[len("file://"):]
+26
View File
@@ -0,0 +1,26 @@
from galaxy.util import download_to_file
from ..locations import (
ToolLocationResolver,
)
class HttpToolResolver(ToolLocationResolver):
scheme = "http"
def __init__(self, **kwds):
pass
def get_tool_source_path(self, uri_like):
tmp_path = self._temp_path(uri_like)
download_to_file(uri_like, tmp_path)
return tmp_path
class HttpsToolResolver(HttpToolResolver):
scheme = "https"
__all__ = ("HttpToolResolver", "HttpsToolResolver")
+8 -5
View File
@@ -270,10 +270,13 @@
</output_collection>
</test>
</tests>
<help>
This tool takes two list and creates a single unified list.
<help><![CDATA[
This tool takes two lists and creates a single unified list.
This tool will create new history datasets for your collection
but your quota usage will not increase.
</help>
.. class:: infomark
This tool will create new history datasets for your collection but your quota usage will not increase.
]]></help>
</tool>
+4 -3
View File
@@ -18,16 +18,17 @@ log = logging.getLogger(__name__)
class CwlToolSource(ToolSource):
def __init__(self, tool_file):
def __init__(self, tool_file, strict_cwl_validation=True):
self._cwl_tool_file = tool_file
self._id, _ = os.path.splitext(os.path.basename(tool_file))
self._tool_proxy = None
self._source_path = tool_file
self._strict_cwl_validation = strict_cwl_validation
@property
def tool_proxy(self):
if self._tool_proxy is None:
self._tool_proxy = tool_proxy(self._source_path)
self._tool_proxy = tool_proxy(self._source_path, strict_cwl_validation=self._strict_cwl_validation)
return self._tool_proxy
def parse_tool_type(self):
@@ -95,7 +96,7 @@ class CwlToolSource(ToolSource):
return "0.0.1"
def parse_description(self):
return ""
return self.tool_proxy.description()
def parse_input_pages(self):
page_source = CwlPageSource(self.tool_proxy)
+7 -1
View File
@@ -13,10 +13,12 @@ from .interface import InputSource
from .xml import XmlInputSource, XmlToolSource
from .yaml import YamlToolSource
from ..fetcher import ToolLocationFetcher
log = logging.getLogger(__name__)
def get_tool_source(config_file=None, xml_tree=None, enable_beta_formats=True):
def get_tool_source(config_file=None, xml_tree=None, enable_beta_formats=True, tool_location_fetcher=None):
"""Return a ToolSource object corresponding to supplied source.
The supplied source may be specified as a file path (using the config_file
@@ -27,6 +29,10 @@ def get_tool_source(config_file=None, xml_tree=None, enable_beta_formats=True):
elif config_file is None:
raise ValueError("get_tool_source called with invalid config_file None.")
if tool_location_fetcher is None:
tool_location_fetcher = ToolLocationFetcher()
config_file = tool_location_fetcher.to_tool_path(config_file)
if not enable_beta_formats:
tree = load_tool_xml(config_file)
return XmlToolSource(tree, source_path=config_file)
+17
View File
@@ -1,3 +1,6 @@
import os
from galaxy.util.hash_util import md5_hash_file
class ToolCache(object):
@@ -7,9 +10,20 @@ class ToolCache(object):
"""
def __init__(self):
self._hash_by_tool_paths = {}
self._tools_by_path = {}
self._tool_paths_by_id = {}
def cleanup(self):
"""Remove uninstalled tools from tool cache if they are not on disk anymore or if their content has changed."""
paths_to_cleanup = {path: tool.all_ids for path, tool in self._tools_by_path.items() if not os.path.exists(path) or md5_hash_file(path) != self._hash_by_tool_paths[path]}
for config_filename, tool_ids in paths_to_cleanup.items():
del self._hash_by_tool_paths[config_filename]
del self._tools_by_path[config_filename]
for tool_id in tool_ids:
if tool_id in self._tool_paths_by_id:
del self._tool_paths_by_id[tool_id]
def get_tool(self, config_filename):
""" Get the tool from the cache if the tool is up to date.
"""
@@ -18,10 +32,13 @@ class ToolCache(object):
def expire_tool(self, tool_id):
if tool_id in self._tool_paths_by_id:
config_filename = self._tool_paths_by_id[tool_id]
del self._hash_by_tool_paths[config_filename]
del self._tool_paths_by_id[tool_id]
del self._tools_by_path[config_filename]
def cache_tool(self, config_filename, tool):
tool_hash = md5_hash_file(config_filename)
tool_id = str( tool.id )
self._hash_by_tool_paths[config_filename] = tool_hash
self._tool_paths_by_id[tool_id] = config_filename
self._tools_by_path[config_filename] = tool
+1 -1
View File
@@ -15,7 +15,7 @@ except ImportError:
can_watch = False
from galaxy.util.hash_util import md5_hash_file
from galaxy.util.postfork import register_postfork_function
from galaxy.web.stack import register_postfork_function
log = logging.getLogger( __name__ )
+14 -7
View File
@@ -12,12 +12,19 @@
<data name="forward" label="${on_string} (forward)" />
<data name="reverse" label="${on_string} (reverse)" />
</outputs>
<help>
This tool takes a paired dataset collection and builds two datasets
from it. If mapped over a list of paired datasets, this tool will
produce two lists of datasets.
<help><![CDATA[
This tool will create new history datasets from your collection
but your quota usage will not increase.
</help>
This tool takes a paired dataset collection and builds two datasets from it. If mapped over a list of paired datasets, this tool will produce two lists of datasets.
----
**Example**
If a collection consists of two forward and two reverse datasets (e.g., forward and reverse reads from a sequencing experiment) this tool will output two collections: one consisting of forward reads and one of reverse reads.
.. class:: infomark
This tool will create new history datasets from your collection but your quota usage will not increase.
]]></help>
</tool>
+14 -7
View File
@@ -15,12 +15,19 @@
<data name="reverse" />
</collection>
</outputs>
<help>
This tool takes two datasets and creates a dataset pair from
them. Mapping over two lists, this tool can be used to build
a list of dataset pairs from two individual lists of datasets.
<help><![CDATA[
This tool takes two datasets and creates a dataset pair from them. Mapping over two lists, this tool can be used to build a list of dataset pairs from two individual lists of datasets.
----
**Example**
If you have one collection containing only forward reads and one containing only reverse, this tools will "zip" them together into a simple paired collection.
This tool will create new history datasets for your collection
but your quota usage will not increase.
</help>
.. class:: infomark
This tool will create new history datasets for your collection but your quota usage will not increase.
]]></help>
</tool>
-43
View File
@@ -1,43 +0,0 @@
"""
Handle postfork functions under uWSGI
"""
# The uwsgi module is automatically injected by the parent uwsgi
# process and only exists that way. If anything works, this is a
# uwsgi-managed process.
try:
import uwsgi
if hasattr(uwsgi, "numproc"):
process_is_uwsgi = True
else:
process_is_uwsgi = False
except ImportError:
# This is not a uwsgi process, or something went horribly wrong.
process_is_uwsgi = False
try:
from uwsgidecorators import postfork
except:
def pf_dec(func):
return func
postfork = pf_dec
if process_is_uwsgi:
print("WARNING: This is a uwsgi process but the uwsgidecorators library"
" is unavailable. This is likely due to using an external (not"
" in Galaxy's virtualenv) uwsgi and you may experience errors.")
postfork_functions = []
@postfork
def do_postfork():
for f, args, kwargs in [ t for t in postfork_functions ]:
f(*args, **kwargs)
def register_postfork_function(f, *args, **kwargs):
if process_is_uwsgi:
postfork_functions.append((f, args, kwargs))
else:
f(*args, **kwargs)
+77 -23
View File
@@ -10,9 +10,7 @@ from sqlalchemy import true
from paste.httpexceptions import HTTPBadRequest, HTTPInternalServerError
from paste.httpexceptions import HTTPNotImplemented, HTTPRequestRangeNotSatisfiable
from galaxy.exceptions import ItemAccessibilityException, ItemDeletionException, ItemOwnershipException
from galaxy.exceptions import MessageException, ToolMissingException
from galaxy import exceptions
from galaxy import web
from galaxy import model
from galaxy import security
@@ -163,7 +161,7 @@ class BaseUIController( BaseController ):
return BaseController.get_object( self, trans, id, class_name,
check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted )
except MessageException:
except exceptions.MessageException:
raise # handled in the caller
except:
log.exception( "Exception in get_object check for %s %s:" % ( class_name, str( id ) ) )
@@ -177,9 +175,9 @@ class BaseAPIController( BaseController ):
return BaseController.get_object( self, trans, id, class_name,
check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted )
except ItemDeletionException as e:
except exceptions.ItemDeletionException as e:
raise HTTPBadRequest( detail="Invalid %s id ( %s ) specified: %s" % ( class_name, str( id ), str( e ) ) )
except MessageException as e:
except exceptions.MessageException as e:
raise HTTPBadRequest( detail=e.err_msg )
except Exception as e:
log.exception( "Exception in get_object check for %s %s." % ( class_name, str( id ) ) )
@@ -192,7 +190,7 @@ class BaseAPIController( BaseController ):
def get_id( item, model_class, column ):
try:
return trans.security.decode_id( item )
except:
except Exception:
pass # maybe an email/group name
# this will raise if the item is invalid
return trans.sa_session.query( model_class ).filter( column == item ).first().id
@@ -202,12 +200,12 @@ class BaseAPIController( BaseController ):
for item in util.listify( payload.get( 'in_users', [] ) ):
try:
new_in_users.append( get_id( item, trans.app.model.User, trans.app.model.User.table.c.email ) )
except:
except Exception:
invalid.append( item )
for item in util.listify( payload.get( 'in_groups', [] ) ):
try:
new_in_groups.append( get_id( item, trans.app.model.Group, trans.app.model.Group.table.c.name ) )
except:
except Exception:
invalid.append( item )
if invalid:
msg = "The following value(s) for associated users and/or groups could not be parsed: %s." % ', '.join( invalid )
@@ -455,13 +453,69 @@ class UsesLibraryMixinItems( SharableItemSecurityMixin ):
if check_accessible:
if not trans.app.security_agent.can_access_library_item( current_user_roles, item, trans.user ):
raise ItemAccessibilityException( )
raise exceptions.ItemAccessibilityException('You do not have access to the requested item')
if not trans.app.security_agent.can_add_library_item( trans.get_current_user_roles(), item ):
# Slight misuse of ItemOwnershipException?
raise ItemOwnershipException( "User cannot add to library item." )
raise exceptions.ItemOwnershipException( "User cannot add to library item." )
def copy_hda_to_library_folder( self, trans, hda, library_folder, roles=None, ldda_message='' ):
def _copy_hdca_to_library_folder(self, trans, hda_manager, from_hdca_id, folder_id, ldda_message=''):
"""
Fetches the collection identified by `from_hcda_id` and dispatches individual collection elements to
_copy_hda_to_library_folder
"""
hdca = trans.sa_session.query(trans.app.model.HistoryDatasetCollectionAssociation).get(from_hdca_id)
if hdca.collection.collection_type != 'list':
raise exceptions.NotImplemented('Cannot add nested collections to library. Please flatten your collection first.')
hdas = []
for element in hdca.collection.elements:
hdas.append((element.element_identifier, element.dataset_instance.id))
return [self._copy_hda_to_library_folder(trans,
hda_manager=hda_manager,
from_hda_id=hda_id,
folder_id=folder_id,
ldda_message=ldda_message,
element_identifier=element_identifier) for (element_identifier, hda_id) in hdas]
def _copy_hda_to_library_folder( self, trans, hda_manager, from_hda_id, folder_id, ldda_message='', element_identifier=None ):
"""
Copies hda ``from_hda_id`` to library folder ``folder_id``, optionally
adding ``ldda_message`` to the new ldda's ``message``.
``library_contents.create`` will branch to this if called with 'from_hda_id'
in its payload.
"""
log.debug( '_copy_hda_to_library_folder: %s' % ( str(( from_hda_id, folder_id, ldda_message )) ) )
# PRECONDITION: folder_id has already been altered to remove the folder prefix ('F')
# TODO: allow name and other, editable ldda attrs?
if ldda_message:
ldda_message = util.sanitize_html.sanitize_html( ldda_message, 'utf-8' )
# check permissions on (all three?) resources: hda, library, folder
# TODO: do we really need the library??
hda = hda_manager.get_owned( from_hda_id, trans.user, current_history=trans.history )
hda = hda_manager.error_if_uploading( hda )
folder = self.get_library_folder( trans, folder_id, check_accessible=True )
# TOOD: refactor to use check_user_can_add_to_library_item, eliminate boolean
# can_current_user_add_to_library_item.
if folder.parent_library.deleted:
raise exceptions.ObjectAttributeInvalidException('You cannot add datasets into deleted library. Undelete it first.')
if not self.can_current_user_add_to_library_item( trans, folder ):
raise exceptions.InsufficientPermissionsException('You do not have proper permissions to add a dataset to this folder,')
ldda = self.copy_hda_to_library_folder( trans, hda, folder, ldda_message=ldda_message, element_identifier=element_identifier )
# I don't see a reason why hdas copied into libraries should not be visible.
# If there is, refactor `ldda.visible = True` to do this only when adding HDCAs.
ldda.visible = True
trans.sa_session.flush()
ldda_dict = ldda.to_dict()
rval = trans.security.encode_dict_ids( ldda_dict )
update_time = ldda.update_time.strftime("%Y-%m-%d %I:%M %p")
rval['update_time'] = update_time
return rval
def copy_hda_to_library_folder( self, trans, hda, library_folder, roles=None, ldda_message='', element_identifier=None ):
# PRECONDITION: permissions for this action on hda and library_folder have been checked
roles = roles or []
@@ -470,7 +524,7 @@ class UsesLibraryMixinItems( SharableItemSecurityMixin ):
# create the new ldda and apply the folder perms to it
ldda = hda.to_library_dataset_dataset_association( trans, target_folder=library_folder,
roles=roles, ldda_message=ldda_message )
roles=roles, ldda_message=ldda_message, element_identifier=element_identifier )
self._apply_library_folder_permissions_to_ldda( trans, library_folder, ldda )
self._apply_hda_permissions_to_ldda( trans, hda, ldda )
# TODO:?? not really clear on how permissions are being traded here
@@ -693,15 +747,15 @@ class UsesVisualizationMixin( UsesLibraryMixinItems ):
# default to trans.user, error if anon
if not user:
if not trans.user:
raise ItemAccessibilityException( "You must be logged in to import Galaxy visualizations" )
raise exceptions.ItemAccessibilityException( "You must be logged in to import Galaxy visualizations" )
user = trans.user
# check accessibility
visualization = self.get_visualization( trans, id, check_ownership=False )
if not visualization.importable:
raise ItemAccessibilityException( "The owner of this visualization has disabled imports via this link." )
raise exceptions.ItemAccessibilityException( "The owner of this visualization has disabled imports via this link." )
if visualization.deleted:
raise ItemDeletionException( "You can't import this visualization because it has been deleted." )
raise exceptions.ItemDeletionException( "You can't import this visualization because it has been deleted." )
# copy vis and alter title
# TODO: need to handle custom db keys.
@@ -1139,7 +1193,7 @@ class UsesStoredWorkflowMixin( SharableItemSecurityMixin, UsesAnnotations ):
for step in stored_workflow.latest_workflow.steps:
try:
module_injector.inject( step )
except ToolMissingException:
except exceptions.ToolMissingException:
pass
def _import_shared_workflow( self, trans, stored):
@@ -1353,7 +1407,7 @@ class UsesFormDefinitionsMixin:
form_id_select_field = self.build_form_id_select_field( trans, forms, selected_value=kwd.get( 'form_id', 'none' ) )
try:
decoded_form_id = trans.security.decode_id( form_id )
except:
except Exception:
decoded_form_id = None
if decoded_form_id:
for form in forms:
@@ -1899,7 +1953,7 @@ class UsesFormDefinitionsMixin:
id = library_id
try:
item = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) )
except:
except Exception:
item = None
item_desc = 'data library'
action = 'library_info'
@@ -1908,7 +1962,7 @@ class UsesFormDefinitionsMixin:
id = folder_id
try:
item = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
except:
except Exception:
item = None
item_desc = 'folder'
action = 'folder_info'
@@ -1917,7 +1971,7 @@ class UsesFormDefinitionsMixin:
id = ldda_id
try:
item = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( ldda_id ) )
except:
except Exception:
item = None
item_desc = 'dataset'
action = 'ldda_edit_info'
@@ -1926,7 +1980,7 @@ class UsesFormDefinitionsMixin:
id = request_type_id
try:
item = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id( request_type_id ) )
except:
except Exception:
item = None
item_desc = 'request type'
action = 'view_editable_request_type'
@@ -1935,7 +1989,7 @@ class UsesFormDefinitionsMixin:
id = sample_id
try:
item = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id( sample_id ) )
except:
except Exception:
item = None
item_desc = 'sample'
action = 'view_sample'
+7 -8
View File
@@ -9,7 +9,6 @@ import tarfile
import tempfile
import time
import types
from Cookie import SimpleCookie
import routes
import webob
@@ -17,6 +16,7 @@ import webob
from paste import httpexceptions
from paste.request import get_cookies
from paste.response import HeaderDict
from six.moves.http_cookies import SimpleCookie
from galaxy.util import smart_str
@@ -113,7 +113,7 @@ class WebApplication( object ):
requests
"""
# Create/compile the regular expressions for route mapping
self.mapper.create_regs( self.controllers.keys() )
self.mapper.create_regs( list(self.controllers.keys()) )
self.clientside_routes.create_regs()
def trace( self, **fields ):
@@ -285,7 +285,7 @@ class LazyProperty( object ):
if obj is None:
return self
value = self.func( obj )
setattr( obj, self.func.func_name, value )
setattr( obj, self.func.__name__, value )
return value
@@ -414,14 +414,14 @@ class Response( object ):
self.headers = HeaderDict( { "content-type": "text/html" } )
self.cookies = SimpleCookie()
def set_content_type( self, type ):
def set_content_type( self, type_ ):
"""
Sets the Content-Type header
"""
self.headers[ "content-type" ] = type
self.headers[ "content-type" ] = type_
def get_content_type( self ):
return self.headers[ "content-type" ]
return self.headers.get("content-type", None)
def send_redirect( self, url ):
"""
@@ -435,8 +435,7 @@ class Response( object ):
"""
result = self.headers.headeritems()
# Add cookie to header
for name in self.cookies.keys():
crumb = self.cookies[name]
for name, crumb in self.cookies.items():
header, value = str( crumb ).split( ': ', 1 )
result.append( ( header, value ) )
return result
+2 -3
View File
@@ -164,8 +164,7 @@ def expose_api( func, to_json=True, user_required=True ):
def __extract_payload_from_request( trans, func, kwargs ):
content_type = trans.request.headers[ 'content-type' ]
content_type = trans.request.headers.get('content-type', '')
if content_type.startswith( 'application/x-www-form-urlencoded' ) or content_type.startswith( 'multipart/form-data' ):
# If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
# and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which
@@ -177,7 +176,7 @@ def __extract_payload_from_request( trans, func, kwargs ):
named_args, _, _, _ = inspect.getargspec( func )
for arg in named_args:
payload.pop( arg, None )
for k, v in payload.iteritems():
for k, v in payload.items():
if isinstance( v, string_types ):
try:
# note: parse_non_hex_float only needed here for single string values where something like
+3 -3
View File
@@ -565,7 +565,7 @@ class OwnerAnnotationColumn( TextColumn, UsesAnnotations ):
ann_snippet += "..."
else:
ann_snippet = ""
return ann_snippet
return escape( ann_snippet )
def get_single_filter( self, user, a_filter ):
""" Filter by annotation and annotation owner. """
@@ -603,7 +603,7 @@ class CommunityTagsColumn( TextColumn ):
column_filter = ",".join( column_filter )
raw_tags = trans.app.tag_handler.parse_tags( column_filter.encode( "utf-8" ) )
clause_list = []
for name, value in raw_tags.items():
for name, value in raw_tags:
if name:
# Filter by all tags.
clause_list.append( self.model_class.tags.any( func.lower( self.model_tag_association_class.user_tname ).like( "%" + name.lower() + "%" ) ) )
@@ -633,7 +633,7 @@ class IndividualTagsColumn( CommunityTagsColumn ):
column_filter = ",".join( column_filter )
raw_tags = trans.app.tag_handler.parse_tags( column_filter.encode( "utf-8" ) )
clause_list = []
for name, value in raw_tags.items():
for name, value in raw_tags:
if name:
# Filter by individual's tag names.
clause_list.append( self.model_class.tags.any( and_( func.lower( self.model_tag_association_class.user_tname ).like( "%" + name.lower() + "%" ), self.model_tag_association_class.user == user ) ) )
+3 -3
View File
@@ -59,11 +59,11 @@ class BatchMiddleware( object ):
]
}
def __init__( self, galaxy, application, config=None ):
#: the original galaxy webapp
self.galaxy = galaxy
def __init__( self, application, galaxy, config=None ):
#: the wrapped webapp
self.application = application
#: the original galaxy webapp
self.galaxy = galaxy
self.config = self.DEFAULT_CONFIG.copy()
self.config.update( config )
self.base_url = routes.url_for( '/' )
@@ -12,7 +12,7 @@ try:
except:
Client = None
from galaxy.util.postfork import register_postfork_function
from galaxy.web.stack import register_postfork_function
RAVEN_IMPORT_MESSAGE = ('The Python raven package is required to use this '
+106
View File
@@ -0,0 +1,106 @@
"""Web application stack operations
"""
from __future__ import print_function
import inspect
import logging
import os
# The uwsgi module is automatically injected by the parent uwsgi
# process and only exists that way. If anything works, this is a
# uwsgi-managed process.
try:
import uwsgi
except ImportError:
uwsgi = None
try:
from uwsgidecorators import postfork as uwsgi_postfork
except:
uwsgi_postfork = lambda x: x # noqa: E731
if uwsgi is not None and hasattr(uwsgi, 'numproc'):
print("WARNING: This is a uwsgi process but the uwsgidecorators library"
" is unavailable. This is likely due to using an external (not"
" in Galaxy's virtualenv) uwsgi and you may experience errors. "
"HINT:\n {venv}/bin/pip install uwsgidecorators".format(
venv=os.environ.get('VIRTUAL_ENV', '/path/to/venv')))
log = logging.getLogger(__name__)
class ApplicationStack(object):
name = None
prohibited_middleware = frozenset()
@classmethod
def register_postfork_function(cls, f, *args, **kwargs):
f(*args, **kwargs)
def workers(self):
return []
def allowed_middleware(self, middleware):
if hasattr(middleware, '__name__'):
middleware = middleware.__name__
return middleware not in self.prohibited_middleware
def set_postfork_server_name(self, app):
pass
class UWSGIApplicationStack(ApplicationStack):
name = 'uWSGI'
prohibited_middleware = frozenset([
'wrap_in_static',
'EvalException',
])
postfork_functions = []
@classmethod
def register_postfork_function(cls, f, *args, **kwargs):
cls.postfork_functions.append((f, args, kwargs))
def workers(self):
return uwsgi.workers()
def set_postfork_server_name(self, app):
app.config.server_name += ".%d" % uwsgi.worker_id()
class PasteApplicationStack(ApplicationStack):
name = 'Python Paste'
class WeblessApplicationStack(ApplicationStack):
name = 'Webless'
def application_stack_class():
"""Returns the correct ApplicationStack class for the stack under which
this Galaxy process is running.
"""
if uwsgi is not None and hasattr(uwsgi, 'numproc'):
return UWSGIApplicationStack
else:
# cleverer ideas welcome
for frame in inspect.stack():
if frame[1].endswith(os.path.join('pastescript', 'loadwsgi.py')):
return PasteApplicationStack
return WeblessApplicationStack
def application_stack_instance():
stack_class = application_stack_class()
return stack_class()
def register_postfork_function(f, *args, **kwargs):
application_stack_class().register_postfork_function(f, *args, **kwargs)
@uwsgi_postfork
def _do_postfork():
for f, args, kwargs in [ t for t in UWSGIApplicationStack.postfork_functions ]:
f(*args, **kwargs)
@@ -250,54 +250,43 @@ class FolderContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrary
:param payload: dictionary structure containing:
:param from_hda_id: (optional) the id of an accessible HDA to copy into the library
:type from_hda_id: encoded id
:param from_hdca_id: (optional) the id of an accessible HDCA to copy into the library
:type from_hdca_id: encoded id
:param ldda_message: (optional) the new message attribute of the LDDA created
:type ldda_message: str
:param extended_metadata: (optional) dub-dictionary containing any extended metadata to associate with the item
:type extended_metadata: dict
:type payload: dict
:returns: a dictionary containing the id, name, and 'show' url of the new item
:rtype: dict
:returns: a list of dictionaries containing the id, name, and 'show' url of the new item
:rtype: list
:raises: ObjectAttributeInvalidException,
InsufficientPermissionsException, ItemAccessibilityException,
InternalServerError
"""
encoded_folder_id_16 = self.__decode_library_content_id( trans, encoded_folder_id )
from_hda_id, ldda_message = ( payload.pop( 'from_hda_id', None ), payload.pop( 'ldda_message', '' ) )
from_hda_id = payload.pop( 'from_hda_id', None )
from_hdca_id = payload.pop( 'from_hdca_id', None )
ldda_message = payload.pop( 'ldda_message', '' )
if ldda_message:
ldda_message = util.sanitize_html.sanitize_html( ldda_message, 'utf-8' )
rval = {}
rvals = []
try:
decoded_hda_id = self.decode_id( from_hda_id )
hda = self.hda_manager.get_owned( decoded_hda_id, trans.user, current_history=trans.history )
hda = self.hda_manager.error_if_uploading( hda )
folder = self.get_library_folder( trans, encoded_folder_id_16, check_accessible=True )
library = folder.parent_library
if library.deleted:
raise exceptions.ObjectAttributeInvalidException()
if not self.can_current_user_add_to_library_item( trans, folder ):
raise exceptions.InsufficientPermissionsException()
ldda = self.copy_hda_to_library_folder( trans, hda, folder, ldda_message=ldda_message )
update_time = ldda.update_time.strftime( "%Y-%m-%d %I:%M %p" )
ldda_dict = ldda.to_dict()
rval = trans.security.encode_dict_ids( ldda_dict )
rval['update_time'] = update_time
except exceptions.ObjectAttributeInvalidException:
raise exceptions.ObjectAttributeInvalidException( 'You cannot add datasets into deleted library. Undelete it first.' )
except exceptions.InsufficientPermissionsException:
raise exceptions.exceptions.InsufficientPermissionsException( 'You do not have proper permissions to add a dataset to a folder with id (%s)' % ( encoded_folder_id ) )
if from_hda_id:
decoded_hda_id = self.decode_id( from_hda_id )
rvals.append(self._copy_hda_to_library_folder( trans, self.hda_manager, decoded_hda_id, encoded_folder_id_16, ldda_message ))
if from_hdca_id:
decoded_hdca_id = self.decode_id( from_hdca_id )
rvals.extend(self._copy_hdca_to_library_folder( trans, self.hda_manager, decoded_hdca_id, encoded_folder_id_16, ldda_message ))
except Exception as exc:
# TODO handle exceptions better within the mixins
if ( ( 'not accessible to the current user' in str( exc ) ) or ( 'You are not allowed to access this dataset' in str( exc ) ) ):
if 'not accessible to the current user' in str( exc ) or 'You are not allowed to access this dataset' in str( exc ):
raise exceptions.ItemAccessibilityException( 'You do not have access to the requested item' )
else:
log.exception( exc )
raise exceptions.InternalServerError( 'An unknown error ocurred. Please try again.' )
return rval
raise exc
return rvals
def __decode_library_content_id( self, trans, encoded_folder_id ):
"""
@@ -150,6 +150,9 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
To copy an HDA into a library send ``create_type`` of 'file' and
the HDA's encoded id in ``from_hda_id`` (and optionally ``ldda_message``).
To copy an HDCA into a library send ``create_type`` of 'file' and
the HDCA's encoded id in ``from_hdca_id`` (and optionally ``ldda_message``).
:type library_id: str
:param library_id: the encoded id of the library where to create the new item
:type payload: dict
@@ -160,7 +163,7 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
* from_hda_id: (optional, only if create_type is 'file') the
encoded id of an accessible HDA to copy into the library
* ldda_message: (optional) the new message attribute of the LDDA created
* extended_metadata: (optional) dub-dictionary containing any extended
* extended_metadata: (optional) sub-dictionary containing any extended
metadata to associate with the item
* upload_option: (optional) one of 'upload_file' (default), 'upload_directory' or 'upload_paths'
* server_dir: (optional, only if upload_option is
@@ -180,8 +183,8 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
* description: (optional, only if create_type is 'folder')
description of the folder to create
:rtype: dict
:returns: a dictionary containing the id, name,
:rtype: list
:returns: a list of dictionaries containing the id, name,
and 'show' url of the new item
"""
if 'create_type' not in payload:
@@ -209,9 +212,16 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# are we copying an HDA to the library folder?
# we'll need the id and any message to attach, then branch to that private function
from_hda_id, ldda_message = ( payload.pop( 'from_hda_id', None ), payload.pop( 'ldda_message', '' ) )
if create_type == 'file' and from_hda_id:
return self._copy_hda_to_library_folder( trans, from_hda_id, library_id, real_folder_id, ldda_message )
from_hda_id, from_hdca_id, ldda_message = ( payload.pop( 'from_hda_id', None ), payload.pop( 'from_hdca_id', None ), payload.pop( 'ldda_message', '' ) )
log.debug(payload)
if create_type == 'file':
rval = []
if from_hda_id:
rval.append(self._copy_hda_to_library_folder( trans, self.hda_manager, self.decode_id(from_hda_id), real_folder_id, ldda_message ))
if from_hdca_id:
rval.extend(self._copy_hdca_to_library_folder(trans, self.hda_manager, self.decode_id(from_hdca_id), real_folder_id, ldda_message))
if from_hda_id or from_hdca_id:
return rval
# check for extended metadata, store it and pop it out of the param
# otherwise sanitize_param will have a fit
@@ -285,52 +295,6 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# for cross type comparisions, ie "True" == True
yield prefix, ("%s" % (meta)).encode("utf8", errors='replace')
def _copy_hda_to_library_folder( self, trans, from_hda_id, library_id, folder_id, ldda_message='' ):
"""
Copies hda ``from_hda_id`` to library folder ``folder_id``, optionally
adding ``ldda_message`` to the new ldda's ``message``.
``library_contents.create`` will branch to this if called with 'from_hda_id'
in its payload.
"""
log.debug( '_copy_hda_to_library_folder: %s' % ( str(( from_hda_id, library_id, folder_id, ldda_message )) ) )
# PRECONDITION: folder_id has already been altered to remove the folder prefix ('F')
# TODO: allow name and other, editable ldda attrs?
if ldda_message:
ldda_message = util.sanitize_html.sanitize_html( ldda_message, 'utf-8' )
rval = {}
try:
# check permissions on (all three?) resources: hda, library, folder
# TODO: do we really need the library??
from_hda_id = self.decode_id( from_hda_id )
hda = self.hda_manager.get_owned( from_hda_id, trans.user, current_history=trans.history )
hda = self.hda_manager.error_if_uploading( hda )
# library = self.get_library( trans, library_id, check_accessible=True )
folder = self.get_library_folder( trans, folder_id, check_accessible=True )
# TOOD: refactor to use check_user_can_add_to_library_item, eliminate boolean
# can_current_user_add_to_library_item.
if not self.can_current_user_add_to_library_item( trans, folder ):
trans.response.status = 403
return { 'error': 'user has no permission to add to library folder (%s)' % ( folder_id ) }
ldda = self.copy_hda_to_library_folder( trans, hda, folder, ldda_message=ldda_message )
ldda_dict = ldda.to_dict()
rval = trans.security.encode_dict_ids( ldda_dict )
except Exception as exc:
# TODO: grrr...
if 'not accessible to the current user' in str( exc ):
trans.response.status = 403
return { 'error': str( exc ) }
else:
log.exception( exc )
trans.response.status = 500
return { 'error': str( exc ) }
return rval
@web.expose_api
def update( self, trans, id, library_id, payload, **kwd ):
"""
+68 -13
View File
@@ -3,9 +3,12 @@ API operations on User objects.
"""
import logging
import datetime
import random
import re
import socket
from datetime import datetime
from markupsafe import escape
from sqlalchemy import false, true, and_, or_
from galaxy import exceptions, util, web
@@ -14,6 +17,7 @@ from galaxy.managers import users
from galaxy.security.validate_user_input import validate_email
from galaxy.security.validate_user_input import validate_password
from galaxy.security.validate_user_input import validate_publicname
from galaxy.web import url_for
from galaxy.web import _future_expose_api as expose_api
from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
from galaxy.web.base.controller import BaseAPIController
@@ -24,7 +28,7 @@ from galaxy.web.base.controller import BaseUIController
from galaxy.web.base.controller import UsesFormDefinitionsMixin
from galaxy.web.form_builder import AddressField
from galaxy.tools.toolbox.filters import FilterFactory
from galaxy.util import docstring_trim, listify
from galaxy.util import docstring_trim, listify, hash_util
from galaxy.util.odict import odict
@@ -231,9 +235,7 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
# TODO: move to more basal, common resource than this
def anon_user_api_value( self, trans ):
"""
Returns data for an anonymous user, truncated to only usage and quota_percent
"""
"""Return data for an anonymous user, truncated to only usage and quota_percent"""
usage = trans.app.quota_agent.get_usage( trans )
percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage )
return {'total_disk_usage': int( usage ),
@@ -242,9 +244,7 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
@expose_api
def get_information(self, trans, id, **kwd):
'''
Returns user details such as public username, type, addresses, etc.
'''
"""Return user details such as username, email, addresses etc."""
user = self._get_user(trans, id)
email = user.email
username = user.username
@@ -315,9 +315,7 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
@expose_api
def set_information(self, trans, id, payload={}, **kwd):
'''
Save a user's email address, public username, type, addresses etc.
'''
"""Save a user's email, username, addresses etc."""
user = self._get_user(trans, id)
email = payload.get('email')
username = payload.get('username')
@@ -327,14 +325,17 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
message = validate_publicname(trans, username, user)
if message:
raise MessageException(message)
# Update user email and user's private role name which must match
if user.email != email:
# Update user email and user's private role name which must match
private_role = trans.app.security_agent.get_private_user_role(user)
private_role.name = email
private_role.description = 'Private role for ' + email
user.email = email
trans.sa_session.add(user)
trans.sa_session.add(private_role)
trans.sa_session.flush()
if trans.app.config.user_activation_on:
# Deactivate the user if email was changed and activation is on.
user.active = False
if self.send_verification_email(trans, user.email, user.username):
message = 'The login information has been updated with the changes.<br>Verification email has been sent to your new email address. Please verify it by clicking the activation link in the email.<br>Please check your spam/trash folder in case you cannot find the message.'
@@ -343,8 +344,8 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
if trans.app.config.error_email_to is not None:
message += ' Contact: %s' % trans.app.config.error_email_to
raise MessageException(message)
# Update public name
if user.username != username:
# Update public name
user.username = username
# Update user custom form
user_info_form_id = payload.get('info|form_id')
@@ -393,6 +394,60 @@ class UserAPIController( BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cr
trans.log_event('User information added')
return {'message': 'User information has been saved.'}
def send_verification_email( self, trans, email, username ):
"""
Send the verification email containing the activation link to the user's email.
"""
if username is None:
username = trans.user.username
activation_link = self.prepare_activation_link( trans, escape( email ) )
host = trans.request.host.split( ':' )[ 0 ]
if host in [ 'localhost', '127.0.0.1', '0.0.0.0' ]:
host = socket.getfqdn()
body = ("Hello %s,\n\n"
"In order to complete the activation process for %s begun on %s at %s, please click on the following link to verify your account:\n\n"
"%s \n\n"
"By clicking on the above link and opening a Galaxy account you are also confirming that you have read and agreed to Galaxy's Terms and Conditions for use of this service (%s). This includes a quota limit of one account per user. Attempts to subvert this limit by creating multiple accounts or through any other method may result in termination of all associated accounts and data.\n\n"
"Please contact us if you need help with your account at: %s. You can also browse resources available at: %s. \n\n"
"More about the Galaxy Project can be found at galaxyproject.org\n\n"
"Your Galaxy Team" % (escape( username ), escape( email ),
datetime.utcnow().strftime( "%D"),
trans.request.host, activation_link,
trans.app.config.terms_url,
trans.app.config.error_email_to,
trans.app.config.instance_resource_url))
to = email
frm = trans.app.config.email_from or 'galaxy-no-reply@' + host
subject = 'Galaxy Account Activation'
try:
util.send_mail( frm, to, subject, body, trans.app.config )
return True
except Exception:
log.exception( 'Unable to send the activation email.' )
return False
def prepare_activation_link( self, trans, email ):
"""
Prepare the account activation link for the user.
"""
activation_token = self.get_activation_token( trans, email )
activation_link = url_for( controller='user', action='activate', activation_token=activation_token, email=email, qualified=True )
return activation_link
def get_activation_token( self, trans, email ):
"""
Check for the activation token. Create new activation token and store it in the database if no token found.
"""
user = trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.email == email ).first()
activation_token = user.activation_token
if activation_token is None:
activation_token = hash_util.new_secure_hash( str( random.getrandbits( 256 ) ) )
user.activation_token = activation_token
trans.sa_session.add( user )
trans.sa_session.flush()
return activation_token
def _validate_email_publicname(self, email, username):
''' Validate email and username using regex '''
if email == '' or not isinstance( email, basestring ):
+47 -52
View File
@@ -19,10 +19,14 @@ import galaxy.model.mapping
import galaxy.datatypes.registry
import galaxy.web.framework
import galaxy.web.framework.webapp
from galaxy.webapps.util import build_template_error_formatters
from galaxy.webapps.util import (
MiddlewareWrapUnsupported,
build_template_error_formatters,
wrap_if_allowed,
wrap_if_allowed_or_fail
)
from galaxy import util
from galaxy.util import asbool
from galaxy.util.postfork import process_is_uwsgi, register_postfork_function
from galaxy.util.properties import load_app_properties
from paste import httpexceptions
@@ -117,12 +121,11 @@ def paste_app_factory( global_conf, **kwargs ):
# Wrap the webapp in some useful middleware
if kwargs.get( 'middleware', True ):
webapp = wrap_in_middleware( webapp, global_conf, **kwargs )
webapp = wrap_in_middleware(webapp, global_conf, app.application_stack, **kwargs)
if asbool( kwargs.get( 'static_enabled', True) ):
if process_is_uwsgi:
log.error("Static middleware is enabled in your configuration but this is a uwsgi process. Refusing to wrap in static middleware.")
else:
webapp = wrap_in_static( webapp, global_conf, plugin_frameworks=[ app.visualizations_registry ], **kwargs )
webapp = wrap_if_allowed(webapp, app.application_stack, wrap_in_static,
args=(global_conf,),
kwargs=dict(plugin_frameworks=[app.visualizations_registry], **kwargs))
# Close any pooled database connections before forking
try:
galaxy.model.mapping.metadata.bind.dispose()
@@ -135,7 +138,7 @@ def paste_app_factory( global_conf, **kwargs ):
except:
log.exception("Unable to dispose of pooled toolshed install model database connections.")
register_postfork_function(postfork_setup)
app.application_stack.register_postfork_function(postfork_setup)
for th in threading.enumerate():
if th.is_alive():
@@ -162,9 +165,7 @@ def uwsgi_app_factory():
def postfork_setup():
from galaxy.app import app
if process_is_uwsgi:
import uwsgi
app.config.server_name += ".%s" % uwsgi.worker_id()
app.application_stack.set_postfork_server_name(app)
app.control_worker.bind_and_start()
@@ -848,12 +849,13 @@ def _add_item_provenance_controller( webapp, name_prefix, path_prefix, **kwd ):
webapp.mapper.resource(name, "provenance", path_prefix=path_prefix, controller=controller)
def wrap_in_middleware( app, global_conf, **local_conf ):
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.
"""
webapp = app
stack = application_stack
# Merge the global and local configurations
conf = global_conf.copy()
@@ -862,16 +864,15 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# First put into place httpexceptions, which must be most closely
# wrapped around the application (it can interact poorly with
# other middleware):
app = httpexceptions.make_middleware( app, conf )
log.debug( "Enabling 'httpexceptions' middleware" )
app = wrap_if_allowed( app, stack, httpexceptions.make_middleware, name='paste.httpexceptions', args=(conf,) )
# Statsd request timing and profiling
statsd_host = conf.get('statsd_host', None)
if statsd_host:
from galaxy.web.framework.middleware.statsd import StatsdMiddleware
app = StatsdMiddleware( app,
statsd_host,
conf.get('statsd_port', 8125),
conf.get('statsd_prefix', 'galaxy') )
app = wrap_if_allowed( app, stack, StatsdMiddleware,
args=( statsd_host,
conf.get('statsd_port', 8125),
conf.get('statsd_prefix', 'galaxy') ) )
log.debug( "Enabling 'statsd' middleware" )
# If we're using remote_user authentication, add middleware that
# protects Galaxy from improperly configured authentication in the
@@ -880,25 +881,26 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
use_remote_user = asbool(conf.get( 'use_remote_user', False )) or single_user
if use_remote_user:
from galaxy.web.framework.middleware.remoteuser import RemoteUser
app = RemoteUser( app, maildomain=conf.get( 'remote_user_maildomain', None ),
display_servers=util.listify( conf.get( 'display_servers', '' ) ),
single_user=single_user,
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))
app = wrap_if_allowed( app, stack, RemoteUser,
kwargs=dict(
maildomain=conf.get('remote_user_maildomain', None),
display_servers=util.listify( conf.get('display_servers', '')),
single_user=single_user,
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 = recursive.RecursiveMiddleware( app, conf )
log.debug( "Enabling 'recursive' middleware" )
app = wrap_if_allowed( app, stack, recursive.RecursiveMiddleware, args=(conf,) )
# If sentry logging is enabled, log here before propogating up to
# the error middleware
sentry_dsn = conf.get( 'sentry_dsn', None )
if sentry_dsn:
from galaxy.web.framework.middleware.sentry import Sentry
app = Sentry( app, sentry_dsn )
app = wrap_if_allowed( app, stack, Sentry, args=(sentry_dsn,) )
# Various debug middleware that can only be turned on if the debug
# flag is set, either because they are insecure or greatly hurt
# performance
@@ -906,48 +908,41 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# Middleware to check for WSGI compliance
if asbool( conf.get( 'use_lint', False ) ):
from paste import lint
app = lint.make_middleware( app, conf )
log.debug( "Enabling 'lint' middleware" )
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 = profile.ProfileMiddleware( app, conf )
log.debug( "Enabling 'profile' middleware" )
if debug and asbool( conf.get( 'use_interactive', False ) ) and not process_is_uwsgi:
app = wrap_if_allowed( app, stack, profile.ProfileMiddleware, args=(conf,) )
if debug and asbool( conf.get( 'use_interactive', False ) ):
# Interactive exception debugging, scary dangerous if publicly
# accessible, if not enabled we'll use the regular error printing
# middleware.
from weberror import evalexception
app = evalexception.EvalException( app, conf,
templating_formatters=build_template_error_formatters() )
log.debug( "Enabling 'eval exceptions' middleware" )
try:
from weberror import evalexception
app = wrap_if_allowed_or_fail( app, stack, evalexception.EvalException,
args=(conf,),
kwargs=dict(templating_formatters=build_template_error_formatters()) )
except MiddlewareWrapUnsupported as exc:
log.warning(str(exc))
import galaxy.web.framework.middleware.error
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
else:
if debug and asbool( conf.get( 'use_interactive', False ) ) and process_is_uwsgi:
log.error("Interactive debugging middleware is enabled in your configuration "
"but this is a uwsgi process. Refusing to wrap in interactive error middleware.")
# Not in interactive debug mode, just use the regular error middleware
import galaxy.web.framework.middleware.error
app = galaxy.web.framework.middleware.error.ErrorMiddleware( app, conf )
log.debug( "Enabling 'error' middleware" )
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
# Transaction logging (apache access.log style)
if asbool( conf.get( 'use_translogger', True ) ):
from galaxy.web.framework.middleware.translogger import TransLogger
app = TransLogger( app )
log.debug( "Enabling 'trans logger' middleware" )
app = wrap_if_allowed( app, stack, TransLogger )
# X-Forwarded-Host handling
from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware
app = XForwardedHostMiddleware( app )
log.debug( "Enabling 'x-forwarded-host' middleware" )
app = wrap_if_allowed( app, stack, XForwardedHostMiddleware )
# Request ID middleware
from galaxy.web.framework.middleware.request_id import RequestIDMiddleware
app = RequestIDMiddleware( app )
log.debug( "Enabling 'Request ID' middleware" )
app = wrap_if_allowed( app, stack, RequestIDMiddleware )
# api batch call processing middleware
from galaxy.web.framework.middleware.batch import BatchMiddleware
app = BatchMiddleware( webapp, app, {})
log.debug( "Enabling 'Batch' middleware" )
app = wrap_if_allowed( app, stack, BatchMiddleware, args=(webapp, {}) )
return app
@@ -1049,8 +1049,7 @@ class AdminToolshed( AdminGalaxy ):
repo_info_dicts = [ encoding_util.tool_shed_decode( encoded_repo_info_dict ) for encoded_repo_info_dict in encoded_repo_info_dicts ]
dd = dependency_display.DependencyDisplayer( trans.app )
install_repository_manager = install_manager.InstallRepositoryManager( trans.app )
if ( ( not includes_tools_for_display_in_tool_panel and kwd.get( 'select_shed_tool_panel_config_button', False ) ) or
( includes_tools_for_display_in_tool_panel and kwd.get( 'select_tool_panel_section_button', False ) ) ):
if kwd.get( 'select_tool_panel_section_button', False ):
if updating:
repository = repository_util.get_tool_shed_repository_by_id( trans.app, updating_repository_id )
decoded_updated_metadata = encoding_util.tool_shed_decode( encoded_updated_metadata )
@@ -1214,57 +1213,29 @@ class AdminToolshed( AdminGalaxy ):
install_resolver_dependencies_check_box = None
encoded_repo_info_dicts = encoding_util.encoding_sep.join( encoded_repo_info_dicts )
tool_shed_url = kwd[ 'tool_shed_url' ]
if includes_tools_for_display_in_tool_panel:
return trans.fill_template( '/admin/tool_shed_repository/select_tool_panel_section.mako',
encoded_repo_info_dicts=encoded_repo_info_dicts,
updating=updating,
updating_repository_id=updating_repository_id,
updating_to_ctx_rev=updating_to_ctx_rev,
updating_to_changeset_revision=updating_to_changeset_revision,
encoded_updated_metadata=encoded_updated_metadata,
includes_tools=includes_tools,
includes_tools_for_display_in_tool_panel=includes_tools_for_display_in_tool_panel,
includes_tool_dependencies=includes_tool_dependencies,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
install_resolver_dependencies_check_box=install_resolver_dependencies_check_box,
has_repository_dependencies=has_repository_dependencies,
install_repository_dependencies_check_box=install_repository_dependencies_check_box,
new_tool_panel_section_label=new_tool_panel_section_label,
containers_dict=containers_dict,
shed_tool_conf=shed_tool_conf,
shed_tool_conf_select_field=shed_tool_conf_select_field,
tool_panel_section_select_field=tool_panel_section_select_field,
tool_shed_url=tool_shed_url,
requirements_status=requirements_status,
message=message,
status=status )
else:
# If installing repositories that includes no tools and has no repository dependencies, display a page
# allowing the Galaxy administrator to select a shed-related tool panel configuration file whose tool_path
# setting will be the location the repositories will be installed.
return trans.fill_template( '/admin/tool_shed_repository/select_shed_tool_panel_config.mako',
encoded_repo_info_dicts=encoded_repo_info_dicts,
updating=updating,
updating_repository_id=updating_repository_id,
updating_to_ctx_rev=updating_to_ctx_rev,
updating_to_changeset_revision=updating_to_changeset_revision,
encoded_updated_metadata=encoded_updated_metadata,
includes_tools=includes_tools,
includes_tools_for_display_in_tool_panel=includes_tools_for_display_in_tool_panel,
includes_tool_dependencies=includes_tool_dependencies,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
install_resolver_dependencies_check_box=install_resolver_dependencies_check_box,
has_repository_dependencies=has_repository_dependencies,
install_repository_dependencies_check_box=install_repository_dependencies_check_box,
new_tool_panel_section_label=new_tool_panel_section_label,
containers_dict=containers_dict,
shed_tool_conf=shed_tool_conf,
shed_tool_conf_select_field=shed_tool_conf_select_field,
tool_panel_section_select_field=tool_panel_section_select_field,
tool_shed_url=tool_shed_url,
tool_requirements=tool_requirements,
message=message,
status=status )
return trans.fill_template( '/admin/tool_shed_repository/select_tool_panel_section.mako',
encoded_repo_info_dicts=encoded_repo_info_dicts,
updating=updating,
updating_repository_id=updating_repository_id,
updating_to_ctx_rev=updating_to_ctx_rev,
updating_to_changeset_revision=updating_to_changeset_revision,
encoded_updated_metadata=encoded_updated_metadata,
includes_tools=includes_tools,
includes_tools_for_display_in_tool_panel=includes_tools_for_display_in_tool_panel,
includes_tool_dependencies=includes_tool_dependencies,
install_tool_dependencies_check_box=install_tool_dependencies_check_box,
install_resolver_dependencies_check_box=install_resolver_dependencies_check_box,
has_repository_dependencies=has_repository_dependencies,
install_repository_dependencies_check_box=install_repository_dependencies_check_box,
new_tool_panel_section_label=new_tool_panel_section_label,
containers_dict=containers_dict,
shed_tool_conf=shed_tool_conf,
shed_tool_conf_select_field=shed_tool_conf_select_field,
tool_panel_section_select_field=tool_panel_section_select_field,
tool_shed_url=tool_shed_url,
requirements_status=requirements_status,
message=message,
status=status )
@web.expose
@web.require_admin
+2
View File
@@ -4,6 +4,7 @@ import time
import galaxy.model
from galaxy.web import security
from galaxy.web.stack import application_stack_instance
import logging
log = logging.getLogger( __name__ )
@@ -17,6 +18,7 @@ class UniverseApplication( object ):
self.config = config.Configuration( **kwargs )
self.config.check()
config.configure_logging( self.config )
self.application_stack = application_stack_instance()
# Determine the database url
if self.config.database_connection:
db_url = self.config.database_connection
+31 -31
View File
@@ -11,9 +11,12 @@ from inspect import isclass
from paste import httpexceptions
from galaxy.util import asbool
from galaxy.util.postfork import process_is_uwsgi
from galaxy.webapps.util import build_template_error_formatters
from galaxy.webapps.util import (
MiddlewareWrapUnsupported,
build_template_error_formatters,
wrap_if_allowed,
wrap_if_allowed_or_fail
)
import galaxy.model
import galaxy.model.mapping
import galaxy.web.framework.webapp
@@ -69,9 +72,11 @@ def app_factory( global_conf, **kwargs ):
webapp.finalize_config()
# Wrap the webapp in some useful middleware
if kwargs.get( 'middleware', True ):
webapp = wrap_in_middleware( webapp, global_conf, **kwargs )
webapp = wrap_in_middleware(webapp, global_conf, app.application_stack, **kwargs)
if asbool( kwargs.get( 'static_enabled', True ) ):
webapp = wrap_in_static( webapp, global_conf, **kwargs )
webapp = wrap_if_allowed(webapp, app.application_stack, wrap_in_static,
args=(global_conf,),
kwargs=kwargs)
# Close any pooled database connections before forking
try:
galaxy.model.mapping.metadata.bind.dispose()
@@ -81,8 +86,9 @@ def app_factory( global_conf, **kwargs ):
return webapp
def wrap_in_middleware( app, global_conf, **local_conf ):
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)
@@ -90,14 +96,12 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# First put into place httpexceptions, which must be most closely
# wrapped around the application (it can interact poorly with
# other middleware):
app = httpexceptions.make_middleware( app, conf )
log.debug( "Enabling 'httpexceptions' middleware" )
app = wrap_if_allowed( app, stack, httpexceptions.make_middleware, name='paste.httpexceptions', args=(conf,) )
# 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 = recursive.RecursiveMiddleware( app, conf )
log.debug( "Enabling 'recursive' middleware" )
app = wrap_if_allowed( app, stack, recursive.RecursiveMiddleware, args=(conf,) )
# Various debug middleware that can only be turned on if the debug
# flag is set, either because they are insecure or greatly hurt
# performance
@@ -105,44 +109,40 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# Middleware to check for WSGI compliance
if asbool( conf.get( 'use_lint', True ) ):
from paste import lint
app = lint.make_middleware( app, conf )
log.debug( "Enabling 'lint' middleware" )
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 ) ):
import profile
app = profile.ProfileMiddleware( app, conf )
log.debug( "Enabling 'profile' middleware" )
app = wrap_if_allowed( app, stack, profile.ProfileMiddleware, args=(conf,) )
# Middleware that intercepts print statements and shows them on the
# returned page
if asbool( conf.get( 'use_printdebug', True ) ):
from paste.debug import prints
app = prints.PrintDebugMiddleware( app, conf )
log.debug( "Enabling 'print debug' middleware" )
if debug and asbool( conf.get( 'use_interactive', False ) ) and not process_is_uwsgi:
app = wrap_if_allowed( app, stack, prints.PrintDebugMiddleware, args=(conf,) )
if debug and asbool( conf.get( 'use_interactive', False ) ):
# Interactive exception debugging, scary dangerous if publicly
# accessible, if not enabled we'll use the regular error printing
# middleware.
from weberror import evalexception
app = evalexception.EvalException( app, conf,
templating_formatters=build_template_error_formatters() )
log.debug( "Enabling 'eval exceptions' middleware" )
try:
from weberror import evalexception
app = wrap_if_allowed_or_fail( app, stack, evalexception.EvalException,
args=(conf,),
kwargs=dict(templating_formatters=build_template_error_formatters()) )
except MiddlewareWrapUnsupported as exc:
log.warning(str(exc))
import galaxy.web.framework.middleware.error
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
else:
if debug and asbool( conf.get( 'use_interactive', False ) ) and process_is_uwsgi:
log.error("Interactive debugging middleware is enabled in your configuration "
"but this is a uwsgi process. Refusing to wrap in interactive error middleware.")
# Not in interactive debug mode, just use the regular error middleware
from paste.exceptions import errormiddleware
app = errormiddleware.ErrorMiddleware( app, conf )
log.debug( "Enabling 'error' middleware" )
import galaxy.web.framework.middleware.error
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
# Transaction logging (apache access.log style)
if asbool( conf.get( 'use_translogger', True ) ):
from paste.translogger import TransLogger
app = TransLogger( app )
log.debug( "Enabling 'trans logger' middleware" )
app = wrap_if_allowed( app, stack, TransLogger )
# X-Forwarded-Host handling
from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware
app = XForwardedHostMiddleware( app )
log.debug( "Enabling 'x-forwarded-host' middleware" )
app = wrap_if_allowed( app, stack, XForwardedHostMiddleware )
return app
+2
View File
@@ -11,6 +11,7 @@ from galaxy.managers.tags import CommunityTagManager
from galaxy.openid.providers import OpenIDProviders
from galaxy.util.dbkeys import GenomeBuilds
from galaxy.web import security
from galaxy.web.stack import application_stack_instance
import tool_shed.repository_registry
import tool_shed.repository_types.registry
from tool_shed.grids.repository_grid_filter_manager import RepositoryGridFilterManager
@@ -29,6 +30,7 @@ class UniverseApplication( object ):
self.config = config.Configuration( **kwd )
self.config.check()
configure_logging( self.config )
self.application_stack = application_stack_instance()
# Initialize the Galaxy datatypes registry.
self.datatypes_registry = galaxy.datatypes.registry.Registry()
self.datatypes_registry.load_datatypes( self.config.root, self.config.datatypes_config )
+41 -41
View File
@@ -14,9 +14,13 @@ from galaxy.util import asbool
import galaxy.webapps.tool_shed.model
import galaxy.webapps.tool_shed.model.mapping
import galaxy.web.framework.webapp
from galaxy.webapps.util import build_template_error_formatters
from galaxy.webapps.util import (
MiddlewareWrapUnsupported,
build_template_error_formatters,
wrap_if_allowed,
wrap_if_allowed_or_fail
)
from galaxy import util
from galaxy.util.postfork import process_is_uwsgi
from galaxy.util.properties import load_app_properties
from routes.middleware import RoutesMiddleware
@@ -186,12 +190,11 @@ def app_factory( global_conf, **kwargs ):
webapp.finalize_config()
# Wrap the webapp in some useful middleware
if kwargs.get( 'middleware', True ):
webapp = wrap_in_middleware( webapp, global_conf, **kwargs )
webapp = wrap_in_middleware(webapp, global_conf, app.application_stack, **kwargs)
if asbool( kwargs.get( 'static_enabled', True) ):
if process_is_uwsgi:
log.error("Static middleware is enabled in your configuration but this is a uwsgi process. Refusing to wrap in static middleware.")
else:
webapp = wrap_in_static( webapp, global_conf, **kwargs )
webapp = wrap_if_allowed(webapp, app.application_stack, wrap_in_static,
args=(global_conf,),
kwargs=kwargs)
# Close any pooled database connections before forking
try:
galaxy.webapps.tool_shed.model.mapping.metadata.bind.dispose()
@@ -201,8 +204,9 @@ def app_factory( global_conf, **kwargs ):
return webapp
def wrap_in_middleware( app, global_conf, **local_conf ):
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 )
@@ -210,63 +214,62 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# First put into place httpexceptions, which must be most closely
# wrapped around the application (it can interact poorly with
# other middleware):
app = httpexceptions.make_middleware( app, conf )
log.debug( "Enabling 'httpexceptions' 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 = RoutesMiddleware( app, redirect_mapper )
log.debug( "Enabling 'routes' middleware" )
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 galaxy.webapps.tool_shed.framework.middleware.remoteuser import RemoteUser
app = RemoteUser( app, maildomain=conf.get( 'remote_user_maildomain', None ),
display_servers=util.listify( conf.get( 'display_servers', '' ) ),
admin_users=conf.get( 'admin_users', '' ).split( ',' ),
remote_user_secret_header=conf.get('remote_user_secret', None) )
log.debug( "Enabling 'remote user' middleware" )
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 = recursive.RecursiveMiddleware( app, conf )
log.debug( "Enabling 'recursive' middleware" )
if debug and asbool( conf.get( 'use_interactive', False ) ) and not process_is_uwsgi:
app = wrap_if_allowed( app, stack, recursive.RecursiveMiddleware, args=(conf,) )
if debug and asbool( conf.get( 'use_interactive', False ) ):
# Interactive exception debugging, scary dangerous if publicly
# accessible, if not enabled we'll use the regular error printing
# middleware.
from weberror import evalexception
app = evalexception.EvalException( app, conf,
templating_formatters=build_template_error_formatters() )
log.debug( "Enabling 'eval exceptions' middleware" )
try:
from weberror import evalexception
app = wrap_if_allowed_or_fail( app, stack, evalexception.EvalException,
args=(conf,),
kwargs=dict(templating_formatters=build_template_error_formatters()) )
except MiddlewareWrapUnsupported as exc:
log.warning(str(exc))
import galaxy.web.framework.middleware.error
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
else:
if debug and asbool( conf.get( 'use_interactive', False ) ) and process_is_uwsgi:
log.error("Interactive debugging middleware is enabled in your configuration "
"but this is a uwsgi process. Refusing to wrap in interactive error middleware.")
# Not in interactive debug mode, just use the regular error middleware
import galaxy.web.framework.middleware.error
app = galaxy.web.framework.middleware.error.ErrorMiddleware( app, conf )
log.debug( "Enabling 'error' middleware" )
app = wrap_if_allowed( app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,) )
# Transaction logging (apache access.log style)
if asbool( conf.get( 'use_translogger', True ) ):
from paste.translogger import TransLogger
app = TransLogger( app )
log.debug( "Enabling 'trans logger' middleware" )
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
log.debug( "Enabling 'sentry' middleware" )
app = Sentry( app, sentry_dsn )
app = wrap_if_allowed( app, stack, Sentry, args=(sentry_dsn,) )
# X-Forwarded-Host handling
from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware
app = XForwardedHostMiddleware( app )
log.debug( "Enabling 'x-forwarded-host' middleware" )
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. The print debug middleware needs to be loaded last,
@@ -276,19 +279,16 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
# Middleware to check for WSGI compliance
if asbool( conf.get( 'use_lint', True ) ):
from paste import lint
app = lint.make_middleware( app, conf )
log.debug( "Enabling 'lint' middleware" )
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 ) ):
import profile
app = profile.ProfileMiddleware( app, conf )
log.debug( "Enabling 'profile' middleware" )
app = wrap_if_allowed( app, stack, profile.ProfileMiddleware, args=(conf,) )
# Middleware that intercepts print statements and shows them on the
# returned page
if asbool( conf.get( 'use_printdebug', True ) ):
from paste.debug import prints
app = prints.PrintDebugMiddleware( app, conf )
log.debug( "Enabling 'print debug' middleware" )
app = wrap_if_allowed( app, stack, prints.PrintDebugMiddleware, args=(conf,) )
return app
+54
View File
@@ -1,8 +1,17 @@
from __future__ import absolute_import
import logging
import mako.exceptions
log = logging.getLogger(__name__)
class MiddlewareWrapUnsupported(Exception):
pass
def build_template_error_formatters():
"""
Build a list of template error formatters for WebError. When an error
@@ -19,3 +28,48 @@ def build_template_error_formatters():
return mako.exceptions.html_error_template().render( full=False, css=False )
formatters.append( mako_html_data )
return formatters
def wrap_if_allowed_or_fail(app, stack, wrap, name=None, args=None, kwargs=None):
"""
Wrap the application with the given method if the application stack allows for it.
Arguments are the same as for :func:`wrap_if_allowed`.
Raises :exception:`MiddlewareWrapUnsupported` if the stack does not allow the middleware.
"""
name = name or wrap.__name__
if not stack.allowed_middleware(wrap):
raise MiddlewareWrapUnsupported(
"'%s' is enabled in your configuration but the %s application stack does not support it, this "
"middleware has been disabled" % (name, stack.name))
args = args or []
kwargs = kwargs or {}
log.debug("Enabling '%s' middleware", name)
return wrap(app, *args, **kwargs)
def wrap_if_allowed(app, stack, wrap, name=None, args=None, kwargs=None):
"""
Wrap the application with the given method if the application stack allows for it.
:type app: :class:`galaxy.web.framework.webapp.WebApplication` subclass
:param app: application to wrap
:type stack: :class:`galaxy.web.stack.ApplicationStack` subclass
:param stack: instance of application stack implementing `allowed_middleware()` method
:type wrap: types.FunctionType or types.LambdaType
:param wrap: function to wrap application with
:type name: str
:param name: alternative wrap function name for logging purposes (`wrap.__name__` if None)
:type args: list
:param args: arguments to pass to `wrap` (not including `app` itself)
:type kwargs: dict
:param kwargs: keyword arguments to pass to `wrap`
Returns `app` unmodified if the stack does not allow the middleware.
"""
try:
return wrap_if_allowed_or_fail(app, stack, wrap, name=name, args=args, kwargs=kwargs)
except MiddlewareWrapUnsupported as exc:
log.warning(str(exc))
return app
@@ -8,7 +8,6 @@ from sqlalchemy import false
import tool_shed.util.shed_util_common as suc
from galaxy import util
from galaxy.util.postfork import register_postfork_function
from tool_shed.util import common_util
from tool_shed.util import encoding_util
from tool_shed.util import repository_util
@@ -27,7 +26,7 @@ class UpdateRepositoryManager( object ):
self.sleeper = Sleeper()
self.restarter = threading.Thread( target=self.__restarter )
self.restarter.daemon = True
register_postfork_function(self.restarter.start)
self.app.application_stack.register_postfork_function(self.restarter.start)
self.seconds_to_sleep = int( app.config.hours_between_check * 3600 )
def get_update_to_changeset_revision_and_ctx_rev( self, repository ):
+2 -2
View File
@@ -276,8 +276,8 @@ then
DOCKER_RUN_EXTRA_ARGS="-v ${tmp}:/tmp ${DOCKER_RUN_EXTRA_ARGS}"
shift
fi
UID=$(id -u)
DOCKER_RUN_EXTRA_ARGS="-e GALAXY_TEST_UID=${UID} ${DOCKER_RUN_EXTRA_ARGS}"
MY_UID=$(id -u)
DOCKER_RUN_EXTRA_ARGS="-e GALAXY_TEST_UID=${MY_UID} ${DOCKER_RUN_EXTRA_ARGS}"
echo "Launching docker container for testing..."
docker $DOCKER_EXTRA_ARGS run $DOCKER_RUN_EXTRA_ARGS -e "BUILD_NUMBER=$BUILD_NUMBER" -e "GALAXY_TEST_DATABASE_TYPE=$db_type" --rm -v `pwd`:/galaxy $DOCKER_IMAGE "$@"
exit $?
+3 -115
View File
@@ -1,116 +1,4 @@
This is not documentation. These are hints and examples to get you started
until the documentation is written.
To learn about Galaxy API please use the following resources:
Set these options in galaxy.ini and start the server:
admin_users = you@example.org
library_import_dir = /path/to/some/directory
In the directory you specified for 'library_import_dir', create some
subdirectories, and put (or symlink) files to import into Galaxy into those
subdirectories.
In Galaxy, create an account that matches the address you put in 'admin_users',
then browse to that user's preferences and generate a new API Key. Copy the
key to your clipboard and then use these scripts:
% ./display.py my_key http://localhost:4096/api/libraries
Collection Members
------------------
0 elements in collection
% ./library_create_library.py my_key http://localhost:4096/api/libraries api_test 'API Test Library'
Response
--------
/api/libraries/f3f73e481f432006
name: api_test
id: f3f73e481f432006
% ./display.py my_key http://localhost:4096/api/libraries
Collection Members
------------------
/api/libraries/f3f73e481f432006
name: api_test
id: f3f73e481f432006
% ./display.py my_key http://localhost:4096/api/libraries/f3f73e481f432006
Member Information
------------------
synopsis: None
contents_url: /api/libraries/f3f73e481f432006/contents
description: API Test Library
name: api_test
% ./display.py my_key http://localhost:4096/api/libraries/f3f73e481f432006/contents
Collection Members
------------------
/api/libraries/f3f73e481f432006/contents/28202595c0d2591f61ddda595d2c3670
name: /
type: folder
id: 28202595c0d2591f61ddda595d2c3670
% ./library_create_folder.py my_key http://localhost:4096/api/libraries/f3f73e481f432006/contents 28202595c0d2591f61ddda595d2c3670 api_test_folder1 'API Test Folder 1'
Response
--------
/api/libraries/f3f73e481f432006/contents/28202595c0d2591fa4f9089d2303fd89
name: api_test_folder1
id: 28202595c0d2591fa4f9089d2303fd89
% ./library_upload_from_import_dir.py my_key http://localhost:4096/api/libraries/f3f73e481f432006/contents 28202595c0d2591fa4f9089d2303fd89 bed bed hg19
Response
--------
/api/libraries/f3f73e481f432006/contents/e9ef7fdb2db87d7b
name: 2.bed
id: e9ef7fdb2db87d7b
/api/libraries/f3f73e481f432006/contents/3b7f6a31f80a5018
name: 3.bed
id: 3b7f6a31f80a5018
% ./display.py my_key http://localhost:4096/api/libraries/f3f73e481f432006/contents
Collection Members
------------------
/api/libraries/f3f73e481f432006/contents/28202595c0d2591f61ddda595d2c3670
name: /
type: folder
id: 28202595c0d2591f61ddda595d2c3670
/api/libraries/f3f73e481f432006/contents/28202595c0d2591fa4f9089d2303fd89
name: /api_test_folder1
type: folder
id: 28202595c0d2591fa4f9089d2303fd89
/api/libraries/f3f73e481f432006/contents/e9ef7fdb2db87d7b
name: /api_test_folder1/2.bed
type: file
id: e9ef7fdb2db87d7b
/api/libraries/f3f73e481f432006/contents/3b7f6a31f80a5018
name: /api_test_folder1/3.bed
type: file
id: 3b7f6a31f80a5018
% ./display.py my_key http://localhost:4096/api/libraries/f3f73e481f432006/contents/e9ef7fdb2db87d7b
Member Information
------------------
misc_blurb: 68 regions
metadata_endCol: 3
data_type: bed
metadata_columns: 6
metadata_nameCol: 4
uploaded_by: nate@...
metadata_strandCol: 6
name: 2.bed
genome_build: hg19
metadata_comment_lines: None
metadata_startCol: 2
metadata_chromCol: 1
file_size: 4272
metadata_data_lines: 68
message:
metadata_dbkey: hg19
misc_info: uploaded bed file
date_uploaded: 2010-06-22T17:01:51.266119
metadata_column_types: str, int, int, str, int, str
Other parameters are valid when uploading, they are the same parameters as are
used in the web form, like 'link_data_only' and etc.
The request and response format should be considered alpha and are subject to change.
https://galaxyproject.org/develop/api/
https://docs.galaxyproject.org/en/latest/api_doc.html
+1
View File
@@ -37,6 +37,7 @@ security_helper = SecurityHelper(id_secret=id_secret)
if len(sys.argv) != 3:
sys.stdout.write("python %s (encode|decode) value\n" % sys.argv[0])
sys.exit(1)
action = sys.argv[1]
value = sys.argv[2]
@@ -0,0 +1 @@
{"version":3,"file":"base-creator.js","sources":["../../../src/mvc/collection/base-creator.js"],"names":["define","CollectionCreatorMixin","_validationWarning","what","clear","VALIDATION_CLASS","this","$","add","focus","select","removeClass","addClass","_changeName","_getName","_nameCheckForEnter","ev","keyCode","blocking","_clickCreate","_","escape","val","_clickMoreHelp","stopPropagation","hide","_clickLessHelp","show","_toggleHelp","toggleClass","toggle","_showAlert","message","alertClass","attr","find","html","_hideAlert","_cancelCreate","oncancel","call","name","createList","_creatorTemplates","main","template","join"],"mappings":"AAAAA,UACG,WAKH,GAAIC,IAGAC,mBAAqB,SAAUC,EAAMC,GACjC,GAAIC,GAAmB,oBACV,UAATF,IACAA,EAAOG,KAAKC,EAAG,oBAAqBC,IAAKF,KAAKC,EAAG,4BACjDD,KAAKC,EAAG,oBAAqBE,QAAQC,UAErCN,GACAD,EAAOA,GAAQG,KAAKC,EAAG,IAAMF,GAC7BF,EAAKQ,YAAaN,IAElBF,EAAKS,SAAUP,IAMvBQ,YAAc,WACVP,KAAKJ,mBAAoB,SAAUI,KAAKQ,aAI5CC,mBAAqB,SAAUC,GACR,KAAfA,EAAGC,SAAmBX,KAAKY,UAC3BZ,KAAKa,gBAKbL,SAAW,WACP,MAAOM,GAAEC,OAAQf,KAAKC,EAAG,oBAAqBe,QAKlDC,eAAiB,SAAUP,GACvBA,EAAGQ,kBACHlB,KAAKC,EAAG,cAAeK,SAAU,YACjCN,KAAKC,EAAG,cAAekB,QAG3BC,eAAiB,SAAUV,GACvBA,EAAGQ,kBACHlB,KAAKC,EAAG,cAAeI,YAAa,YACpCL,KAAKC,EAAG,cAAeoB,QAG3BC,YAAc,SAAUZ,GACpBA,EAAGQ,kBACHlB,KAAKC,EAAG,cAAesB,YAAa,YACpCvB,KAAKC,EAAG,cAAeuB,UAI3BC,WAAa,SAAUC,EAASC,GAC5BA,EAAaA,GAAc,eAC3B3B,KAAKC,EAAG,cAAekB,OACvBnB,KAAKC,EAAG,kBACH2B,KAAM,QAAS,2BAA4BtB,SAAUqB,GAAaN,OAClEQ,KAAM,kBAAmBC,KAAMJ,IAGxCK,WAAa,WACT/B,KAAKC,EAAG,cAAeoB,OACvBrB,KAAKC,EAAG,kBAAmBkB,QAG/Ba,cAAe,WACkB,kBAAlBhC,MAAKiC,UACZjC,KAAKiC,SAASC,KAAMlC,OAK5Ba,aAAe,WACX,GAAIsB,GAAOnC,KAAKQ,UACX2B,GAEOnC,KAAKY,UACbZ,KAAKoC,WAAYD,GAFjBnC,KAAKJ,mBAAoB,SAMjCyC,mBACIC,KAAOxB,EAAEyB,UACL,8CACA,yDACA,+CACFC,KAAK,MAMX,QACI7C,uBAAwBA"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"locale.js","sources":["../../../src/nls/fr/locale.js"],"names":["define","Analyze Data","Workflow","Shared Data","Data Libraries","Histories","Workflows","Visualizations","Pages","Visualization","New Track Browser","Saved Visualizations","Interactive Environments","Admin","Help","Support","Search","Mailing Lists","Videos","Wiki","How to Cite Galaxy","Interactive Tours","User","Login","Register","Logged in as","Preferences","Custom Builds","Logout","Saved Histories","Saved Datasets","Saved Pages","History Lists","Histories Shared with Me","Current History","Create New","Copy History","Share or Publish","Show Structure","Extract Workflow","Delete Permanently","Dataset Actions","Copy Datasets","Dataset Security","Resume Paused Jobs","Collapse Expanded Datasets","Unhide Hidden Datasets","Delete Hidden Datasets","Purge Deleted Datasets","Downloads","Export Tool Citations","Export History to File","Other Actions","Import from File","Webhooks","This history is empty","No matching datasets found","An error occurred while getting updates from the server","Please contact a Galaxy administrator if the problem persists","search datasets","You are currently viewing a deleted history!","You are over your disk quota","Tool execution is on hold until your disk usage drops below your allocated quota","All","None","For all selected","Edit history tags","Edit history Annotation","Click to rename history","Operations on multiple datasets","Hide datasets","Unhide datasets","Delete datasets","Undelete datasets","Permanently delete datasets","This will permanently remove the data in your datasets. Are you sure?","Dataset","Annotation","This history is empty. Click 'Get Data' on the left tool menu to start","You must be logged in to create histories","load your own data","get data from an external source","Include Deleted Datasets","Include Hidden Datasets","Unable to purge dataset","Cannot display datasets removed from disk","This dataset must finish uploading before it can be viewed","This dataset is not yet viewable","View data","Download","Download dataset","Additional files","View details","This is a new dataset and not all of its data are available yet","You do not have permission to view this dataset","The job creating this dataset was cancelled before completion","This job is waiting to run","This dataset is currently uploading","Metadata is being auto-detected","This job is currently running","This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume","An error occurred with this dataset","No data","An error occurred setting the metadata for this dataset","There was an error getting the data for this dataset","This dataset has been deleted and removed from disk","This dataset has been deleted","This dataset has been hidden","format","database","Edit attributes","Cannot edit attributes of datasets removed from disk","Undelete dataset to edit attributes","This dataset must finish uploading before it can be edited","This dataset is not yet editable","Delete","Dataset is already deleted","View or report this error","Run this job again","Visualize","Visualize in","Undelete it","Permanently remove it from disk","Unhide it","You may be able to","set it manually or retry auto-detection","Edit dataset tags","Edit dataset annotation","Tags","Edit annotation"],"mappings":"AACAA,QAEAC,eACI,qBACJC,SACI,WACJC,cACI,oBACJC,iBACI,0BACJC,UACI,cACJC,UACI,YACJC,eACI,iBACJC,MACI,QACJC,cACI,gBACJC,oBACI,sCACJC,uBACI,6BACJC,2BACI,6BACJC,MACI,QACJC,KACI,OACJC,QACI,aACJC,OACI,YACJC,gBACI,qBACJC,OACI,SACJC,KACI,iBACJC,qBACI,uBACJC,oBACI,qBACJC,KACI,cACJC,MACI,mBACJC,SACI,iBACJC,eACI,0BACJC,YACI,cACJC,gBACI,kCACJC,OACI,eACJC,kBACI,0BACJC,iBACI,sBACJC,cACI,qBAGJC,iBACI,EAIJC,4BACI,EACJC,mBACI,EACJC,cACI,EACJC,gBACI,EACJC,oBACI,EACJC,kBACI,EACJC,oBACI,EAIJC,sBACI,EACJC,mBACI,EACJC,iBACI,EACJC,oBACI,EACJC,sBACI,EACJC,8BACI,EACJC,0BACI,EACJC,0BACI,EACJC,0BACI,EACJC,WACI,EACJC,yBACI,EACJC,0BACI,EACJC,iBACI,EACJC,oBACI,EACJC,UACI,EAIJC,wBACI,0BACJC,6BACI,mDACJC,0DACI,+EACJC,gEACI,oFAIJC,kBACI,yBACJC,+CACI,sDACJC,+BACI,gDACJC,mFACI,6GACJC,IACI,OACJC,KACI,QACJC,mBACI,0BAGJC,oBACI,uCACJC,0BACI,sCACJC,0BACI,qCAEJC,kCACI,qDACJC,gBACI,6BACJC,kBACI,sCACJC,kBACI,gCACJC,oBACI,0CACJC,8BACI,+CACJC,wEACI,4FAMJC,QACI,iBACJC,WACI,aAGJC,yEACI,oGACJC,4CACI,oDAIJC,qBACI,8BACJC,mCACI,gDAGJC,2BACI,wCACJC,0BACI,qCAKJC,0BACI,yCAIJC,4CACI,mEACJC,6DACI,oFACJC,mCACI,2CACJC,YACI,mBAEJC,SACI,cACJC,mBACI,gCACJC,mBACI,wBAEJC,eACI,mBAIJC,kEACI,sGAEJC,kDACI,0DAEJC,gEACI,2EAEJC,6BACI,yCAEJC,sCACI,kDAEJC,kCACI,sCAEJC,gCACI,6BAEJC,iFACI,0HAEJC,sCACI,gDAEJC,UACI,gBAEJC,0DACI,uFAGJC,uDACI,mFAGJC,sDACI,uDACJC,gCACI,mCACJC,+BACI,gCAEJC,OACI,SACJC,SACI,sBAGJC,kBACI,uBACJC,uDACI,yEACJC,sCACI,2DACJC,6DACI,6EACJC,mCACI,8CAEJC,OACI,YACJC,6BACI,qDAEJC,4BACI,gCAEJC,qBACI,mCAEJC,UACI,aACJC,eACI,iBAEJC,cACI,YACJC,kCACI,qCACJC,YACI,iBAEJC,qBACI,iCACJC,0CACI,+DAEJC,oBACI,yCACJC,0BACI,2CAIJC,KACI,YACJC,kBACI"}
{"version":3,"file":"locale.js","sources":["../../../src/nls/fr/locale.js"],"names":["define","Analyze Data","Workflow","Shared Data","Data Libraries","Histories","Workflows","Visualizations","Pages","Visualization","New Track Browser","Saved Visualizations","Interactive Environments","Admin","Help","Support","Search","Mailing Lists","Videos","Wiki","How to Cite Galaxy","Interactive Tours","User","Login","Register","Logged in as","Preferences","Custom Builds","Logout","Saved Histories","Saved Datasets","Saved Pages","History Lists","Histories Shared with Me","Current History","Create New","Copy History","Share or Publish","Show Structure","Extract Workflow","Delete Permanently","Dataset Actions","Copy Datasets","Dataset Security","Resume Paused Jobs","Collapse Expanded Datasets","Unhide Hidden Datasets","Delete Hidden Datasets","Purge Deleted Datasets","Downloads","Export Tool Citations","Export History to File","Other Actions","Import from File","Webhooks","This history is empty","No matching datasets found","An error occurred while getting updates from the server","Please contact a Galaxy administrator if the problem persists","search datasets","You are currently viewing a deleted history!","You are over your disk quota","Tool execution is on hold until your disk usage drops below your allocated quota","All","None","For all selected","Edit history tags","Edit history Annotation","Click to rename history","Operations on multiple datasets","Hide datasets","Unhide datasets","Delete datasets","Undelete datasets","Permanently delete datasets","This will permanently remove the data in your datasets. Are you sure?","Dataset","Annotation","This history is empty. Click 'Get Data' on the left tool menu to start","You must be logged in to create histories","load your own data","get data from an external source","Include Deleted Datasets","Include Hidden Datasets","Unable to purge dataset","Cannot display datasets removed from disk","This dataset must finish uploading before it can be viewed","This dataset is not yet viewable","View data","Download","Download dataset","Additional files","View details","This is a new dataset and not all of its data are available yet","You do not have permission to view this dataset","The job creating this dataset was cancelled before completion","This job is waiting to run","This dataset is currently uploading","Metadata is being auto-detected","This job is currently running","This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume","An error occurred with this dataset","No data","An error occurred setting the metadata for this dataset","There was an error getting the data for this dataset","This dataset has been deleted and removed from disk","This dataset has been deleted","This dataset has been hidden","format","database","Edit attributes","Cannot edit attributes of datasets removed from disk","Undelete dataset to edit attributes","This dataset must finish uploading before it can be edited","This dataset is not yet editable","Delete","Dataset is already deleted","View or report this error","Run this job again","Visualize","Visualize in","Undelete it","Permanently remove it from disk","Unhide it","You may be able to","set it manually or retry auto-detection","Edit dataset tags","Edit dataset annotation","Tags","Edit annotation"],"mappings":"AACAA,QAEAC,eACI,qBACJC,SACI,WACJC,cACI,oBACJC,iBACI,0BACJC,UACI,cACJC,UACI,YACJC,eACI,iBACJC,MACI,QACJC,cACI,gBACJC,oBACI,sCACJC,uBACI,6BACJC,2BACI,6BACJC,MACI,QACJC,KACI,OACJC,QACI,aACJC,OACI,YACJC,gBACI,qBACJC,OACI,SACJC,KACI,iBACJC,qBACI,uBACJC,oBACI,qBACJC,KACI,cACJC,MACI,mBACJC,SACI,iBACJC,eACI,0BACJC,YACI,cACJC,gBACI,kCACJC,OACI,eACJC,kBACI,0BACJC,iBACI,8BACJC,cACI,qBAGJC,gBACI,2BAIJC,2BACI,gCACJC,kBACI,iBACJC,aACI,mBACJC,eACI,sBACJC,mBACI,sBACJC,iBACI,uBACJC,mBACI,uBAIJC,qBACI,2BACJC,kBACI,kCACJC,gBACI,6BACJC,mBACI,uBACJC,qBACI,mCACJC,6BACI,8BACJC,yBACI,8BACJC,yBACI,+BACJC,yBACI,+BACJC,UACI,cACJC,wBACI,oCACJC,yBACI,wCACJC,gBACI,iBACJC,mBACI,6BACJC,SACI,WAIJC,wBACI,0BACJC,6BACI,mDACJC,0DACI,+EACJC,gEACI,oFAIJC,kBACI,yBACJC,+CACI,sDACJC,+BACI,gDACJC,mFACI,6GACJC,IACI,OACJC,KACI,QACJC,mBACI,0BAGJC,oBACI,uCACJC,0BACI,sCACJC,0BACI,qCAEJC,kCACI,qDACJC,gBACI,6BACJC,kBACI,sCACJC,kBACI,gCACJC,oBACI,0CACJC,8BACI,+CACJC,wEACI,4FAMJC,QACI,iBACJC,WACI,aAGJC,yEACI,oGACJC,4CACI,oDAIJC,qBACI,8BACJC,mCACI,gDAGJC,2BACI,wCACJC,0BACI,qCAKJC,0BACI,yCAIJC,4CACI,mEACJC,6DACI,oFACJC,mCACI,2CACJC,YACI,mBAEJC,SACI,cACJC,mBACI,gCACJC,mBACI,wBAEJC,eACI,mBAIJC,kEACI,sGAEJC,kDACI,0DAEJC,gEACI,2EAEJC,6BACI,yCAEJC,sCACI,kDAEJC,kCACI,sCAEJC,gCACI,6BAEJC,iFACI,0HAEJC,sCACI,gDAEJC,UACI,gBAEJC,0DACI,uFAGJC,uDACI,mFAGJC,sDACI,uDACJC,gCACI,mCACJC,+BACI,gCAEJC,OACI,SACJC,SACI,sBAGJC,kBACI,uBACJC,uDACI,yEACJC,sCACI,2DACJC,6DACI,6EACJC,mCACI,8CAEJC,OACI,YACJC,6BACI,qDAEJC,4BACI,gCAEJC,qBACI,mCAEJC,UACI,aACJC,eACI,iBAEJC,cACI,YACJC,kCACI,qCACJC,YACI,iBAEJC,qBACI,iCACJC,0CACI,+DAEJC,oBACI,yCACJC,0BACI,2CAIJC,KACI,YACJC,kBACI"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
webpackJsonp([4],[function(e,r,n){(function(e){var r=n(1),a=r,i=n(57).GalaxyApp,o=n(10),t=n(5),l=n(59);window.app=function(n,c){window.Galaxy=new i(n,c),Galaxy.debug("login app");var d=encodeURI(n.redirect);if(!n.show_welcome_with_login){var w=r.param({use_panels:"True",redirect:d});return void(window.location.href=Galaxy.root+"user/login?"+w)}var p=new l.PageLayoutView(e.extend(n,{el:"body",center:new o.CenterPanel({el:"#center"}),right:new o.RightPanel({title:t("Login required"),el:"#right"})}));a(function(){var e=r.param({redirect:d}),a=Galaxy.root+"user/login?"+e;p.render(),p.center.$("#galaxy_main").prop("src",n.welcome_url),p.right.$(".unified-panel-body").css("overflow","hidden").html('<iframe src="'+a+'" frameborder="0" style="width: 100%; height: 100%;"/>')})}}).call(r,n(2))}]);
webpackJsonp([4],[function(e,r,n){(function(e){var r=n(1),a=r,i=n(56).GalaxyApp,o=n(10),t=n(5),l=n(58);window.app=function(n,c){window.Galaxy=new i(n,c),Galaxy.debug("login app");var d=encodeURI(n.redirect);if(!n.show_welcome_with_login){var w=r.param({use_panels:"True",redirect:d});return void(window.location.href=Galaxy.root+"user/login?"+w)}var p=new l.PageLayoutView(e.extend(n,{el:"body",center:new o.CenterPanel({el:"#center"}),right:new o.RightPanel({title:t("Login required"),el:"#right"})}));a(function(){var e=r.param({redirect:d}),a=Galaxy.root+"user/login?"+e;p.render(),p.center.$("#galaxy_main").prop("src",n.welcome_url),p.right.$(".unified-panel-body").css("overflow","hidden").html('<iframe src="'+a+'" frameborder="0" style="width: 100%; height: 100%;"/>')})}}).call(r,n(2))}]);
//# sourceMappingURL=login.bundled.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
define([],function(){var a={_validationWarning:function(a,b){var c="validation-warning";"name"===a&&(a=this.$(".collection-name").add(this.$(".collection-name-prompt")),this.$(".collection-name").focus().select()),b?(a=a||this.$("."+c),a.removeClass(c)):a.addClass(c)},_changeName:function(){this._validationWarning("name",!!this._getName())},_nameCheckForEnter:function(a){13!==a.keyCode||this.blocking||this._clickCreate()},_getName:function(){return _.escape(this.$(".collection-name").val())},_clickMoreHelp:function(a){a.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(a){a.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(a){a.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(a,b){b=b||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(b).show().find(".alert-message").html(a)},_hideAlert:function(){this.$(".main-help").show(),this.$(".header .alert").hide()},_cancelCreate:function(){"function"==typeof this.oncancel&&this.oncancel.call(this)},_clickCreate:function(){var a=this._getName();a?this.blocking||this.createList(a):this._validationWarning("name")},_creatorTemplates:{main:_.template(['<div class="header flex-row no-flex"></div>','<div class="middle flex-row flex-row-container"></div>','<div class="footer flex-row no-flex"></div>'].join(""))}};return{CollectionCreatorMixin:a}});
//# sourceMappingURL=../../../maps/mvc/collection/base-creator.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,128 +0,0 @@
<%inherit file="/base.mako"/>
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_dependencies_section" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_readme_section" />
<%namespace file="/webapps/tool_shed/repository/common.mako" import="*" />
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "library" )}
</%def>
<%def name="javascripts()">
${parent.javascripts()}
${h.js("libs/jquery/jquery.rating", "libs/jquery/jstorage" )}
${container_javascripts()}
</%def>
<%
# Handle the case where an uninstalled repository encountered errors during the process of being reinstalled. In
# this case, the repository metadata is an empty dictionary, but one or both of has_repository_dependencies
# and includes_tool_dependencies may be True. If either of these are True but we have no metadata, we cannot install
# repository dependencies on this pass.
if has_repository_dependencies:
repository_dependencies = containers_dict[ 'repository_dependencies' ]
missing_repository_dependencies = containers_dict[ 'missing_repository_dependencies' ]
if repository_dependencies or missing_repository_dependencies:
can_display_repository_dependencies = True
else:
can_display_repository_dependencies = False
else:
can_display_repository_dependencies = False
if includes_tool_dependencies:
tool_dependencies = containers_dict[ 'tool_dependencies' ]
missing_tool_dependencies = containers_dict[ 'missing_tool_dependencies' ]
if tool_dependencies or missing_tool_dependencies:
can_display_tool_dependencies = True
else:
can_display_tool_dependencies = False
else:
can_display_tool_dependencies = False
can_display_resolver_installation = install_resolver_dependencies_check_box is not None
%>
%if message:
${render_msg( message, status )}
%endif
<div class="warningmessage">
<p>
The Galaxy development team does not maintain the contents of many Galaxy Tool Shed repositories. Some
repository tools may include code that produces malicious behavior, so be aware of what you are installing.
</p>
<p>
If you discover a repository that causes problems after installation, contact <a href="https://wiki.galaxyproject.org/Support" target="_blank">Galaxy support</a>,
sending all necessary information, and appropriate action will be taken.
</p>
<p>
<a href="https://wiki.galaxyproject.org/ToolShedRepositoryFeatures#Contact_repository_owner" target="_blank">Contact the repository owner</a> for
general questions or concerns.
</p>
</div>
<div class="toolForm">
<div class="toolFormBody">
<form name="select_shed_tool_panel_config" id="select_shed_tool_panel_config" action="${h.url_for( controller='admin_toolshed', action='prepare_for_install' )}" method="post" >
<div class="form-row">
<input type="hidden" name="encoded_repo_info_dicts" value="${encoded_repo_info_dicts}" />
<input type="hidden" name="updating" value="${updating}" />
<input type="hidden" name="updating_repository_id" value="${updating_repository_id}" />
<input type="hidden" name="updating_to_ctx_rev" value="${updating_to_ctx_rev}" />
<input type="hidden" name="updating_to_changeset_revision" value="${updating_to_changeset_revision}" />
<input type="hidden" name="encoded_updated_metadata" value="${encoded_updated_metadata}" />
<input type="hidden" name="includes_tools" value="${includes_tools}" />
<input type="hidden" name="includes_tool_dependencies" value="${includes_tool_dependencies}" />
<input type="hidden" name="includes_tools_for_display_in_tool_panel" value="${includes_tools_for_display_in_tool_panel}" />
<input type="hidden" name="tool_shed_url" value="${tool_shed_url|h}" />
</div>
<div style="clear: both"></div>
<% readme_files_dict = containers_dict.get( 'readme_files', None ) %>
%if readme_files_dict:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Repository README file - may contain important installation or license information</th>
</table>
</div>
${render_readme_section( containers_dict )}
<div style="clear: both"></div>
%endif
%if can_display_repository_dependencies or can_display_tool_dependencies or can_display_resolver_installation:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Confirm dependency installation</th>
</table>
</div>
${render_dependencies_section( install_resolver_dependencies_check_box, install_repository_dependencies_check_box, install_tool_dependencies_check_box, containers_dict, revision_label=None, export=False )}
<div style="clear: both"></div>
%endif
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Choose the configuration file whose tool_path setting will be used for installing repositories</th>
</table>
</div>
%if shed_tool_conf_select_field:
<%
if len( shed_tool_conf_select_field.options ) == 1:
select_help = "Your Galaxy instance is configured with 1 shed-related tool configuration file, so repositories will be "
select_help += "installed using its <b>tool_path</b> setting."
else:
select_help = "Your Galaxy instance is configured with %d shed-related tool configuration files, " % len( shed_tool_conf_select_field.options )
select_help += "so select the file whose <b>tool_path</b> setting you want used for installing repositories."
%>
<div class="form-row">
<label>Shed tool configuration file:</label>
${shed_tool_conf_select_field.get_html()}
<div class="toolParamHelp" style="clear: both;">
${select_help|h}
</div>
</div>
<div style="clear: both"></div>
%else:
<input type="hidden" name="shed_tool_conf" value="${shed_tool_conf|h}"/>
%endif
<div class="form-row">
<input type="submit" name="select_shed_tool_panel_config_button" value="Install"/>
</div>
</form>
</div>
</div>
@@ -131,12 +131,14 @@ or manually satisfy the dependencies listed below.</p>
<div style="clear: both"></div>
%endif
%if shed_tool_conf_select_field:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Choose the tool panel section to contain the installed tools (optional)</th>
</table>
</div>
%if includes_tools_for_display_in_tool_panel:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Choose the tool panel section to contain the installed tools (optional)</th>
</table>
</div>
<div class="detail-section">
%endif
<%
if len( shed_tool_conf_select_field.options ) == 1:
select_help = "Your Galaxy instance is configured with 1 shed-related tool configuration file, so repositories will be "
@@ -157,24 +159,28 @@ or manually satisfy the dependencies listed below.</p>
%else:
<input type="hidden" name="shed_tool_conf" value="${shed_tool_conf|h}"/>
%endif
<div class="form-row">
<label>Add new tool panel section:</label>
<input name="new_tool_panel_section_label" type="textfield" value="${new_tool_panel_section_label|h}" size="40"/>
<div class="toolParamHelp" style="clear: both;">
Add a new tool panel section to contain the installed tools (optional).
%if includes_tools_for_display_in_tool_panel:
<div class="form-row">
<label>Add new tool panel section:</label>
<input name="new_tool_panel_section_label" type="textfield" value="${new_tool_panel_section_label|h}" size="40"/>
<div class="toolParamHelp" style="clear: both;">
Add a new tool panel section to contain the installed tools (optional).
</div>
</div>
</div>
<div class="form-row">
<label>Select existing tool panel section:</label>
${tool_panel_section_select_field.get_html()}
<div class="toolParamHelp" style="clear: both;">
Choose an existing section in your tool panel to contain the installed tools (optional).
<div class="form-row">
<label>Select existing tool panel section:</label>
${tool_panel_section_select_field.get_html()}
<div class="toolParamHelp" style="clear: both;">
Choose an existing section in your tool panel to contain the installed tools (optional).
</div>
</div>
</div>
%endif
<div class="form-row">
<input type="submit" name="select_tool_panel_section_button" value="Install"/>
<div class="toolParamHelp" style="clear: both;">
Clicking <b>Install</b> without selecting a tool panel section will load the installed tools into the tool panel outside of any sections.
%if includes_tools_for_display_in_tool_panel:
Clicking <b>Install</b> without selecting a tool panel section will load the installed tools into the tool panel outside of any sections.
%endif
</div>
</div>
</form>
+59
View File
@@ -1,5 +1,7 @@
from base import api
from base.populators import (
DatasetCollectionPopulator,
DatasetPopulator,
LibraryPopulator,
TestsDatasets,
wait_on_state
@@ -10,6 +12,8 @@ class LibrariesApiTestCase( api.ApiTestCase, TestsDatasets ):
def setUp( self ):
super( LibrariesApiTestCase, self ).setUp()
self.dataset_populator = DatasetPopulator( self.galaxy_interactor )
self.dataset_collection_populator = DatasetCollectionPopulator( self.galaxy_interactor )
self.library_populator = LibraryPopulator( self )
def test_create( self ):
@@ -67,6 +71,18 @@ class LibrariesApiTestCase( api.ApiTestCase, TestsDatasets ):
create_response = self._create_folder( library )
self._assert_status_code_is( create_response, 200 )
def test_create_dataset_denied( self ):
library = self.library_populator.new_private_library( "ForCreateDatasets" )
folder_response = self._create_folder( library )
self._assert_status_code_is( folder_response, 200)
folder_id = folder_response.json()[0]['id']
history_id = self.dataset_populator.new_history()
hda_id = self.dataset_populator.new_dataset( history_id, content="1 2 3" )['id']
with self._different_user():
payload = {'from_hda_id': hda_id}
create_response = self._post( "folders/%s/contents" % folder_id, payload )
self._assert_status_code_is( create_response, 403 )
def test_create_dataset( self ):
library = self.library_populator.new_private_library( "ForCreateDatasets" )
payload, files = self.library_populator.create_dataset_request( library, file_type="txt", contents="create_test" )
@@ -85,6 +101,49 @@ class LibrariesApiTestCase( api.ApiTestCase, TestsDatasets ):
assert library_dataset[ "peek" ].find("create_test") >= 0
assert library_dataset[ "file_ext" ] == "txt", library_dataset[ "file_ext" ]
def test_create_dataset_in_folder( self ):
library = self.library_populator.new_private_library( "ForCreateDatasets" )
folder_response = self._create_folder( library )
self._assert_status_code_is( folder_response, 200)
folder_id = folder_response.json()[0]['id']
history_id = self.dataset_populator.new_history()
hda_id = self.dataset_populator.new_dataset( history_id, content="1 2 3" )['id']
payload = {'from_hda_id': hda_id}
create_response = self._post( "folders/%s/contents" % folder_id, payload )
self._assert_status_code_is( create_response, 200 )
library_datasets = create_response.json()
assert len( library_datasets ) == 1
def test_create_datasets_in_library_from_collection( self ):
library = self.library_populator.new_private_library( "ForCreateDatasetsFromCollection" )
folder_response = self._create_folder( library )
self._assert_status_code_is( folder_response, 200)
folder_id = folder_response.json()[0]['id']
history_id = self.dataset_populator.new_history()
hdca_id = self.dataset_collection_populator.create_list_in_history( history_id, contents=["xxx", "yyy"] ).json()["id"]
payload = {'from_hdca_id': hdca_id, 'create_type': 'file', 'folder_id': folder_id}
create_response = self._post( "libraries/%s/contents" % library['id'], payload )
self._assert_status_code_is(create_response, 200)
def test_create_datasets_in_folder_from_collection( self ):
library = self.library_populator.new_private_library( "ForCreateDatasetsFromCollection" )
history_id = self.dataset_populator.new_history()
hdca_id = self.dataset_collection_populator.create_list_in_history( history_id, contents=["xxx", "yyy"] ).json()["id"]
folder_response = self._create_folder( library )
self._assert_status_code_is( folder_response, 200)
folder_id = folder_response.json()[0]['id']
payload = {'from_hdca_id': hdca_id}
create_response = self._post( "folders/%s/contents" % folder_id, payload )
self._assert_status_code_is( create_response, 200 )
assert len(create_response.json()) == 2
# Also test that anything different from a flat dataset collection list
# is refused
hdca_pair_id = self.dataset_collection_populator.create_list_of_pairs_in_history( history_id).json()['id']
payload = {'from_hdca_id': hdca_pair_id}
create_response = self._post( "folders/%s/contents" % folder_id, payload )
self._assert_status_code_is( create_response, 501 )
assert create_response.json()['err_msg'] == 'Cannot add nested collections to library. Please flatten your collection first.'
def _create_folder( self, library ):
create_data = dict(
folder_id=library[ "root_folder_id" ],
+5 -2
View File
@@ -4,6 +4,8 @@ set -e
echo "Deleting galaxy user - it may not exist and this is fine."
deluser galaxy | true
: ${GALAXY_TEST_UID:-"1"}
echo "Creating galaxy group with gid $GALAXY_TEST_UID - it may already exist and this is fine."
groupadd -r galaxy -g "$GALAXY_TEST_UID" | true
echo "Creating galaxy user with uid $GALAXY_TEST_UID - it may already exist and this is fine."
@@ -12,7 +14,7 @@ echo "Setting galaxy user password - the operation may fail."
echo "galaxy:galaxy" | chpasswd | true
chown -R "$GALAXY_TEST_UID:$GALAXY_TEST_UID" /galaxy_venv
GALAXY_TEST_DATABASE_TYPE=${GALAXY_TEST_DATABASE_TYPE:-"postgres"}
: ${GALAXY_TEST_DATABASE_TYPE:-"postgres"}
if [ "$GALAXY_TEST_DATABASE_TYPE" = "postgres" ];
then
su -c '/usr/lib/postgresql/9.3/bin/pg_ctl -o "-F" start -D /opt/galaxy/db' postgres
@@ -48,10 +50,11 @@ export TOOL_SHED_CONFIG_OVERRIDE_DATABASE_CONNECTION
: ${GALAXY_VIRTUAL_ENV:=.venv}
HOME=/galaxy
sudo -E -u "#${GALAXY_TEST_UID}" ./scripts/common_startup.sh || { echo "common_startup.sh failed"; exit 1; }
dev_requirements=./lib/galaxy/dependencies/dev-requirements.txt
[ -f $dev_requirements ] && $GALAXY_VIRTUAL_ENV/bin/pip install -r $dev_requirements
[ -f $dev_requirements ] && sudo -E -u "#${GALAXY_TEST_UID}" $GALAXY_VIRTUAL_ENV/bin/pip install -r $dev_requirements
echo "Upgrading test database..."
sudo -E -u "#${GALAXY_TEST_UID}" sh manage_db.sh upgrade
@@ -64,7 +64,7 @@ class TestDatatypeConverters( ShedTwillTestCase ):
self.galaxy_login( email=common.admin_email, username=common.admin_username )
repository = self.test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
preview_strings_displayed = [ repository.name, self.get_repository_tip( repository ) ]
strings_displayed = [ 'Choose the configuration file' ]
strings_displayed = [ 'Your Galaxy instance is configured with', 'shed-related tool configuration file' ]
strings_not_displayed = [ 'tool panel section' ]
self.install_repository( repository_name,
common.test_user_1_name,