mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Rebuild client and merge from upstream.
This commit is contained in:
@@ -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
@@ -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(''));
|
||||
|
||||
@@ -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
@@ -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 ):
|
||||
|
||||
@@ -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__ )
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Galaxy tagging classes and methods.
|
||||
"""
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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", [])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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",)
|
||||
@@ -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://"):]
|
||||
@@ -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")
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__ )
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ) ) )
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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 ):
|
||||
"""
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 @@
|
||||
{"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,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>
|
||||
|
||||
@@ -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" ],
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user