mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merged in fbacall/galaxy-central-myexp-integration (pull request #45)
This commit is contained in:
@@ -36,6 +36,7 @@ shed_tool_conf.xml
|
||||
tool_data_table_conf.xml
|
||||
tool_sheds_conf.xml
|
||||
integrated_tool_panel.xml
|
||||
openid_conf.xml
|
||||
|
||||
static/welcome.html.*
|
||||
static/welcome.html
|
||||
@@ -63,8 +64,12 @@ run_functional_tests.html
|
||||
# Jars
|
||||
tool-data/shared/jars/
|
||||
|
||||
# CSS build artifacts.
|
||||
*/variables.less
|
||||
static/june_2007_style/blue/base_sprites.less
|
||||
|
||||
# Misc
|
||||
*.orig
|
||||
.DS_Store
|
||||
*.rej
|
||||
*~
|
||||
*~
|
||||
@@ -4,7 +4,12 @@
|
||||
|
||||
use = egg:Paste#http
|
||||
port = 9009
|
||||
|
||||
# The address on which to listen. By default, only listen to localhost (the tool shed will not
|
||||
# be accessible over the network). Use '0.0.0.0' to listen on all available network interfaces.
|
||||
#host = 0.0.0.0
|
||||
host = 127.0.0.1
|
||||
|
||||
use_threadpool = true
|
||||
threadpool_workers = 10
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#! /usr/bin/env python
|
||||
'''
|
||||
universe_merger.py
|
||||
|
||||
Created by Anne Pajon on 31 Jan 2012
|
||||
|
||||
Copyright (c) 2012 Cancer Research UK - Cambridge Research Institute.
|
||||
|
||||
This source file is licensed under the Academic Free License version
|
||||
3.0 available at http://www.opensource.org/licenses/AFL-3.0.
|
||||
|
||||
Permission is hereby granted to reproduce, translate, adapt, alter,
|
||||
transform, modify, or arrange this source file (the "Original Work");
|
||||
to distribute or communicate copies of it under any license of your
|
||||
choice that does not contradict the terms and conditions; to perform
|
||||
or display the Original Work publicly.
|
||||
|
||||
THE ORIGINAL WORK IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS
|
||||
AND WITHOUT WARRANTY, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
|
||||
LIMITATION, THE WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY OR
|
||||
FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF
|
||||
THE ORIGINAL WORK IS WITH YOU.
|
||||
|
||||
Script for merging specific local Galaxy config universe_wsgi.ini.cri with default Galaxy universe_wsgi.ini.sample
|
||||
'''
|
||||
import ConfigParser
|
||||
import sys
|
||||
import optparse
|
||||
import logging
|
||||
|
||||
def main():
|
||||
# logging configuration
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
|
||||
|
||||
# get the options
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option("-s", "--sample", dest="sample", action="store", help="path to Galaxy universe_wsgi.ini.sample file")
|
||||
parser.add_option("-c", "--config", dest="config", action="store", help="path to your own universe_wsgi.ini file")
|
||||
parser.add_option("-o", "--output", dest="output", action="store", help="path to the new merged universe_wsgi.ini.new file")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
for option in ['sample', 'config']:
|
||||
if getattr(options, option) == None:
|
||||
print "Please supply a --%s parameter.\n" % (option)
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
config_sample = ConfigParser.RawConfigParser()
|
||||
config_sample.read(options.sample)
|
||||
config_sample_content = open(options.sample, 'r').read()
|
||||
|
||||
config = ConfigParser.RawConfigParser()
|
||||
config.read(options.config)
|
||||
|
||||
logging.info("Merging your own config file %s into the sample one %s." % (options.config, options.sample))
|
||||
logging.info("---------- DIFFERENCE ANALYSIS BEGIN ----------")
|
||||
for section in config.sections():
|
||||
if not config_sample.has_section(section):
|
||||
logging.warning("-MISSING- section [%s] not found in sample file. It will be ignored." % section)
|
||||
else:
|
||||
for (name, value) in config.items(section):
|
||||
if not config_sample.has_option(section, name):
|
||||
if not "#%s" % name in config_sample_content:
|
||||
logging.warning("-MISSING- section [%s] option '%s' not found in sample file. It will be ignored." % (section, name))
|
||||
else:
|
||||
logging.info("-notset- section [%s] option '%s' not set in sample file. It will be added." % (section, name))
|
||||
config_sample.set(section, name, value)
|
||||
else:
|
||||
if not config_sample.get(section, name) == value:
|
||||
logging.info("- diff - section [%s] option '%s' has different value ('%s':'%s'). It will be modified." % (section, name, config_sample.get(section, name), value))
|
||||
config_sample.set(section, name, value)
|
||||
logging.info("---------- DIFFERENCE ANALYSIS END ----------")
|
||||
|
||||
if options.output:
|
||||
outputfile = open(options.output, 'w')
|
||||
config_sample.write(outputfile)
|
||||
outputfile.close()
|
||||
else:
|
||||
#print "----------"
|
||||
#config_sample.write(sys.stdout)
|
||||
#print "----------"
|
||||
logging.info("use -o OUTPUT to write the merged configuration into a file.")
|
||||
|
||||
logging.info("read Galaxy universe_wsgi.ini.sample for detailed information.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -10,6 +10,7 @@
|
||||
<display file="ucsc/bam.xml" />
|
||||
<display file="ensembl/ensembl_bam.xml" />
|
||||
<display file="igv/bam.xml" />
|
||||
<display file="igb/bam.xml" />
|
||||
</datatype>
|
||||
<datatype extension="bed" type="galaxy.datatypes.interval:Bed" display_in_upload="true">
|
||||
<converter file="bed_to_gff_converter.xml" target_datatype="gff"/>
|
||||
@@ -19,6 +20,7 @@
|
||||
<converter file="bed_to_summary_tree_converter.xml" target_datatype="summary_tree"/>
|
||||
<!-- <display file="ucsc/interval_as_bed.xml" /> -->
|
||||
<display file="genetrack.xml" />
|
||||
<display file="igb/bed.xml" />
|
||||
</datatype>
|
||||
<datatype extension="bedgraph" type="galaxy.datatypes.interval:BedGraph" display_in_upload="true" />
|
||||
<datatype extension="bedstrict" type="galaxy.datatypes.interval:BedStrict" />
|
||||
@@ -31,9 +33,11 @@
|
||||
</datatype>
|
||||
<datatype extension="bigbed" type="galaxy.datatypes.binary:BigBed" mimetype="application/octet-stream" display_in_upload="true">
|
||||
<display file="ucsc/bigbed.xml" />
|
||||
<display file="igb/bb.xml" />
|
||||
</datatype>
|
||||
<datatype extension="bigwig" type="galaxy.datatypes.binary:BigWig" mimetype="application/octet-stream" display_in_upload="true">
|
||||
<display file="ucsc/bigwig.xml" />
|
||||
<display file="igb/bigwig.xml" />
|
||||
</datatype>
|
||||
<!-- MSI added Datatypes -->
|
||||
<datatype extension="csv" type="galaxy.datatypes.tabular:Tabular" subclass="True" display_in_upload="true" /> <!-- FIXME: csv is 'tabular'ized data, but not 'tab-delimited'; the class used here is intended for 'tab-delimited' -->
|
||||
@@ -81,7 +85,11 @@
|
||||
<datatype extension="gff3" type="galaxy.datatypes.interval:Gff3" display_in_upload="true"/>
|
||||
<datatype extension="gif" type="galaxy.datatypes.images:Gif" mimetype="image/gif"/>
|
||||
<datatype extension="gmaj.zip" type="galaxy.datatypes.images:Gmaj" mimetype="application/zip"/>
|
||||
<datatype extension="gtf" type="galaxy.datatypes.interval:Gtf" display_in_upload="true"/>
|
||||
<datatype extension="gtf" type="galaxy.datatypes.interval:Gtf" display_in_upload="true">
|
||||
<converter file="gff_to_bgzip_converter.xml" target_datatype="bgzip"/>
|
||||
<converter file="gff_to_tabix_converter.xml" target_datatype="tabix" depends_on="bgzip"/>
|
||||
<converter file="gff_to_summary_tree_converter.xml" target_datatype="summary_tree"/>
|
||||
</datatype>
|
||||
<datatype extension="h5" type="galaxy.datatypes.binary:Binary" mimetype="application/octet-stream" subclass="True" />
|
||||
<datatype extension="html" type="galaxy.datatypes.images:Html" mimetype="text/html"/>
|
||||
<datatype extension="interval" type="galaxy.datatypes.interval:Interval" display_in_upload="true">
|
||||
@@ -170,13 +178,13 @@
|
||||
<display file="igv/vcf.xml" />
|
||||
<display file="rviewer/vcf.xml" inherit="True"/>
|
||||
</datatype>
|
||||
<datatype extension="bcf" type="galaxy.datatypes.binary:Binary" subclass="True"/>
|
||||
<datatype extension="wsf" type="galaxy.datatypes.wsf:SnpFile" display_in_upload="true"/>
|
||||
<datatype extension="bcf" type="galaxy.datatypes.binary:Binary" subclass="True" display_in_upload="True"/>
|
||||
<datatype extension="velvet" type="galaxy.datatypes.assembly:Velvet" display_in_upload="false"/>
|
||||
<datatype extension="wig" type="galaxy.datatypes.interval:Wiggle" display_in_upload="true">
|
||||
<converter file="wig_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
<converter file="wiggle_to_simple_converter.xml" target_datatype="interval"/>
|
||||
<!-- <display file="gbrowse/gbrowse_wig.xml" /> -->
|
||||
<display file="igb/wig.xml" />
|
||||
</datatype>
|
||||
<datatype extension="summary_tree" type="galaxy.datatypes.binary:Binary" subclass="True" />
|
||||
<datatype extension="interval_index" type="galaxy.datatypes.binary:Binary" subclass="True" />
|
||||
@@ -268,7 +276,6 @@
|
||||
<sniffer type="galaxy.datatypes.images:Ppm"/>
|
||||
<sniffer type="galaxy.datatypes.images:Psd"/>
|
||||
<sniffer type="galaxy.datatypes.images:Xbm"/>
|
||||
<sniffer type="galaxy.datatypes.images:Xpm"/>
|
||||
<sniffer type="galaxy.datatypes.images:Rgb"/>
|
||||
<sniffer type="galaxy.datatypes.images:Pbm"/>
|
||||
<sniffer type="galaxy.datatypes.images:Pgm"/>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<display id="igb_bam" version="0.0.0" name="display in IGB">
|
||||
<link id="Local" name="Local">
|
||||
<url>http://localhost:7085/UnibrowControl?version=${bam_file.dbkey}&feature_url_0=${bam_file.url}&sym_name_0=${thenicename}&sym_method_0=${bam_file.url}&query_url=${bam_file.url}&server_url=galaxy</url>
|
||||
<param type="data" name="bam_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bam_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bai_file" url="${thenicename}_${DATASET_HASH}.bam.bai" metadata="bam_index" strip_https="True" />
|
||||
<param type="data" name="bam_file" url="${thenicename}_${DATASET_HASH}.bam" strip_https="True" />
|
||||
</link>
|
||||
|
||||
<link id="Web" name="Web">
|
||||
<url>$jnlp.url</url>
|
||||
<param type="data" name="bam_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bam_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bai_file" url="${thenicename}_${DATASET_HASH}.bam.bai" metadata="bam_index" strip_https="True" />
|
||||
<param type="data" name="bam_file" url="${thenicename}_${DATASET_HASH}.bam" strip_https="True" />
|
||||
<param type="template" name="jnlp" url="${thenicename}_${DATASET_HASH}.jnlp" viewable="True" strip_https="True" mimetype="application/x-java-jnlp-file">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<jnlp spec="6.0+" version="" codebase="http://www.bioviz.org/igb/releases/current/" >
|
||||
<information>
|
||||
<title>IGB</title>
|
||||
<vendor>Genoviz Project</vendor>
|
||||
<homepage href="http://genoviz.sourceforge.net/" />
|
||||
<description>Integrated Genome Browser</description>
|
||||
<icon href="igb.gif"/>
|
||||
<offline-allowed/>
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="IGB" />
|
||||
</shortcut>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
</security>
|
||||
<resources>
|
||||
<java version="1.6+" initial-heap-size="32m" max-heap-size="1024m"/>
|
||||
<jar href="igb_exe.jar" main="true" />
|
||||
<jar href="igb-i18n.jar" />
|
||||
<property name="apple.laf.useScreenMenuBar" value="true" />
|
||||
<property name="http.agent" value="IGB (Webstart)" />
|
||||
</resources>
|
||||
<application-desc main-class="com.affymetrix.main.Main">
|
||||
<argument>-prefs</argument>
|
||||
<argument>http://www.bioviz.org/igb/releases/current/igb_prefs.xml</argument>
|
||||
<argument>-href</argument>
|
||||
<argument>http://localhost:7085/UnibrowControl?version=${bam_file.dbkey}&feature_url_0=${bam_file.url}&sym_name_0=${thenicename}&sym_method_0=${bam_file.url}&query_url=${bam_file.url}&server_url=galaxy </argument>
|
||||
</application-desc>
|
||||
</jnlp>
|
||||
</param>
|
||||
</link>
|
||||
</display>
|
||||
@@ -0,0 +1,58 @@
|
||||
<display id="igb_bb" version="1.0.0" name="display in IGB">
|
||||
|
||||
<link id="Local" name="Local">
|
||||
<url>http://localhost:7085/UnibrowControl?version=${bigbed_file.dbkey}&loadresidues=false&feature_url_0=${bigbed_file.url}&sym_name_0=${thenicename}&sym_method_0=${bigbed_file.url}&query_url=${bigbed_file.url}&server_url=galaxy</url>
|
||||
<param type="data" name="bigbed_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bigbed_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bigbed_file" url="${thenicename}_${DATASET_HASH}.bigbed" strip_https="True" />
|
||||
</link>
|
||||
|
||||
<link id="Web" name="Web">
|
||||
<url>$jnlp.url</url>
|
||||
<param type="data" name="bigbed_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bigbed_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bigbed_file" url="${thenicename}_${DATASET_HASH}.bigbed" strip_https="True" />
|
||||
<param type="template" name="jnlp" url="${thenicename}_${DATASET_HASH}.jnlp" viewable="True" strip_https="True" mimetype="application/x-java-jnlp-file">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<jnlp spec="6.0+" version="" codebase="http://www.bioviz.org/igb/releases/current/" >
|
||||
<information>
|
||||
<title>IGB</title>
|
||||
<vendor>Genoviz Project</vendor>
|
||||
<homepage href="http://genoviz.sourceforge.net/" />
|
||||
<description>Integrated Genome Browser</description>
|
||||
<icon href="igb.gif"/>
|
||||
<offline-allowed/>
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="IGB" />
|
||||
</shortcut>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
</security>
|
||||
<resources>
|
||||
<java version="1.6+" initial-heap-size="32m" max-heap-size="1024m"/>
|
||||
<jar href="igb_exe.jar" main="true" />
|
||||
<jar href="igb-i18n.jar" />
|
||||
<property name="apple.laf.useScreenMenuBar" value="true" />
|
||||
<property name="http.agent" value="IGB (Webstart)" />
|
||||
</resources>
|
||||
<application-desc main-class="com.affymetrix.main.Main">
|
||||
<argument>-prefs</argument>
|
||||
<argument>http://www.bioviz.org/igb/releases/current/igb_prefs.xml</argument>
|
||||
<argument>-href</argument>
|
||||
<argument>http://localhost:7085/UnibrowControl?version=${bigbed_file.dbkey}&loadresidues=false&feature_url_0=${bigbed_file.url}&sym_name_0=${thenicename}&sym_method_0=${bigbed_file.url}&query_url=${bigbed_file.url}&server_url=galaxy </argument>
|
||||
</application-desc>
|
||||
</jnlp>
|
||||
</param>
|
||||
</link>
|
||||
</display>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<display id="igb_bed" version="1.0.0" name="display in IGB">
|
||||
|
||||
<link id="Local" name="Local">
|
||||
<url>http://localhost:7085/UnibrowControl?version=${bed_file.dbkey}&${position}&loadresidues=false&feature_url_0=${bed_file.url}&sym_name_0=${thenicename}&sym_method_0=${bed_file.url}&query_url=${bed_file.url}&server_url=galaxy</url>
|
||||
<param type="data" name="bed_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bed_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bed_file" url="${thenicename}_${DATASET_HASH}.bed" strip_https="True" />
|
||||
<param type="template" name="position" strip="True" >
|
||||
#set chrom, start, end = $bed_file.datatype.get_estimated_display_viewport( $bed_file )
|
||||
#if $chrom is not None:
|
||||
seqid=${chrom}&start=${start}&end=${int(end) + 1}
|
||||
#else:
|
||||
seqid=&start=&end=
|
||||
#end if
|
||||
</param>
|
||||
</link>
|
||||
|
||||
<link id="Web" name="Web">
|
||||
<url>$jnlp.url</url>
|
||||
<param type="data" name="bed_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bed_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bed_file" url="${thenicename}_${DATASET_HASH}.bed" strip_https="True" />
|
||||
<param type="template" name="position" strip="True" >
|
||||
#set chrom, start, end = $bed_file.datatype.get_estimated_display_viewport( $bed_file )
|
||||
#if $chrom is not None:
|
||||
seqid=${chrom}&start=${start}&end=${int(end) + 1}
|
||||
#else:
|
||||
seqid=&start=&end=
|
||||
#end if
|
||||
</param>
|
||||
<param type="template" name="jnlp" url="${thenicename}_${DATASET_HASH}.jnlp" viewable="True" strip_https="True" mimetype="application/x-java-jnlp-file">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<jnlp spec="6.0+" version="" codebase="http://www.bioviz.org/igb/releases/current/" >
|
||||
<information>
|
||||
<title>IGB</title>
|
||||
<vendor>Genoviz Project</vendor>
|
||||
<homepage href="http://genoviz.sourceforge.net/" />
|
||||
<description>Integrated Genome Browser</description>
|
||||
<icon href="igb.gif"/>
|
||||
<offline-allowed/>
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="IGB" />
|
||||
</shortcut>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
</security>
|
||||
<resources>
|
||||
<java version="1.6+" initial-heap-size="32m" max-heap-size="1024m"/>
|
||||
<jar href="igb_exe.jar" main="true" />
|
||||
<jar href="igb-i18n.jar" />
|
||||
<property name="apple.laf.useScreenMenuBar" value="true" />
|
||||
<property name="http.agent" value="IGB (Webstart)" />
|
||||
</resources>
|
||||
<application-desc main-class="com.affymetrix.main.Main">
|
||||
<argument>-prefs</argument>
|
||||
<argument>http://www.bioviz.org/igb/releases/current/igb_prefs.xml</argument>
|
||||
<argument>-href</argument>
|
||||
<argument>http://localhost:7085/UnibrowControl?version=${bed_file.dbkey}&${position}&loadresidues=false&feature_url_0=${bed_file.url}&sym_name_0=${thenicename}&sym_method_0=${bed_file.url}&query_url=${bed_file.url}&server_url=galaxy </argument>
|
||||
</application-desc>
|
||||
</jnlp>
|
||||
</param>
|
||||
</link>
|
||||
</display>
|
||||
@@ -0,0 +1,65 @@
|
||||
<display id="igb_bigwig" version="1.0.0" name="display in IGB">
|
||||
|
||||
<link id="Local" name="Local">
|
||||
<url>http://localhost:7085/UnibrowControl?version=${bigwig_file.dbkey}&loadresidues=false&feature_url_0=${bigwig_file.url}&sym_name_0=${thenicename}&sym_method_0=${bigwig_file.url}&query_url=${bigwig_file.url}&server_url=galaxy</url>
|
||||
<param type="data" name="bigwig_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bigwig_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bigwig_file" url="${thenicename}_${DATASET_HASH}.bigwig" strip_https="True" />
|
||||
<!--<param type="template" name="position" strip="True" >
|
||||
#set chrom, start, end = $bigwig_file.datatype.get_estimated_display_viewport( $bigwig_file )
|
||||
#if $chrom is not None:
|
||||
seqid=${chrom}&start=${start}&end=${int(end) + 1}
|
||||
#else:
|
||||
seqid=&start=&end=
|
||||
#end if
|
||||
</param>-->
|
||||
</link>
|
||||
|
||||
<link id="Web" name="Web">
|
||||
<url>$jnlp.url</url>
|
||||
<param type="data" name="bigwig_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$bigwig_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="bigwig_file" url="${thenicename}_${DATASET_HASH}.bigwig" strip_https="True" />
|
||||
<param type="template" name="jnlp" url="${thenicename}_${DATASET_HASH}.jnlp" viewable="True" strip_https="True" mimetype="application/x-java-jnlp-file">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<jnlp spec="6.0+" version="" codebase="http://www.bioviz.org/igb/releases/current/" >
|
||||
<information>
|
||||
<title>IGB</title>
|
||||
<vendor>Genoviz Project</vendor>
|
||||
<homepage href="http://genoviz.sourceforge.net/" />
|
||||
<description>Integrated Genome Browser</description>
|
||||
<icon href="igb.gif"/>
|
||||
<offline-allowed/>
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="IGB" />
|
||||
</shortcut>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
</security>
|
||||
<resources>
|
||||
<java version="1.6+" initial-heap-size="32m" max-heap-size="1024m"/>
|
||||
<jar href="igb_exe.jar" main="true" />
|
||||
<jar href="igb-i18n.jar" />
|
||||
<property name="apple.laf.useScreenMenuBar" value="true" />
|
||||
<property name="http.agent" value="IGB (Webstart)" />
|
||||
</resources>
|
||||
<application-desc main-class="com.affymetrix.main.Main">
|
||||
<argument>-prefs</argument>
|
||||
<argument>http://www.bioviz.org/igb/releases/current/igb_prefs.xml</argument>
|
||||
<argument>-href</argument>
|
||||
<argument>http://localhost:7085/UnibrowControl?version=${bigwig_file.dbkey}&loadresidues=false&feature_url_0=${bigwig_file.url}&sym_name_0=${thenicename}&sym_method_0=${bigwig_file.url}&query_url=${bigwig_file.url}&server_url=galaxy </argument>
|
||||
</application-desc>
|
||||
</jnlp>
|
||||
</param>
|
||||
</link>
|
||||
</display>
|
||||
@@ -0,0 +1,74 @@
|
||||
<display id="igb_wig" version="1.0.0" name="display in IGB">
|
||||
|
||||
<link id="Local" name="Local">
|
||||
<url>http://localhost:7085/UnibrowControl?version=${wig_file.dbkey}&${position}&loadresidues=false&feature_url_0=${wig_file.url}&sym_name_0=${thenicename}&sym_method_0=${wig_file.url}&query_url=${wig_file.url}&server_url=galaxy</url>
|
||||
<param type="data" name="wig_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$wig_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="wig_file" url="${thenicename}_${DATASET_HASH}.wig" strip_https="True" />
|
||||
<param type="template" name="position" strip="True" >
|
||||
#set chrom, start, end = $wig_file.datatype.get_estimated_display_viewport( $wig_file )
|
||||
#if $chrom is not None:
|
||||
seqid=${chrom}&start=${start}&end=${int(end) + 1}
|
||||
#else:
|
||||
seqid=&start=&end=
|
||||
#end if
|
||||
</param>
|
||||
</link>
|
||||
|
||||
<link id="Web" name="Web">
|
||||
<url>$jnlp.url</url>
|
||||
<param type="data" name="wig_file_for_name" viewable="False"/>
|
||||
<param type="template" name="thenicename" viewable="False" strip="True">
|
||||
#import re
|
||||
#set nm=$wig_file_for_name.name
|
||||
${re.sub('\W',"_",nm)}
|
||||
</param>
|
||||
<param type="data" name="wig_file" url="${thenicename}_${DATASET_HASH}.wig" strip_https="True" />
|
||||
<param type="template" name="position" strip="True" >
|
||||
#set chrom, start, end = $wig_file.datatype.get_estimated_display_viewport( $wig_file )
|
||||
#if $chrom is not None:
|
||||
seqid=${chrom}&start=${start}&end=${int(end) + 1}
|
||||
#else:
|
||||
seqid=&start=&end=
|
||||
#end if
|
||||
</param>
|
||||
<param type="template" name="jnlp" url="${thenicename}_${DATASET_HASH}.jnlp" viewable="True" strip_https="True" mimetype="application/x-java-jnlp-file">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<jnlp spec="6.0+" version="" codebase="http://www.bioviz.org/igb/releases/current/" >
|
||||
<information>
|
||||
<title>IGB</title>
|
||||
<vendor>Genoviz Project</vendor>
|
||||
<homepage href="http://genoviz.sourceforge.net/" />
|
||||
<description>Integrated Genome Browser</description>
|
||||
<icon href="igb.gif"/>
|
||||
<offline-allowed/>
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="IGB" />
|
||||
</shortcut>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
</security>
|
||||
<resources>
|
||||
<java version="1.6+" initial-heap-size="32m" max-heap-size="1024m"/>
|
||||
<jar href="igb_exe.jar" main="true" />
|
||||
<jar href="igb-i18n.jar" />
|
||||
<property name="apple.laf.useScreenMenuBar" value="true" />
|
||||
<property name="http.agent" value="IGB (Webstart)" />
|
||||
</resources>
|
||||
<application-desc main-class="com.affymetrix.main.Main">
|
||||
<argument>-prefs</argument>
|
||||
<argument>http://www.bioviz.org/igb/releases/current/igb_prefs.xml</argument>
|
||||
<argument>-href</argument>
|
||||
<argument>http://localhost:7085/UnibrowControl?version=${wig_file.dbkey}&${position}&loadresidues=false&feature_url_0=${wig_file.url}&sym_name_0=${thenicename}&sym_method_0=${wig_file.url}&query_url=${wig_file.url}&server_url=galaxy </argument>
|
||||
</application-desc>
|
||||
</jnlp>
|
||||
</param>
|
||||
</link>
|
||||
</display>
|
||||
|
||||
+12
-35
@@ -7,105 +7,82 @@
|
||||
;
|
||||
|
||||
[hosts]
|
||||
py2.4-linux-i686-ucs2 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs2/bin/python2.4
|
||||
py2.4-linux-i686-ucs4 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs4/bin/python2.4
|
||||
py2.5-linux-i686-ucs2 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs2/bin/python2.5
|
||||
py2.5-linux-i686-ucs4 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs4/bin/python2.5
|
||||
py2.6-linux-i686-ucs2 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs2/bin/python2.6
|
||||
py2.6-linux-i686-ucs4 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs4/bin/python2.6
|
||||
py2.7-linux-i686-ucs2 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs2/bin/python2.7
|
||||
py2.7-linux-i686-ucs4 = stegmaier.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-i686-ucs4/bin/python2.7
|
||||
py2.4-linux-x86_64-ucs2 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs2/bin/python2.4
|
||||
py2.4-linux-x86_64-ucs4 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.4
|
||||
py2.5-linux-x86_64-ucs2 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs2/bin/python2.5
|
||||
py2.5-linux-x86_64-ucs4 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.5
|
||||
py2.6-linux-x86_64-ucs2 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs2/bin/python2.6
|
||||
py2.6-linux-x86_64-ucs4 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.6
|
||||
py2.7-linux-x86_64-ucs2 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs2/bin/python2.7
|
||||
py2.7-linux-x86_64-ucs4 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.7
|
||||
py2.4-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /usr/local/bin/python2.4
|
||||
py2.5-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /usr/local/bin/python2.5
|
||||
py2.6-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /usr/local/bin/python2.6
|
||||
py2.7-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /usr/local/bin/python2.7
|
||||
py2.5-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /Library/Frameworks/Python.framework/Versions/2.5/bin/python2.5
|
||||
py2.6-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /Library/Frameworks/Python.framework/Versions/2.6/bin/python2.6
|
||||
py2.7-macosx-10.3-fat-ucs2 = weyerbacher.bx.psu.edu /Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7
|
||||
py2.6-macosx-10.6-universal-ucs2 = lion.bx.psu.edu /usr/bin/python2.6
|
||||
py2.7-macosx-10.6-intel-ucs2 = lion.bx.psu.edu /usr/local/bin/python2.7
|
||||
py2.4-solaris-2.10-i86pc_32-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_32-ucs2/bin/python2.4
|
||||
py2.5-solaris-2.10-i86pc_32-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_32-ucs2/bin/python2.5
|
||||
py2.6-solaris-2.10-i86pc_32-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_32-ucs2/bin/python2.6
|
||||
py2.7-solaris-2.10-i86pc_32-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_32-ucs2/bin/python2.7
|
||||
py2.4-solaris-2.10-i86pc_64-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_64-ucs2/bin/python2.4
|
||||
py2.5-solaris-2.10-i86pc_64-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_64-ucs2/bin/python2.5
|
||||
py2.6-solaris-2.10-i86pc_64-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_64-ucs2/bin/python2.6
|
||||
py2.7-solaris-2.10-i86pc_64-ucs2 = thumper.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-i86pc_64-ucs2/bin/python2.7
|
||||
py2.4-solaris-2.10-sun4u_32-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_32-ucs2/bin/python2.4
|
||||
py2.5-solaris-2.10-sun4u_32-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_32-ucs2/bin/python2.5
|
||||
py2.6-solaris-2.10-sun4u_32-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_32-ucs2/bin/python2.6
|
||||
py2.7-solaris-2.10-sun4u_32-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_32-ucs2/bin/python2.7
|
||||
py2.4-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_64-ucs2/bin/python2.4
|
||||
py2.5-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_64-ucs2/bin/python2.5
|
||||
py2.6-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_64-ucs2/bin/python2.6
|
||||
py2.7-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.8-sun4u_64-ucs2/bin/python2.7
|
||||
py2.5-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-sun4u_64-ucs2/bin/python2.5
|
||||
py2.6-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-sun4u_64-ucs2/bin/python2.6
|
||||
py2.7-solaris-2.10-sun4u_64-ucs2 = early.bx.psu.edu /afs/bx.psu.edu/project/pythons/solaris-2.10-sun4u_64-ucs2/bin/python2.7
|
||||
|
||||
; these hosts are used to build eggs with no C extensions
|
||||
py2.4 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.4
|
||||
py2.5 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.5
|
||||
py2.6 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.6
|
||||
py2.7 = straub.bx.psu.edu /afs/bx.psu.edu/project/pythons/linux-x86_64-ucs4/bin/python2.7
|
||||
|
||||
[groups]
|
||||
py2.4-linux-i686 = py2.4-linux-i686-ucs2 py2.4-linux-i686-ucs4
|
||||
py2.4-linux-x86_64 = py2.4-linux-x86_64-ucs2 py2.4-linux-x86_64-ucs4
|
||||
py2.5-linux-i686 = py2.5-linux-i686-ucs2 py2.5-linux-i686-ucs4
|
||||
py2.5-linux-x86_64 = py2.5-linux-x86_64-ucs2 py2.5-linux-x86_64-ucs4
|
||||
py2.6-linux-i686 = py2.6-linux-i686-ucs2 py2.6-linux-i686-ucs4
|
||||
py2.6-linux-x86_64 = py2.6-linux-x86_64-ucs2 py2.6-linux-x86_64-ucs4
|
||||
py2.7-linux-i686 = py2.7-linux-i686-ucs2 py2.7-linux-i686-ucs4
|
||||
py2.7-linux-x86_64 = py2.7-linux-x86_64-ucs2 py2.7-linux-x86_64-ucs4
|
||||
py2.4-linux = py2.4-linux-i686 py2.4-linux-x86_64
|
||||
py2.5-linux = py2.5-linux-i686 py2.5-linux-x86_64
|
||||
py2.6-linux = py2.6-linux-i686 py2.6-linux-x86_64
|
||||
py2.7-linux = py2.7-linux-i686 py2.7-linux-x86_64
|
||||
linux-i686 = py2.4-linux-i686 py2.5-linux-i686 py2.6-linux-i686 py2.7-linux-i686
|
||||
linux-x86_64 = py2.4-linux-x86_64 py2.5-linux-x86_64 py2.6-linux-x86_64 py2.7-linux-x86_64
|
||||
linux-i686 = py2.5-linux-i686 py2.6-linux-i686 py2.7-linux-i686
|
||||
linux-x86_64 = py2.5-linux-x86_64 py2.6-linux-x86_64 py2.7-linux-x86_64
|
||||
linux = linux-i686 linux-x86_64
|
||||
py2.4-macosx = py2.4-macosx-10.3-fat-ucs2
|
||||
py2.5-macosx = py2.5-macosx-10.3-fat-ucs2
|
||||
py2.6-macosx = py2.6-macosx-10.3-fat-ucs2 py2.6-macosx-10.6-universal-ucs2
|
||||
py2.7-macosx = py2.7-macosx-10.3-fat-ucs2 py2.7-macosx-10.6-intel-ucs2
|
||||
macosx = py2.4-macosx py2.5-macosx py2.6-macosx py2.7-macosx
|
||||
py2.4-solaris-i86pc = py2.4-solaris-2.10-i86pc_32-ucs2 py2.4-solaris-2.10-i86pc_64-ucs2
|
||||
macosx = py2.5-macosx py2.6-macosx py2.7-macosx
|
||||
py2.5-solaris-i86pc = py2.5-solaris-2.10-i86pc_32-ucs2 py2.5-solaris-2.10-i86pc_64-ucs2
|
||||
py2.6-solaris-i86pc = py2.6-solaris-2.10-i86pc_32-ucs2 py2.6-solaris-2.10-i86pc_64-ucs2
|
||||
py2.7-solaris-i86pc = py2.7-solaris-2.10-i86pc_32-ucs2 py2.7-solaris-2.10-i86pc_64-ucs2
|
||||
py2.4-solaris-sun4u = py2.4-solaris-2.10-sun4u_32-ucs2 py2.4-solaris-2.10-sun4u_64-ucs2
|
||||
py2.5-solaris-sun4u = py2.5-solaris-2.10-sun4u_32-ucs2 py2.5-solaris-2.10-sun4u_64-ucs2
|
||||
py2.6-solaris-sun4u = py2.6-solaris-2.10-sun4u_32-ucs2 py2.6-solaris-2.10-sun4u_64-ucs2
|
||||
py2.7-solaris-sun4u = py2.7-solaris-2.10-sun4u_32-ucs2 py2.7-solaris-2.10-sun4u_64-ucs2
|
||||
py2.4-solaris = py2.4-solaris-i86pc py2.4-solaris-sun4u
|
||||
py2.5-solaris = py2.5-solaris-i86pc py2.5-solaris-sun4u
|
||||
py2.6-solaris = py2.6-solaris-i86pc py2.6-solaris-sun4u
|
||||
py2.7-solaris = py2.7-solaris-i86pc py2.7-solaris-sun4u
|
||||
solaris-i86pc = py2.4-solaris-i86pc py2.5-solaris-i86pc py2.6-solaris-i86pc py2.7-solaris-i86pc
|
||||
solaris-sun4u = py2.4-solaris-sun4u py2.5-solaris-sun4u py2.6-solaris-sun4u py2.7-solaris-sun4u
|
||||
solaris-i86pc = py2.5-solaris-i86pc py2.6-solaris-i86pc py2.7-solaris-i86pc
|
||||
solaris-sun4u = py2.5-solaris-sun4u py2.6-solaris-sun4u py2.7-solaris-sun4u
|
||||
solaris = solaris-i86pc solaris-sun4u
|
||||
py2.4-all = py2.4-linux py2.4-macosx py2.4-solaris
|
||||
py2.5-all = py2.5-linux py2.5-macosx py2.5-solaris
|
||||
py2.6-all = py2.6-linux py2.6-macosx py2.6-solaris
|
||||
py2.7-all = py2.7-linux py2.7-macosx py2.7-solaris
|
||||
|
||||
; group for building pysam on solaris 10 sparc
|
||||
;solaris-2.10-sun4u = py2.4-solaris-2.10-sun4u_32-ucs2 py2.5-solaris-2.10-sun4u_32-ucs2 py2.6-solaris-2.10-sun4u_32-ucs2 py2.4-solaris-2.10-sun4u_64-ucs2 py2.5-solaris-2.10-sun4u_64-ucs2 py2.6-solaris-2.10-sun4u_64-ucs2
|
||||
|
||||
; the 'all' key is used internally by the build system to specify which hosts
|
||||
; to build on when no hosts are specified on the dist-eggs.py command line.
|
||||
all = linux macosx solaris
|
||||
|
||||
; the 'noplatform' key, likewise, is for which build hosts should be used when
|
||||
; building pure python (noplatform) eggs.
|
||||
noplatform = py2.4 py2.5 py2.6 py2.7
|
||||
noplatform = py2.5 py2.6 py2.7
|
||||
|
||||
; don't build these eggs on these platforms:
|
||||
[ignore]
|
||||
GeneTrack = py2.4
|
||||
python-daemon = py2.4
|
||||
ctypes = py2.5-linux-i686-ucs2 py2.5-linux-i686-ucs4 py2.6-linux-i686-ucs2 py2.6-linux-i686-ucs4 py2.7-linux-i686-ucs2 py2.7-linux-i686-ucs4 py2.5-linux-x86_64-ucs2 py2.5-linux-x86_64-ucs4 py2.6-linux-x86_64-ucs2 py2.6-linux-x86_64-ucs4 py2.7-linux-x86_64-ucs2 py2.7-linux-x86_64-ucs4 py2.5-macosx-10.3-fat-ucs2 py2.6-macosx-10.3-fat-ucs2 py2.6-macosx-10.6-universal-ucs2 py2.7-macosx-10.3-fat-ucs2 py2.5-solaris-2.10-i86pc_32-ucs2 py2.6-solaris-2.10-i86pc_32-ucs2 py2.7-solaris-2.10-i86pc_32-ucs2 py2.5-solaris-2.10-i86pc_64-ucs2 py2.6-solaris-2.10-i86pc_64-ucs2 py2.7-solaris-2.10-i86pc_64-ucs2 py2.5-solaris-2.10-sun4u_32-ucs2 py2.6-solaris-2.10-sun4u_32-ucs2 py2.7-solaris-2.10-sun4u_32-ucs2 py2.5-solaris-2.10-sun4u_64-ucs2 py2.6-solaris-2.10-sun4u_64-ucs2 py2.7-solaris-2.10-sun4u_64-ucs2
|
||||
|
||||
@@ -17,6 +17,7 @@ Cheetah = 2.2.2
|
||||
ctypes = 1.0.2
|
||||
DRMAA_python = 0.2
|
||||
MarkupSafe = 0.12
|
||||
mercurial = 2.1.2
|
||||
MySQL_python = 1.2.3c1
|
||||
numpy = 1.6.0
|
||||
pbs_python = 4.1.0
|
||||
|
||||
+6
-2
@@ -12,6 +12,7 @@ from galaxy.objectstore import build_object_store_from_config
|
||||
import galaxy.quota
|
||||
from galaxy.tags.tag_handler import GalaxyTagHandler
|
||||
from galaxy.tools.imp_exp import load_history_imp_exp_tools
|
||||
from galaxy.tools.genome_index import load_genome_index_tools
|
||||
from galaxy.sample_tracking import external_service_types
|
||||
from galaxy.openid.providers import OpenIDProviders
|
||||
|
||||
@@ -90,6 +91,8 @@ class UniverseApplication( object ):
|
||||
self.datatypes_registry.load_external_metadata_tool( self.toolbox )
|
||||
# Load history import/export tools.
|
||||
load_history_imp_exp_tools( self.toolbox )
|
||||
# Load genome indexer tool.
|
||||
load_genome_index_tools( self.toolbox )
|
||||
# Load security policy.
|
||||
self.security_agent = self.model.security_agent
|
||||
self.host_security_agent = galaxy.security.HostAgent( model=self.security_agent.model, permitted_actions=self.security_agent.permitted_actions )
|
||||
@@ -124,8 +127,9 @@ class UniverseApplication( object ):
|
||||
if self.config.get_bool( 'enable_beta_job_managers', False ):
|
||||
from jobs import transfer_manager
|
||||
self.transfer_manager = transfer_manager.TransferManager( self )
|
||||
# Start the job queue
|
||||
self.job_manager = jobs.JobManager( self )
|
||||
# Start the job manager
|
||||
from jobs import manager
|
||||
self.job_manager = manager.JobManager( self )
|
||||
# FIXME: These are exposed directly for backward compatibility
|
||||
self.job_queue = self.job_manager.job_queue
|
||||
self.job_stop_queue = self.job_manager.job_stop_queue
|
||||
|
||||
+42
-33
@@ -42,8 +42,7 @@ class Configuration( object ):
|
||||
tempfile.tempdir = self.new_file_path
|
||||
self.openid_consumer_cache_path = resolve_path( kwargs.get( "openid_consumer_cache_path", "database/openid_consumer_cache" ), self.root )
|
||||
self.cookie_path = kwargs.get( "cookie_path", "/" )
|
||||
# web API
|
||||
self.enable_api = string_as_bool( kwargs.get( 'enable_api', False ) )
|
||||
self.genome_data_path = kwargs.get( "genome_data_path", "tool-data/genome" )
|
||||
# Galaxy OpenID settings
|
||||
self.enable_openid = string_as_bool( kwargs.get( 'enable_openid', False ) )
|
||||
self.openid_config = kwargs.get( 'openid_config_file', 'openid_conf.xml' )
|
||||
@@ -86,6 +85,7 @@ class Configuration( object ):
|
||||
self.allow_user_dataset_purge = string_as_bool( kwargs.get( "allow_user_dataset_purge", "False" ) )
|
||||
self.allow_user_impersonation = string_as_bool( kwargs.get( "allow_user_impersonation", "False" ) )
|
||||
self.new_user_dataset_access_role_default_private = string_as_bool( kwargs.get( "new_user_dataset_access_role_default_private", "False" ) )
|
||||
self.collect_outputs_from = [ x.strip() for x in kwargs.get( 'collect_outputs_from', 'new_file_path,job_working_directory' ).lower().split(',') ]
|
||||
self.template_path = resolve_path( kwargs.get( "template_path", "templates" ), self.root )
|
||||
self.template_cache = resolve_path( kwargs.get( "template_cache_path", "database/compiled_templates" ), self.root )
|
||||
self.local_job_queue_workers = int( kwargs.get( "local_job_queue_workers", "5" ) )
|
||||
@@ -105,6 +105,7 @@ class Configuration( object ):
|
||||
self.smtp_username = kwargs.get( 'smtp_username', None )
|
||||
self.smtp_password = kwargs.get( 'smtp_password', None )
|
||||
self.start_job_runners = kwargs.get( 'start_job_runners', None )
|
||||
self.expose_dataset_path = string_as_bool( kwargs.get( 'expose_dataset_path', 'False' ) )
|
||||
# External Service types used in sample tracking
|
||||
self.external_service_type_config_file = resolve_path( kwargs.get( 'external_service_type_config_file', 'external_service_types_conf.xml' ), self.root )
|
||||
self.external_service_type_path = resolve_path( kwargs.get( 'external_service_type_path', 'external_service_types' ), self.root )
|
||||
@@ -184,14 +185,43 @@ class Configuration( object ):
|
||||
# Heartbeat log file name override
|
||||
if global_conf is not None:
|
||||
self.heartbeat_log = global_conf.get( 'heartbeat_log', 'heartbeat.log' )
|
||||
#Store per-tool runner configs.
|
||||
# Determine which 'server:' this is
|
||||
self.server_name = 'main'
|
||||
for arg in sys.argv:
|
||||
# Crummy, but PasteScript does not give you a way to determine this
|
||||
if arg.lower().startswith('--server-name='):
|
||||
self.server_name = arg.split('=', 1)[-1]
|
||||
# Store advanced job management config
|
||||
self.job_manager = kwargs.get('job_manager', self.server_name).strip()
|
||||
self.job_handlers = [ x.strip() for x in kwargs.get('job_handlers', self.server_name).split(',') ]
|
||||
self.default_job_handlers = [ x.strip() for x in kwargs.get('default_job_handlers', ','.join( self.job_handlers ) ).split(',') ]
|
||||
# Use database for IPC unless this is a standalone server (or multiple servers doing self dispatching in memory)
|
||||
self.track_jobs_in_database = True
|
||||
if ( len( self.job_handlers ) == 1 ) and ( self.job_handlers[0] == self.server_name ) and ( self.job_manager == self.server_name ):
|
||||
self.track_jobs_in_database = False
|
||||
# Store per-tool runner configs
|
||||
self.tool_handlers = self.__read_tool_job_config( global_conf_parser, 'galaxy:tool_handlers', 'name' )
|
||||
self.tool_runners = self.__read_tool_job_config( global_conf_parser, 'galaxy:tool_runners', 'url' )
|
||||
self.datatypes_config = kwargs.get( 'datatypes_config_file', 'datatypes_conf.xml' )
|
||||
# Cloud configuration options
|
||||
self.enable_cloud_launch = string_as_bool( kwargs.get( 'enable_cloud_launch', False ) )
|
||||
# Galaxy messaging (AMQP) configuration options
|
||||
self.amqp = {}
|
||||
try:
|
||||
tool_runners_config = global_conf_parser.items("galaxy:tool_runners")
|
||||
amqp_config = global_conf_parser.items("galaxy_amqp")
|
||||
except ConfigParser.NoSectionError:
|
||||
amqp_config = {}
|
||||
for k, v in amqp_config:
|
||||
self.amqp[k] = v
|
||||
self.running_functional_tests = string_as_bool( kwargs.get( 'running_functional_tests', False ) )
|
||||
def __read_tool_job_config( self, global_conf_parser, section, key ):
|
||||
try:
|
||||
tool_runners_config = global_conf_parser.items( section )
|
||||
|
||||
# Process config to group multiple configs for the same tool.
|
||||
tool_runners = {}
|
||||
rval = {}
|
||||
for entry in tool_runners_config:
|
||||
tool_config, url = entry
|
||||
tool_config, val = entry
|
||||
tool = None
|
||||
runner_dict = {}
|
||||
if tool_config.find("[") != -1:
|
||||
@@ -206,29 +236,18 @@ class Configuration( object ):
|
||||
tool = tool_config
|
||||
|
||||
# Add runner URL.
|
||||
runner_dict[ 'url' ] = url
|
||||
runner_dict[ key ] = val
|
||||
|
||||
# Create tool entry if necessary.
|
||||
if tool not in tool_runners:
|
||||
tool_runners[ tool ] = []
|
||||
if tool not in rval:
|
||||
rval[ tool ] = []
|
||||
|
||||
# Add entry to runners.
|
||||
tool_runners[ tool ].append( runner_dict )
|
||||
rval[ tool ].append( runner_dict )
|
||||
|
||||
self.tool_runners = tool_runners
|
||||
return rval
|
||||
except ConfigParser.NoSectionError:
|
||||
self.tool_runners = []
|
||||
self.datatypes_config = kwargs.get( 'datatypes_config_file', 'datatypes_conf.xml' )
|
||||
# Cloud configuration options
|
||||
self.enable_cloud_launch = string_as_bool( kwargs.get( 'enable_cloud_launch', False ) )
|
||||
# Galaxy messaging (AMQP) configuration options
|
||||
self.amqp = {}
|
||||
try:
|
||||
amqp_config = global_conf_parser.items("galaxy_amqp")
|
||||
except ConfigParser.NoSectionError:
|
||||
amqp_config = {}
|
||||
for k, v in amqp_config:
|
||||
self.amqp[k] = v
|
||||
return []
|
||||
def get( self, key, default ):
|
||||
return self.config_dict.get( key, default )
|
||||
def get_bool( self, key, default ):
|
||||
@@ -238,16 +257,6 @@ class Configuration( object ):
|
||||
return default
|
||||
def check( self ):
|
||||
paths_to_check = [ self.root, self.tool_path, self.tool_data_path, self.template_path ]
|
||||
# Look for any tool shed configs and retrieve the tool_path attribute from the <toolbox> tag.
|
||||
tool_configs = self.tool_configs
|
||||
if self.migrated_tools_config not in tool_configs:
|
||||
tool_configs.append( self.migrated_tools_config )
|
||||
for config_filename in tool_configs:
|
||||
tree = parse_xml( config_filename )
|
||||
root = tree.getroot()
|
||||
tool_path = root.get( 'tool_path' )
|
||||
if tool_path not in [ None, False ]:
|
||||
paths_to_check.append( resolve_path( tool_path, self.root ) )
|
||||
# Check that required directories exist
|
||||
for path in paths_to_check:
|
||||
if path not in [ None, False ] and not os.path.isdir( path ):
|
||||
|
||||
@@ -34,6 +34,17 @@ class Binary( data.Data ):
|
||||
"""Returns the mime type of the datatype"""
|
||||
return 'application/octet-stream'
|
||||
|
||||
def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, size=None, offset=None, **kwd):
|
||||
trans.response.set_content_type(dataset.get_mime())
|
||||
trans.log_event( "Display dataset id: %s" % str( dataset.id ) )
|
||||
trans.response.headers['Content-Length'] = int( os.stat( dataset.file_name ).st_size )
|
||||
to_ext = dataset.extension
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in dataset.name)[0:150]
|
||||
trans.response.set_content_type( "application/octet-stream" ) #force octet-stream so Safari doesn't append mime extensions to filename
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (dataset.hid, fname, to_ext)
|
||||
return open( dataset.file_name )
|
||||
|
||||
class Ab1( Binary ):
|
||||
"""Class describing an ab1 binary sequence file"""
|
||||
file_ext = "ab1"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
#Dan Blankenberg
|
||||
|
||||
"""
|
||||
A wrapper script for converting SAM to BAM, with sorting.
|
||||
%prog input_filename.sam output_filename.bam
|
||||
"""
|
||||
|
||||
import sys, optparse, os, tempfile, subprocess, shutil
|
||||
|
||||
CHUNK_SIZE = 2**20 #1mb
|
||||
|
||||
|
||||
def cleanup_before_exit( tmp_dir ):
|
||||
if tmp_dir and os.path.exists( tmp_dir ):
|
||||
shutil.rmtree( tmp_dir )
|
||||
|
||||
def __main__():
|
||||
#Parse Command Line
|
||||
parser = optparse.OptionParser()
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
assert len( args ) == 2, 'You must specify the input and output filenames'
|
||||
input_filename, output_filename = args
|
||||
|
||||
tmp_dir = tempfile.mkdtemp( prefix='tmp-sam_to_bam_converter-' )
|
||||
|
||||
#convert to SAM
|
||||
unsorted_bam_filename = os.path.join( tmp_dir, 'unsorted.bam' )
|
||||
unsorted_stderr_filename = os.path.join( tmp_dir, 'unsorted.stderr' )
|
||||
cmd = 'samtools view -bS "%s" > "%s"' % ( input_filename, unsorted_bam_filename )
|
||||
proc = subprocess.Popen( args=cmd, stderr=open( unsorted_stderr_filename, 'wb' ), shell=True, cwd=tmp_dir )
|
||||
return_code = proc.wait()
|
||||
if return_code:
|
||||
stderr_target = sys.stderr
|
||||
else:
|
||||
stderr_target = sys.stdout
|
||||
stderr = open( unsorted_stderr_filename )
|
||||
while True:
|
||||
chunk = stderr.read( CHUNK_SIZE )
|
||||
if chunk:
|
||||
stderr_target.write( chunk )
|
||||
else:
|
||||
break
|
||||
stderr.close()
|
||||
|
||||
#sort sam, so indexing will not fail
|
||||
sorted_stderr_filename = os.path.join( tmp_dir, 'sorted.stderr' )
|
||||
sorting_prefix = os.path.join( tmp_dir, 'sorted_bam' )
|
||||
cmd = 'samtools sort -o "%s" "%s" > "%s"' % ( unsorted_bam_filename, sorting_prefix, output_filename )
|
||||
proc = subprocess.Popen( args=cmd, stderr=open( sorted_stderr_filename, 'wb' ), shell=True, cwd=tmp_dir )
|
||||
return_code = proc.wait()
|
||||
|
||||
if return_code:
|
||||
stderr_target = sys.stderr
|
||||
else:
|
||||
stderr_target = sys.stdout
|
||||
stderr = open( sorted_stderr_filename )
|
||||
while True:
|
||||
chunk = stderr.read( CHUNK_SIZE )
|
||||
if chunk:
|
||||
stderr_target.write( chunk )
|
||||
else:
|
||||
break
|
||||
stderr.close()
|
||||
|
||||
cleanup_before_exit( tmp_dir )
|
||||
|
||||
if __name__=="__main__": __main__()
|
||||
@@ -1,11 +1,11 @@
|
||||
<tool id="CONVERTER_sam_to_bam" name="Convert SAM to BAM" version="1.0.0">
|
||||
<tool id="CONVERTER_sam_to_bam" name="Convert SAM to BAM" version="2.0.0">
|
||||
<!-- <description>__NOT_USED_CURRENTLY_FOR_CONVERTERS__</description> -->
|
||||
<!-- Used on the metadata edit page. -->
|
||||
<!-- FIXME: conversion will only work if headers for reference sequences are in input file.
|
||||
To fix this: (a) merge sam_to_bam tool in tools with this conversion (like fasta_to_len
|
||||
conversion); and (b) define a datatype-specific way to set converter parameters.
|
||||
-->
|
||||
<command>samtools view -bS $input1 > $output 2> /dev/null </command>
|
||||
<command interpreter="python">sam_to_bam.py $input1 $output</command>
|
||||
<inputs>
|
||||
<param name="input1" type="data" format="sam" label="SAM file"/>
|
||||
</inputs>
|
||||
|
||||
+194
-19
@@ -3,11 +3,39 @@ from galaxy import util
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util import inflector
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from cgi import escape
|
||||
import mimetypes
|
||||
import metadata
|
||||
import zipfile
|
||||
from metadata import MetadataElement #import directly to maintain ease of use in Datatype class definitions
|
||||
|
||||
|
||||
if sys.version_info[:2] < ( 2, 6 ):
|
||||
zipfile.BadZipFile = zipfile.error
|
||||
if sys.version_info[:2] < ( 2, 5 ):
|
||||
zipfile.LargeZipFile = zipfile.error
|
||||
|
||||
tmpd = tempfile.mkdtemp()
|
||||
comptypes=[]
|
||||
ziptype = '32'
|
||||
tmpf = os.path.join( tmpd, 'compression_test.zip' )
|
||||
try:
|
||||
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
|
||||
archive.close()
|
||||
comptypes.append( 'zip' )
|
||||
ziptype = '64'
|
||||
except RuntimeError:
|
||||
log.exception( "Compression error when testing zip compression. This option will be disabled for library downloads." )
|
||||
except (TypeError, zipfile.LargeZipFile): # ZIP64 is only in Python2.5+. Remove TypeError when 2.4 support is dropped
|
||||
log.warning( 'Max zip file size is 2GB, ZIP64 not supported' )
|
||||
comptypes.append( 'zip' )
|
||||
try:
|
||||
os.unlink( tmpf )
|
||||
except OSError:
|
||||
pass
|
||||
os.rmdir( tmpd )
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Valid first column and strand column values vor bed, other formats
|
||||
@@ -39,7 +67,7 @@ class Data( object ):
|
||||
'test'
|
||||
>>> type( DataTest.metadata_spec.test.param )
|
||||
<class 'galaxy.datatypes.metadata.MetadataParameter'>
|
||||
|
||||
|
||||
"""
|
||||
__metaclass__ = DataMeta
|
||||
# Add metadata elements
|
||||
@@ -60,7 +88,7 @@ class Data( object ):
|
||||
primary_file_name = 'index'
|
||||
#A per datatype setting (inherited): max file size (in bytes) for setting optional metadata
|
||||
_max_optional_metadata_filesize = None
|
||||
|
||||
|
||||
def __init__(self, **kwd):
|
||||
"""Initialize the datatype"""
|
||||
object.__init__(self, **kwd)
|
||||
@@ -118,7 +146,7 @@ class Data( object ):
|
||||
to_check = dataset.metadata.items()
|
||||
for key, value in to_check:
|
||||
if key in skip or ( not check and dataset.metadata.spec[key].get( "optional" ) ):
|
||||
continue #we skip check for optional and nonrequested values here
|
||||
continue #we skip check for optional and nonrequested values here
|
||||
if not value:
|
||||
return True
|
||||
return False
|
||||
@@ -142,6 +170,7 @@ class Data( object ):
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disk'
|
||||
|
||||
def display_peek(self, dataset ):
|
||||
"""Create HTML table, used for displaying peek"""
|
||||
out = ['<table cellspacing="0" cellpadding="3">']
|
||||
@@ -163,6 +192,151 @@ class Data( object ):
|
||||
except Exception, exc:
|
||||
out = "Can't create peek %s" % str( exc )
|
||||
return out
|
||||
|
||||
def _archive_composite_dataset( self, trans, data=None, **kwd ):
|
||||
# save a composite object into a compressed archive for downloading
|
||||
params = util.Params( kwd )
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
outfname = data.name[0:150]
|
||||
outfname = ''.join(c in valid_chars and c or '_' for c in outfname)
|
||||
if (params.do_action == None):
|
||||
params.do_action = 'zip' # default
|
||||
msg = util.restore_text( params.get( 'msg', '' ) )
|
||||
messagetype = params.get( 'messagetype', 'done' )
|
||||
if not data:
|
||||
msg = "You must select at least one dataset"
|
||||
messagetype = 'error'
|
||||
else:
|
||||
error = False
|
||||
try:
|
||||
if (params.do_action == 'zip'):
|
||||
# Can't use mkstemp - the file must not exist first
|
||||
tmpd = tempfile.mkdtemp()
|
||||
tmpf = os.path.join( tmpd, 'library_download.' + params.do_action )
|
||||
if ziptype == '64':
|
||||
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
|
||||
else:
|
||||
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED )
|
||||
archive.add = lambda x, y: archive.write( x, y.encode('CP437') )
|
||||
elif params.do_action == 'tgz':
|
||||
archive = util.streamball.StreamBall( 'w|gz' )
|
||||
elif params.do_action == 'tbz':
|
||||
archive = util.streamball.StreamBall( 'w|bz2' )
|
||||
except (OSError, zipfile.BadZipFile):
|
||||
error = True
|
||||
log.exception( "Unable to create archive for download" )
|
||||
msg = "Unable to create archive for %s for download, please report this error" % outfname
|
||||
messagetype = 'error'
|
||||
if not error:
|
||||
current_user_roles = trans.get_current_user_roles()
|
||||
ext = data.extension
|
||||
path = data.file_name
|
||||
fname = os.path.split(path)[-1]
|
||||
efp = data.extra_files_path
|
||||
htmlname = os.path.splitext(outfname)[0]
|
||||
if not htmlname.endswith(ext):
|
||||
htmlname = '%s_%s' % (htmlname,ext)
|
||||
archname = '%s.html' % htmlname # fake the real nature of the html file
|
||||
try:
|
||||
archive.add(data.file_name,archname)
|
||||
except IOError:
|
||||
error = True
|
||||
log.exception( "Unable to add composite parent %s to temporary library download archive" % data.file_name)
|
||||
msg = "Unable to create archive for download, please report this error"
|
||||
messagetype = 'error'
|
||||
for root, dirs, files in os.walk(efp):
|
||||
for fname in files:
|
||||
fpath = os.path.join(root,fname)
|
||||
rpath = os.path.relpath(fpath,efp)
|
||||
try:
|
||||
archive.add( fpath,rpath )
|
||||
except IOError:
|
||||
error = True
|
||||
log.exception( "Unable to add %s to temporary library download archive" % rpath)
|
||||
msg = "Unable to create archive for download, please report this error"
|
||||
messagetype = 'error'
|
||||
continue
|
||||
if not error:
|
||||
if params.do_action == 'zip':
|
||||
archive.close()
|
||||
tmpfh = open( tmpf )
|
||||
# CANNOT clean up - unlink/rmdir was always failing because file handle retained to return - must rely on a cron job to clean up tmp
|
||||
trans.response.set_content_type( "application/x-zip-compressed" )
|
||||
trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.zip"' % outfname
|
||||
return tmpfh
|
||||
else:
|
||||
trans.response.set_content_type( "application/x-tar" )
|
||||
outext = 'tgz'
|
||||
if params.do_action == 'tbz':
|
||||
outext = 'tbz'
|
||||
trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % (outfname,outext)
|
||||
archive.wsgi_status = trans.response.wsgi_status()
|
||||
archive.wsgi_headeritems = trans.response.wsgi_headeritems()
|
||||
return archive.stream
|
||||
return trans.show_error_message( msg )
|
||||
|
||||
def _serve_raw(self, trans, dataset, to_ext):
|
||||
trans.response.headers['Content-Length'] = int( os.stat( dataset.file_name ).st_size )
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in dataset.name)[0:150]
|
||||
trans.response.set_content_type( "application/octet-stream" ) #force octet-stream so Safari doesn't append mime extensions to filename
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (dataset.hid, fname, to_ext)
|
||||
return open( dataset.file_name )
|
||||
|
||||
def display_data(self, trans, data, preview=False, filename=None, to_ext=None, size=None, offset=None, **kwd):
|
||||
""" Old display method, for transition """
|
||||
#Relocate all composite datatype display to a common location.
|
||||
composite_extensions = trans.app.datatypes_registry.get_composite_extensions( )
|
||||
composite_extensions.append('html') # for archiving composite datatypes
|
||||
if isinstance( data, basestring ):
|
||||
return data
|
||||
if filename and filename != "index":
|
||||
# For files in extra_files_path
|
||||
file_path = trans.app.object_store.get_filename(data.dataset, extra_dir='dataset_%s_files' % data.dataset.id, alt_name=filename)
|
||||
if os.path.exists( file_path ):
|
||||
if os.path.isdir( file_path ):
|
||||
return trans.show_error_message( "Directory listing is not allowed." ) #TODO: Reconsider allowing listing of directories?
|
||||
mime, encoding = mimetypes.guess_type( file_path )
|
||||
if not mime:
|
||||
try:
|
||||
mime = trans.app.datatypes_registry.get_mimetype_by_extension( ".".split( file_path )[-1] )
|
||||
except:
|
||||
mime = "text/plain"
|
||||
trans.response.set_content_type( mime )
|
||||
return open( file_path )
|
||||
else:
|
||||
return trans.show_error_message( "Could not find '%s' on the extra files path %s." % ( filename, file_path ) )
|
||||
trans.response.set_content_type(data.get_mime())
|
||||
trans.log_event( "Display dataset id: %s" % str( data.id ) )
|
||||
from galaxy import datatypes #DBTODO REMOVE THIS AT REFACTOR
|
||||
if to_ext or isinstance(data.datatype, datatypes.binary.Binary): # Saving the file, or binary file
|
||||
if data.extension in composite_extensions:
|
||||
return self._archive_composite_dataset( trans, data, **kwd )
|
||||
else:
|
||||
trans.response.headers['Content-Length'] = int( os.stat( data.file_name ).st_size )
|
||||
if not to_ext:
|
||||
to_ext = data.extension
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in data.name)[0:150]
|
||||
trans.response.set_content_type( "application/octet-stream" ) #force octet-stream so Safari doesn't append mime extensions to filename
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (data.hid, fname, to_ext)
|
||||
return open( data.file_name )
|
||||
if not os.path.exists( data.file_name ):
|
||||
raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % data.file_name )
|
||||
max_peek_size = 1000000 # 1 MB
|
||||
if isinstance(data.datatype, datatypes.images.Html):
|
||||
max_peek_size = 10000000 # 10 MB for html
|
||||
if not preview or isinstance(data.datatype, datatypes.images.Image) or os.stat( data.file_name ).st_size < max_peek_size:
|
||||
if trans.app.config.sanitize_all_html and trans.response.get_content_type() == "text/html":
|
||||
# Sanitize anytime we respond with plain text/html content.
|
||||
return sanitize_html(open( data.file_name ).read())
|
||||
return open( data.file_name )
|
||||
else:
|
||||
trans.response.set_content_type( "text/html" )
|
||||
return trans.stream_template_mako( "/dataset/large_file.mako",
|
||||
truncated_data = open( data.file_name ).read(max_peek_size),
|
||||
data = data)
|
||||
|
||||
def display_name(self, dataset):
|
||||
"""Returns formatted html of dataset name"""
|
||||
try:
|
||||
@@ -183,11 +357,11 @@ class Data( object ):
|
||||
info = info.replace( '\r', '<br/>' )
|
||||
if info.find( '\n' ) >= 0:
|
||||
info = info.replace( '\n', '<br/>' )
|
||||
|
||||
|
||||
# Convert to unicode to display non-ascii characters.
|
||||
if type( info ) is not unicode:
|
||||
info = unicode( info, 'utf-8')
|
||||
|
||||
|
||||
return info
|
||||
except:
|
||||
return "info unavailable"
|
||||
@@ -272,7 +446,7 @@ class Data( object ):
|
||||
def convert_dataset(self, trans, original_dataset, target_type, return_output=False, visible=True, deps=None, set_output_history=True):
|
||||
"""This function adds a job to the queue to convert a dataset to another type. Returns a message about success/failure."""
|
||||
converter = trans.app.datatypes_registry.get_converter_by_target_type( original_dataset.ext, target_type )
|
||||
|
||||
|
||||
if converter is None:
|
||||
raise Exception( "A converter does not exist for %s to %s." % ( original_dataset.ext, target_type ) )
|
||||
#Generate parameter dictionary
|
||||
@@ -284,7 +458,7 @@ class Data( object ):
|
||||
params[value.name] = deps[value.name]
|
||||
elif value.type == 'data':
|
||||
input_name = key
|
||||
|
||||
|
||||
params[input_name] = original_dataset
|
||||
#Run converter, job is dispatched through Queue
|
||||
converted_dataset = converter.execute( trans, incoming=params, set_output_hid=visible, set_output_history=set_output_history)[1]
|
||||
@@ -351,18 +525,18 @@ class Data( object ):
|
||||
@property
|
||||
def has_resolution(self):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def merge( split_files, output_file):
|
||||
|
||||
|
||||
def merge( split_files, output_file):
|
||||
"""
|
||||
TODO: Do we need to merge gzip files using gzjoin? cat seems to work,
|
||||
but might be brittle. Need to revisit this.
|
||||
"""
|
||||
if len(split_files) == 1:
|
||||
cmd = 'mv -f %s %s' % ( split_files[0], output_file )
|
||||
cmd = 'mv -f %s %s' % ( split_files[0], output_file )
|
||||
else:
|
||||
cmd = 'cat %s > %s' % ( ' '.join(split_files), output_file )
|
||||
cmd = 'cat %s > %s' % ( ' '.join(split_files), output_file )
|
||||
result = os.system(cmd)
|
||||
if result != 0:
|
||||
raise Exception('Result %s from %s' % (result, cmd))
|
||||
@@ -377,7 +551,7 @@ class Text( Data ):
|
||||
|
||||
def write_from_stream(self, dataset, stream):
|
||||
"""Writes data from a stream"""
|
||||
# write it twice for now
|
||||
# write it twice for now
|
||||
fd, temp_name = tempfile.mkstemp()
|
||||
while 1:
|
||||
chunk = stream.read(1048576)
|
||||
@@ -468,11 +642,11 @@ class Text( Data ):
|
||||
"""
|
||||
if split_params is None:
|
||||
return
|
||||
|
||||
|
||||
if len(input_datasets) > 1:
|
||||
raise Exception("Text file splitting does not support multiple files")
|
||||
input_files = [ds.file_name for ds in input_datasets]
|
||||
|
||||
|
||||
lines_per_file = None
|
||||
chunk_size = None
|
||||
if split_params['split_mode'] == 'number_of_parts':
|
||||
@@ -501,7 +675,7 @@ class Text( Data ):
|
||||
chunk_size = int(split_params['split_size'])
|
||||
else:
|
||||
raise Exception('Unsupported split mode %s' % split_params['split_mode'])
|
||||
|
||||
|
||||
f = open(input_files[0], 'rt')
|
||||
try:
|
||||
chunk_idx = 0
|
||||
@@ -562,7 +736,7 @@ def get_test_fname( fname ):
|
||||
def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5, skipchars=[] ):
|
||||
"""
|
||||
Returns the first LINE_COUNT lines wrapped to WIDTH
|
||||
|
||||
|
||||
## >>> fname = get_test_fname('4.bed')
|
||||
## >>> get_file_peek(fname)
|
||||
## 'chr22 30128507 31828507 uc003bnx.1_cds_2_0_chr22_29227_f 0 +\n'
|
||||
@@ -601,11 +775,12 @@ def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5, skip
|
||||
lines.append( line )
|
||||
count += 1
|
||||
temp.close()
|
||||
if file_type in [ 'gzipped', 'binary' ]:
|
||||
text = "%s file" % file_type
|
||||
if file_type in [ 'gzipped', 'binary' ]:
|
||||
text = "%s file" % file_type
|
||||
else:
|
||||
try:
|
||||
text = unicode( '\n'.join( lines ), 'utf-8' )
|
||||
except UnicodeDecodeError:
|
||||
text = "binary/unknown file"
|
||||
return text
|
||||
|
||||
|
||||
@@ -328,6 +328,9 @@ class Interval( Tabular ):
|
||||
|
||||
def get_track_resolution( self, dataset, start, end):
|
||||
return None
|
||||
|
||||
def get_track_type( self ):
|
||||
return "FeatureTrack", {"data": "tabix", "index": "summary_tree"}
|
||||
|
||||
class BedGraph( Interval ):
|
||||
"""Tab delimited chrom/start/end/datavalue dataset"""
|
||||
@@ -950,6 +953,9 @@ class Gtf( Gff ):
|
||||
dataset.metadata.attribute_types = attribute_types
|
||||
dataset.metadata.attributes = len( attribute_types )
|
||||
Gff.set_meta( self, dataset, overwrite = overwrite, skip = i )
|
||||
|
||||
def get_track_type( self ):
|
||||
return "FeatureTrack", {"data": "tabix", "index": "summary_tree"}
|
||||
|
||||
|
||||
class Wiggle( Tabular, _RemoteCallMixin ):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Provides mapping between extensions and datatypes, mime-types, etc.
|
||||
"""
|
||||
import os, sys, tempfile, threading, logging
|
||||
import data, tabular, interval, images, sequence, qualityscore, genetics, xml, coverage, tracks, chrominfo, binary, assembly, ngsindex, wsf
|
||||
import data, tabular, interval, images, sequence, qualityscore, genetics, xml, coverage, tracks, chrominfo, binary, assembly, ngsindex
|
||||
import galaxy.util
|
||||
from galaxy.util.odict import odict
|
||||
from display_applications.application import DisplayApplication
|
||||
@@ -141,7 +141,7 @@ class Registry( object ):
|
||||
if hasattr( imported_module, datatype_class_name ):
|
||||
datatype_class = getattr( imported_module, datatype_class_name )
|
||||
except Exception, e:
|
||||
full_path = os.path.join( full_path, proprietary_datatype_module )
|
||||
full_path = os.path.join( proprietary_path, proprietary_datatype_module )
|
||||
self.log.debug( "Exception importing proprietary code file %s: %s" % ( str( full_path ), str( e ) ) )
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
@@ -13,34 +13,37 @@ from galaxy.datatypes import metadata
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
import galaxy_utils.sequence.vcf
|
||||
from sniff import *
|
||||
from galaxy.util.json import to_json_string
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Tabular( data.Text ):
|
||||
"""Tab delimited data"""
|
||||
CHUNK_SIZE = 20000
|
||||
|
||||
"""Add metadata elements"""
|
||||
MetadataElement( name="comment_lines", default=0, desc="Number of comment lines", readonly=False, optional=True, no_value=0 )
|
||||
MetadataElement( name="columns", default=0, desc="Number of columns", readonly=True, visible=False, no_value=0 )
|
||||
MetadataElement( name="column_types", default=[], desc="Column types", param=metadata.ColumnTypesParameter, readonly=True, visible=False, no_value=[] )
|
||||
MetadataElement( name="column_names", default=[], desc="Column names", readonly=True, visible=False, optional=True, no_value=[] )
|
||||
|
||||
def init_meta( self, dataset, copy_from=None ):
|
||||
data.Text.init_meta( self, dataset, copy_from=copy_from )
|
||||
def set_meta( self, dataset, overwrite = True, skip = None, max_data_lines = 100000, **kwd ):
|
||||
def set_meta( self, dataset, overwrite = True, skip = None, max_data_lines = 100000, max_guess_type_data_lines = None, **kwd ):
|
||||
"""
|
||||
Tries to determine the number of columns as well as those columns
|
||||
that contain numerical values in the dataset. A skip parameter is
|
||||
used because various tabular data types reuse this function, and
|
||||
their data type classes are responsible to determine how many invalid
|
||||
comment lines should be skipped. Using None for skip will cause skip
|
||||
to be zero, but the first line will be processed as a header. A
|
||||
max_data_lines parameter is used because various tabular data types
|
||||
reuse this function, and their data type classes are responsible to
|
||||
comment lines should be skipped. Using None for skip will cause skip
|
||||
to be zero, but the first line will be processed as a header. A
|
||||
max_data_lines parameter is used because various tabular data types
|
||||
reuse this function, and their data type classes are responsible to
|
||||
determine how many data lines should be processed to ensure that the
|
||||
non-optional metadata parameters are properly set; if used, optional
|
||||
metadata parameters will be set to None, unless the entire file has
|
||||
already been read. Using None (default) for max_data_lines will
|
||||
process all data lines.
|
||||
non-optional metadata parameters are properly set; if used, optional
|
||||
metadata parameters will be set to None, unless the entire file has
|
||||
already been read. Using None (default) for max_data_lines will
|
||||
process all data lines.
|
||||
|
||||
Items of interest:
|
||||
1. We treat 'overwrite' as always True (we always want to set tabular metadata when called).
|
||||
@@ -57,7 +60,7 @@ class Tabular( data.Text ):
|
||||
column_type_set_order = [ 'int', 'float', 'list', 'str' ] #Order to set column types in
|
||||
default_column_type = column_type_set_order[-1] # Default column type is lowest in list
|
||||
column_type_compare_order = list( column_type_set_order ) #Order to compare column types
|
||||
column_type_compare_order.reverse()
|
||||
column_type_compare_order.reverse()
|
||||
def type_overrules_type( column_type1, column_type2 ):
|
||||
if column_type1 is None or column_type1 == column_type2:
|
||||
return False
|
||||
@@ -74,13 +77,13 @@ class Tabular( data.Text ):
|
||||
try:
|
||||
int( column_text )
|
||||
return True
|
||||
except:
|
||||
except:
|
||||
return False
|
||||
def is_float( column_text ):
|
||||
try:
|
||||
float( column_text )
|
||||
return True
|
||||
except:
|
||||
except:
|
||||
if column_text.strip().lower() == 'na':
|
||||
return True #na is special cased to be a float
|
||||
return False
|
||||
@@ -116,15 +119,16 @@ class Tabular( data.Text ):
|
||||
comment_lines += 1
|
||||
else:
|
||||
data_lines += 1
|
||||
fields = line.split( '\t' )
|
||||
for field_count, field in enumerate( fields ):
|
||||
if field_count >= len( column_types ): #found a previously unknown column, we append None
|
||||
column_types.append( None )
|
||||
column_type = guess_column_type( field )
|
||||
if type_overrules_type( column_type, column_types[field_count] ):
|
||||
column_types[field_count] = column_type
|
||||
if max_guess_type_data_lines is None or data_lines <= max_guess_type_data_lines:
|
||||
fields = line.split( '\t' )
|
||||
for field_count, field in enumerate( fields ):
|
||||
if field_count >= len( column_types ): #found a previously unknown column, we append None
|
||||
column_types.append( None )
|
||||
column_type = guess_column_type( field )
|
||||
if type_overrules_type( column_type, column_types[field_count] ):
|
||||
column_types[field_count] = column_type
|
||||
if i == 0 and requested_skip is None:
|
||||
# This is our first line, people seem to like to upload files that have a header line, but do not
|
||||
# This is our first line, people seem to like to upload files that have a header line, but do not
|
||||
# start with '#' (i.e. all column types would then most likely be detected as str). We will assume
|
||||
# that the first line is always a header (this was previous behavior - it was always skipped). When
|
||||
# the requested skip is None, we only use the data from the first line if we have no other data for
|
||||
@@ -146,7 +150,7 @@ class Tabular( data.Text ):
|
||||
break
|
||||
i += 1
|
||||
dataset_fh.close()
|
||||
|
||||
|
||||
#we error on the larger number of columns
|
||||
#first we pad our column_types by using data from first line
|
||||
if len( first_line_column_types ) > len( column_types ):
|
||||
@@ -175,9 +179,19 @@ class Tabular( data.Text ):
|
||||
except Exception, exc:
|
||||
out = "Can't create peek %s" % str( exc )
|
||||
return out
|
||||
def make_html_peek_header( self, dataset, skipchars=[], column_names=[], column_number_format='%s', column_parameter_alias={}, **kwargs ):
|
||||
|
||||
def make_html_peek_header( self, dataset, skipchars=None, column_names=None, column_number_format='%s', column_parameter_alias=None, **kwargs ):
|
||||
if skipchars is None:
|
||||
skipchars = []
|
||||
if column_names is None:
|
||||
column_names = []
|
||||
if column_parameter_alias is None:
|
||||
column_parameter_alias = {}
|
||||
out = []
|
||||
try:
|
||||
if not column_names and dataset.metadata.column_names:
|
||||
column_names = dataset.metadata.column_names
|
||||
|
||||
column_headers = [None] * dataset.metadata.columns
|
||||
|
||||
# fill in empty headers with data from column_names
|
||||
@@ -207,7 +221,10 @@ class Tabular( data.Text ):
|
||||
except Exception, exc:
|
||||
raise Exception, "Can't create peek header %s" % str( exc )
|
||||
return "".join( out )
|
||||
def make_html_peek_rows( self, dataset, skipchars=[], **kwargs ):
|
||||
|
||||
def make_html_peek_rows( self, dataset, skipchars=None, **kwargs ):
|
||||
if skipchars is None:
|
||||
skipchars = []
|
||||
out = []
|
||||
try:
|
||||
if not dataset.peek:
|
||||
@@ -228,6 +245,28 @@ class Tabular( data.Text ):
|
||||
except Exception, exc:
|
||||
raise Exception, "Can't create peek rows %s" % str( exc )
|
||||
return "".join( out )
|
||||
|
||||
def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, chunk=None):
|
||||
#TODO Prevent failure when displaying extremely long > 50kb lines.
|
||||
if to_ext:
|
||||
return self._serve_raw(trans, dataset, to_ext)
|
||||
if chunk:
|
||||
ck_index = int(chunk)
|
||||
f = open(dataset.file_name)
|
||||
f.seek(ck_index * self.CHUNK_SIZE)
|
||||
# If we aren't at the start of the file, seek to next newline. Do this better eventually.
|
||||
if f.tell() != 0:
|
||||
cursor = f.read(1)
|
||||
while cursor and cursor != '\n':
|
||||
cursor = f.read(1)
|
||||
ck_data = f.read(self.CHUNK_SIZE)
|
||||
cursor = f.read(1)
|
||||
while cursor and ck_data[-1] != '\n':
|
||||
ck_data += cursor
|
||||
cursor = f.read(1)
|
||||
return to_json_string({'ck_data': ck_data, 'ck_index': ck_index+1})
|
||||
return trans.fill_template( "/dataset/tabular_chunked.mako",dataset = dataset)
|
||||
|
||||
def set_peek( self, dataset, line_count=None, is_multi_byte=False):
|
||||
super(Tabular, self).set_peek( dataset, line_count=line_count, is_multi_byte=is_multi_byte)
|
||||
if dataset.metadata.comment_lines:
|
||||
@@ -276,7 +315,7 @@ class Sam( Tabular ):
|
||||
def sniff( self, filename ):
|
||||
"""
|
||||
Determines whether the file is in SAM format
|
||||
|
||||
|
||||
A file in SAM format consists of lines of tab-separated data.
|
||||
The following header line may be the first line:
|
||||
@QNAME FLAG RNAME POS MAPQ CIGAR MRNM MPOS ISIZE SEQ QUAL
|
||||
@@ -285,12 +324,12 @@ class Sam( Tabular ):
|
||||
Data in the OPT column is optional and can consist of tab-separated data
|
||||
|
||||
For complete details see http://samtools.sourceforge.net/SAM1.pdf
|
||||
|
||||
|
||||
Rules for sniffing as True:
|
||||
There must be 11 or more columns of data on each line
|
||||
Columns 2 (FLAG), 4(POS), 5 (MAPQ), 8 (MPOS), and 9 (ISIZE) must be numbers (9 can be negative)
|
||||
We will only check that up to the first 5 alignments are correctly formatted.
|
||||
|
||||
|
||||
>>> fname = get_test_fname( 'sequence.maf' )
|
||||
>>> Sam().sniff( fname )
|
||||
False
|
||||
@@ -306,7 +345,7 @@ class Sam( Tabular ):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
break #EOF
|
||||
if line:
|
||||
if line:
|
||||
if line[0] != '@':
|
||||
linePieces = line.split('\t')
|
||||
if len(linePieces) < 11:
|
||||
@@ -368,10 +407,10 @@ class Sam( Tabular ):
|
||||
if result != 0:
|
||||
raise Exception('Result %s from %s' % (result, cmd))
|
||||
merge = staticmethod(merge)
|
||||
|
||||
|
||||
def get_track_type( self ):
|
||||
return "ReadTrack", {"data": "bam", "index": "summary_tree"}
|
||||
|
||||
|
||||
class Pileup( Tabular ):
|
||||
"""Tab delimited data in pileup (6- or 10-column) format"""
|
||||
file_ext = "pileup"
|
||||
@@ -397,7 +436,7 @@ class Pileup( Tabular ):
|
||||
"""
|
||||
Checks for 'pileup-ness'
|
||||
|
||||
There are two main types of pileup: 6-column and 10-column. For both,
|
||||
There are two main types of pileup: 6-column and 10-column. For both,
|
||||
the first three and last two columns are the same. We only check the
|
||||
first three to allow for some personalization of the format.
|
||||
|
||||
@@ -431,27 +470,27 @@ class Pileup( Tabular ):
|
||||
|
||||
class ElandMulti( Tabular ):
|
||||
file_ext = 'elandmulti'
|
||||
|
||||
|
||||
def sniff( self, filename ):
|
||||
return False
|
||||
|
||||
|
||||
class Vcf( Tabular ):
|
||||
""" Variant Call Format for describing SNPs and other simple genome variations. """
|
||||
|
||||
|
||||
file_ext = 'vcf'
|
||||
column_names = [ 'Chrom', 'Pos', 'ID', 'Ref', 'Alt', 'Qual', 'Filter', 'Info', 'Format', 'data' ]
|
||||
|
||||
|
||||
MetadataElement( name="columns", default=10, desc="Number of columns", readonly=True, visible=False )
|
||||
MetadataElement( name="column_types", default=['str','int','str','str','str','int','str','list','str','str'], param=metadata.ColumnTypesParameter, desc="Column types", readonly=True, visible=False )
|
||||
MetadataElement( name="viz_filter_cols", desc="Score column for visualization", default=[5], param=metadata.ColumnParameter, multiple=True )
|
||||
|
||||
|
||||
def sniff( self, filename ):
|
||||
headers = get_headers( filename, '\n', count=1 )
|
||||
return headers[0][0].startswith("##fileformat=VCF")
|
||||
def display_peek( self, dataset ):
|
||||
"""Returns formated html of peek"""
|
||||
return Tabular.make_html_table( self, dataset, column_names=self.column_names )
|
||||
|
||||
|
||||
def get_track_type( self ):
|
||||
return "VcfTrack", {"data": "tabix", "index": "summary_tree"}
|
||||
|
||||
@@ -473,8 +512,10 @@ class Eland( Tabular ):
|
||||
'POSITION', 'STRAND', 'DESC', 'SRAS', 'PRAS', 'PART_CHROM'
|
||||
'PART_CONTIG', 'PART_OFFSET', 'PART_STRAND', 'FILT'
|
||||
]
|
||||
def make_html_table( self, dataset, skipchars=[] ):
|
||||
def make_html_table( self, dataset, skipchars=None ):
|
||||
"""Create HTML table, used for displaying peek"""
|
||||
if skipchars is None:
|
||||
skipchars = []
|
||||
out = ['<table cellspacing="0" cellpadding="3">']
|
||||
try:
|
||||
# Generate column header
|
||||
@@ -495,10 +536,10 @@ class Eland( Tabular ):
|
||||
def sniff( self, filename ):
|
||||
"""
|
||||
Determines whether the file is in ELAND export format
|
||||
|
||||
|
||||
A file in ELAND export format consists of lines of tab-separated data.
|
||||
There is no header.
|
||||
|
||||
|
||||
Rules for sniffing as True:
|
||||
There must be 22 columns on each line
|
||||
LANE, TILEm X, Y, INDEX, READ_NO, SEQ, QUAL, POSITION, *STRAND, FILT must be correct
|
||||
@@ -517,7 +558,7 @@ class Eland( Tabular ):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
break #EOF
|
||||
if line:
|
||||
if line:
|
||||
linePieces = line.split('\t')
|
||||
if len(linePieces) != 22:
|
||||
return False
|
||||
@@ -563,7 +604,7 @@ class Eland( Tabular ):
|
||||
#else:
|
||||
# # Otherwise, read the whole thing and set num data lines.
|
||||
for i, line in enumerate(dataset_fh):
|
||||
if line:
|
||||
if line:
|
||||
linePieces = line.split('\t')
|
||||
if len(linePieces) != 22:
|
||||
raise Exception('%s:%d:Corrupt line!' % (dataset.file_name,i))
|
||||
@@ -581,5 +622,5 @@ class Eland( Tabular ):
|
||||
dataset.metadata.tiles = ["%04d" % int(t) for t in tiles.keys()]
|
||||
dataset.metadata.barcodes = filter(lambda x: x != '0', barcodes.keys()) + ['NoIndex' for x in barcodes.keys() if x == '0']
|
||||
dataset.metadata.reads = reads.keys()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ class GFFInterval( GenomicInterval ):
|
||||
A GFF interval, including attributes. If file is strictly a GFF file,
|
||||
only attribute is 'group.'
|
||||
"""
|
||||
def __init__( self, reader, fields, chrom_col, feature_col, start_col, end_col, \
|
||||
strand_col, score_col, default_strand, fix_strand=False ):
|
||||
def __init__( self, reader, fields, chrom_col=0, feature_col=2, start_col=3, end_col=4, \
|
||||
strand_col=6, score_col=5, default_strand='.', fix_strand=False ):
|
||||
# HACK: GFF format allows '.' for strand but GenomicInterval does not. To get around this,
|
||||
# temporarily set strand and then unset after initing GenomicInterval.
|
||||
unknown_strand = False
|
||||
@@ -45,8 +45,8 @@ class GFFFeature( GFFInterval ):
|
||||
"""
|
||||
A GFF feature, which can include multiple intervals.
|
||||
"""
|
||||
def __init__( self, reader, chrom_col, feature_col, start_col, end_col, \
|
||||
strand_col, score_col, default_strand, fix_strand=False, intervals=[], \
|
||||
def __init__( self, reader, chrom_col=0, feature_col=2, start_col=3, end_col=4, \
|
||||
strand_col=6, score_col=5, default_strand='.', fix_strand=False, intervals=[], \
|
||||
raw_size=0 ):
|
||||
GFFInterval.__init__( self, reader, intervals[0].fields, chrom_col, feature_col, \
|
||||
start_col, end_col, strand_col, score_col, default_strand, \
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
"""
|
||||
SnpFile datatype
|
||||
"""
|
||||
|
||||
import re
|
||||
import data
|
||||
from galaxy import util
|
||||
from galaxy.datatypes.sniff import *
|
||||
from galaxy.datatypes.tabular import Tabular
|
||||
from galaxy.datatypes import metadata
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
|
||||
class SnpFile( Tabular ):
|
||||
""" Webb's SNP file format """
|
||||
file_ext = 'wsf'
|
||||
species_regex = re.compile('species=(\S+)')
|
||||
MetadataElement( name="species", desc="species", default='', no_value='', visible=False, readonly=True )
|
||||
MetadataElement( name="scaffold", desc="scaffold column", param=metadata.ColumnParameter, default=0 )
|
||||
MetadataElement( name="pos", desc="pos column", param=metadata.ColumnParameter, default=0 )
|
||||
MetadataElement( name="ref", desc="ref column", param=metadata.ColumnParameter, default=0 )
|
||||
MetadataElement( name="rPos", desc="rPos column", param=metadata.ColumnParameter, default=0 )
|
||||
MetadataElement( name="labels", desc="Number of labels", default=0, no_value=0, visible=False, readonly=True )
|
||||
MetadataElement( name="label_for_column", desc="Mapping from column to label", default=[], no_value=[], visible=False, readonly=True )
|
||||
MetadataElement( name="columns_with_label", desc="Mapping from label to columns", param=metadata.DictParameter, default={}, no_value={}, visible=False, readonly=True )
|
||||
MetadataElement( name="column_headers", desc="Column headers", default=[], no_value=[], visible=False, readonly=True )
|
||||
|
||||
|
||||
def set_meta( self, dataset, overwrite = True, **kwd ):
|
||||
Tabular.set_meta( self, dataset, overwrite=overwrite, max_data_lines=None, **kwd )
|
||||
# these two if statements work around a potential bug in metadata.py
|
||||
if dataset.metadata.labels is None or dataset.metadata.labels == dataset.metadata.spec['labels'].no_value:
|
||||
self._set_column_labels_metadata( dataset )
|
||||
if dataset.metadata.column_headers is None or dataset.metadata.column_headers == dataset.metadata.spec['column_headers'].no_value:
|
||||
self._set_column_headers_metadata( dataset )
|
||||
self._set_columnParameter_metadata( dataset )
|
||||
|
||||
|
||||
def _set_column_labels_metadata( self, dataset ):
|
||||
def build_map_from_label_to_comma_separated_column_list( labels ):
|
||||
map = {}
|
||||
for index, label in enumerate( labels ):
|
||||
map.setdefault( label, [] ).append( index )
|
||||
|
||||
for label in map:
|
||||
map[label] = ','.join( [ str( index + 1 ) for index in map[label] ] )
|
||||
return map
|
||||
|
||||
def strip_list_elements( list ):
|
||||
return [ element.strip() for element in list ]
|
||||
|
||||
def initial_comment_lines_of_dataset( dataset ):
|
||||
comment_lines = []
|
||||
if dataset.has_data():
|
||||
try:
|
||||
fh = open( dataset.file_name, 'r' )
|
||||
for line in fh:
|
||||
if not line.startswith('#'):
|
||||
break
|
||||
line = line[1:]
|
||||
line = line.rstrip( '\r\n' )
|
||||
if line:
|
||||
comment_lines.append( line )
|
||||
fh.close()
|
||||
except:
|
||||
pass
|
||||
return comment_lines
|
||||
|
||||
def set_metadata_from_comment_lines( dataset ):
|
||||
labels = []
|
||||
comment_lines = initial_comment_lines_of_dataset( dataset )
|
||||
|
||||
for line in comment_lines:
|
||||
match = SnpFile.species_regex.match( line )
|
||||
if match:
|
||||
dataset.metadata.species = match.group(1)
|
||||
continue
|
||||
elems = line.split( '\t' )
|
||||
if len(elems) > 1:
|
||||
labels = strip_list_elements( elems )
|
||||
|
||||
dataset.metadata.labels = len( labels )
|
||||
dataset.metadata.label_for_column = labels[:]
|
||||
if labels:
|
||||
dataset.metadata.label_for_column.insert(0, '')
|
||||
dataset.metadata.columns_with_label = build_map_from_label_to_comma_separated_column_list( labels )
|
||||
|
||||
set_metadata_from_comment_lines( dataset )
|
||||
|
||||
|
||||
def _set_column_headers_metadata( self, dataset ):
|
||||
if dataset.metadata.labels < dataset.metadata.columns:
|
||||
column_headers = dataset.metadata.label_for_column[1:] + [ '' ] * ( dataset.metadata.columns - dataset.metadata.labels )
|
||||
else:
|
||||
column_headers = dataset.metadata.label_for_column[1:dataset.metadata.columns+1]
|
||||
|
||||
dataset.metadata.column_headers = column_headers
|
||||
|
||||
|
||||
def _set_columnParameter_metadata( self, dataset ):
|
||||
def unique_column_number_or_zero( string ):
|
||||
try:
|
||||
val = int( string )
|
||||
except:
|
||||
val = 0
|
||||
return val
|
||||
|
||||
for name in self._metadata_columnParameter_names( dataset ):
|
||||
if name in dataset.metadata.columns_with_label:
|
||||
if dataset.metadata.columns_with_label[name]:
|
||||
column = unique_column_number_or_zero( dataset.metadata.columns_with_label[name] )
|
||||
if column:
|
||||
setattr( dataset.metadata, name, column )
|
||||
|
||||
|
||||
def _metadata_columnParameter_names( self, dataset ):
|
||||
for name, spec in dataset.metadata.spec.items():
|
||||
if isinstance( spec.param, metadata.ColumnParameter ):
|
||||
yield name
|
||||
|
||||
|
||||
def set_peek( self, dataset, line_count=None, is_multi_byte=False ):
|
||||
super(Tabular, self).set_peek( dataset, line_count=line_count, is_multi_byte=is_multi_byte, skipchars=[ '#' ])
|
||||
|
||||
|
||||
def make_html_table( self, dataset, skipchars=[ '#' ] ):
|
||||
"""Create HTML table, used for displaying peek"""
|
||||
def table_header_values( dataset ):
|
||||
headers = dataset.metadata.column_headers[:]
|
||||
for name in self._metadata_columnParameter_names( dataset ):
|
||||
col = getattr( dataset.metadata, name )
|
||||
assert col <= dataset.metadata.columns, Exception( 'ColumnParameter %s %d > %d columns for dataset %s.' % ( name, col, dataset.metadata.columns, dataset.id ) )
|
||||
if col > 0:
|
||||
headers[ col - 1 ] = name
|
||||
return headers
|
||||
|
||||
def table_headers( dataset ):
|
||||
out = [ '<tr>' ]
|
||||
headers = table_header_values( dataset )
|
||||
for index, header in enumerate( headers ):
|
||||
column = index + 1
|
||||
if header:
|
||||
out.append( "<th>%d.%s</th>" % ( column, header ) )
|
||||
else:
|
||||
out.append( "<th>%d.</th>" % column )
|
||||
out.append( '</tr>' )
|
||||
return out
|
||||
|
||||
try:
|
||||
out = ['<table cellspacing="0" cellpadding="3">']
|
||||
out.extend( table_headers( dataset ) )
|
||||
out.append( self.make_html_peek_rows( dataset, skipchars=skipchars ) )
|
||||
out.append( '</table>' )
|
||||
out = "".join( out )
|
||||
except Exception, exc:
|
||||
out = "Can't create peek %s" % exc
|
||||
return out
|
||||
@@ -104,13 +104,41 @@ class BlastXml( GenericXml ):
|
||||
for f in split_files:
|
||||
h = open(f)
|
||||
body = False
|
||||
header = []
|
||||
header = h.readline()
|
||||
if not header:
|
||||
out.close()
|
||||
h.close()
|
||||
raise ValueError("BLAST XML file %s was empty" % f)
|
||||
if header.strip() != '<?xml version="1.0"?>':
|
||||
out.write(header) #for diagnosis
|
||||
out.close()
|
||||
h.close()
|
||||
raise ValueError("%s is not an XML file!" % f)
|
||||
line = h.readline()
|
||||
header += line
|
||||
if line.strip() not in ['<!DOCTYPE BlastOutput PUBLIC "-//NCBI//NCBI BlastOutput/EN" "http://www.ncbi.nlm.nih.gov/dtd/NCBI_BlastOutput.dtd">',
|
||||
'<!DOCTYPE BlastOutput PUBLIC "-//NCBI//NCBI BlastOutput/EN" "NCBI_BlastOutput.dtd">']:
|
||||
out.write(header) #for diagnosis
|
||||
out.close()
|
||||
h.close()
|
||||
raise ValueError("%s is not a BLAST XML file!" % f)
|
||||
while True:
|
||||
line = h.readline()
|
||||
header.append(line)
|
||||
if not line:
|
||||
out.write(header) #for diagnosis
|
||||
out.close()
|
||||
h.close()
|
||||
raise ValueError("BLAST XML file %s ended prematurely" % f)
|
||||
header += line
|
||||
if "<Iteration>" in line:
|
||||
break
|
||||
header = "".join(header)
|
||||
if len(header) > 10000:
|
||||
#Something has gone wrong, don't load too much into memory!
|
||||
#Write what we have to the merged file for diagnostics
|
||||
out.write(header)
|
||||
out.close()
|
||||
h.close()
|
||||
raise ValueError("BLAST XML file %s has too long a header!" % f)
|
||||
if "<BlastOutput>" not in header:
|
||||
out.close()
|
||||
h.close()
|
||||
|
||||
+34
-447
@@ -1,4 +1,15 @@
|
||||
import logging, threading, sys, os, time, traceback, shutil
|
||||
"""
|
||||
Support for running a tool in Galaxy via an internal job management system
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pwd
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import traceback
|
||||
import subprocess
|
||||
|
||||
import galaxy
|
||||
from galaxy import util, model
|
||||
@@ -9,51 +20,16 @@ from galaxy.datatypes import metadata
|
||||
from galaxy.util.json import from_json_string
|
||||
from galaxy.util.expressions import ExpressionContext
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
import subprocess, pwd
|
||||
from galaxy.exceptions import ObjectInvalid
|
||||
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
|
||||
import pkg_resources
|
||||
pkg_resources.require( "PasteDeploy" )
|
||||
|
||||
from Queue import Queue, Empty
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
# States for running a job. These are NOT the same as data states
|
||||
JOB_WAIT, JOB_ERROR, JOB_INPUT_ERROR, JOB_INPUT_DELETED, JOB_READY, JOB_DELETED, JOB_ADMIN_DELETED = 'wait', 'error', 'input_error', 'input_deleted', 'ready', 'deleted', 'admin_deleted'
|
||||
|
||||
# This file, if created in the job's working directory, will be used for
|
||||
# setting advanced metadata properties on the job and its associated outputs.
|
||||
# This interface is currently experimental, is only used by the upload tool,
|
||||
# and should eventually become API'd
|
||||
TOOL_PROVIDED_JOB_METADATA_FILE = 'galaxy.json'
|
||||
|
||||
class JobManager( object ):
|
||||
"""
|
||||
Highest level interface to job management.
|
||||
|
||||
TODO: Currently the app accesses "job_queue" and "job_stop_queue" directly.
|
||||
This should be decoupled.
|
||||
"""
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
if self.app.config.get_bool( "enable_job_running", True ):
|
||||
# The dispatcher launches the underlying job runners
|
||||
self.dispatcher = DefaultJobDispatcher( app )
|
||||
# Queues for starting and stopping jobs
|
||||
self.job_queue = JobQueue( app, self.dispatcher )
|
||||
self.job_stop_queue = JobStopQueue( app, self.dispatcher )
|
||||
if self.app.config.enable_beta_job_managers:
|
||||
from galaxy.jobs.deferred import DeferredJobQueue
|
||||
self.deferred_job_queue = DeferredJobQueue( app )
|
||||
else:
|
||||
self.job_queue = self.job_stop_queue = NoopQueue()
|
||||
def shutdown( self ):
|
||||
self.job_queue.shutdown()
|
||||
self.job_stop_queue.shutdown()
|
||||
|
||||
class Sleeper( object ):
|
||||
"""
|
||||
Provides a 'sleep' method that sleeps for a number of seconds *unless*
|
||||
@@ -70,238 +46,6 @@ class Sleeper( object ):
|
||||
self.condition.notify()
|
||||
self.condition.release()
|
||||
|
||||
class JobQueue( object ):
|
||||
"""
|
||||
Job manager, waits for jobs to be runnable and then dispatches to
|
||||
a JobRunner.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, dispatcher ):
|
||||
"""Start the job manager"""
|
||||
self.app = app
|
||||
self.sa_session = app.model.context
|
||||
self.job_lock = False
|
||||
# Should we read jobs form the database, or use an in memory queue
|
||||
self.track_jobs_in_database = app.config.get_bool( 'track_jobs_in_database', False )
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
# Contains jobs that are waiting (only use from monitor thread)
|
||||
## This and jobs_to_check[] are closest to a "Job Queue"
|
||||
self.waiting_jobs = []
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.dispatcher = dispatcher
|
||||
self.monitor_thread = threading.Thread( target=self.__monitor )
|
||||
# Recover jobs at startup
|
||||
if app.config.get_bool( 'enable_job_recovery', True ):
|
||||
self.__check_jobs_at_startup()
|
||||
# Start the queue
|
||||
self.monitor_thread.start()
|
||||
log.info( "job manager started" )
|
||||
|
||||
def __check_jobs_at_startup( self ):
|
||||
"""
|
||||
Checks all jobs that are in the 'new', 'queued' or 'running' state in
|
||||
the database and requeues or cleans up as necessary. Only run as the
|
||||
job manager starts.
|
||||
"""
|
||||
model = self.app.model # DBTODO Why?
|
||||
for job in self.sa_session.query( model.Job ).filter( model.Job.state == model.Job.states.NEW ):
|
||||
if job.tool_id not in self.app.toolbox.tools_by_id:
|
||||
log.warning( "Tool '%s' removed from tool config, unable to recover job: %s" % ( job.tool_id, job.id ) )
|
||||
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator, or' )
|
||||
else:
|
||||
log.debug( "no runner: %s is still in new state, adding to the jobs queue" %job.id )
|
||||
self.queue.put( ( job.id, job.tool_id ) )
|
||||
for job in self.sa_session.query( model.Job ).enable_eagerloads( False ).filter( ( model.Job.state == model.Job.states.RUNNING ) | ( model.Job.state == model.Job.states.QUEUED ) ):
|
||||
if job.tool_id not in self.app.toolbox.tools_by_id:
|
||||
log.warning( "Tool '%s' removed from tool config, unable to recover job: %s" % ( job.tool_id, job.id ) )
|
||||
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator, or' )
|
||||
elif job.job_runner_name is None:
|
||||
log.debug( "no runner: %s is still in queued state, adding to the jobs queue" %job.id )
|
||||
if self.track_jobs_in_database:
|
||||
job.state = model.Job.states.NEW
|
||||
else:
|
||||
self.queue.put( ( job.id, job.tool_id ) )
|
||||
else:
|
||||
job_wrapper = JobWrapper( job, self )
|
||||
self.dispatcher.recover( job, job_wrapper )
|
||||
if self.sa_session.dirty:
|
||||
self.sa_session.flush()
|
||||
|
||||
def __monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs, checking is each is ready to
|
||||
run and dispatching if so.
|
||||
"""
|
||||
# HACK: Delay until after forking, we need a way to do post fork notification!!!
|
||||
time.sleep( 10 )
|
||||
while self.running:
|
||||
try:
|
||||
self.__monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def __monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to process waiting jobs. Gets any new
|
||||
jobs (either from the database or from its own queue), then iterates
|
||||
over all new and waiting jobs to check the state of the jobs each
|
||||
depends on. If the job has dependencies that have not finished, it
|
||||
it goes to the waiting queue. If the job has dependencies with errors,
|
||||
it is marked as having errors and removed from the queue. Otherwise,
|
||||
the job is dispatched.
|
||||
"""
|
||||
# Pull all new jobs from the queue at once
|
||||
jobs_to_check = []
|
||||
if self.track_jobs_in_database:
|
||||
# Clear the session so we get fresh states for job and all datasets
|
||||
self.sa_session.expunge_all()
|
||||
# Fetch all new jobs
|
||||
jobs_to_check = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( model.Job.state == model.Job.states.NEW ).all()
|
||||
else:
|
||||
# Get job objects and append to watch queue for any which were
|
||||
# previously waiting
|
||||
for job_id in self.waiting_jobs:
|
||||
jobs_to_check.append( self.sa_session.query( model.Job ).get( job_id ) )
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, tool_id = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( self.sa_session.query( model.Job ).get( job_id ) )
|
||||
except Empty:
|
||||
pass
|
||||
# Iterate over new and waiting jobs and look for any that are
|
||||
# ready to run
|
||||
new_waiting_jobs = []
|
||||
for job in jobs_to_check:
|
||||
try:
|
||||
# Check the job's dependencies, requeue if they're not done
|
||||
job_state = self.__check_if_ready_to_run( job )
|
||||
if job_state == JOB_WAIT:
|
||||
if not self.track_jobs_in_database:
|
||||
new_waiting_jobs.append( job.id )
|
||||
elif job_state == JOB_INPUT_ERROR:
|
||||
log.info( "job %d unable to run: one or more inputs in error state" % job.id )
|
||||
elif job_state == JOB_INPUT_DELETED:
|
||||
log.info( "job %d unable to run: one or more inputs deleted" % job.id )
|
||||
elif job_state == JOB_READY:
|
||||
if self.job_lock:
|
||||
log.info( "Job dispatch attempted for %s, but prevented by administrative lock." % job.id )
|
||||
if not self.track_jobs_in_database:
|
||||
new_waiting_jobs.append( job.id )
|
||||
else:
|
||||
self.dispatcher.put( JobWrapper( job, self ) )
|
||||
log.info( "job %d dispatched" % job.id )
|
||||
elif job_state == JOB_DELETED:
|
||||
log.info( "job %d deleted by user while still queued" % job.id )
|
||||
elif job_state == JOB_ADMIN_DELETED:
|
||||
log.info( "job %d deleted by admin while still queued" % job.id )
|
||||
else:
|
||||
log.error( "unknown job state '%s' for job %d" % ( job_state, job.id ) )
|
||||
if not self.track_jobs_in_database:
|
||||
new_waiting_jobs.append( job.id )
|
||||
except Exception:
|
||||
log.exception( "failure running job %d" % job.id )
|
||||
# Update the waiting list
|
||||
self.waiting_jobs = new_waiting_jobs
|
||||
# Done with the session
|
||||
self.sa_session.remove()
|
||||
|
||||
def __check_if_ready_to_run( self, job ):
|
||||
"""
|
||||
Check if a job is ready to run by verifying that each of its input
|
||||
datasets is ready (specifically in the OK state). If any input dataset
|
||||
has an error, fail the job and return JOB_INPUT_ERROR. If any input
|
||||
dataset is deleted, fail the job and return JOB_INPUT_DELETED. If all
|
||||
input datasets are in OK state, return JOB_READY indicating that the
|
||||
job can be dispatched. Otherwise, return JOB_WAIT indicating that input
|
||||
datasets are still being prepared.
|
||||
"""
|
||||
if job.state == model.Job.states.DELETED:
|
||||
return JOB_DELETED
|
||||
elif job.state == model.Job.states.ERROR:
|
||||
return JOB_ADMIN_DELETED
|
||||
elif self.app.config.enable_quotas:
|
||||
quota = self.app.quota_agent.get_quota( job.user )
|
||||
if quota is not None:
|
||||
try:
|
||||
usage = self.app.quota_agent.get_usage( user=job.user, history=job.history )
|
||||
if usage > quota:
|
||||
return JOB_WAIT
|
||||
except AssertionError, e:
|
||||
pass # No history, should not happen with an anon user
|
||||
for dataset_assoc in job.input_datasets + job.input_library_datasets:
|
||||
idata = dataset_assoc.dataset
|
||||
if not idata:
|
||||
continue
|
||||
# don't run jobs for which the input dataset was deleted
|
||||
if idata.deleted:
|
||||
JobWrapper( job, self ).fail( "input data %d (file: %s) was deleted before the job started" % ( idata.hid, idata.file_name ) )
|
||||
return JOB_INPUT_DELETED
|
||||
# an error in the input data causes us to bail immediately
|
||||
elif idata.state == idata.states.ERROR:
|
||||
JobWrapper( job, self ).fail( "input data %d is in error state" % ( idata.hid ) )
|
||||
return JOB_INPUT_ERROR
|
||||
elif idata.state == idata.states.FAILED_METADATA:
|
||||
JobWrapper( job, self ).fail( "input data %d failed to properly set metadata" % ( idata.hid ) )
|
||||
return JOB_INPUT_ERROR
|
||||
elif idata.state != idata.states.OK and not ( idata.state == idata.states.SETTING_METADATA and job.tool_id is not None and job.tool_id == self.app.datatypes_registry.set_external_metadata_tool.id ):
|
||||
# need to requeue
|
||||
return JOB_WAIT
|
||||
return self.__check_user_jobs( job )
|
||||
|
||||
def __check_user_jobs( self, job ):
|
||||
if not self.app.config.user_job_limit:
|
||||
return JOB_READY
|
||||
if job.user:
|
||||
count = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( and_( model.Job.user_id == job.user.id,
|
||||
or_( model.Job.state == model.Job.states.RUNNING,
|
||||
model.Job.state == model.Job.states.QUEUED ) ) ).count()
|
||||
elif job.galaxy_session:
|
||||
count = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( and_( model.Job.session_id == job.galaxy_session.id,
|
||||
or_( model.Job.state == model.Job.states.RUNNING,
|
||||
model.Job.state == model.Job.states.QUEUED ) ) ).count()
|
||||
else:
|
||||
log.warning( 'Job %s is not associated with a user or session so job concurrency limit cannot be checked.' % job.id )
|
||||
return JOB_READY
|
||||
if count >= self.app.config.user_job_limit:
|
||||
return JOB_WAIT
|
||||
return JOB_READY
|
||||
|
||||
def put( self, job_id, tool ):
|
||||
"""Add a job to the queue (by job identifier)"""
|
||||
if not self.track_jobs_in_database:
|
||||
self.queue.put( ( job_id, tool.id ) )
|
||||
self.sleeper.wake()
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job queue stopped" )
|
||||
self.dispatcher.shutdown()
|
||||
|
||||
class JobWrapper( object ):
|
||||
"""
|
||||
Wraps a 'model.Job' with convenience methods for running processes and
|
||||
@@ -400,9 +144,12 @@ class JobWrapper( object ):
|
||||
self.file_name = dataset.file_name
|
||||
self.metadata = dict()
|
||||
self.children = []
|
||||
jeha = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
|
||||
if jeha:
|
||||
out_data[ "output_file" ] = FakeDatasetAssociation( dataset=jeha.dataset )
|
||||
special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
|
||||
if not special:
|
||||
special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
|
||||
if special:
|
||||
out_data[ "output_file" ] = FakeDatasetAssociation( dataset=special.dataset )
|
||||
|
||||
# These can be passed on the command line if wanted as $userId $userEmail
|
||||
if job.history and job.history.user: # check for anonymous user!
|
||||
userId = '%d' % job.history.user.id
|
||||
@@ -698,8 +445,11 @@ class JobWrapper( object ):
|
||||
param_dict = self.tool.params_from_strings( param_dict, self.app )
|
||||
# Check for and move associated_files
|
||||
self.tool.collect_associated_files(out_data, self.working_directory)
|
||||
gitd = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
|
||||
if gitd:
|
||||
self.tool.collect_associated_files({'' : gitd}, self.working_directory)
|
||||
# Create generated output children and primary datasets and add to param_dict
|
||||
collected_datasets = {'children':self.tool.collect_child_datasets(out_data),'primary':self.tool.collect_primary_datasets(out_data)}
|
||||
collected_datasets = {'children':self.tool.collect_child_datasets(out_data, self.working_directory),'primary':self.tool.collect_primary_datasets(out_data, self.working_directory)}
|
||||
param_dict.update({'__collected_datasets__':collected_datasets})
|
||||
# Certain tools require tasks to be completed after job execution
|
||||
# ( this used to be performed in the "exec_after_process" hook, but hooks are deprecated ).
|
||||
@@ -736,6 +486,7 @@ class JobWrapper( object ):
|
||||
self.external_output_metadata.cleanup_external_metadata( self.sa_session )
|
||||
galaxy.tools.imp_exp.JobExportHistoryArchiveWrapper( self.job_id ).cleanup_after_job( self.sa_session )
|
||||
galaxy.tools.imp_exp.JobImportHistoryArchiveWrapper( self.job_id ).cleanup_after_job( self.sa_session )
|
||||
galaxy.tools.genome_index.GenomeIndexToolWrapper( self.job_id ).postprocessing( self.sa_session, self.app )
|
||||
self.app.object_store.delete(self.get_job(), base_dir='job_work', entire_dir=True, dir_only=True, extra_dir=str(self.job_id))
|
||||
except:
|
||||
log.exception( "Unable to cleanup job %d" % self.job_id )
|
||||
@@ -791,9 +542,11 @@ class JobWrapper( object ):
|
||||
else:
|
||||
return self.false_path
|
||||
job = self.get_job()
|
||||
# Job output datasets are combination of output datasets, library datasets, and jeha datasets.
|
||||
jeha = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
|
||||
jeha_false_path = None
|
||||
# Job output datasets are combination of history, library, jeha and gitd datasets.
|
||||
special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
|
||||
if not special:
|
||||
special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
|
||||
false_path = None
|
||||
if self.app.config.outputs_to_working_directory:
|
||||
self.output_paths = []
|
||||
self.output_hdas_and_paths = {}
|
||||
@@ -802,14 +555,14 @@ class JobWrapper( object ):
|
||||
dsp = DatasetPath( hda.dataset.id, hda.dataset.file_name, false_path )
|
||||
self.output_paths.append( dsp )
|
||||
self.output_hdas_and_paths[name] = hda, dsp
|
||||
if jeha:
|
||||
jeha_false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % jeha.dataset.id ) )
|
||||
if special:
|
||||
false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % special.dataset.id ) )
|
||||
else:
|
||||
results = [ ( da.name, da.dataset, DatasetPath( da.dataset.dataset.id, da.dataset.file_name ) ) for da in job.output_datasets + job.output_library_datasets ]
|
||||
self.output_paths = [t[2] for t in results]
|
||||
self.output_hdas_and_paths = dict([(t[0], t[1:]) for t in results])
|
||||
if jeha:
|
||||
dsp = DatasetPath( jeha.dataset.id, jeha.dataset.file_name, jeha_false_path )
|
||||
if special:
|
||||
dsp = DatasetPath( special.dataset.id, special.dataset.file_name, false_path )
|
||||
self.output_paths.append( dsp )
|
||||
return self.output_paths
|
||||
|
||||
@@ -1177,179 +930,13 @@ class TaskWrapper(JobWrapper):
|
||||
# There is no metadata setting for tasks. This is handled after the merge, at the job level.
|
||||
return ""
|
||||
|
||||
class DefaultJobDispatcher( object ):
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.job_runners = {}
|
||||
start_job_runners = ["local"]
|
||||
if app.config.start_job_runners is not None:
|
||||
start_job_runners.extend( app.config.start_job_runners.split(",") )
|
||||
if app.config.use_tasked_jobs:
|
||||
start_job_runners.append("tasks")
|
||||
for name in start_job_runners:
|
||||
self._load_plugin( name )
|
||||
|
||||
def _load_plugin( self, name ):
|
||||
module_name = 'galaxy.jobs.runners.' + name
|
||||
try:
|
||||
module = __import__( module_name )
|
||||
except:
|
||||
log.exception( 'Job runner is not loadable: %s' % module_name )
|
||||
return
|
||||
for comp in module_name.split( "." )[1:]:
|
||||
module = getattr( module, comp )
|
||||
if '__all__' not in dir( module ):
|
||||
log.error( 'Runner "%s" does not contain a list of exported classes in __all__' % module_name )
|
||||
return
|
||||
for obj in module.__all__:
|
||||
display_name = ':'.join( ( module_name, obj ) )
|
||||
runner = getattr( module, obj )
|
||||
self.job_runners[name] = runner( self.app )
|
||||
log.debug( 'Loaded job runner: %s' % display_name )
|
||||
|
||||
def __get_runner_name( self, job_wrapper ):
|
||||
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and not isinstance(job_wrapper, TaskWrapper):
|
||||
runner_name = "tasks"
|
||||
else:
|
||||
runner_name = ( job_wrapper.get_job_runner().split(":", 1) )[0]
|
||||
return runner_name
|
||||
|
||||
def put( self, job_wrapper ):
|
||||
try:
|
||||
runner_name = self.__get_runner_name( job_wrapper )
|
||||
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and isinstance(job_wrapper, TaskWrapper):
|
||||
#DBTODO Refactor
|
||||
log.debug( "dispatching task %s, of job %d, to %s runner" %( job_wrapper.task_id, job_wrapper.job_id, runner_name ) )
|
||||
else:
|
||||
log.debug( "dispatching job %d to %s runner" %( job_wrapper.job_id, runner_name ) )
|
||||
self.job_runners[runner_name].put( job_wrapper )
|
||||
except KeyError:
|
||||
log.error( 'put(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
|
||||
|
||||
def stop( self, job ):
|
||||
runner_name = ( job.job_runner_name.split(":", 1) )[0]
|
||||
log.debug( "stopping job %d in %s runner" %( job.id, runner_name ) )
|
||||
try:
|
||||
self.job_runners[runner_name].stop_job( job )
|
||||
except KeyError:
|
||||
log.error( 'stop(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
# Job and output dataset states have already been updated, so nothing is done here.
|
||||
|
||||
def recover( self, job, job_wrapper ):
|
||||
runner_name = ( job.job_runner_name.split(":", 1) )[0]
|
||||
log.debug( "recovering job %d in %s runner" %( job.id, runner_name ) )
|
||||
try:
|
||||
self.job_runners[runner_name].recover( job, job_wrapper )
|
||||
except KeyError:
|
||||
log.error( 'recover(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
|
||||
|
||||
def shutdown( self ):
|
||||
for runner in self.job_runners.itervalues():
|
||||
runner.shutdown()
|
||||
|
||||
class JobStopQueue( object ):
|
||||
"""
|
||||
A queue for jobs which need to be terminated prematurely.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, dispatcher ):
|
||||
self.app = app
|
||||
self.sa_session = app.model.context
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.track_jobs_in_database = app.config.get_bool( 'track_jobs_in_database', False )
|
||||
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
|
||||
# Contains jobs that are waiting (only use from monitor thread)
|
||||
self.waiting = []
|
||||
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.monitor_thread = threading.Thread( target=self.monitor )
|
||||
self.monitor_thread.start()
|
||||
log.info( "job stopper started" )
|
||||
|
||||
def monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs, stop any that are found.
|
||||
"""
|
||||
# HACK: Delay until after forking, we need a way to do post fork notification!!!
|
||||
time.sleep( 10 )
|
||||
while self.running:
|
||||
try:
|
||||
self.monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to stop jobs.
|
||||
"""
|
||||
# Pull all new jobs from the queue at once
|
||||
jobs_to_check = []
|
||||
if self.track_jobs_in_database:
|
||||
# Clear the session so we get fresh states for job and all datasets
|
||||
self.sa_session.expunge_all()
|
||||
# Fetch all new jobs
|
||||
newly_deleted_jobs = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( model.Job.state == model.Job.states.DELETED_NEW ).all()
|
||||
for job in newly_deleted_jobs:
|
||||
jobs_to_check.append( ( job, None ) )
|
||||
# Also pull from the queue (in the case of Administrative stopped jobs)
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, error_msg = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( ( self.sa_session.query( model.Job ).get( job_id ), error_msg ) )
|
||||
except Empty:
|
||||
pass
|
||||
for job, error_msg in jobs_to_check:
|
||||
if error_msg is not None:
|
||||
job.state = job.states.ERROR
|
||||
job.info = error_msg
|
||||
else:
|
||||
job.state = job.states.DELETED
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
if job.job_runner_name is not None:
|
||||
# tell the dispatcher to stop the job
|
||||
self.dispatcher.stop( job )
|
||||
|
||||
def put( self, job_id, error_msg=None ):
|
||||
self.queue.put( ( job_id, error_msg ) )
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job stopper stopped" )
|
||||
|
||||
class NoopQueue( object ):
|
||||
"""
|
||||
Implements the JobQueue / JobStopQueue interface but does nothing
|
||||
"""
|
||||
def put( self, *args ):
|
||||
return
|
||||
def put_stop( self, *args ):
|
||||
return
|
||||
def shutdown( self ):
|
||||
return
|
||||
|
||||
@@ -203,11 +203,10 @@ class RenameDatasetAction(DefaultJobAction):
|
||||
@classmethod
|
||||
def get_config_form(cls, trans):
|
||||
form = """
|
||||
if ((pja.action_arguments !== undefined) && (pja.action_arguments.newname !== undefined)){
|
||||
if (pja.action_arguments && pja.action_arguments.newname){
|
||||
p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\
|
||||
<input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=\\"" + pja.action_arguments.newname.replace(/"/g, """) + "\\"/>";
|
||||
}
|
||||
|
||||
else{
|
||||
p_str += "<label for='pja__"+pja.output_name+"__RenameDatasetAction__newname'>New output name:</label>\
|
||||
<input type='text' name='pja__"+pja.output_name+"__RenameDatasetAction__newname' value=''/>";
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Module for managing genome transfer jobs.
|
||||
"""
|
||||
import logging, shutil, gzip, bz2, zipfile, tempfile, tarfile, sys
|
||||
|
||||
from galaxy import eggs
|
||||
from sqlalchemy import and_
|
||||
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.workflow.modules import module_factory
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
|
||||
from galaxy.tools.parameters import visit_input_values
|
||||
from galaxy.tools.parameters.basic import DataToolParameter
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
from galaxy.datatypes.checkers import *
|
||||
from galaxy.datatypes.sequence import Fasta
|
||||
from data_transfer import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
__all__ = [ 'GenomeTransferPlugin' ]
|
||||
|
||||
class GenomeTransferPlugin( DataTransfer ):
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__( self, app ):
|
||||
super( GenomeTransferPlugin, self ).__init__( app )
|
||||
self.app = app
|
||||
self.tool = app.toolbox.tools_by_id['__GENOME_INDEX__']
|
||||
self.sa_session = app.model.context.current
|
||||
tdtman = ToolDataTableManager()
|
||||
xmltree = tdtman.load_from_config_file(app.config.tool_data_table_config_path)
|
||||
for node in xmltree:
|
||||
table = node.get('name')
|
||||
location = node.findall('file')[0].get('path')
|
||||
self.locations[table] = location
|
||||
|
||||
def create_job( self, trans, url, dbkey, intname, indexes ):
|
||||
job = trans.app.transfer_manager.new( protocol='http', url=url )
|
||||
params = dict( user=trans.user.id, transfer_job_id=job.id, protocol='http', type='init_transfer', url=url, dbkey=dbkey, indexes=indexes, intname=intname, liftover=None )
|
||||
deferred = trans.app.model.DeferredJob( state = self.app.model.DeferredJob.states.NEW, plugin = 'GenomeTransferPlugin', params = params )
|
||||
self.sa_session.add( deferred )
|
||||
self.sa_session.flush()
|
||||
return deferred.id
|
||||
|
||||
def check_job( self, job ):
|
||||
if job.params['type'] == 'init_transfer':
|
||||
if not hasattr(job, 'transfer_job'):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
if job.transfer_job.state == 'done':
|
||||
transfer = job.transfer_job
|
||||
transfer.state = 'downloaded'
|
||||
job.params['type'] = 'extract_transfer'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.READY
|
||||
elif job.transfer_job.state == 'running':
|
||||
return self.job_states.WAIT
|
||||
elif job.transfer_job.state == 'new':
|
||||
assert job.params[ 'protocol' ] in [ 'http', 'ftp', 'https' ], 'Unknown protocol %s' % job.params[ 'protocol' ]
|
||||
self.app.transfer_manager.run( job.transfer_job )
|
||||
self.sa_session.add( job.transfer_job )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.WAIT
|
||||
else:
|
||||
log.error( "An error occurred while downloading from %s" % job.params[ 'url' ] )
|
||||
return self.job_states.INVALID
|
||||
elif job.params[ 'type' ] == 'extract_transfer':
|
||||
return self.job_states.READY
|
||||
|
||||
def get_job_status( self, jobid ):
|
||||
job = self.sa_session.query( self.app.model.DeferredJob ).get( int( jobid ) )
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
return job
|
||||
|
||||
def run_job( self, job ):
|
||||
params = job.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
transfer = job.transfer_job
|
||||
if params[ 'type' ] == 'extract_transfer':
|
||||
CHUNK_SIZE = 2**20
|
||||
destpath = os.path.join( self.app.config.get( 'genome_data_path', 'tool-data/genome' ), job.params[ 'dbkey' ], 'seq' )
|
||||
destfile = '%s.fa' % job.params[ 'dbkey' ]
|
||||
destfilepath = os.path.join( destpath, destfile )
|
||||
tmpprefix = '%s_%s_download_unzip_' % ( job.params['dbkey'], job.params[ 'transfer_job_id' ] )
|
||||
tmppath = os.path.dirname( os.path.abspath( transfer.path ) )
|
||||
if not os.path.exists( destpath ):
|
||||
os.makedirs( destpath )
|
||||
protocol = job.params[ 'protocol' ]
|
||||
data_type = self._check_compress( transfer.path )
|
||||
if data_type is None:
|
||||
sniffer = Fasta()
|
||||
if sniffer.sniff( transfer.path ):
|
||||
data_type = 'fasta'
|
||||
fd, uncompressed = tempfile.mkstemp( prefix=tmpprefix, dir=tmppath, text=False )
|
||||
if data_type in [ 'tar.gzip', 'tar.bzip' ]:
|
||||
fp = open( transfer.path, 'r' )
|
||||
tar = tarfile.open( mode = 'r:*', bufsize = CHUNK_SIZE, fileobj = fp )
|
||||
files = tar.getmembers()
|
||||
for filename in files:
|
||||
z = tar.extractfile(filename)
|
||||
try:
|
||||
chunk = z.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.write( fd, '\n' )
|
||||
os.close( fd )
|
||||
tar.close()
|
||||
fp.close()
|
||||
elif data_type == 'gzip':
|
||||
compressed = gzip.open( transfer.path, mode = 'rb' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = compressed.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
compressed.close()
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.write( fd, '\n' )
|
||||
os.close( fd )
|
||||
compressed.close()
|
||||
elif data_type == 'bzip':
|
||||
compressed = bz2.BZ2File( transfer.path, mode = 'r' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = compressed.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
compressed.close()
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.write( fd, '\n' )
|
||||
os.close( fd )
|
||||
compressed.close()
|
||||
elif data_type == 'zip':
|
||||
uncompressed_name = None
|
||||
unzipped = False
|
||||
z = zipfile.ZipFile( transfer.path )
|
||||
z.debug = 3
|
||||
for name in z.namelist():
|
||||
if name.endswith('/'):
|
||||
continue
|
||||
if sys.version_info[:2] >= ( 2, 6 ):
|
||||
zipped_file = z.open( name )
|
||||
while 1:
|
||||
try:
|
||||
chunk = zipped_file.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing zipped data' )
|
||||
return self.app.model.DeferredJob.states.INVALID
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.write( fd, '\n' )
|
||||
zipped_file.close()
|
||||
else:
|
||||
try:
|
||||
outfile = open( fd, 'wb' )
|
||||
outfile.write( z.read( name ) )
|
||||
outfile.close()
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing zipped data' )
|
||||
return
|
||||
os.close( fd )
|
||||
z.close()
|
||||
elif data_type == 'fasta':
|
||||
uncompressed = transfer.path
|
||||
else:
|
||||
job.state = self.app.model.DeferredJob.states.INVALID
|
||||
log.error( "Unrecognized compression format for file %s." % transfer.path )
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
return
|
||||
shutil.move( uncompressed, destfilepath )
|
||||
if os.path.exists( transfer.path ):
|
||||
os.remove( transfer.path )
|
||||
os.chmod( destfilepath, 0644 )
|
||||
fastaline = '\t'.join( [ dbkey, dbkey, params[ 'intname' ], os.path.abspath( destfilepath ) ] )
|
||||
self._add_line( 'all_fasta', fastaline )
|
||||
job.state = self.app.model.DeferredJob.states.OK
|
||||
job.params[ 'type' ] = 'finish_transfer'
|
||||
transfer.path = os.path.abspath(destfilepath)
|
||||
transfer.state = 'done'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
if transfer.state == 'done' and params[ 'indexes' ] is not None:
|
||||
for indexer in params[ 'indexes' ]:
|
||||
incoming = dict(indexer=indexer, dbkey=params[ 'dbkey' ], intname=params[ 'intname' ], path=transfer.path, user=params['user'] )
|
||||
deferred = self.tool.execute( self, set_output_hid=False, history=None, incoming=incoming, transfer=transfer, deferred=job )
|
||||
return self.app.model.DeferredJob.states.OK
|
||||
|
||||
def _check_compress( self, filepath ):
|
||||
retval = ''
|
||||
if tarfile.is_tarfile( filepath ):
|
||||
retval = 'tar.'
|
||||
if check_zip( filepath ):
|
||||
return 'zip'
|
||||
is_bzipped, is_valid = check_bz2( filepath )
|
||||
if is_bzipped and is_valid:
|
||||
return retval + 'bzip'
|
||||
is_gzipped, is_valid = check_gzip( filepath )
|
||||
if is_gzipped and is_valid:
|
||||
return retval + 'gzip'
|
||||
return None
|
||||
|
||||
def _add_line( self, locfile, newline ):
|
||||
filepath = self.locations[ locfile ]
|
||||
origlines = []
|
||||
output = []
|
||||
comments = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
if line.startswith( '#' ):
|
||||
comments.append( line.strip() )
|
||||
else:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
output.extend( comments )
|
||||
origlines.sort()
|
||||
output.extend( origlines )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
destfile.write( '\n'.join( output ) )
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Module for managing genome transfer jobs.
|
||||
"""
|
||||
import logging, shutil, gzip, tempfile, sys
|
||||
|
||||
from galaxy import eggs
|
||||
from sqlalchemy import and_
|
||||
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.workflow.modules import module_factory
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
|
||||
from galaxy.tools.parameters import visit_input_values
|
||||
from galaxy.tools.parameters.basic import DataToolParameter
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
from galaxy.datatypes.checkers import *
|
||||
|
||||
from data_transfer import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
__all__ = [ 'LiftOverTransferPlugin' ]
|
||||
|
||||
class LiftOverTransferPlugin( DataTransfer ):
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__( self, app ):
|
||||
super( LiftOverTransferPlugin, self ).__init__( app )
|
||||
self.app = app
|
||||
self.sa_session = app.model.context.current
|
||||
|
||||
def create_job( self, trans, url, dbkey, from_genome, to_genome, destfile ):
|
||||
job = trans.app.transfer_manager.new( protocol='http', url=url )
|
||||
params = dict( user=trans.user.id, transfer_job_id=job.id, protocol='http',
|
||||
type='init_transfer', dbkey=dbkey, from_genome=from_genome,
|
||||
to_genome=to_genome, destfile=destfile )
|
||||
deferred = trans.app.model.DeferredJob( state = self.app.model.DeferredJob.states.NEW, plugin = 'LiftOverTransferPlugin', params = params )
|
||||
self.sa_session.add( deferred )
|
||||
self.sa_session.flush()
|
||||
return deferred.id
|
||||
|
||||
def check_job( self, job ):
|
||||
if job.params['type'] == 'init_transfer':
|
||||
if not hasattr(job, 'transfer_job'):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
if job.transfer_job.state == 'done':
|
||||
transfer = job.transfer_job
|
||||
transfer.state = 'downloaded'
|
||||
job.params['type'] = 'extract_transfer'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.READY
|
||||
elif job.transfer_job.state == 'running':
|
||||
return self.job_states.WAIT
|
||||
elif job.transfer_job.state == 'new':
|
||||
assert job.params[ 'protocol' ] in [ 'http', 'ftp', 'https' ], 'Unknown protocol %s' % job.params[ 'protocol' ]
|
||||
self.app.transfer_manager.run( job.transfer_job )
|
||||
self.sa_session.add( job.transfer_job )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.WAIT
|
||||
else:
|
||||
log.error( "An error occurred while downloading from %s" % job.params[ 'url' ] )
|
||||
return self.job_states.INVALID
|
||||
elif job.params[ 'type' ] == 'extract_transfer':
|
||||
return self.job_states.READY
|
||||
|
||||
def get_job_status( self, jobid ):
|
||||
job = self.sa_session.query( self.app.model.DeferredJob ).get( int( jobid ) )
|
||||
return job
|
||||
|
||||
def run_job( self, job ):
|
||||
params = job.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
source = params[ 'from_genome' ]
|
||||
target = params[ 'to_genome' ]
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
transfer = job.transfer_job
|
||||
if params[ 'type' ] == 'extract_transfer':
|
||||
CHUNK_SIZE = 2**20
|
||||
destpath = os.path.join( self.app.config.get( 'genome_data_path', 'tool-data/genome' ), job.params[ 'dbkey' ], 'liftOver' )
|
||||
destfile = job.params[ 'destfile' ]
|
||||
destfilepath = os.path.join( destpath, destfile )
|
||||
tmpprefix = '%s_%s_download_unzip_' % ( job.params['dbkey'], job.params[ 'transfer_job_id' ] )
|
||||
tmppath = os.path.dirname( os.path.abspath( transfer.path ) )
|
||||
if not os.path.exists( destpath ):
|
||||
os.makedirs( destpath )
|
||||
fd, uncompressed = tempfile.mkstemp( prefix=tmpprefix, dir=tmppath, text=False )
|
||||
chain = gzip.open( transfer.path, 'rb' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = chain.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.close( fd )
|
||||
chain.close()
|
||||
# Replace the gzipped file with the decompressed file if it's safe to do so
|
||||
shutil.move( uncompressed, destfilepath )
|
||||
os.remove( transfer.path )
|
||||
os.chmod( destfilepath, 0644 )
|
||||
locline = '\t'.join( [ source, target, os.path.abspath( destfilepath ) ] )
|
||||
self._add_line( locline )
|
||||
job.state = self.app.model.DeferredJob.states.OK
|
||||
job.params[ 'type' ] = 'finish_transfer'
|
||||
transfer.path = os.path.abspath(destfilepath)
|
||||
transfer.state = 'done'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.app.model.DeferredJob.states.OK
|
||||
|
||||
def _add_line( self, newline ):
|
||||
filepath = 'tool-data/liftOver.loc'
|
||||
origlines = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
destfile.write( '\n'.join( origlines ) )
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
"""
|
||||
Galaxy job handler, prepares, runs, tracks, and finishes Galaxy jobs
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
|
||||
from galaxy import util, model
|
||||
from galaxy.jobs import Sleeper, JobWrapper, TaskWrapper
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
# States for running a job. These are NOT the same as data states
|
||||
JOB_WAIT, JOB_ERROR, JOB_INPUT_ERROR, JOB_INPUT_DELETED, JOB_READY, JOB_DELETED, JOB_ADMIN_DELETED = 'wait', 'error', 'input_error', 'input_deleted', 'ready', 'deleted', 'admin_deleted'
|
||||
|
||||
class JobHandler( object ):
|
||||
"""
|
||||
Handle the preparation, running, tracking, and finishing of jobs
|
||||
"""
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
# The dispatcher launches the underlying job runners
|
||||
self.dispatcher = DefaultJobDispatcher( app )
|
||||
# Queues for starting and stopping jobs
|
||||
self.job_queue = JobHandlerQueue( app, self.dispatcher )
|
||||
self.job_stop_queue = JobHandlerStopQueue( app, self.dispatcher )
|
||||
def start( self ):
|
||||
self.job_queue.start()
|
||||
def shutdown( self ):
|
||||
self.job_queue.shutdown()
|
||||
self.job_stop_queue.shutdown()
|
||||
|
||||
class JobHandlerQueue( object ):
|
||||
"""
|
||||
Job manager, waits for jobs to be runnable and then dispatches to
|
||||
a JobRunner.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, dispatcher ):
|
||||
"""Start the job manager"""
|
||||
self.app = app
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.sa_session = app.model.context
|
||||
self.track_jobs_in_database = self.app.config.track_jobs_in_database
|
||||
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
# Contains jobs that are waiting (only use from monitor thread)
|
||||
## This and jobs_to_check[] are closest to a "Job Queue"
|
||||
self.waiting_jobs = []
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.monitor_thread = threading.Thread( target=self.__monitor )
|
||||
|
||||
def start( self ):
|
||||
"""
|
||||
The JobManager should start, and then start its Handler, if it has one.
|
||||
"""
|
||||
# Recover jobs at startup
|
||||
self.__check_jobs_at_startup()
|
||||
# Start the queue
|
||||
self.monitor_thread.start()
|
||||
log.info( "job handler queue started" )
|
||||
|
||||
def __check_jobs_at_startup( self ):
|
||||
"""
|
||||
Checks all jobs that are in the 'new', 'queued' or 'running' state in
|
||||
the database and requeues or cleans up as necessary. Only run as the
|
||||
job manager starts.
|
||||
"""
|
||||
for job in self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( ( ( model.Job.state == model.Job.states.NEW ) \
|
||||
| ( model.Job.state == model.Job.states.RUNNING ) \
|
||||
| ( model.Job.state == model.Job.states.QUEUED ) ) \
|
||||
& ( model.Job.handler == self.app.config.server_name ) ):
|
||||
if job.tool_id not in self.app.toolbox.tools_by_id:
|
||||
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
|
||||
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator.' )
|
||||
elif job.job_runner_name is None:
|
||||
log.debug( "(%s) No job runner assigned and job still in '%s' state, adding to the job handler queue" % ( job.id, job.state ) )
|
||||
if self.track_jobs_in_database:
|
||||
job.state = model.Job.states.NEW
|
||||
else:
|
||||
self.queue.put( ( job.id, job.tool_id ) )
|
||||
else:
|
||||
job_wrapper = JobWrapper( job, self )
|
||||
self.dispatcher.recover( job, job_wrapper )
|
||||
if self.sa_session.dirty:
|
||||
self.sa_session.flush()
|
||||
|
||||
def __monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs, checking is each is ready to
|
||||
run and dispatching if so.
|
||||
"""
|
||||
while self.running:
|
||||
try:
|
||||
self.__monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def __monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to process waiting jobs. Gets any new
|
||||
jobs (either from the database or from its own queue), then iterates
|
||||
over all new and waiting jobs to check the state of the jobs each
|
||||
depends on. If the job has dependencies that have not finished, it
|
||||
it goes to the waiting queue. If the job has dependencies with errors,
|
||||
it is marked as having errors and removed from the queue. Otherwise,
|
||||
the job is dispatched.
|
||||
"""
|
||||
# Pull all new jobs from the queue at once
|
||||
jobs_to_check = []
|
||||
if self.track_jobs_in_database:
|
||||
# Clear the session so we get fresh states for job and all datasets
|
||||
self.sa_session.expunge_all()
|
||||
# Fetch all new jobs
|
||||
jobs_to_check = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( ( model.Job.state == model.Job.states.NEW ) \
|
||||
& ( model.Job.handler == self.app.config.server_name ) ).all()
|
||||
else:
|
||||
# Get job objects and append to watch queue for any which were
|
||||
# previously waiting
|
||||
for job_id in self.waiting_jobs:
|
||||
jobs_to_check.append( self.sa_session.query( model.Job ).get( job_id ) )
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, tool_id = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( self.sa_session.query( model.Job ).get( job_id ) )
|
||||
except Empty:
|
||||
pass
|
||||
# Iterate over new and waiting jobs and look for any that are
|
||||
# ready to run
|
||||
new_waiting_jobs = []
|
||||
for job in jobs_to_check:
|
||||
try:
|
||||
# Check the job's dependencies, requeue if they're not done
|
||||
job_state = self.__check_if_ready_to_run( job )
|
||||
if job_state == JOB_WAIT:
|
||||
if not self.track_jobs_in_database:
|
||||
new_waiting_jobs.append( job.id )
|
||||
elif job_state == JOB_INPUT_ERROR:
|
||||
log.info( "(%d) Job unable to run: one or more inputs in error state" % job.id )
|
||||
elif job_state == JOB_INPUT_DELETED:
|
||||
log.info( "(%d) Job unable to run: one or more inputs deleted" % job.id )
|
||||
elif job_state == JOB_READY:
|
||||
self.dispatcher.put( JobWrapper( job, self ) )
|
||||
log.info( "(%d) Job dispatched" % job.id )
|
||||
elif job_state == JOB_DELETED:
|
||||
log.info( "(%d) Job deleted by user while still queued" % job.id )
|
||||
elif job_state == JOB_ADMIN_DELETED:
|
||||
log.info( "(%d) Job deleted by admin while still queued" % job.id )
|
||||
else:
|
||||
log.error( "(%d) Job in unknown state '%s'" % ( job.id, job_state ) )
|
||||
if not self.track_jobs_in_database:
|
||||
new_waiting_jobs.append( job.id )
|
||||
except Exception:
|
||||
log.exception( "failure running job %d" % job.id )
|
||||
# Update the waiting list
|
||||
self.waiting_jobs = new_waiting_jobs
|
||||
# Done with the session
|
||||
self.sa_session.remove()
|
||||
|
||||
def __check_if_ready_to_run( self, job ):
|
||||
"""
|
||||
Check if a job is ready to run by verifying that each of its input
|
||||
datasets is ready (specifically in the OK state). If any input dataset
|
||||
has an error, fail the job and return JOB_INPUT_ERROR. If any input
|
||||
dataset is deleted, fail the job and return JOB_INPUT_DELETED. If all
|
||||
input datasets are in OK state, return JOB_READY indicating that the
|
||||
job can be dispatched. Otherwise, return JOB_WAIT indicating that input
|
||||
datasets are still being prepared.
|
||||
"""
|
||||
if job.state == model.Job.states.DELETED:
|
||||
return JOB_DELETED
|
||||
elif job.state == model.Job.states.ERROR:
|
||||
return JOB_ADMIN_DELETED
|
||||
elif self.app.config.enable_quotas:
|
||||
quota = self.app.quota_agent.get_quota( job.user )
|
||||
if quota is not None:
|
||||
try:
|
||||
usage = self.app.quota_agent.get_usage( user=job.user, history=job.history )
|
||||
if usage > quota:
|
||||
return JOB_WAIT
|
||||
except AssertionError, e:
|
||||
pass # No history, should not happen with an anon user
|
||||
for dataset_assoc in job.input_datasets + job.input_library_datasets:
|
||||
idata = dataset_assoc.dataset
|
||||
if not idata:
|
||||
continue
|
||||
# don't run jobs for which the input dataset was deleted
|
||||
if idata.deleted:
|
||||
JobWrapper( job, self ).fail( "input data %s (file: %s) was deleted before the job started" % ( idata.hid, idata.file_name ) )
|
||||
return JOB_INPUT_DELETED
|
||||
# an error in the input data causes us to bail immediately
|
||||
elif idata.state == idata.states.ERROR:
|
||||
JobWrapper( job, self ).fail( "input data %s is in error state" % ( idata.hid ) )
|
||||
return JOB_INPUT_ERROR
|
||||
elif idata.state == idata.states.FAILED_METADATA:
|
||||
JobWrapper( job, self ).fail( "input data %s failed to properly set metadata" % ( idata.hid ) )
|
||||
return JOB_INPUT_ERROR
|
||||
elif idata.state != idata.states.OK and not ( idata.state == idata.states.SETTING_METADATA and job.tool_id is not None and job.tool_id == self.app.datatypes_registry.set_external_metadata_tool.id ):
|
||||
# need to requeue
|
||||
return JOB_WAIT
|
||||
return self.__check_user_jobs( job )
|
||||
|
||||
def __check_user_jobs( self, job ):
|
||||
if not self.app.config.user_job_limit:
|
||||
return JOB_READY
|
||||
if job.user:
|
||||
count = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( and_( model.Job.user_id == job.user.id,
|
||||
or_( model.Job.state == model.Job.states.RUNNING,
|
||||
model.Job.state == model.Job.states.QUEUED ) ) ).count()
|
||||
elif job.galaxy_session:
|
||||
count = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( and_( model.Job.session_id == job.galaxy_session.id,
|
||||
or_( model.Job.state == model.Job.states.RUNNING,
|
||||
model.Job.state == model.Job.states.QUEUED ) ) ).count()
|
||||
else:
|
||||
log.warning( 'Job %s is not associated with a user or session so job concurrency limit cannot be checked.' % job.id )
|
||||
return JOB_READY
|
||||
if count >= self.app.config.user_job_limit:
|
||||
return JOB_WAIT
|
||||
return JOB_READY
|
||||
|
||||
def put( self, job_id, tool_id ):
|
||||
"""Add a job to the queue (by job identifier)"""
|
||||
if not self.track_jobs_in_database:
|
||||
self.queue.put( ( job_id, tool_id ) )
|
||||
self.sleeper.wake()
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job handler queue stopped" )
|
||||
self.dispatcher.shutdown()
|
||||
|
||||
class JobHandlerStopQueue( object ):
|
||||
"""
|
||||
A queue for jobs which need to be terminated prematurely.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, dispatcher ):
|
||||
self.app = app
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.sa_session = app.model.context
|
||||
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
|
||||
# Contains jobs that are waiting (only use from monitor thread)
|
||||
self.waiting = []
|
||||
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.monitor_thread = threading.Thread( target=self.monitor )
|
||||
self.monitor_thread.start()
|
||||
log.info( "job handler stop queue started" )
|
||||
|
||||
def monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs, stop any that are found.
|
||||
"""
|
||||
# HACK: Delay until after forking, we need a way to do post fork notification!!!
|
||||
time.sleep( 10 )
|
||||
while self.running:
|
||||
try:
|
||||
self.monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to stop jobs.
|
||||
"""
|
||||
# Pull all new jobs from the queue at once
|
||||
jobs_to_check = []
|
||||
if self.app.config.track_jobs_in_database:
|
||||
# Clear the session so we get fresh states for job and all datasets
|
||||
self.sa_session.expunge_all()
|
||||
# Fetch all new jobs
|
||||
newly_deleted_jobs = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( ( model.Job.state == model.Job.states.DELETED_NEW ) \
|
||||
& ( model.Job.handler == self.app.config.server_name ) ).all()
|
||||
for job in newly_deleted_jobs:
|
||||
jobs_to_check.append( ( job, None ) )
|
||||
# Also pull from the queue (in the case of Administrative stopped jobs)
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, error_msg = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( ( self.sa_session.query( model.Job ).get( job_id ), error_msg ) )
|
||||
except Empty:
|
||||
pass
|
||||
for job, error_msg in jobs_to_check:
|
||||
if error_msg is not None:
|
||||
job.state = job.states.ERROR
|
||||
job.info = error_msg
|
||||
else:
|
||||
job.state = job.states.DELETED
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
if job.job_runner_name is not None:
|
||||
# tell the dispatcher to stop the job
|
||||
self.dispatcher.stop( job )
|
||||
|
||||
def put( self, job_id, error_msg=None ):
|
||||
self.queue.put( ( job_id, error_msg ) )
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job handler stop queue stopped" )
|
||||
|
||||
class DefaultJobDispatcher( object ):
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.job_runners = {}
|
||||
start_job_runners = ["local"]
|
||||
if app.config.start_job_runners is not None:
|
||||
start_job_runners.extend( [ x.strip() for x in util.listify( app.config.start_job_runners ) ] )
|
||||
if app.config.use_tasked_jobs:
|
||||
start_job_runners.append("tasks")
|
||||
for name in start_job_runners:
|
||||
self._load_plugin( name )
|
||||
|
||||
def _load_plugin( self, name ):
|
||||
module_name = 'galaxy.jobs.runners.' + name
|
||||
try:
|
||||
module = __import__( module_name )
|
||||
except:
|
||||
log.exception( 'Job runner is not loadable: %s' % module_name )
|
||||
return
|
||||
for comp in module_name.split( "." )[1:]:
|
||||
module = getattr( module, comp )
|
||||
if '__all__' not in dir( module ):
|
||||
log.error( 'Runner "%s" does not contain a list of exported classes in __all__' % module_name )
|
||||
return
|
||||
for obj in module.__all__:
|
||||
display_name = ':'.join( ( module_name, obj ) )
|
||||
runner = getattr( module, obj )
|
||||
self.job_runners[name] = runner( self.app )
|
||||
log.debug( 'Loaded job runner: %s' % display_name )
|
||||
|
||||
def __get_runner_name( self, job_wrapper ):
|
||||
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and not isinstance(job_wrapper, TaskWrapper):
|
||||
runner_name = "tasks"
|
||||
else:
|
||||
runner_name = ( job_wrapper.get_job_runner().split(":", 1) )[0]
|
||||
return runner_name
|
||||
|
||||
def put( self, job_wrapper ):
|
||||
try:
|
||||
runner_name = self.__get_runner_name( job_wrapper )
|
||||
if self.app.config.use_tasked_jobs and job_wrapper.tool.parallelism is not None and isinstance(job_wrapper, TaskWrapper):
|
||||
#DBTODO Refactor
|
||||
log.debug( "dispatching task %s, of job %d, to %s runner" %( job_wrapper.task_id, job_wrapper.job_id, runner_name ) )
|
||||
else:
|
||||
log.debug( "dispatching job %d to %s runner" %( job_wrapper.job_id, runner_name ) )
|
||||
self.job_runners[runner_name].put( job_wrapper )
|
||||
except KeyError:
|
||||
log.error( 'put(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
|
||||
|
||||
def stop( self, job ):
|
||||
runner_name = ( job.job_runner_name.split(":", 1) )[0]
|
||||
log.debug( "stopping job %d in %s runner" %( job.id, runner_name ) )
|
||||
try:
|
||||
self.job_runners[runner_name].stop_job( job )
|
||||
except KeyError:
|
||||
log.error( 'stop(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
# Job and output dataset states have already been updated, so nothing is done here.
|
||||
|
||||
def recover( self, job, job_wrapper ):
|
||||
runner_name = ( job.job_runner_name.split(":", 1) )[0]
|
||||
log.debug( "recovering job %d in %s runner" %( job.id, runner_name ) )
|
||||
try:
|
||||
self.job_runners[runner_name].recover( job, job_wrapper )
|
||||
except KeyError:
|
||||
log.error( 'recover(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
|
||||
job_wrapper.fail( 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.' )
|
||||
|
||||
def shutdown( self ):
|
||||
for runner in self.job_runners.itervalues():
|
||||
runner.shutdown()
|
||||
@@ -0,0 +1,276 @@
|
||||
"""
|
||||
Top-level Galaxy job manager, moves jobs to handler(s)
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import logging
|
||||
import threading
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.jobs import handler, Sleeper, NoopQueue
|
||||
from galaxy.util.json import from_json_string
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class JobManager( object ):
|
||||
"""
|
||||
Highest level interface to job management.
|
||||
|
||||
TODO: Currently the app accesses "job_queue" and "job_stop_queue" directly.
|
||||
This should be decoupled.
|
||||
"""
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.job_handler = NoopHandler()
|
||||
if self.app.config.server_name in self.app.config.job_handlers:
|
||||
self.job_handler = handler.JobHandler( app )
|
||||
if self.app.config.server_name == self.app.config.job_manager:
|
||||
job_handler = NoopHandler()
|
||||
# In the case that webapp == manager == handler, pass jobs in memory
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
job_handler = self.job_handler
|
||||
# Otherwise, even if the manager == one of the handlers, its handler will pick up jobs from the database
|
||||
self.job_queue = JobManagerQueue( app, job_handler )
|
||||
self.job_stop_queue = JobManagerStopQueue( app, job_handler )
|
||||
if self.app.config.enable_beta_job_managers:
|
||||
from galaxy.jobs.deferred import DeferredJobQueue
|
||||
self.deferred_job_queue = DeferredJobQueue( app )
|
||||
else:
|
||||
self.job_queue = self.job_stop_queue = NoopQueue()
|
||||
self.job_handler.start()
|
||||
def shutdown( self ):
|
||||
self.job_queue.shutdown()
|
||||
self.job_stop_queue.shutdown()
|
||||
self.job_handler.shutdown()
|
||||
|
||||
class JobManagerQueue( object ):
|
||||
"""
|
||||
Job manager, waits for jobs to be runnable and then dispatches to a
|
||||
JobHandler.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, job_handler ):
|
||||
self.app = app
|
||||
self.job_handler = job_handler # the (singular) handler if we are passing jobs in memory
|
||||
|
||||
self.sa_session = app.model.context
|
||||
self.job_lock = False
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.monitor_thread = threading.Thread( target=self.__monitor )
|
||||
# Recover jobs at startup
|
||||
self.__check_jobs_at_startup()
|
||||
# Start the queue
|
||||
self.monitor_thread.start()
|
||||
log.info( "job manager queue started" )
|
||||
|
||||
def __check_jobs_at_startup( self ):
|
||||
"""
|
||||
Checks all jobs that are in the 'new', 'queued' or 'running' state in
|
||||
the database and requeues or cleans up as necessary. Only run as the
|
||||
job manager starts.
|
||||
"""
|
||||
for job in self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( ( ( model.Job.state == model.Job.states.NEW ) \
|
||||
| ( model.Job.state == model.Job.states.RUNNING ) \
|
||||
| ( model.Job.state == model.Job.states.QUEUED ) ) \
|
||||
& ( model.Job.handler == None ) ):
|
||||
if job.tool_id not in self.app.toolbox.tools_by_id:
|
||||
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
|
||||
JobWrapper( job, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator.' )
|
||||
else:
|
||||
job.handler = self.__get_handler( job ) # handler's recovery method will take it from here
|
||||
log.info( "(%d) Job in '%s' state had no handler at job manager startup, assigned '%s' handler" % ( job.id, job.state, job.handler ) )
|
||||
if self.sa_session.dirty:
|
||||
self.sa_session.flush()
|
||||
|
||||
def __monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs and dispatch to a handler
|
||||
"""
|
||||
# HACK: Delay until after forking, we need a way to do post fork notification!!!
|
||||
time.sleep( 10 )
|
||||
while self.running:
|
||||
try:
|
||||
self.__monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def __monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to process waiting jobs. Gets any new
|
||||
jobs (either from the database or from its own queue), then assigns a
|
||||
handler.
|
||||
"""
|
||||
# Do nothing if the queue is locked
|
||||
if self.job_lock:
|
||||
log.info( 'Job queue is administratively locked, sleeping...' )
|
||||
time.sleep( 10 )
|
||||
return
|
||||
# Pull all new jobs from the queue at once
|
||||
jobs_to_check = []
|
||||
if self.app.config.track_jobs_in_database:
|
||||
# Clear the session so we get fresh states for job and all datasets
|
||||
self.sa_session.expunge_all()
|
||||
# Fetch all new jobs
|
||||
jobs_to_check = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
|
||||
.filter( ( model.Job.state == model.Job.states.NEW ) \
|
||||
& ( model.Job.handler == None ) ).all()
|
||||
else:
|
||||
# Get job objects and append to watch queue for any which were
|
||||
# previously waiting
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, tool_id = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( self.sa_session.query( model.Job ).get( job_id ) )
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
for job in jobs_to_check:
|
||||
job.handler = self.__get_handler( job )
|
||||
log.debug( "(%s) Job assigned to handler '%s'" % ( job.id, job.handler ) )
|
||||
self.sa_session.add( job )
|
||||
|
||||
# If tracking in the database, handlers will pick up the job now
|
||||
self.sa_session.flush()
|
||||
|
||||
time.sleep( 5 )
|
||||
|
||||
# This only does something in the case that there is only one handler and it is this Galaxy process
|
||||
for job in jobs_to_check:
|
||||
self.job_handler.job_queue.put( job.id, job.tool_id )
|
||||
|
||||
def __get_handler( self, job ):
|
||||
try:
|
||||
params = None
|
||||
if job.params:
|
||||
params = from_json_string( job.params )
|
||||
return self.app.toolbox.tools_by_id.get( job.tool_id, None ).get_job_handler( params )
|
||||
except:
|
||||
log.exception( "(%s) Caught exception attempting to get tool-specific job handler for tool '%s', selecting at random from available handlers instead:" % ( job.id, job.tool_id ) )
|
||||
return random.choice( self.app.config.job_handlers )
|
||||
|
||||
def put( self, job_id, tool ):
|
||||
"""Add a job to the queue (by job identifier)"""
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
self.queue.put( ( job_id, tool.id ) )
|
||||
self.sleeper.wake()
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job manager queue stopped" )
|
||||
|
||||
class JobManagerStopQueue( object ):
|
||||
"""
|
||||
A queue for jobs which need to be terminated prematurely.
|
||||
"""
|
||||
STOP_SIGNAL = object()
|
||||
def __init__( self, app, job_handler ):
|
||||
self.app = app
|
||||
self.job_handler = job_handler
|
||||
|
||||
self.sa_session = app.model.context
|
||||
|
||||
# Keep track of the pid that started the job manager, only it
|
||||
# has valid threads
|
||||
self.parent_pid = os.getpid()
|
||||
# Contains new jobs. Note this is not used if track_jobs_in_database is True
|
||||
self.queue = Queue()
|
||||
|
||||
# Contains jobs that are waiting (only use from monitor thread)
|
||||
self.waiting = []
|
||||
|
||||
# Helper for interruptable sleep
|
||||
self.sleeper = Sleeper()
|
||||
self.running = True
|
||||
self.monitor_thread = threading.Thread( target=self.monitor )
|
||||
self.monitor_thread.start()
|
||||
log.info( "job manager stop queue started" )
|
||||
|
||||
def monitor( self ):
|
||||
"""
|
||||
Continually iterate the waiting jobs, stop any that are found.
|
||||
"""
|
||||
# HACK: Delay until after forking, we need a way to do post fork notification!!!
|
||||
time.sleep( 10 )
|
||||
while self.running:
|
||||
try:
|
||||
self.monitor_step()
|
||||
except:
|
||||
log.exception( "Exception in monitor_step" )
|
||||
# Sleep
|
||||
self.sleeper.sleep( 1 )
|
||||
|
||||
def monitor_step( self ):
|
||||
"""
|
||||
Called repeatedly by `monitor` to stop jobs.
|
||||
"""
|
||||
jobs_to_check = []
|
||||
# Pull from the queue even if tracking in the database (in the case of Administrative stopped jobs)
|
||||
try:
|
||||
while 1:
|
||||
message = self.queue.get_nowait()
|
||||
if message is self.STOP_SIGNAL:
|
||||
return
|
||||
# Unpack the message
|
||||
job_id, error_msg = message
|
||||
# Get the job object and append to watch queue
|
||||
jobs_to_check.append( ( self.sa_session.query( model.Job ).get( job_id ), error_msg ) )
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
# If tracking in the database, the handler will pick up the stop itself. Otherwise, notify the handler.
|
||||
for job, error_msg in jobs_to_check:
|
||||
self.job_handler.job_stop_queue.put( job.id, error_msg )
|
||||
|
||||
def put( self, job_id, error_msg=None ):
|
||||
self.queue.put( ( job_id, error_msg ) )
|
||||
|
||||
def shutdown( self ):
|
||||
"""Attempts to gracefully shut down the worker thread"""
|
||||
if self.parent_pid != os.getpid():
|
||||
# We're not the real job queue, do nothing
|
||||
return
|
||||
else:
|
||||
log.info( "sending stop signal to worker thread" )
|
||||
self.running = False
|
||||
if not self.app.config.track_jobs_in_database:
|
||||
self.queue.put( self.STOP_SIGNAL )
|
||||
self.sleeper.wake()
|
||||
log.info( "job manager stop queue stopped" )
|
||||
|
||||
class NoopHandler( object ):
|
||||
def __init__( self, *args, **kwargs ):
|
||||
self.job_queue = NoopQueue()
|
||||
self.job_stop_queue = NoopQueue()
|
||||
def start( self ):
|
||||
pass
|
||||
def shutdown( self, *args ):
|
||||
pass
|
||||
@@ -83,7 +83,7 @@ class TaskedJobRunner( object ):
|
||||
for task in tasks:
|
||||
tw = TaskWrapper(task, job_wrapper.queue)
|
||||
task_wrappers.append(tw)
|
||||
self.app.job_manager.dispatcher.put(tw)
|
||||
self.app.job_manager.job_handler.dispatcher.put(tw)
|
||||
tasks_incomplete = False
|
||||
count_complete = 0
|
||||
sleep_time = 1
|
||||
|
||||
@@ -132,6 +132,7 @@ class Job( object ):
|
||||
self.job_runner_external_id = None
|
||||
self.post_job_actions = []
|
||||
self.imported = False
|
||||
self.handler = None
|
||||
|
||||
def add_parameter( self, name, value ):
|
||||
self.parameters.append( JobParameter( name, value ) )
|
||||
@@ -171,14 +172,11 @@ class Job( object ):
|
||||
if not dataset.deleted:
|
||||
return False
|
||||
return True
|
||||
def mark_deleted( self, enable_job_running=True, track_jobs_in_database=False ):
|
||||
def mark_deleted( self, track_jobs_in_database=False ):
|
||||
"""
|
||||
Mark this job as deleted, and mark any output datasets as discarded.
|
||||
"""
|
||||
# This could be handled with *just* track_jobs_in_database, but I
|
||||
# didn't want to make setting track_jobs_in_database required in
|
||||
# non-runner configs.
|
||||
if not enable_job_running or track_jobs_in_database:
|
||||
if track_jobs_in_database:
|
||||
self.state = Job.states.DELETED_NEW
|
||||
else:
|
||||
self.state = Job.states.DELETED
|
||||
@@ -303,6 +301,20 @@ class JobImportHistoryArchive( object ):
|
||||
self.history = history
|
||||
self.archive_dir=archive_dir
|
||||
|
||||
class GenomeIndexToolData( object ):
|
||||
def __init__( self, job=None, params=None, dataset=None, deferred_job=None, \
|
||||
transfer_job=None, fasta_path=None, created_time=None, modified_time=None, \
|
||||
dbkey=None, user=None, indexer=None ):
|
||||
self.job = job
|
||||
self.dataset = dataset
|
||||
self.fasta_path = fasta_path
|
||||
self.user = user
|
||||
self.indexer = indexer
|
||||
self.created_time = created_time
|
||||
self.modified_time = modified_time
|
||||
self.deferred = deferred_job
|
||||
self.transfer = transfer_job
|
||||
|
||||
class DeferredJob( object ):
|
||||
states = Bunch( NEW = 'new',
|
||||
WAITING = 'waiting',
|
||||
@@ -1128,8 +1140,10 @@ class HistoryDatasetAssociation( DatasetInstance ):
|
||||
def clear_associated_files( self, metadata_safe = False, purge = False ):
|
||||
# metadata_safe = True means to only clear when assoc.metadata_safe == False
|
||||
for assoc in self.implicitly_converted_datasets:
|
||||
if not metadata_safe or not assoc.metadata_safe:
|
||||
if not assoc.deleted and ( not metadata_safe or not assoc.metadata_safe ):
|
||||
assoc.clear( purge = purge )
|
||||
for assoc in self.implicitly_converted_parent_datasets:
|
||||
assoc.clear( purge = purge, delete_dataset = False )
|
||||
def get_display_name( self ):
|
||||
## Name can be either a string or a unicode object. If string, convert to unicode object assuming 'utf-8' format.
|
||||
hda_name = self.name
|
||||
@@ -1188,6 +1202,12 @@ class HistoryDatasetAssociationDisplayAtAuthorization( object ):
|
||||
self.history_dataset_association = hda
|
||||
self.user = user
|
||||
self.site = site
|
||||
|
||||
class HistoryDatasetAssociationSubset( object ):
|
||||
def __init__(self, hda, subset, location):
|
||||
self.hda = hda
|
||||
self.subset = subset
|
||||
self.location = location
|
||||
|
||||
class Library( object, APIItem ):
|
||||
permitted_actions = get_permitted_actions( filter='LIBRARY' )
|
||||
@@ -1536,21 +1556,26 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ):
|
||||
else:
|
||||
return template.get_widgets( trans.user )
|
||||
return []
|
||||
def templates_dict( self ):
|
||||
def templates_dict( self, use_name=False ):
|
||||
"""
|
||||
Returns a dict of template info
|
||||
"""
|
||||
#TODO: Should have a method that allows names and labels to be returned together in a structured way
|
||||
template_data = {}
|
||||
for temp_info in self.info_association:
|
||||
template = temp_info.template
|
||||
content = temp_info.info.content
|
||||
tmp_dict = {}
|
||||
for field in template.fields:
|
||||
tmp_dict[field['label']] = content[field['name']]
|
||||
if use_name:
|
||||
name = field[ 'name' ]
|
||||
else:
|
||||
name = field[ 'label' ]
|
||||
tmp_dict[ name ] = content.get( field[ 'name' ] )
|
||||
template_data[template.name] = tmp_dict
|
||||
return template_data
|
||||
def templates_json( self ):
|
||||
return simplejson.dumps( self.templates_dict() )
|
||||
def templates_json( self, use_name=False ):
|
||||
return simplejson.dumps( self.templates_dict( use_name=use_name ) )
|
||||
|
||||
def get_display_name( self ):
|
||||
"""
|
||||
@@ -1617,12 +1642,14 @@ class ImplicitlyConvertedDatasetAssociation( object ):
|
||||
self.purged = purged
|
||||
self.metadata_safe = metadata_safe
|
||||
|
||||
def clear( self, purge = False ):
|
||||
def clear( self, purge = False, delete_dataset = True ):
|
||||
self.deleted = True
|
||||
if self.dataset:
|
||||
self.dataset.deleted = True
|
||||
self.dataset.purged = purge
|
||||
if purge: #do something with purging
|
||||
if delete_dataset:
|
||||
self.dataset.deleted = True
|
||||
if purge:
|
||||
self.dataset.purged = True
|
||||
if purge and self.dataset.deleted: #do something with purging
|
||||
self.purged = True
|
||||
try: os.unlink( self.file_name )
|
||||
except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
|
||||
@@ -2620,8 +2647,8 @@ class APIKeys( object ):
|
||||
|
||||
class ToolShedRepository( object ):
|
||||
def __init__( self, id=None, create_time=None, tool_shed=None, name=None, description=None, owner=None, installed_changeset_revision=None,
|
||||
changeset_revision=None, metadata=None, includes_datatypes=False, update_available=False, deleted=False, uninstalled=False,
|
||||
dist_to_shed=False ):
|
||||
changeset_revision=None, ctx_rev=None, metadata=None, includes_datatypes=False, update_available=False, deleted=False,
|
||||
uninstalled=False, dist_to_shed=False ):
|
||||
self.id = id
|
||||
self.create_time = create_time
|
||||
self.tool_shed = tool_shed
|
||||
@@ -2630,6 +2657,7 @@ class ToolShedRepository( object ):
|
||||
self.owner = owner
|
||||
self.installed_changeset_revision = installed_changeset_revision
|
||||
self.changeset_revision = changeset_revision
|
||||
self.ctx_rev = ctx_rev
|
||||
self.metadata = metadata
|
||||
self.includes_datatypes = includes_datatypes
|
||||
self.update_available = update_available
|
||||
|
||||
@@ -76,6 +76,7 @@ UserOpenID.table = Table( "galaxy_user_openid", metadata,
|
||||
Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True ),
|
||||
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
|
||||
Column( "openid", TEXT, index=True, unique=True ),
|
||||
Column( "provider", TrimmedString( 255 ) ),
|
||||
)
|
||||
|
||||
History.table = Table( "history", metadata,
|
||||
@@ -143,6 +144,12 @@ HistoryDatasetAssociationDisplayAtAuthorization.table = Table( "history_dataset_
|
||||
Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
|
||||
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
|
||||
Column( "site", TrimmedString( 255 ) ) )
|
||||
|
||||
HistoryDatasetAssociationSubset.table = Table( "history_dataset_association_subset", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
|
||||
Column( "history_dataset_association_subset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
|
||||
Column( "location", Unicode(255), index=True) )
|
||||
|
||||
ImplicitlyConvertedDatasetAssociation.table = Table( "implicitly_converted_dataset_association", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
@@ -375,6 +382,7 @@ ToolShedRepository.table = Table( "tool_shed_repository", metadata,
|
||||
Column( "owner", TrimmedString( 255 ), index=True ),
|
||||
Column( "installed_changeset_revision", TrimmedString( 255 ) ),
|
||||
Column( "changeset_revision", TrimmedString( 255 ), index=True ),
|
||||
Column( "ctx_rev", TrimmedString( 10 ) ),
|
||||
Column( "metadata", JSONType, nullable=True ),
|
||||
Column( "includes_datatypes", Boolean, index=True, default=False ),
|
||||
Column( "update_available", Boolean, default=False ),
|
||||
@@ -421,7 +429,8 @@ Job.table = Table( "job", metadata,
|
||||
Column( "job_runner_external_id", String( 255 ) ),
|
||||
Column( "object_store_id", TrimmedString( 255 ), index=True ),
|
||||
Column( "imported", Boolean, default=False, index=True ),
|
||||
Column( "params", TrimmedString(255), index=True ) )
|
||||
Column( "params", TrimmedString(255), index=True ),
|
||||
Column( "handler", TrimmedString( 255 ), index=True ) )
|
||||
|
||||
JobParameter.table = Table( "job_parameter", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
@@ -483,6 +492,19 @@ JobImportHistoryArchive.table = Table( "job_import_history_archive", metadata,
|
||||
Column( "archive_dir", TEXT )
|
||||
)
|
||||
|
||||
GenomeIndexToolData.table = Table( "genome_index_tool_data", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ),
|
||||
Column( "deferred_job_id", Integer, ForeignKey( "deferred_job.id" ), index=True ),
|
||||
Column( "transfer_job_id", Integer, ForeignKey( "transfer_job.id" ), index=True ),
|
||||
Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
|
||||
Column( "fasta_path", String( 255 ) ),
|
||||
Column( "created_time", DateTime, default=now ),
|
||||
Column( "modified_time", DateTime, default=now, onupdate=now ),
|
||||
Column( "indexer", String( 64 ) ),
|
||||
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
|
||||
)
|
||||
|
||||
Task.table = Table( "task", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
Column( "create_time", DateTime, default=now ),
|
||||
@@ -1196,6 +1218,9 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab
|
||||
implicitly_converted_datasets=relation(
|
||||
ImplicitlyConvertedDatasetAssociation,
|
||||
primaryjoin=( ImplicitlyConvertedDatasetAssociation.table.c.hda_parent_id == HistoryDatasetAssociation.table.c.id ) ),
|
||||
implicitly_converted_parent_datasets=relation(
|
||||
ImplicitlyConvertedDatasetAssociation,
|
||||
primaryjoin=( ImplicitlyConvertedDatasetAssociation.table.c.hda_id == HistoryDatasetAssociation.table.c.id ) ),
|
||||
children=relation(
|
||||
HistoryDatasetAssociation,
|
||||
primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ),
|
||||
@@ -1231,6 +1256,13 @@ assign_mapper( context, Dataset, Dataset.table,
|
||||
assign_mapper( context, HistoryDatasetAssociationDisplayAtAuthorization, HistoryDatasetAssociationDisplayAtAuthorization.table,
|
||||
properties=dict( history_dataset_association = relation( HistoryDatasetAssociation ),
|
||||
user = relation( User ) ) )
|
||||
|
||||
assign_mapper( context, HistoryDatasetAssociationSubset, HistoryDatasetAssociationSubset.table,
|
||||
properties=dict( hda = relation( HistoryDatasetAssociation,
|
||||
primaryjoin=( HistoryDatasetAssociationSubset.table.c.history_dataset_association_id == HistoryDatasetAssociation.table.c.id ) ),
|
||||
subset = relation( HistoryDatasetAssociation,
|
||||
primaryjoin=( HistoryDatasetAssociationSubset.table.c.history_dataset_association_subset_id == HistoryDatasetAssociation.table.c.id ) )
|
||||
) )
|
||||
|
||||
assign_mapper( context, ImplicitlyConvertedDatasetAssociation, ImplicitlyConvertedDatasetAssociation.table,
|
||||
properties=dict( parent_hda=relation(
|
||||
@@ -1493,6 +1525,13 @@ assign_mapper( context, JobExportHistoryArchive, JobExportHistoryArchive.table,
|
||||
assign_mapper( context, JobImportHistoryArchive, JobImportHistoryArchive.table,
|
||||
properties=dict( job = relation( Job ), history = relation( History ) ) )
|
||||
|
||||
assign_mapper( context, GenomeIndexToolData, GenomeIndexToolData.table,
|
||||
properties=dict( job = relation( Job ),
|
||||
dataset = relation( Dataset ),
|
||||
user = relation( User ),
|
||||
deferred = relation( DeferredJob, backref='deferred_job' ),
|
||||
transfer = relation( TransferJob, backref='transfer_job' ) ) )
|
||||
|
||||
assign_mapper( context, PostJobAction, PostJobAction.table,
|
||||
properties=dict(workflow_step = relation( WorkflowStep, backref='post_job_actions', primaryjoin=(WorkflowStep.table.c.id == PostJobAction.table.c.workflow_step_id))))
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Migration script to create "handler" column in job table.
|
||||
"""
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from migrate import *
|
||||
from migrate.changeset import *
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
# Need our custom types, but don't import anything else from model
|
||||
from galaxy.model.custom_types import *
|
||||
|
||||
metadata = MetaData( migrate_engine )
|
||||
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
|
||||
|
||||
# Column to add.
|
||||
handler_col = Column( "handler", TrimmedString(255), index=True )
|
||||
|
||||
def display_migration_details():
|
||||
print ""
|
||||
print "This migration script adds a 'handler' column to the Job table."
|
||||
|
||||
def upgrade():
|
||||
print __doc__
|
||||
metadata.reflect()
|
||||
|
||||
# Add column to Job table.
|
||||
try:
|
||||
Job_table = Table( "job", metadata, autoload=True )
|
||||
handler_col.create( Job_table )
|
||||
assert handler_col is Job_table.c.handler
|
||||
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
log.debug( "Adding column 'handler' to job table failed: %s" % str( e ) )
|
||||
|
||||
def downgrade():
|
||||
metadata.reflect()
|
||||
|
||||
# Drop column from Job table.
|
||||
try:
|
||||
Job_table = Table( "job", metadata, autoload=True )
|
||||
handler_col = Job_table.c.handler
|
||||
handler_col.drop()
|
||||
except Exception, e:
|
||||
log.debug( "Dropping column 'handler' from job table failed: %s" % ( str( e ) ) )
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Migration script to create table for tracking history_dataset_association subsets.
|
||||
"""
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from migrate import *
|
||||
from migrate.changeset import *
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
metadata = MetaData( migrate_engine )
|
||||
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
|
||||
|
||||
# Table to add.
|
||||
|
||||
HistoryDatasetAssociationSubset_table = Table( "history_dataset_association_subset", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
|
||||
Column( "history_dataset_association_subset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
|
||||
Column( "location", Unicode(255), index=True)
|
||||
)
|
||||
|
||||
def upgrade():
|
||||
print __doc__
|
||||
metadata.reflect()
|
||||
|
||||
# Create history_dataset_association_subset.
|
||||
try:
|
||||
HistoryDatasetAssociationSubset_table.create()
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
log.debug( "Creating history_dataset_association_subset table failed: %s" % str( e ) )
|
||||
|
||||
def downgrade():
|
||||
metadata.reflect()
|
||||
|
||||
# Drop history_dataset_association_subset table.
|
||||
try:
|
||||
HistoryDatasetAssociationSubset_table.drop()
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
log.debug( "Dropping history_dataset_association_subset table failed: %s" % str( e ) )
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Migration script to add column to openid table for provider.
|
||||
Remove any OpenID entries with nonunique GenomeSpace Identifier
|
||||
"""
|
||||
|
||||
BAD_IDENTIFIER = 'https://identity.genomespace.org/identityServer/xrd.jsp'
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from migrate import *
|
||||
from migrate.changeset import *
|
||||
from galaxy.model.custom_types import TrimmedString
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
metadata = MetaData( migrate_engine )
|
||||
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
|
||||
|
||||
def upgrade():
|
||||
print __doc__
|
||||
metadata.reflect()
|
||||
|
||||
try:
|
||||
OpenID_table = Table( "galaxy_user_openid", metadata, autoload=True )
|
||||
c = Column( "provider", TrimmedString( 255 ) )
|
||||
c.create( OpenID_table )
|
||||
assert c is OpenID_table.c.provider
|
||||
except Exception, e:
|
||||
print "Adding provider column to galaxy_user_openid table failed: %s" % str( e )
|
||||
log.debug( "Adding provider column to galaxy_user_openid table failed: %s" % str( e ) )
|
||||
|
||||
try:
|
||||
cmd = "DELETE FROM galaxy_user_openid WHERE openid='%s'" % ( BAD_IDENTIFIER )
|
||||
db_session.execute( cmd )
|
||||
except Exception, e:
|
||||
log.debug( "Deleting bad Identifiers from galaxy_user_openid failed: %s" % str( e ) )
|
||||
|
||||
def downgrade():
|
||||
metadata.reflect()
|
||||
try:
|
||||
OpenID_table = Table( "galaxy_user_openid", metadata, autoload=True )
|
||||
OpenID_table.c.provider.drop()
|
||||
except Exception, e:
|
||||
print "Dropping provider column from galaxy_user_openid table failed: %s" % str( e )
|
||||
log.debug( "Dropping provider column from galaxy_user_openid table failed: %s" % str( e ) )
|
||||
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
Migration script to add the ctx_rev column to the tool_shed_repository table.
|
||||
"""
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from migrate import *
|
||||
from migrate.changeset import *
|
||||
|
||||
import datetime
|
||||
now = datetime.datetime.utcnow
|
||||
# Need our custom types, but don't import anything else from model
|
||||
from galaxy.model.custom_types import *
|
||||
|
||||
import sys, logging
|
||||
log = logging.getLogger( __name__ )
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler( sys.stdout )
|
||||
format = "%(name)s %(levelname)s %(asctime)s %(message)s"
|
||||
formatter = logging.Formatter( format )
|
||||
handler.setFormatter( formatter )
|
||||
log.addHandler( handler )
|
||||
|
||||
metadata = MetaData( migrate_engine )
|
||||
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
|
||||
|
||||
def upgrade():
|
||||
print __doc__
|
||||
metadata.reflect()
|
||||
ToolShedRepository_table = Table( "tool_shed_repository", metadata, autoload=True )
|
||||
col = Column( "ctx_rev", TrimmedString( 10 ) )
|
||||
try:
|
||||
col.create( ToolShedRepository_table )
|
||||
assert col is ToolShedRepository_table.c.ctx_rev
|
||||
except Exception, e:
|
||||
print "Adding ctx_rev column to the tool_shed_repository table failed: %s" % str( e )
|
||||
def downgrade():
|
||||
metadata.reflect()
|
||||
ToolShedRepository_table = Table( "tool_shed_repository", metadata, autoload=True )
|
||||
try:
|
||||
ToolShedRepository_table.c.ctx_rev.drop()
|
||||
except Exception, e:
|
||||
print "Dropping column ctx_rev from the tool_shed_repository table failed: %s" % str( e )
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Migration script to create the genome_index_tool_data table.
|
||||
"""
|
||||
|
||||
from sqlalchemy import *
|
||||
from migrate import *
|
||||
|
||||
import datetime
|
||||
now = datetime.datetime.utcnow
|
||||
|
||||
# Need our custom types, but don't import anything else from model
|
||||
from galaxy.model.custom_types import *
|
||||
|
||||
import sys, logging
|
||||
log = logging.getLogger( __name__ )
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler( sys.stdout )
|
||||
format = "%(name)s %(levelname)s %(asctime)s %(message)s"
|
||||
formatter = logging.Formatter( format )
|
||||
handler.setFormatter( formatter )
|
||||
log.addHandler( handler )
|
||||
|
||||
metadata = MetaData( migrate_engine )
|
||||
|
||||
# New table in changeset TODO:TODO
|
||||
GenomeIndexToolData_table = Table( "genome_index_tool_data", metadata,
|
||||
Column( "id", Integer, primary_key=True ),
|
||||
Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ),
|
||||
Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
|
||||
Column( "deferred_job_id", Integer, ForeignKey( "deferred_job.id" ), index=True ),
|
||||
Column( "transfer_job_id", Integer, ForeignKey( "transfer_job.id" ), index=True ),
|
||||
Column( "fasta_path", String( 255 ) ),
|
||||
Column( "created_time", DateTime, default=now ),
|
||||
Column( "modified_time", DateTime, default=now, onupdate=now ),
|
||||
Column( "indexer", String( 64 ) ),
|
||||
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
|
||||
)
|
||||
|
||||
def upgrade():
|
||||
print __doc__
|
||||
|
||||
metadata.reflect()
|
||||
try:
|
||||
GenomeIndexToolData_table.create()
|
||||
except Exception, e:
|
||||
log.debug( "Creating genome_index_tool_data table failed: %s" % str( e ) )
|
||||
|
||||
def downgrade():
|
||||
metadata.reflect()
|
||||
try:
|
||||
GenomeIndexToolData_table.drop()
|
||||
except Exception, e:
|
||||
log.debug( "Dropping genome_index_tool_data table failed: %s" % str( e ) )
|
||||
@@ -223,7 +223,7 @@ class DiskObjectStore(ObjectStore):
|
||||
if not os.path.exists(path):
|
||||
return self._construct_path(obj, base_dir=base_dir, dir_only=dir_only, extra_dir=extra_dir, extra_dir_at_root=extra_dir_at_root, alt_name=alt_name)
|
||||
|
||||
def _construct_path(self, obj, old_style=False, base_dir=None, dir_only=False, extra_dir=None, extra_dir_at_root=False, alt_name=None):
|
||||
def _construct_path(self, obj, old_style=False, base_dir=None, dir_only=False, extra_dir=None, extra_dir_at_root=False, alt_name=None, **kwargs):
|
||||
""" Construct the expected absolute path for accessing the object
|
||||
identified by `obj`.id.
|
||||
|
||||
@@ -963,6 +963,9 @@ class DistributedObjectStore(ObjectStore):
|
||||
return self.__call_method('get_filename', obj, ObjectNotFound, True, **kwargs)
|
||||
|
||||
def update_from_file(self, obj, **kwargs):
|
||||
if kwargs.get('create', False):
|
||||
self.create(obj, **kwargs)
|
||||
kwargs['create'] = False
|
||||
return self.__call_method('update_from_file', obj, ObjectNotFound, True, **kwargs)
|
||||
|
||||
def get_object_url(self, obj, **kwargs):
|
||||
|
||||
@@ -9,6 +9,9 @@ from galaxy.util.odict import odict
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
NO_PROVIDER_ID = 'None'
|
||||
RESERVED_PROVIDER_IDS = [ NO_PROVIDER_ID ]
|
||||
|
||||
class OpenIDProvider( object ):
|
||||
'''An OpenID Provider object.'''
|
||||
@classmethod
|
||||
@@ -22,7 +25,9 @@ class OpenIDProvider( object ):
|
||||
op_endpoint_url = provider_elem.find( 'op_endpoint_url' )
|
||||
if op_endpoint_url is not None:
|
||||
op_endpoint_url = op_endpoint_url.text
|
||||
never_associate_with_user = string_as_bool( provider_elem.get( 'never_associate_with_user', 'False' ) )
|
||||
assert (provider_id and provider_name and op_endpoint_url), Exception( "OpenID Provider improperly configured" )
|
||||
assert provider_id not in RESERVED_PROVIDER_IDS, Exception( 'Specified OpenID Provider uses a reserved id: %s' % ( provider_id ) )
|
||||
sreg_required = []
|
||||
sreg_optional = []
|
||||
use_for = {}
|
||||
@@ -45,8 +50,8 @@ class OpenIDProvider( object ):
|
||||
sreg_required = None
|
||||
sreg_optional = None
|
||||
use_for = None
|
||||
return cls( provider_id, provider_name, op_endpoint_url, sreg_required, sreg_optional, use_for, store_user_preference )
|
||||
def __init__( self, id, name, op_endpoint_url, sreg_required=None, sreg_optional=None, use_for=None, store_user_preference=None ):
|
||||
return cls( provider_id, provider_name, op_endpoint_url, sreg_required=sreg_required, sreg_optional=sreg_optional, use_for=use_for, store_user_preference=store_user_preference, never_associate_with_user=never_associate_with_user )
|
||||
def __init__( self, id, name, op_endpoint_url, sreg_required=None, sreg_optional=None, use_for=None, store_user_preference=None, never_associate_with_user=None ):
|
||||
'''When sreg options are not specified, defaults are used.'''
|
||||
self.id = id
|
||||
self.name = name
|
||||
@@ -71,6 +76,10 @@ class OpenIDProvider( object ):
|
||||
self.store_user_preference = store_user_preference
|
||||
else:
|
||||
self.store_user_preference = {}
|
||||
if never_associate_with_user:
|
||||
self.never_associate_with_user = True
|
||||
else:
|
||||
self.never_associate_with_user = False
|
||||
def post_authentication( self, trans, openid_manager, info ):
|
||||
sreg_attributes = openid_manager.get_sreg( info )
|
||||
for store_pref_name, store_pref_value_name in self.store_user_preference.iteritems():
|
||||
@@ -80,9 +89,12 @@ class OpenIDProvider( object ):
|
||||
raise Exception( 'Only sreg is currently supported.' )
|
||||
trans.sa_session.add( trans.user )
|
||||
trans.sa_session.flush()
|
||||
def has_post_authentication_actions( self ):
|
||||
return bool( self.store_user_preference )
|
||||
|
||||
class OpenIDProviders( object ):
|
||||
'''Collection of OpenID Providers'''
|
||||
NO_PROVIDER_ID = NO_PROVIDER_ID
|
||||
@classmethod
|
||||
def from_file( cls, filename ):
|
||||
try:
|
||||
@@ -107,6 +119,7 @@ class OpenIDProviders( object ):
|
||||
self.providers = providers
|
||||
else:
|
||||
self.providers = odict()
|
||||
self._banned_identifiers = [ provider.op_endpoint_url for provider in self.providers.itervalues() if provider.never_associate_with_user ]
|
||||
def __iter__( self ):
|
||||
for provider in self.providers.itervalues():
|
||||
yield provider
|
||||
@@ -115,3 +128,5 @@ class OpenIDProviders( object ):
|
||||
return self.providers[ name ]
|
||||
else:
|
||||
return default
|
||||
def new_provider_from_identifier( self, identifier ):
|
||||
return OpenIDProvider( None, identifier, identifier, never_associate_with_user = identifier in self._banned_identifiers )
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import re
|
||||
|
||||
VALID_PUBLICNAME_RE = re.compile( "^[a-z0-9\-]+$" )
|
||||
VALID_PUBLICNAME_SUB = re.compile( "[^a-z0-9\-]" )
|
||||
FILL_CHAR = '-'
|
||||
|
||||
def validate_email( trans, email, user=None, check_dup=True ):
|
||||
message = ''
|
||||
@@ -31,6 +33,20 @@ def validate_publicname( trans, publicname, user=None ):
|
||||
return "Public name is taken; please choose another"
|
||||
return ''
|
||||
|
||||
def transform_publicname( trans, publicname, user=None ):
|
||||
# User names must be at least four characters in length and contain only lower-case
|
||||
# letters, numbers, and the '-' character.
|
||||
#TODO: Enhance to allow generation of semi-random publicnnames e.g., when valid but taken
|
||||
if user and user.username == publicname:
|
||||
return publicname
|
||||
elif publicname not in [ 'None', None, '' ]:
|
||||
publicname = publicname.lower()
|
||||
publicname = re.sub( VALID_PUBLICNAME_SUB, FILL_CHAR, publicname )
|
||||
publicname = publicname.ljust( 4, FILL_CHAR )[:255]
|
||||
if not trans.sa_session.query( trans.app.model.User ).filter_by( username=publicname ).first():
|
||||
return publicname
|
||||
return ''
|
||||
|
||||
def validate_password( trans, password, confirm ):
|
||||
if len( password ) < 6:
|
||||
return "Use a password of at least 6 characters"
|
||||
|
||||
@@ -7,6 +7,7 @@ from galaxy.tools import ToolSection
|
||||
from galaxy.util.json import from_json_string, to_json_string
|
||||
from galaxy.util.shed_util import *
|
||||
from galaxy.util.odict import odict
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class InstallManager( object ):
|
||||
@@ -119,7 +120,7 @@ class InstallManager( object ):
|
||||
is_displayed = True
|
||||
return is_displayed, tool_sections
|
||||
def handle_repository_contents( self, current_working_dir, repository_clone_url, relative_install_dir, repository_elem, repository_name, description,
|
||||
changeset_revision, tmp_name ):
|
||||
changeset_revision, ctx_rev ):
|
||||
# Generate the metadata for the installed tool shed repository, among other things. It is critical that the installed repository is
|
||||
# updated to the desired changeset_revision before metadata is set because the process for setting metadata uses the repository files on disk.
|
||||
# The values for the keys in each of the following dictionaries will be a list to allow for the same tool to be displayed in multiple places
|
||||
@@ -144,6 +145,7 @@ class InstallManager( object ):
|
||||
repository_name,
|
||||
description,
|
||||
changeset_revision,
|
||||
ctx_rev,
|
||||
repository_clone_url,
|
||||
metadata_dict,
|
||||
dist_to_shed=True )
|
||||
@@ -154,9 +156,15 @@ class InstallManager( object ):
|
||||
# Handle missing data table entries for tool parameters that are dynamically generated select lists.
|
||||
repository_tools_tups = handle_missing_data_table_entry( self.app, self.tool_path, sample_files, repository_tools_tups )
|
||||
# Handle missing index files for tool parameters that are dynamically generated select lists.
|
||||
repository_tools_tups = handle_missing_index_file( self.app, self.tool_path, sample_files, repository_tools_tups )
|
||||
# Handle tools that use fabric scripts to install dependencies.
|
||||
handle_tool_dependencies( current_working_dir, relative_install_dir, repository_tools_tups )
|
||||
repository_tools_tups, sample_files_copied = handle_missing_index_file( self.app, self.tool_path, sample_files, repository_tools_tups )
|
||||
# Copy remaining sample files included in the repository to the ~/tool-data directory of the local Galaxy instance.
|
||||
copy_sample_files( self.app, sample_files, sample_files_copied=sample_files_copied )
|
||||
if 'tool_dependencies_config' in metadata_dict:
|
||||
# Install tool dependencies.
|
||||
status, message = handle_tool_dependencies( self.app, repository_clone_url, metadata_dict[ 'tool_dependencies_config' ] )
|
||||
if status != 'ok' and message:
|
||||
print 'The following error occurred while installing tool dependencies:'
|
||||
print message
|
||||
add_to_tool_panel( self.app,
|
||||
repository_name,
|
||||
repository_clone_url,
|
||||
@@ -166,11 +174,6 @@ class InstallManager( object ):
|
||||
self.migrated_tools_config,
|
||||
tool_panel_dict=tool_panel_dict_for_display,
|
||||
new_install=True )
|
||||
# Remove the temporary file
|
||||
try:
|
||||
os.unlink( tmp_name )
|
||||
except:
|
||||
pass
|
||||
if 'datatypes_config' in metadata_dict:
|
||||
datatypes_config = os.path.abspath( metadata_dict[ 'datatypes_config' ] )
|
||||
# Load proprietary data types required by tools. The value of override is not important here since the Galaxy server will be started
|
||||
@@ -193,7 +196,7 @@ class InstallManager( object ):
|
||||
self.app.datatypes_registry.load_display_applications( installed_repository_dict=repository_dict )
|
||||
return tool_shed_repository, metadata_dict
|
||||
def install_repository( self, repository_elem ):
|
||||
# Install a single repository, loading contained tools into the tool config.
|
||||
# Install a single repository, loading contained tools into the tool panel.
|
||||
name = repository_elem.get( 'name' )
|
||||
description = repository_elem.get( 'description' )
|
||||
changeset_revision = repository_elem.get( 'changeset_revision' )
|
||||
@@ -206,65 +209,55 @@ class InstallManager( object ):
|
||||
tool_shed_url = self.__get_url_from_tool_shed( self.tool_shed )
|
||||
repository_clone_url = os.path.join( tool_shed_url, 'repos', self.repository_owner, name )
|
||||
relative_install_dir = os.path.join( clone_dir, name )
|
||||
returncode, tmp_name = clone_repository( name, clone_dir, current_working_dir, repository_clone_url )
|
||||
if returncode == 0:
|
||||
returncode, tmp_name = update_repository( current_working_dir, relative_install_dir, changeset_revision )
|
||||
if returncode == 0:
|
||||
tool_shed_repository, metadata_dict = self.handle_repository_contents( current_working_dir,
|
||||
repository_clone_url,
|
||||
relative_install_dir,
|
||||
repository_elem,
|
||||
name,
|
||||
description,
|
||||
changeset_revision,
|
||||
tmp_name )
|
||||
if 'tools' in metadata_dict:
|
||||
# Get the tool_versions from the tool shed for each tool in the installed change set.
|
||||
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
|
||||
( tool_shed_url, tool_shed_repository.name, self.repository_owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
response.close()
|
||||
if text:
|
||||
tool_version_dicts = from_json_string( text )
|
||||
handle_tool_versions( self.app, tool_version_dicts, tool_shed_repository )
|
||||
else:
|
||||
# Set the tool versions since they seem to be missing for this repository in the tool shed.
|
||||
# CRITICAL NOTE: These default settings may not properly handle all parent/child associations.
|
||||
for tool_dict in metadata_dict[ 'tools' ]:
|
||||
flush_needed = False
|
||||
tool_id = tool_dict[ 'guid' ]
|
||||
old_tool_id = tool_dict[ 'id' ]
|
||||
tool_version = tool_dict[ 'version' ]
|
||||
tool_version_using_old_id = get_tool_version( self.app, old_tool_id )
|
||||
tool_version_using_guid = get_tool_version( self.app, tool_id )
|
||||
if not tool_version_using_old_id:
|
||||
tool_version_using_old_id = self.app.model.ToolVersion( tool_id=old_tool_id,
|
||||
tool_shed_repository=tool_shed_repository )
|
||||
self.app.sa_session.add( tool_version_using_old_id )
|
||||
self.app.sa_session.flush()
|
||||
if not tool_version_using_guid:
|
||||
tool_version_using_guid = self.app.model.ToolVersion( tool_id=tool_id,
|
||||
tool_shed_repository=tool_shed_repository )
|
||||
self.app.sa_session.add( tool_version_using_guid )
|
||||
self.app.sa_session.flush()
|
||||
# Associate the two versions as parent / child.
|
||||
tool_version_association = get_tool_version_association( self.app,
|
||||
tool_version_using_old_id,
|
||||
tool_version_using_guid )
|
||||
if not tool_version_association:
|
||||
tool_version_association = self.app.model.ToolVersionAssociation( tool_id=tool_version_using_guid.id,
|
||||
parent_id=tool_version_using_old_id.id )
|
||||
self.app.sa_session.add( tool_version_association )
|
||||
self.app.sa_session.flush()
|
||||
ctx_rev = get_ctx_rev( tool_shed_url, name, self.repository_owner, changeset_revision )
|
||||
clone_repository( repository_clone_url, os.path.abspath( relative_install_dir ), ctx_rev )
|
||||
tool_shed_repository, metadata_dict = self.handle_repository_contents( current_working_dir,
|
||||
repository_clone_url,
|
||||
relative_install_dir,
|
||||
repository_elem,
|
||||
name,
|
||||
description,
|
||||
changeset_revision,
|
||||
ctx_rev )
|
||||
if 'tools' in metadata_dict:
|
||||
# Get the tool_versions from the tool shed for each tool in the installed change set.
|
||||
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % \
|
||||
( tool_shed_url, tool_shed_repository.name, self.repository_owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
response.close()
|
||||
if text:
|
||||
tool_version_dicts = from_json_string( text )
|
||||
handle_tool_versions( self.app, tool_version_dicts, tool_shed_repository )
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
print "Error updating repository ', name, "': ', str( tmp_stderr.read() )
|
||||
tmp_stderr.close()
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
print "Error cloning repository '", name, "': ", str( tmp_stderr.read() )
|
||||
tmp_stderr.close()
|
||||
# Set the tool versions since they seem to be missing for this repository in the tool shed.
|
||||
# CRITICAL NOTE: These default settings may not properly handle all parent/child associations.
|
||||
for tool_dict in metadata_dict[ 'tools' ]:
|
||||
flush_needed = False
|
||||
tool_id = tool_dict[ 'guid' ]
|
||||
old_tool_id = tool_dict[ 'id' ]
|
||||
tool_version = tool_dict[ 'version' ]
|
||||
tool_version_using_old_id = get_tool_version( self.app, old_tool_id )
|
||||
tool_version_using_guid = get_tool_version( self.app, tool_id )
|
||||
if not tool_version_using_old_id:
|
||||
tool_version_using_old_id = self.app.model.ToolVersion( tool_id=old_tool_id,
|
||||
tool_shed_repository=tool_shed_repository )
|
||||
self.app.sa_session.add( tool_version_using_old_id )
|
||||
self.app.sa_session.flush()
|
||||
if not tool_version_using_guid:
|
||||
tool_version_using_guid = self.app.model.ToolVersion( tool_id=tool_id,
|
||||
tool_shed_repository=tool_shed_repository )
|
||||
self.app.sa_session.add( tool_version_using_guid )
|
||||
self.app.sa_session.flush()
|
||||
# Associate the two versions as parent / child.
|
||||
tool_version_association = get_tool_version_association( self.app,
|
||||
tool_version_using_old_id,
|
||||
tool_version_using_guid )
|
||||
if not tool_version_association:
|
||||
tool_version_association = self.app.model.ToolVersionAssociation( tool_id=tool_version_using_guid.id,
|
||||
parent_id=tool_version_using_old_id.id )
|
||||
self.app.sa_session.add( tool_version_association )
|
||||
self.app.sa_session.flush()
|
||||
@property
|
||||
def non_shed_tool_panel_configs( self ):
|
||||
# Get the non-shed related tool panel config file names from the Galaxy config - the default is tool_conf.xml.
|
||||
@@ -276,7 +269,6 @@ class InstallManager( object ):
|
||||
root = tree.getroot()
|
||||
tool_path = root.get( 'tool_path', None )
|
||||
if tool_path is None:
|
||||
# There will be a problem here if the user has defined 2 non-shed related configs.
|
||||
config_filenames.append( config_filename )
|
||||
return config_filenames
|
||||
def __get_url_from_tool_shed( self, tool_shed ):
|
||||
|
||||
@@ -54,51 +54,52 @@ def verify_tools( app, url, galaxy_config_file, engine_options={} ):
|
||||
config_arg = ''
|
||||
if os.path.abspath( os.path.join( os.getcwd(), 'universe_wsgi.ini' ) ) != galaxy_config_file:
|
||||
config_arg = ' -c %s' % galaxy_config_file.replace( os.path.abspath( os.getcwd() ), '.' )
|
||||
# Automatically update the value of the migrate_tools.version database table column.
|
||||
cmd = 'sh manage_tools.sh%s upgrade' % config_arg
|
||||
proc = subprocess.Popen( args=cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
return_code = proc.wait()
|
||||
output = proc.stdout.read( 32768 )
|
||||
if return_code != 0:
|
||||
raise Exception( "Error attempting to update the value of migrate_tools.version: %s" % output )
|
||||
elif missing_tool_configs:
|
||||
if len( tool_panel_configs ) == 1:
|
||||
plural = ''
|
||||
tool_panel_config_file_names = tool_panel_configs[ 0 ]
|
||||
else:
|
||||
plural = 's'
|
||||
tool_panel_config_file_names = ', '.join( tool_panel_configs )
|
||||
msg = "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
|
||||
msg += "\n\nThe list of files at the end of this message refers to tools that are configured to load into the tool panel for\n"
|
||||
msg += "this Galaxy instance, but have been removed from the Galaxy distribution. These tools can be automatically installed\n"
|
||||
msg += "from the Galaxy tool shed at http://toolshed.g2.bx.psu.edu.\n\n"
|
||||
msg += "To skip this process, attempt to start your Galaxy server again (e.g., sh run.sh or whatever you use). If you do this,\n"
|
||||
msg += "be aware that these tools will no longer be available in your Galaxy tool panel, and entries for each of them should\n"
|
||||
msg += "be removed from your file%s named %s.\n\n" % ( plural, tool_panel_config_file_names )
|
||||
msg += "CRITICAL NOTE IF YOU PLAN TO INSTALL\n"
|
||||
msg += "The location in which the tool repositories will be installed is the value of the 'tool_path' attribute in the <tool>\n"
|
||||
msg += 'tag of the file named ./migrated_tool_conf.xml (i.e., <toolbox tool_path="../shed_tools">). The default location\n'
|
||||
msg += "setting is '../shed_tools', which may be problematic for some cluster environments, so make sure to change it before\n"
|
||||
msg += "you execute the installation process if appropriate. The configured location must be outside of the Galaxy installation\n"
|
||||
msg += "directory or it must be in a sub-directory protected by a properly configured .hgignore file if the directory is within\n"
|
||||
msg += "the Galaxy installation directory hierarchy. This is because tool shed repositories will be installed using mercurial's\n"
|
||||
msg += "clone feature, which creates .hg directories and associated mercurial repository files. Not having .hgignore properly\n"
|
||||
msg += "configured could result in undesired behavior when modifying or updating your local Galaxy instance or the tool shed\n"
|
||||
msg += "repositories if they are in directories that pose conflicts. See mercurial's .hgignore documentation at the following\n"
|
||||
msg += "URL for details.\n\nhttp://mercurial.selenic.com/wiki/.hgignore\n\n"
|
||||
msg += output
|
||||
msg += "After the installation process finishes, you can start your Galaxy server. As part of this installation process,\n"
|
||||
msg += "entries for each of the following tool config files will be added to the file named ./migrated_tool_conf.xml, so these\n"
|
||||
msg += "tools will continue to be loaded into your tool panel. Because of this, existing entries for these files should be\n"
|
||||
msg += "removed from your file%s named %s, but only after the installation process finishes.\n\n" % ( plural, tool_panel_config_file_names )
|
||||
for i, missing_tool_config in enumerate( missing_tool_configs ):
|
||||
msg += "%s\n" % missing_tool_config
|
||||
# Should we do the following?
|
||||
#if i > 10:
|
||||
# msg += "\n...and %d more tools...\n" % ( len( missing_tool_configs ) - ( i + 1 ) )
|
||||
# break
|
||||
msg += "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"
|
||||
raise Exception( msg )
|
||||
if not app.config.running_functional_tests:
|
||||
# Automatically update the value of the migrate_tools.version database table column.
|
||||
cmd = 'sh manage_tools.sh%s upgrade' % config_arg
|
||||
proc = subprocess.Popen( args=cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
return_code = proc.wait()
|
||||
output = proc.stdout.read( 32768 )
|
||||
if return_code != 0:
|
||||
raise Exception( "Error attempting to update the value of migrate_tools.version: %s" % output )
|
||||
elif missing_tool_configs:
|
||||
if len( tool_panel_configs ) == 1:
|
||||
plural = ''
|
||||
tool_panel_config_file_names = tool_panel_configs[ 0 ]
|
||||
else:
|
||||
plural = 's'
|
||||
tool_panel_config_file_names = ', '.join( tool_panel_configs )
|
||||
msg = "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
|
||||
msg += "\n\nThe list of files at the end of this message refers to tools that are configured to load into the tool panel for\n"
|
||||
msg += "this Galaxy instance, but have been removed from the Galaxy distribution. These tools can be automatically installed\n"
|
||||
msg += "from the Galaxy tool shed at http://toolshed.g2.bx.psu.edu.\n\n"
|
||||
msg += "To skip this process, attempt to start your Galaxy server again (e.g., sh run.sh or whatever you use). If you do this,\n"
|
||||
msg += "be aware that these tools will no longer be available in your Galaxy tool panel, and entries for each of them should\n"
|
||||
msg += "be removed from your file%s named %s.\n\n" % ( plural, tool_panel_config_file_names )
|
||||
msg += "CRITICAL NOTE IF YOU PLAN TO INSTALL\n"
|
||||
msg += "The location in which the tool repositories will be installed is the value of the 'tool_path' attribute in the <tool>\n"
|
||||
msg += 'tag of the file named ./migrated_tool_conf.xml (i.e., <toolbox tool_path="../shed_tools">). The default location\n'
|
||||
msg += "setting is '../shed_tools', which may be problematic for some cluster environments, so make sure to change it before\n"
|
||||
msg += "you execute the installation process if appropriate. The configured location must be outside of the Galaxy installation\n"
|
||||
msg += "directory or it must be in a sub-directory protected by a properly configured .hgignore file if the directory is within\n"
|
||||
msg += "the Galaxy installation directory hierarchy. This is because tool shed repositories will be installed using mercurial's\n"
|
||||
msg += "clone feature, which creates .hg directories and associated mercurial repository files. Not having .hgignore properly\n"
|
||||
msg += "configured could result in undesired behavior when modifying or updating your local Galaxy instance or the tool shed\n"
|
||||
msg += "repositories if they are in directories that pose conflicts. See mercurial's .hgignore documentation at the following\n"
|
||||
msg += "URL for details.\n\nhttp://mercurial.selenic.com/wiki/.hgignore\n\n"
|
||||
msg += output
|
||||
msg += "After the installation process finishes, you can start your Galaxy server. As part of this installation process,\n"
|
||||
msg += "entries for each of the following tool config files will be added to the file named ./migrated_tool_conf.xml, so these\n"
|
||||
msg += "tools will continue to be loaded into your tool panel. Because of this, existing entries for these files should be\n"
|
||||
msg += "removed from your file%s named %s, but only after the installation process finishes.\n\n" % ( plural, tool_panel_config_file_names )
|
||||
for i, missing_tool_config in enumerate( missing_tool_configs ):
|
||||
msg += "%s\n" % missing_tool_config
|
||||
# Should we do the following?
|
||||
#if i > 10:
|
||||
# msg += "\n...and %d more tools...\n" % ( len( missing_tool_configs ) - ( i + 1 ) )
|
||||
# break
|
||||
msg += "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"
|
||||
raise Exception( msg )
|
||||
else:
|
||||
log.info( "At migrate_tools version %d" % db_schema.version )
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class UpdateManager( object ):
|
||||
log.info( 'Transfer job restarter shutting down...' )
|
||||
def check_for_update( self, repository ):
|
||||
tool_shed_url = get_url_from_repository_tool_shed( self.app, repository )
|
||||
url = '%s/repository/check_for_updates?name=%s&owner=%s&changeset_revision=%s&webapp=update_manager' % \
|
||||
url = '%s/repository/check_for_updates?name=%s&owner=%s&changeset_revision=%s&webapp=update_manager&no_reset=true' % \
|
||||
( tool_shed_url, repository.name, repository.owner, repository.changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
|
||||
+261
-71
@@ -5,7 +5,7 @@ import pkg_resources
|
||||
|
||||
pkg_resources.require( "simplejson" )
|
||||
|
||||
import logging, os, string, sys, tempfile, glob, shutil, types, urllib, subprocess
|
||||
import logging, os, string, sys, tempfile, glob, shutil, types, urllib, subprocess, random
|
||||
import simplejson
|
||||
import binascii
|
||||
from UserDict import DictMixin
|
||||
@@ -31,6 +31,7 @@ from cgi import FieldStorage
|
||||
from galaxy.util.hash_util import *
|
||||
from galaxy.util import listify
|
||||
from galaxy.util.shed_util import *
|
||||
from galaxy.web import url_for
|
||||
|
||||
from galaxy.visualization.tracks.visual_analytics import TracksterConfig
|
||||
|
||||
@@ -114,17 +115,17 @@ class ToolBox( object ):
|
||||
tool_path = self.tool_root_dir
|
||||
# Only load the panel_dict under certain conditions.
|
||||
load_panel_dict = not self.integrated_tool_panel_config_has_contents
|
||||
for elem in root:
|
||||
for index, elem in enumerate( root ):
|
||||
if parsing_shed_tool_conf:
|
||||
config_elems.append( elem )
|
||||
if elem.tag == 'tool':
|
||||
self.load_tool_tag_set( elem, self.tool_panel, self.integrated_tool_panel, tool_path, load_panel_dict, guid=elem.get( 'guid' ) )
|
||||
self.load_tool_tag_set( elem, self.tool_panel, self.integrated_tool_panel, tool_path, load_panel_dict, guid=elem.get( 'guid' ), index=index )
|
||||
elif elem.tag == 'workflow':
|
||||
self.load_workflow_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict )
|
||||
self.load_workflow_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
|
||||
elif elem.tag == 'section':
|
||||
self.load_section_tag_set( elem, tool_path, load_panel_dict )
|
||||
self.load_section_tag_set( elem, tool_path, load_panel_dict, index=index )
|
||||
elif elem.tag == 'label':
|
||||
self.load_label_tag_set( elem, self.tool_panel, self.integrated_tool_panel )
|
||||
self.load_label_tag_set( elem, self.tool_panel, self.integrated_tool_panel, load_panel_dict, index=index )
|
||||
if parsing_shed_tool_conf:
|
||||
shed_tool_conf_dict = dict( config_filename=config_filename,
|
||||
tool_path=tool_path,
|
||||
@@ -286,7 +287,7 @@ class ToolBox( object ):
|
||||
self.app.model.ToolShedRepository.table.c.owner == owner,
|
||||
self.app.model.ToolShedRepository.table.c.installed_changeset_revision == installed_changeset_revision ) ) \
|
||||
.first()
|
||||
def load_tool_tag_set( self, elem, panel_dict, integrated_panel_dict, tool_path, load_panel_dict, guid=None ):
|
||||
def load_tool_tag_set( self, elem, panel_dict, integrated_panel_dict, tool_path, load_panel_dict, guid=None, index=None ):
|
||||
try:
|
||||
path = elem.get( "file" )
|
||||
if guid is None:
|
||||
@@ -354,10 +355,13 @@ class ToolBox( object ):
|
||||
if load_panel_dict:
|
||||
panel_dict[ key ] = tool
|
||||
# Always load the tool into the integrated_panel_dict, or it will not be included in the integrated_tool_panel.xml file.
|
||||
integrated_panel_dict[ key ] = tool
|
||||
if key in integrated_panel_dict or index is None:
|
||||
integrated_panel_dict[ key ] = tool
|
||||
else:
|
||||
integrated_panel_dict.insert( index, key, tool )
|
||||
except:
|
||||
log.exception( "Error reading tool from path: %s" % path )
|
||||
def load_workflow_tag_set( self, elem, panel_dict, integrated_panel_dict, load_panel_dict ):
|
||||
def load_workflow_tag_set( self, elem, panel_dict, integrated_panel_dict, load_panel_dict, index=None ):
|
||||
try:
|
||||
# TODO: should id be encoded?
|
||||
workflow_id = elem.get( 'id' )
|
||||
@@ -367,16 +371,22 @@ class ToolBox( object ):
|
||||
if load_panel_dict:
|
||||
panel_dict[ key ] = workflow
|
||||
# Always load workflows into the integrated_panel_dict.
|
||||
integrated_panel_dict[ key ] = workflow
|
||||
if key in integrated_panel_dict or index is None:
|
||||
integrated_panel_dict[ key ] = workflow
|
||||
else:
|
||||
integrated_panel_dict.insert( index, key, workflow )
|
||||
except:
|
||||
log.exception( "Error loading workflow: %s" % workflow_id )
|
||||
def load_label_tag_set( self, elem, panel_dict, integrated_panel_dict ):
|
||||
def load_label_tag_set( self, elem, panel_dict, integrated_panel_dict, load_panel_dict, index=None ):
|
||||
label = ToolSectionLabel( elem )
|
||||
key = 'label_' + label.id
|
||||
if not self.integrated_tool_panel_config_has_contents:
|
||||
if load_panel_dict:
|
||||
panel_dict[ key ] = label
|
||||
integrated_panel_dict[ key ] = label
|
||||
def load_section_tag_set( self, elem, tool_path, load_panel_dict ):
|
||||
if key in integrated_panel_dict or index is None:
|
||||
integrated_panel_dict[ key ] = label
|
||||
else:
|
||||
integrated_panel_dict.insert( index, key, label )
|
||||
def load_section_tag_set( self, elem, tool_path, load_panel_dict, index=None ):
|
||||
key = 'section_' + elem.get( "id" )
|
||||
if key in self.tool_panel:
|
||||
section = self.tool_panel[ key ]
|
||||
@@ -390,17 +400,20 @@ class ToolBox( object ):
|
||||
else:
|
||||
integrated_section = ToolSection( elem )
|
||||
integrated_elems = integrated_section.elems
|
||||
for sub_elem in elem:
|
||||
for sub_index, sub_elem in enumerate( elem ):
|
||||
if sub_elem.tag == 'tool':
|
||||
self.load_tool_tag_set( sub_elem, elems, integrated_elems, tool_path, load_panel_dict, guid=sub_elem.get( 'guid' ) )
|
||||
self.load_tool_tag_set( sub_elem, elems, integrated_elems, tool_path, load_panel_dict, guid=sub_elem.get( 'guid' ), index=sub_index )
|
||||
elif sub_elem.tag == 'workflow':
|
||||
self.load_workflow_tag_set( sub_elem, elems, integrated_elems, load_panel_dict )
|
||||
self.load_workflow_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
|
||||
elif sub_elem.tag == 'label':
|
||||
self.load_label_tag_set( sub_elem, elems, integrated_elems )
|
||||
self.load_label_tag_set( sub_elem, elems, integrated_elems, load_panel_dict, index=sub_index )
|
||||
if load_panel_dict:
|
||||
self.tool_panel[ key ] = section
|
||||
# Always load sections into the integrated_tool_panel.
|
||||
self.integrated_tool_panel[ key ] = integrated_section
|
||||
if key in self.integrated_tool_panel or index is None:
|
||||
self.integrated_tool_panel[ key ] = integrated_section
|
||||
else:
|
||||
self.integrated_tool_panel.insert( index, key, integrated_section )
|
||||
def load_tool( self, config_file, guid=None ):
|
||||
"""Load a single tool from the file named by `config_file` and return an instance of `Tool`."""
|
||||
# Parse XML configuration file and get the root element
|
||||
@@ -474,17 +487,119 @@ class ToolBox( object ):
|
||||
Returns a SQLAlchemy session
|
||||
"""
|
||||
return self.app.model.context
|
||||
|
||||
def to_dict( self, trans, in_panel=True, trackster=False ):
|
||||
|
||||
def filter_for_panel( item, filters ):
|
||||
"""
|
||||
Filters tool panel elements so that only those that are compatible
|
||||
with provided filters are kept.
|
||||
"""
|
||||
def _apply_filter( filter_item, filter_list ):
|
||||
for filter_method in filter_list:
|
||||
if not filter_method( filter_item ):
|
||||
return False
|
||||
return True
|
||||
if isinstance( item, Tool ):
|
||||
if _apply_filter( item, filters[ 'tool' ] ):
|
||||
return item
|
||||
elif isinstance( item, ToolSectionLabel ):
|
||||
if _apply_filter( item, filters[ 'label' ] ):
|
||||
return item
|
||||
elif isinstance( item, ToolSection ):
|
||||
# Filter section item-by-item. Only show a label if there are
|
||||
# non-filtered tools below it.
|
||||
|
||||
if _apply_filter( item, filters[ 'section' ] ):
|
||||
cur_label_key = None
|
||||
tools_under_label = False
|
||||
filtered_elems = item.elems.copy()
|
||||
for key, section_item in item.elems.items():
|
||||
if isinstance( section_item, Tool ):
|
||||
# Filter tool.
|
||||
if _apply_filter( section_item, filters[ 'tool' ] ):
|
||||
tools_under_label = True
|
||||
else:
|
||||
del filtered_elems[ key ]
|
||||
elif isinstance( section_item, ToolSectionLabel ):
|
||||
# If there is a label and it does not have tools,
|
||||
# remove it.
|
||||
if ( cur_label_key and not tools_under_label ) or not _apply_filter( section_item, filters[ 'label' ] ):
|
||||
del filtered_elems[ cur_label_key ]
|
||||
|
||||
# Reset attributes for new label.
|
||||
cur_label_key = key
|
||||
tools_under_label = False
|
||||
|
||||
|
||||
# Handle last label.
|
||||
if cur_label_key and not tools_under_label:
|
||||
del filtered_elems[ cur_label_key ]
|
||||
|
||||
# Only return section if there are elements.
|
||||
if len( filtered_elems ) != 0:
|
||||
copy = item.copy()
|
||||
copy.elems = filtered_elems
|
||||
return copy
|
||||
|
||||
return None
|
||||
|
||||
#
|
||||
# Dictify toolbox.
|
||||
#
|
||||
|
||||
if in_panel:
|
||||
panel_elts = [ val for val in self.tool_panel.itervalues() ]
|
||||
|
||||
# Filter if necessary.
|
||||
filters = dict( tool=[ lambda x: not x._is_hidden_for_user( trans.user ) ], section=[], label=[] ) #hidden tools filter
|
||||
if trackster:
|
||||
filters[ 'tool' ].append( lambda x: x.trackster_conf ) # If tool has a trackster config, it can be used in Trackster.
|
||||
filtered_panel_elts = []
|
||||
for index, elt in enumerate( panel_elts ):
|
||||
elt = filter_for_panel( elt, filters )
|
||||
if elt:
|
||||
filtered_panel_elts.append( elt )
|
||||
panel_elts = filtered_panel_elts
|
||||
|
||||
# Produce panel.
|
||||
rval = []
|
||||
for elt in panel_elts:
|
||||
rval.append( elt.to_dict( trans, for_link=True ) )
|
||||
else:
|
||||
tools = []
|
||||
for id, tool in self.app.toolbox.tools_by_id.items():
|
||||
tools.append( tool.to_dict( trans ) )
|
||||
rval = tools
|
||||
|
||||
return rval
|
||||
|
||||
class ToolSection( object ):
|
||||
"""
|
||||
A group of tools with similar type/purpose that will be displayed as a
|
||||
group in the user interface.
|
||||
"""
|
||||
def __init__( self, elem ):
|
||||
self.name = elem.get( "name" )
|
||||
self.id = elem.get( "id" )
|
||||
self.version = elem.get( "version" ) or ''
|
||||
def __init__( self, elem=None ):
|
||||
f = lambda elem, val: elem is not None and elem.get( val ) or ''
|
||||
self.name = f( elem, 'name' )
|
||||
self.id = f( elem, 'id' )
|
||||
self.version = f( elem, 'version' )
|
||||
self.elems = odict()
|
||||
|
||||
def copy( self ):
|
||||
copy = ToolSection()
|
||||
copy.name = self.name
|
||||
copy.id = self.id
|
||||
copy.version = self.version
|
||||
copy.elems = self.elems.copy()
|
||||
return copy
|
||||
|
||||
def to_dict( self, trans, for_link=False ):
|
||||
""" Return a dict that includes section's attributes. """
|
||||
section_elts = []
|
||||
for key, val in self.elems.items():
|
||||
section_elts.append( val.to_dict( trans, for_link=for_link ) )
|
||||
return { 'type': 'section', 'id': self.id, 'name': self.name, 'version': self.version, 'elems': section_elts }
|
||||
|
||||
class ToolSectionLabel( object ):
|
||||
"""
|
||||
@@ -495,6 +610,10 @@ class ToolSectionLabel( object ):
|
||||
self.text = elem.get( "text" )
|
||||
self.id = elem.get( "id" )
|
||||
self.version = elem.get( "version" ) or ''
|
||||
|
||||
def to_dict( self, trans, **kwargs ):
|
||||
""" Return a dict that includes label's attributes. """
|
||||
return { 'type': 'label', 'id': self.id, 'name': self.text, 'version': self.version }
|
||||
|
||||
class DefaultToolState( object ):
|
||||
"""
|
||||
@@ -572,22 +691,24 @@ class ToolOutput( object ):
|
||||
|
||||
def __iter__( self ):
|
||||
return iter( ( self.format, self.metadata_source, self.parent ) )
|
||||
|
||||
def to_dict( self ):
|
||||
return {
|
||||
'name': self.name,
|
||||
'format': self.format,
|
||||
'label': self.label,
|
||||
'hidden': self.hidden
|
||||
}
|
||||
|
||||
class ToolRequirement( object ):
|
||||
"""
|
||||
Represents an external requirement that must be available for the tool to
|
||||
run (for example, a program, package, or library). Requirements can
|
||||
optionally assert a specific version, or reference a command to execute a
|
||||
fabric script. If fabric is used, the type is 'fabfile' and the version
|
||||
attribute is not used since the fabric script includes all necessary
|
||||
information for automatic dependency installation.
|
||||
Represents an external requirement that must be available for the tool to run (for example, a program, package, or library).
|
||||
Requirements can optionally assert a specific version.
|
||||
"""
|
||||
def __init__( self, name=None, type=None, version=None, fabfile=None, method=None ):
|
||||
def __init__( self, name=None, type=None, version=None ):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.version = version
|
||||
self.fabfile = fabfile
|
||||
self.method = method
|
||||
|
||||
class ToolParallelismInfo(object):
|
||||
"""
|
||||
@@ -625,6 +746,8 @@ class Tool:
|
||||
self.check_values = True
|
||||
self.nginx_upload = False
|
||||
self.input_required = False
|
||||
self.display_interface = True
|
||||
self.require_login = False
|
||||
# Define a place to keep track of all input parameters. These
|
||||
# differ from the inputs dictionary in that inputs can be page
|
||||
# elements like conditionals, but input_params are basic form
|
||||
@@ -669,31 +792,35 @@ class Tool:
|
||||
if tool_version:
|
||||
return tool_version.get_version_ids( self.app )
|
||||
return []
|
||||
def get_job_runner( self, job_params=None ):
|
||||
# Look through runners to find one with matching parameters.
|
||||
selected_runner = None
|
||||
if len( self.job_runners ) == 1:
|
||||
# Most tools have a single runner.
|
||||
selected_runner = self.job_runners[0]
|
||||
def __get_job_run_config( self, run_configs, key, job_params=None ):
|
||||
# Look through runners/handlers to find one with matching parameters.
|
||||
available_configs = []
|
||||
if len( run_configs ) == 1:
|
||||
# Most tools have a single config.
|
||||
return run_configs[0][ key ] # return to avoid random when this will be the case most of the time
|
||||
elif job_params is None:
|
||||
# Use job runner with no params
|
||||
for runner in self.job_runners:
|
||||
if "params" not in runner:
|
||||
selected_runner = runner
|
||||
# Use job config with no params
|
||||
for config in run_configs:
|
||||
if "params" not in config:
|
||||
available_configs.append( config )
|
||||
else:
|
||||
# Find runner with matching parameters.
|
||||
for runner in self.job_runners:
|
||||
if "params" in runner:
|
||||
# Find config with matching parameters.
|
||||
for config in run_configs:
|
||||
if "params" in config:
|
||||
match = True
|
||||
runner_params = runner[ "params" ]
|
||||
config_params = config[ "params" ]
|
||||
for param, value in job_params.items():
|
||||
if param not in runner_params or \
|
||||
runner_params[ param ] != job_params[ param ]:
|
||||
if param not in config_params or \
|
||||
config_params[ param ] != job_params[ param ]:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
selected_runner = runner
|
||||
return selected_runner[ "url" ]
|
||||
available_configs.append( config )
|
||||
return random.choice( available_configs )[ key ]
|
||||
def get_job_runner( self, job_params=None ):
|
||||
return self.__get_job_run_config( self.job_runners, key='url', job_params=job_params )
|
||||
def get_job_handler( self, job_params=None ):
|
||||
return self.__get_job_run_config( self.job_handlers, key='name', job_params=job_params )
|
||||
def parse( self, root, guid=None ):
|
||||
"""
|
||||
Read tool configuration from the element `root` and fill in `self`.
|
||||
@@ -721,6 +848,8 @@ class Tool:
|
||||
# Useful i.e. when an indeterminate number of outputs are created by
|
||||
# a tool.
|
||||
self.force_history_refresh = util.string_as_bool( root.get( 'force_history_refresh', 'False' ) )
|
||||
self.display_interface = util.string_as_bool( root.get( 'display_interface', str( self.display_interface ) ) )
|
||||
self.require_login = util.string_as_bool( root.get( 'require_login', str( self.require_login ) ) )
|
||||
# Load input translator, used by datasource tools to change
|
||||
# names/values of incoming parameters
|
||||
self.input_translator = root.find( "request_param_translation" )
|
||||
@@ -758,6 +887,12 @@ class Tool:
|
||||
self.parallelism = ToolParallelismInfo(parallelism)
|
||||
else:
|
||||
self.parallelism = None
|
||||
# Set job handler(s). Each handler is a dict with 'url' and, optionally, 'params'.
|
||||
self_id = self.id.lower()
|
||||
self.job_handlers = [ { "name" : name } for name in self.app.config.default_job_handlers ]
|
||||
# Set custom handler(s) if they're defined.
|
||||
if self_id in self.app.config.tool_handlers:
|
||||
self.job_handlers = self.app.config.tool_handlers[ self_id ]
|
||||
# Set job runner(s). Each runner is a dict with 'url' and, optionally, 'params'.
|
||||
if self.app.config.start_job_runners is None:
|
||||
# Jobs are always local regardless of tool config if no additional
|
||||
@@ -767,7 +902,6 @@ class Tool:
|
||||
# Set job runner to the cluster default
|
||||
self.job_runners = [ { "url" : self.app.config.default_cluster_job_runner } ]
|
||||
# Set custom runner(s) if they're defined.
|
||||
self_id = self.id.lower()
|
||||
if self_id in self.app.config.tool_runners:
|
||||
self.job_runners = self.app.config.tool_runners[ self_id ]
|
||||
# Is this a 'hidden' tool (hidden in tool menu)
|
||||
@@ -843,7 +977,7 @@ class Tool:
|
||||
self.is_workflow_compatible = self.check_workflow_compatible()
|
||||
# Trackster configuration.
|
||||
trackster_conf = root.find( "trackster_conf" )
|
||||
if trackster_conf:
|
||||
if trackster_conf is not None:
|
||||
self.trackster_conf = TracksterConfig.parse( trackster_conf )
|
||||
else:
|
||||
self.trackster_conf = None
|
||||
@@ -1199,19 +1333,8 @@ class Tool:
|
||||
for requirement_elem in requirements_elem.findall( 'requirement' ):
|
||||
name = util.xml_text( requirement_elem )
|
||||
type = requirement_elem.get( "type", "package" )
|
||||
if type == 'fabfile':
|
||||
# The fabric script will include all necessary information for
|
||||
# automatically installing the tool dependencies.
|
||||
fabfile = requirement_elem.get( "fabfile" )
|
||||
method = requirement_elem.get( "method" )
|
||||
version = None
|
||||
else:
|
||||
# For backward compatibility, requirements tag sets should not require the
|
||||
# use of a fabric script.
|
||||
version = requirement_elem.get( "version" )
|
||||
fabfile = None
|
||||
method = None
|
||||
requirement = ToolRequirement( name=name, type=type, version=version, fabfile=fabfile, method=method )
|
||||
version = requirement_elem.get( "version", None )
|
||||
requirement = ToolRequirement( name=name, type=type, version=version )
|
||||
self.requirements.append( requirement )
|
||||
def check_workflow_compatible( self ):
|
||||
"""
|
||||
@@ -1326,6 +1449,8 @@ class Tool:
|
||||
# on the standard run form) or "URL" (a parameter provided by
|
||||
# external data source tools).
|
||||
if "runtool_btn" not in incoming and "URL" not in incoming:
|
||||
if not self.display_interface:
|
||||
return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
|
||||
return "tool_form.mako", dict( errors={}, tool_state=state, param_values={}, incoming={} )
|
||||
# Process incoming data
|
||||
if not( self.check_values ):
|
||||
@@ -1374,6 +1499,8 @@ class Tool:
|
||||
state.page += 1
|
||||
# Fill in the default values for the next page
|
||||
self.fill_in_new_state( trans, self.inputs_by_page[ state.page ], state.inputs )
|
||||
if not self.display_interface:
|
||||
return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
|
||||
return 'tool_form.mako', dict( errors=errors, tool_state=state )
|
||||
else:
|
||||
try:
|
||||
@@ -1386,6 +1513,8 @@ class Tool:
|
||||
except:
|
||||
pass
|
||||
# Just a refresh, render the form with updated state and errors.
|
||||
if not self.display_interface:
|
||||
return 'message.mako', dict( status='info', message="The interface for this tool cannot be displayed", refresh_frames=['everything'] )
|
||||
return 'tool_form.mako', dict( errors=errors, tool_state=state )
|
||||
def find_fieldstorage( self, x ):
|
||||
if isinstance( x, FieldStorage ):
|
||||
@@ -2110,7 +2239,7 @@ class Tool:
|
||||
shutil.rmtree(temp_file_path)
|
||||
except:
|
||||
continue
|
||||
def collect_child_datasets( self, output):
|
||||
def collect_child_datasets( self, output, job_working_directory ):
|
||||
"""
|
||||
Look for child dataset files, create HDA and attach to parent.
|
||||
"""
|
||||
@@ -2118,7 +2247,12 @@ class Tool:
|
||||
# Loop through output file names, looking for generated children in
|
||||
# form of 'child_parentId_designation_visibility_extension'
|
||||
for name, outdata in output.items():
|
||||
for filename in glob.glob(os.path.join(self.app.config.new_file_path,"child_%i_*" % outdata.id) ):
|
||||
filenames = []
|
||||
if 'new_file_path' in self.app.config.collect_outputs_from:
|
||||
filenames.extend( glob.glob(os.path.join(self.app.config.new_file_path,"child_%i_*" % outdata.id) ) )
|
||||
if 'job_working_directory' in self.app.config.collect_outputs_from:
|
||||
filenames.extend( glob.glob(os.path.join(job_working_directory,"child_%i_*" % outdata.id) ) )
|
||||
for filename in filenames:
|
||||
if not name in children:
|
||||
children[name] = {}
|
||||
fields = os.path.basename(filename).split("_")
|
||||
@@ -2138,7 +2272,7 @@ class Tool:
|
||||
sa_session=self.sa_session )
|
||||
self.app.security_agent.copy_dataset_permissions( outdata.dataset, child_dataset.dataset )
|
||||
# Move data from temp location to dataset location
|
||||
self.app.object_store.update_from_file(child_dataset.dataset, filename, create=True)
|
||||
self.app.object_store.update_from_file(child_dataset.dataset, file_name=filename, create=True)
|
||||
self.sa_session.add( child_dataset )
|
||||
self.sa_session.flush()
|
||||
child_dataset.set_size()
|
||||
@@ -2170,7 +2304,7 @@ class Tool:
|
||||
self.sa_session.add( child_dataset )
|
||||
self.sa_session.flush()
|
||||
return children
|
||||
def collect_primary_datasets( self, output):
|
||||
def collect_primary_datasets( self, output, job_working_directory ):
|
||||
"""
|
||||
Find any additional datasets generated by a tool and attach (for
|
||||
cases where number of outputs is not known in advance).
|
||||
@@ -2180,7 +2314,12 @@ class Tool:
|
||||
# datasets in form of:
|
||||
# 'primary_associatedWithDatasetID_designation_visibility_extension(_DBKEY)'
|
||||
for name, outdata in output.items():
|
||||
for filename in glob.glob(os.path.join(self.app.config.new_file_path,"primary_%i_*" % outdata.id) ):
|
||||
filenames = []
|
||||
if 'new_file_path' in self.app.config.collect_outputs_from:
|
||||
filenames.extend( glob.glob(os.path.join(self.app.config.new_file_path,"primary_%i_*" % outdata.id) ) )
|
||||
if 'job_working_directory' in self.app.config.collect_outputs_from:
|
||||
filenames.extend( glob.glob(os.path.join(job_working_directory,"primary_%i_*" % outdata.id) ) )
|
||||
for filename in filenames:
|
||||
if not name in primary_datasets:
|
||||
primary_datasets[name] = {}
|
||||
fields = os.path.basename(filename).split("_")
|
||||
@@ -2205,7 +2344,7 @@ class Tool:
|
||||
self.sa_session.add( primary_data )
|
||||
self.sa_session.flush()
|
||||
# Move data from temp location to dataset location
|
||||
self.app.object_store.update_from_file(primary_data.dataset, filename, create=True)
|
||||
self.app.object_store.update_from_file(primary_data.dataset, file_name=filename, create=True)
|
||||
primary_data.set_size()
|
||||
primary_data.name = "%s (%s)" % ( outdata.name, designation )
|
||||
primary_data.info = outdata.info
|
||||
@@ -2238,6 +2377,54 @@ class Tool:
|
||||
self.sa_session.add( new_data )
|
||||
self.sa_session.flush()
|
||||
return primary_datasets
|
||||
|
||||
def _is_hidden_for_user( self, user ):
|
||||
if self.hidden or ( not user and self.require_login ):
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_dict( self, trans, for_link=False, for_display=False ):
|
||||
""" Returns dict of tool. """
|
||||
|
||||
# Basic information
|
||||
tool_dict = { 'id': self.id, 'name': self.name,
|
||||
'version': self.version, 'description': self.description }
|
||||
|
||||
if for_link:
|
||||
# Create tool link.
|
||||
if not self.tool_type.startswith( 'data_source' ):
|
||||
link = url_for( controller='tool_runner', tool_id=self.id )
|
||||
else:
|
||||
link = url_for( self.action, **self.get_static_param_values( trans ) )
|
||||
|
||||
# Basic information
|
||||
tool_dict.update( { 'type': 'tool', 'link': link,
|
||||
'min_width': self.uihints.get( 'minwidth', -1 ),
|
||||
'target': self.target } )
|
||||
|
||||
if for_display:
|
||||
# Dictify inputs.
|
||||
inputs = []
|
||||
for name, input in self.inputs.items():
|
||||
param_dict = { 'name' : name, 'label' : input.label }
|
||||
if isinstance( input, DataToolParameter ):
|
||||
param_dict.update( { 'type' : 'data', 'html' : urllib.quote( input.get_html( trans ) ) } )
|
||||
elif isinstance( input, SelectToolParameter ):
|
||||
param_dict.update( { 'type' : 'select', 'html' : urllib.quote( input.get_html( trans ) ) } )
|
||||
elif isinstance( input, Conditional ):
|
||||
# TODO.
|
||||
pass
|
||||
else:
|
||||
param_dict.update( { 'type' : '??', 'init_value' : input.value, \
|
||||
'html' : urllib.quote( input.get_html( trans ) ) } )
|
||||
inputs.append( param_dict )
|
||||
|
||||
tool_dict[ 'inputs' ] = inputs
|
||||
|
||||
# Dictify outputs.
|
||||
pass
|
||||
|
||||
return tool_dict
|
||||
|
||||
class DataSourceTool( Tool ):
|
||||
"""
|
||||
@@ -2369,6 +2556,9 @@ class ExportHistoryTool( Tool ):
|
||||
class ImportHistoryTool( Tool ):
|
||||
tool_type = 'import_history'
|
||||
|
||||
class GenomeIndexTool( Tool ):
|
||||
tool_type = 'index_genome'
|
||||
|
||||
# Populate tool_type to ToolClass mappings
|
||||
tool_types = {}
|
||||
for tool_class in [ Tool, DataDestinationTool, SetMetadataTool, DataSourceTool, AsyncDataSourceTool ]:
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import tempfile
|
||||
from __init__ import ToolAction
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.tools.genome_index import *
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class GenomeIndexToolAction( ToolAction ):
|
||||
"""Tool action used for exporting a history to an archive. """
|
||||
|
||||
def execute( self, tool, trans, *args, **kwargs ):
|
||||
#
|
||||
# Get genome to index.
|
||||
#
|
||||
incoming = kwargs['incoming']
|
||||
#
|
||||
# Create the job and output dataset objects
|
||||
#
|
||||
job = trans.app.model.Job()
|
||||
job.tool_id = tool.id
|
||||
job.user_id = incoming['user']
|
||||
start_job_state = job.state # should be job.states.NEW
|
||||
job.state = job.states.WAITING # we need to set job state to something other than NEW, or else when tracking jobs in db it will be picked up before we have added input / output parameters
|
||||
trans.sa_session.add( job )
|
||||
|
||||
# Create dataset that will serve as archive.
|
||||
temp_dataset = trans.app.model.Dataset( state=trans.app.model.Dataset.states.NEW )
|
||||
trans.sa_session.add( temp_dataset )
|
||||
|
||||
trans.sa_session.flush() # ensure job.id and archive_dataset.id are available
|
||||
trans.app.object_store.create( temp_dataset ) # set the object store id, create dataset (because galaxy likes having datasets)
|
||||
|
||||
#
|
||||
# Setup job and job wrapper.
|
||||
#
|
||||
|
||||
# Add association for keeping track of job, history, archive relationship.
|
||||
user = trans.sa_session.query( trans.app.model.User ).get( int( incoming['user'] ) )
|
||||
assoc = trans.app.model.GenomeIndexToolData( job=job, dataset=temp_dataset, fasta_path=incoming['path'], \
|
||||
indexer=incoming['indexer'], user=user, \
|
||||
deferred_job=kwargs['deferred'], transfer_job=kwargs['transfer'] )
|
||||
trans.sa_session.add( assoc )
|
||||
|
||||
job_wrapper = GenomeIndexToolWrapper( job )
|
||||
cmd_line = job_wrapper.setup_job( assoc )
|
||||
#
|
||||
# Add parameters to job_parameter table.
|
||||
#
|
||||
|
||||
# Set additional parameters.
|
||||
incoming[ '__GENOME_INDEX_COMMAND__' ] = cmd_line
|
||||
for name, value in tool.params_to_strings( incoming, trans.app ).iteritems():
|
||||
job.add_parameter( name, value )
|
||||
|
||||
job.state = start_job_state # job inputs have been configured, restore initial job state
|
||||
trans.sa_session.flush()
|
||||
|
||||
|
||||
# Queue the job for execution
|
||||
trans.app.job_queue.put( job.id, tool )
|
||||
log.info( "Added genome index job to the job queue, id: %s" % str( job.id ) )
|
||||
|
||||
return job, odict()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __init__ import ToolAction
|
||||
from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.util.json import to_json_string
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
@@ -8,7 +9,7 @@ log = logging.getLogger( __name__ )
|
||||
class SetMetadataToolAction( ToolAction ):
|
||||
"""Tool action used for setting external metadata on an existing dataset"""
|
||||
|
||||
def execute( self, tool, trans, incoming = {}, set_output_hid = False, overwrite = True, history=None ):
|
||||
def execute( self, tool, trans, incoming = {}, set_output_hid = False, overwrite = True, history=None, job_params=None ):
|
||||
for name, value in incoming.iteritems():
|
||||
if isinstance( value, trans.app.model.HistoryDatasetAssociation ):
|
||||
dataset = value
|
||||
@@ -30,6 +31,8 @@ class SetMetadataToolAction( ToolAction ):
|
||||
job.tool_id = tool.id
|
||||
if trans.user:
|
||||
job.user_id = trans.user.id
|
||||
if job_params:
|
||||
job.params = to_json_string( job_params )
|
||||
start_job_state = job.state #should be job.states.NEW
|
||||
try:
|
||||
# For backward compatibility, some tools may not have versions yet.
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import os, shutil, logging, tempfile, json, tarfile
|
||||
from galaxy import model, util
|
||||
from galaxy.web.framework.helpers import to_unicode
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util.json import *
|
||||
from galaxy.web.base.controller import UsesHistory
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def load_genome_index_tools( toolbox ):
|
||||
""" Adds tools for indexing genomes via the main job runner. """
|
||||
# Use same process as that used in load_external_metadata_tool; see that
|
||||
# method for why create tool description files on the fly.
|
||||
tool_xml_text = """
|
||||
<tool id="__GENOME_INDEX__" name="Index Genome" version="0.1" tool_type="genome_index">
|
||||
<type class="GenomeIndexTool" module="galaxy.tools"/>
|
||||
<action module="galaxy.tools.actions.index_genome" class="GenomeIndexToolAction"/>
|
||||
<command>$__GENOME_INDEX_COMMAND__ $output_file $output_file.files_path</command>
|
||||
<inputs>
|
||||
<param name="__GENOME_INDEX_COMMAND__" type="hidden"/>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data format="txt" name="output_file"/>
|
||||
</outputs>
|
||||
</tool>
|
||||
"""
|
||||
|
||||
# Load export tool.
|
||||
tmp_name = tempfile.NamedTemporaryFile()
|
||||
tmp_name.write( tool_xml_text )
|
||||
tmp_name.flush()
|
||||
genome_index_tool = toolbox.load_tool( tmp_name.name )
|
||||
toolbox.tools_by_id[ genome_index_tool.id ] = genome_index_tool
|
||||
log.debug( "Loaded genome index tool: %s", genome_index_tool.id )
|
||||
|
||||
class GenomeIndexToolWrapper( object ):
|
||||
""" Provides support for performing jobs that index a genome. """
|
||||
def __init__( self, job_id ):
|
||||
self.locations = dict()
|
||||
self.job_id = job_id
|
||||
|
||||
def setup_job( self, genobj ):
|
||||
""" Perform setup for job to index a genome and return an archive. Method generates
|
||||
attribute files, sets the corresponding attributes in the associated database
|
||||
object, and returns a command line for running the job. The command line
|
||||
includes the command, inputs, and options; it does not include the output
|
||||
file because it must be set at runtime. """
|
||||
|
||||
#
|
||||
# Create and return command line for running tool.
|
||||
#
|
||||
scriptpath = os.path.join( os.path.abspath( os.getcwd() ), "lib/galaxy/tools/genome_index/index_genome.py" )
|
||||
return "python %s %s %s" % ( scriptpath, genobj.indexer, genobj.fasta_path )
|
||||
|
||||
def postprocessing( self, sa_session, app ):
|
||||
""" Finish the job, move the finished indexes to their final resting place,
|
||||
and update the .loc files where applicable. """
|
||||
gitd = sa_session.query( model.GenomeIndexToolData ).filter_by( job_id=self.job_id ).first()
|
||||
indexdirs = dict( bfast='bfast_index', bowtie='bowtie_index', bowtie2='bowtie2_index',
|
||||
bwa='bwa_index', perm='perm_%s_index', picard='srma_index', sam='sam_index' )
|
||||
|
||||
|
||||
if gitd:
|
||||
destination = None
|
||||
tdtman = ToolDataTableManager()
|
||||
xmltree = tdtman.load_from_config_file(app.config.tool_data_table_config_path)
|
||||
for node in xmltree:
|
||||
table = node.get('name')
|
||||
location = node.findall('file')[0].get('path')
|
||||
self.locations[table] = os.path.abspath( location )
|
||||
locbase = os.path.abspath( os.path.split( self.locations['all_fasta'] )[0] )
|
||||
deferred = sa_session.query( model.DeferredJob ).filter_by( id=gitd.deferred_job_id ).first()
|
||||
params = deferred.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
basepath = os.path.join( os.path.abspath( app.config.genome_data_path ), dbkey )
|
||||
intname = params[ 'intname' ]
|
||||
indexer = gitd.indexer
|
||||
workingdir = os.path.abspath( gitd.dataset.extra_files_path )
|
||||
fp = open( gitd.dataset.get_file_name(), 'r' )
|
||||
logloc = json.load( fp )
|
||||
fp.close()
|
||||
location = []
|
||||
indexdata = gitd.dataset.extra_files_path
|
||||
if indexer == '2bit':
|
||||
indexdata = os.path.join( workingdir, '%s.2bit' % dbkey )
|
||||
destination = os.path.join( basepath, 'seq', '%s.2bit' % dbkey )
|
||||
location.append( dict( line='\t'.join( [ 'seq', dbkey, os.path.join( destination, '%s.2bit' % dbkey ) ] ), file= os.path.join( locbase, 'alignseq.loc' ) ) )
|
||||
elif indexer == 'bowtie':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'bowtie_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'bowtie_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'bowtie_indexes_color' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'bowtie2':
|
||||
destination = os.path.join( basepath, 'bowtie2_index' )
|
||||
for line in logloc[ 'nt' ]:
|
||||
idx = line
|
||||
locfile = self.locations[ 'bowtie2_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'bwa':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'bwa_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'bwa_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'bwa_indexes_color' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'perm':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'perm_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line.pop()
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'perm_base_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'perm_color_indexes' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
line.append( locdir )
|
||||
location.append( dict( line='\t'.join( line ), file=locfile ) )
|
||||
elif indexer == 'picard':
|
||||
destination = os.path.join( basepath, 'srma_index' )
|
||||
for var in [ 'nt' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
locfile = self.locations[ 'picard_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'sam':
|
||||
destination = os.path.join( basepath, 'sam_index' )
|
||||
for var in [ 'nt' ]:
|
||||
for line in logloc[ var ]:
|
||||
locfile = self.locations[ 'sam_fa_indexes' ]
|
||||
locdir = os.path.join( destination, line )
|
||||
location.append( dict( line='\t'.join( [ 'index', dbkey, locdir ] ), file=locfile ) )
|
||||
|
||||
if destination is not None and os.path.exists( os.path.split( destination )[0] ) and not os.path.exists( destination ):
|
||||
log.debug( 'Moving %s to %s' % ( indexdata, destination ) )
|
||||
shutil.move( indexdata, destination )
|
||||
if indexer not in [ '2bit' ]:
|
||||
genome = '%s.fa'
|
||||
target = os.path.join( destination, genome )
|
||||
farel = os.path.relpath( os.path.join( basepath, 'seq', genome ), destination )
|
||||
os.symlink( farel, target )
|
||||
if os.path.exists( os.path.join( destination, 'cs' ) ):
|
||||
target = os.path.join( destination, 'cs', genome )
|
||||
farel = os.path.relpath( os.path.join( basepath, 'seq', genome ), os.path.join( destination, 'cs' ) )
|
||||
os.symlink( os.path.join( farel, target ) )
|
||||
for line in location:
|
||||
self._add_line( line[ 'file' ], line[ 'line' ] )
|
||||
|
||||
def _ex_tar( self, directory, filename ):
|
||||
fh = tarfile.open( os.path.join( directory, filename ) )
|
||||
fh.extractall( path=directory )
|
||||
fh.close()
|
||||
os.remove( os.path.join( directory, filename ) )
|
||||
|
||||
def _add_line( self, locfile, newline ):
|
||||
filepath = locfile
|
||||
origlines = []
|
||||
output = []
|
||||
comments = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
destfile.write( '\n'.join( origlines ) )
|
||||
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Export a history to an archive file using attribute files.
|
||||
|
||||
usage: %prog history_attrs dataset_attrs job_attrs out_file
|
||||
-G, --gzip: gzip archive file
|
||||
"""
|
||||
import optparse, sys, os, tempfile, time, subprocess, shlex, json, tarfile, shutil
|
||||
|
||||
class ManagedIndexer():
|
||||
def __init__( self, output_file, infile, workingdir ):
|
||||
self.workingdir = os.path.abspath( workingdir )
|
||||
self.outfile = open( os.path.abspath( output_file ), 'w' )
|
||||
self.basedir = os.path.split( self.workingdir )[0]
|
||||
self.fasta = os.path.abspath( infile )
|
||||
self.locations = dict( nt=[], cs=[] )
|
||||
self.log = []
|
||||
self.indexers = {
|
||||
'bwa': '_bwa',
|
||||
'bowtie': '_bowtie',
|
||||
'bowtie2': '_bowtie2',
|
||||
'2bit': '_twobit',
|
||||
'perm': '_perm',
|
||||
'bfast': '_bfast',
|
||||
'picard': '_picard',
|
||||
'sam': '_sam'
|
||||
}
|
||||
if not os.path.exists( self.workingdir ):
|
||||
os.makedirs( self.workingdir )
|
||||
self.logfile = open( os.path.join( self.workingdir, 'ManagedIndexer.log' ), 'w+' )
|
||||
|
||||
def run_indexer( self, indexer ):
|
||||
self.fapath = self.fasta
|
||||
self.fafile = os.path.basename( self.fapath )
|
||||
with WithChDir( self.basedir ):
|
||||
if indexer not in self.indexers:
|
||||
raise KeyError, 'The requested indexing function does not exist'
|
||||
else:
|
||||
with WithChDir( self.workingdir ):
|
||||
self._log( 'Running indexer %s.' % indexer )
|
||||
result = getattr( self, self.indexers[ indexer ] )()
|
||||
if result is None:
|
||||
self._log( 'Error running indexer %s.' % indexer )
|
||||
self._flush_files()
|
||||
raise Exception
|
||||
else:
|
||||
self._log( 'Indexer %s completed successfully.' % indexer )
|
||||
self._flush_files()
|
||||
|
||||
def _flush_files( self ):
|
||||
json.dump( self.locations, self.outfile )
|
||||
self.outfile.close()
|
||||
self.logfile.close()
|
||||
|
||||
def _log( self, stuff ):
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S %z')
|
||||
self.logfile.write( "[%s] %s\n" % (timestamp, stuff) )
|
||||
|
||||
def _bwa( self ):
|
||||
with WithChDir( self.workingdir ):
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'bwa index -a bwtsw %s' % self.fafile )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
newcommand = shlex.split( 'bwa index -c %s' % self.fafile )
|
||||
result = call( newcommand, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
os.makedirs( 'cs' )
|
||||
with WithChDir( 'cs' ):
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'bwa index -a bwtsw -c %s' % self.fafile )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
newcommand = shlex.split( 'bwa index -c %s' % self.fafile )
|
||||
result = call( newcommand, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'cs' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
self.locations[ 'cs' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _bowtie( self ):
|
||||
ref_base = os.path.splitext(self.fafile)[0]
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'bowtie-build -f %s %s' % ( self.fafile, ref_base ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( ref_base )
|
||||
os.remove( self.fafile )
|
||||
indexdir = os.path.join( os.getcwd(), 'cs' )
|
||||
os.makedirs( indexdir )
|
||||
with WithChDir( indexdir ):
|
||||
ref_base = os.path.splitext(self.fafile)[0]
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'bowtie-build -C -f %s %s' % ( self.fafile, ref_base ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'cs' ].append( ref_base )
|
||||
else:
|
||||
return False
|
||||
os.remove( os.path.join( indexdir, self.fafile ) )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _bowtie2( self ):
|
||||
ref_base = os.path.splitext(self.fafile)[0]
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'bowtie2-build %s %s' % ( self.fafile, ref_base ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( ref_base )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _twobit( self ):
|
||||
"""Index reference files using 2bit for random access.
|
||||
"""
|
||||
ref_base = os.path.splitext(self.fafile)[0]
|
||||
out_file = "%s.2bit" % ref_base
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'faToTwoBit %s %s' % ( self.fafile, out_file ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations['nt'].append( out_file )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _perm( self ):
|
||||
local_ref = self.fafile
|
||||
if not os.path.exists( local_ref ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
genome = os.path.splitext( local_ref )[0]
|
||||
read_length = 50
|
||||
for seed in [ 'F3', 'F4' ]:
|
||||
key = '%s_%s_%s' % (genome, seed, read_length)
|
||||
desc = '%s: seed=%s, read length=%s' % (genome, seed, read_length)
|
||||
index = "%s_base_%s_%s.index" % (genome, seed, read_length)
|
||||
command = shlex.split("PerM %s %s --readFormat fastq --seed %s -m -s %s" % (local_ref, read_length, seed, index))
|
||||
result = subprocess.call( command )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( [ key, desc, index ] )
|
||||
else:
|
||||
return False
|
||||
os.remove( local_ref )
|
||||
os.makedirs( 'cs' )
|
||||
with WithChDir( 'cs' ):
|
||||
if not os.path.exists( local_ref ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
for seed in [ 'F3', 'F4' ]:
|
||||
key = '%s_%s_%s' % (genome, seed, read_length)
|
||||
desc = '%s: seed=%s, read length=%s' % (genome, seed, read_length)
|
||||
index = "%s_color_%s_%s.index" % (genome, seed, read_length)
|
||||
command = shlex.split("PerM %s %s --readFormat csfastq --seed %s -m -s %s" % (local_ref, read_length, seed, index))
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'cs' ].append( [ key, desc, index ] )
|
||||
else:
|
||||
return False
|
||||
os.remove( local_ref )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
|
||||
def _bfast( self ):
|
||||
"""Indexes bfast in color and nucleotide space for longer reads.
|
||||
|
||||
This preps for 40+bp sized reads, which is bfast's strength.
|
||||
"""
|
||||
dir_name_nt = 'nt'
|
||||
dir_name_cs = 'cs'
|
||||
window_size = 14
|
||||
bfast_nt_masks = [
|
||||
"1111111111111111111111",
|
||||
"1111101110111010100101011011111",
|
||||
"1011110101101001011000011010001111111",
|
||||
"10111001101001100100111101010001011111",
|
||||
"11111011011101111011111111",
|
||||
"111111100101001000101111101110111",
|
||||
"11110101110010100010101101010111111",
|
||||
"111101101011011001100000101101001011101",
|
||||
"1111011010001000110101100101100110100111",
|
||||
"1111010010110110101110010110111011",
|
||||
]
|
||||
bfast_color_masks = [
|
||||
"1111111111111111111111",
|
||||
"111110100111110011111111111",
|
||||
"10111111011001100011111000111111",
|
||||
"1111111100101111000001100011111011",
|
||||
"111111110001111110011111111",
|
||||
"11111011010011000011000110011111111",
|
||||
"1111111111110011101111111",
|
||||
"111011000011111111001111011111",
|
||||
"1110110001011010011100101111101111",
|
||||
"111111001000110001011100110001100011111",
|
||||
]
|
||||
local_ref = self.fafile
|
||||
os.makedirs( dir_name_nt )
|
||||
os.makedirs( dir_name_cs )
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
with WithChDir( dir_name_nt ):
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
# nucleotide space
|
||||
command = shlex.split( "bfast fasta2brg -f %s -A 0" % local_ref )
|
||||
result = subprocess.call( command, stderr=self.logfile )
|
||||
for i, mask in enumerate( bfast_nt_masks ):
|
||||
command = shlex.split("bfast index -d 1 -n 4 -f %s -A 0 -m %s -w %s -i %s" %
|
||||
( local_ref, mask, window_size, i + 1 ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
os.remove( self.fafile )
|
||||
if result != 0:
|
||||
return False
|
||||
else:
|
||||
os.remove( self.fafile )
|
||||
with WithChDir( dir_name_cs ):
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
# colorspace
|
||||
command = shlex.split( "bfast fasta2brg -f %s -A 1" % local_ref )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
for i, mask in enumerate( bfast_color_masks ):
|
||||
command = shlex.split( "bfast index -d 1 -n 4 -f %s -A 1 -m %s -w %s -i %s" %
|
||||
( local_ref, mask, window_size, i + 1 ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
return False
|
||||
else:
|
||||
os.remove( self.fafile )
|
||||
self.locations = None
|
||||
return True
|
||||
|
||||
def _picard( self ):
|
||||
local_ref = self.fafile
|
||||
srma = '/Users/dave/srma.jar'
|
||||
genome = os.path.splitext( self.fafile )[0]
|
||||
if not os.path.exists( self.fafile ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split( 'samtools faidx %s' % self.fafile )
|
||||
subprocess.call( command, stderr=self.logfile )
|
||||
os.rename( '%s.fai' % self.fafile, '%s.fai' % genome )
|
||||
command = shlex.split( "java -cp %s net.sf.picard.sam.CreateSequenceDictionary R=%s O=%s/%s.dict URI=%s" \
|
||||
% ( srma, local_ref, os.curdir, genome, local_ref ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
return False
|
||||
else:
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
#os.remove( '%s.fai' % genome )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
|
||||
def _sam( self ):
|
||||
local_ref = self.fafile
|
||||
local_file = os.path.splitext( self.fafile )[ 0 ]
|
||||
if not os.path.exists( local_ref ):
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
command = shlex.split("samtools faidx %s" % local_ref)
|
||||
result = subprocess.call( command, stderr=self.logfile )
|
||||
if result != 0:
|
||||
return False
|
||||
else:
|
||||
self.locations[ 'nt' ].append( local_ref )
|
||||
os.remove( local_ref )
|
||||
return True
|
||||
|
||||
class WithChDir():
|
||||
def __init__( self, target ):
|
||||
self.working = target
|
||||
self.previous = os.getcwd()
|
||||
def __enter__( self ):
|
||||
os.chdir( self.working )
|
||||
def __exit__( self, *args ):
|
||||
os.chdir( self.previous )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Parse command line.
|
||||
parser = optparse.OptionParser()
|
||||
(options, args) = parser.parse_args()
|
||||
indexer, infile, outfile, working_dir = args
|
||||
|
||||
# Create archive.
|
||||
idxobj = ManagedIndexer( outfile, infile, working_dir )
|
||||
idxobj.run_indexer( indexer )
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os, shutil, logging, tempfile, simplejson
|
||||
from galaxy import model
|
||||
from galaxy.tools.parameters.basic import UnvalidatedValue
|
||||
from galaxy.web.framework.helpers import to_unicode
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util.json import *
|
||||
@@ -324,6 +325,8 @@ class JobExportHistoryArchiveWrapper( object, UsesHistory, UsesAnnotations ):
|
||||
"annotation" : to_unicode( getattr( obj, 'annotation', '' ) ),
|
||||
"tags" : get_item_tag_dict( obj ),
|
||||
}
|
||||
if isinstance( obj, UnvalidatedValue ):
|
||||
return obj.__str__()
|
||||
return simplejson.JSONEncoder.default( self, obj )
|
||||
|
||||
#
|
||||
@@ -415,8 +418,13 @@ class JobExportHistoryArchiveWrapper( object, UsesHistory, UsesAnnotations ):
|
||||
params_dict[ name ] = value
|
||||
job_attrs[ 'params' ] = params_dict
|
||||
|
||||
# Get input, output datasets.
|
||||
input_datasets = [ assoc.dataset.hid for assoc in job.input_datasets ]
|
||||
# -- Get input, output datasets. --
|
||||
|
||||
input_datasets = []
|
||||
for assoc in job.input_datasets:
|
||||
# Optional data inputs will not have a dataset.
|
||||
if assoc.dataset:
|
||||
input_datasets.append( assoc.dataset.hid )
|
||||
job_attrs[ 'input_datasets' ] = input_datasets
|
||||
output_datasets = [ assoc.dataset.hid for assoc in job.output_datasets ]
|
||||
job_attrs[ 'output_datasets' ] = output_datasets
|
||||
|
||||
@@ -1056,26 +1056,35 @@ class DrillDownSelectToolParameter( SelectToolParameter ):
|
||||
... </param>
|
||||
... ''' ) )
|
||||
>>> print p.get_html()
|
||||
<div><ul class="toolParameterExpandableCollapsable">
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="some_name" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="checkbox" name="some_name" value="option1"">Option 1
|
||||
</li>
|
||||
<li><input type="checkbox" name="some_name" value="option2"">Option 2
|
||||
</li>
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="some_name" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="checkbox" name="some_name" value="option3"">Option 3
|
||||
</li>
|
||||
<li><input type="checkbox" name="some_name" value="option4"">Option 4
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><input type="checkbox" name="some_name" value="option5"">Option 5
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="form-row drilldown-container" id="drilldown--736f6d655f6e616d65">
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--736f6d655f6e616d65-68656164696e6731-click"></span>
|
||||
<input type="checkbox" name="some_name" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--736f6d655f6e616d65-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="some_name" value="option1" >Option 1
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="some_name" value="option2" >Option 2
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--736f6d655f6e616d65-68656164696e6731-68656164696e6731-click"></span>
|
||||
<input type="checkbox" name="some_name" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--736f6d655f6e616d65-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="some_name" value="option3" >Option 3
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="some_name" value="option4" >Option 4
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="some_name" value="option5" >Option 5
|
||||
</div>
|
||||
</div>
|
||||
>>> p = DrillDownSelectToolParameter( None, XML(
|
||||
... '''
|
||||
... <param name="some_name" type="drill_down" display="radio" hierarchy="recurse" multiple="false">
|
||||
@@ -1093,26 +1102,35 @@ class DrillDownSelectToolParameter( SelectToolParameter ):
|
||||
... </param>
|
||||
... ''' ) )
|
||||
>>> print p.get_html()
|
||||
<div><ul class="toolParameterExpandableCollapsable">
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="some_name" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="radio" name="some_name" value="option1"">Option 1
|
||||
</li>
|
||||
<li><input type="radio" name="some_name" value="option2"">Option 2
|
||||
</li>
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="some_name" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="radio" name="some_name" value="option3"">Option 3
|
||||
</li>
|
||||
<li><input type="radio" name="some_name" value="option4"">Option 4
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><input type="radio" name="some_name" value="option5"">Option 5
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="form-row drilldown-container" id="drilldown--736f6d655f6e616d65">
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--736f6d655f6e616d65-68656164696e6731-click"></span>
|
||||
<input type="radio" name="some_name" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--736f6d655f6e616d65-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="some_name" value="option1" >Option 1
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="some_name" value="option2" >Option 2
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--736f6d655f6e616d65-68656164696e6731-68656164696e6731-click"></span>
|
||||
<input type="radio" name="some_name" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--736f6d655f6e616d65-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="some_name" value="option3" >Option 3
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="some_name" value="option4" >Option 4
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="some_name" value="option5" >Option 5
|
||||
</div>
|
||||
</div>
|
||||
>>> print p.options
|
||||
[{'selected': False, 'name': 'Heading 1', 'value': 'heading1', 'options': [{'selected': False, 'name': 'Option 1', 'value': 'option1', 'options': []}, {'selected': False, 'name': 'Option 2', 'value': 'option2', 'options': []}, {'selected': False, 'name': 'Heading 1', 'value': 'heading1', 'options': [{'selected': False, 'name': 'Option 3', 'value': 'option3', 'options': []}, {'selected': False, 'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'selected': False, 'name': 'Option 5', 'value': 'option5', 'options': []}]
|
||||
"""
|
||||
@@ -1133,7 +1151,9 @@ class DrillDownSelectToolParameter( SelectToolParameter ):
|
||||
from_file = os.path.join( tool.app.config.tool_data_path, from_file )
|
||||
elem = XML( "<root>%s</root>" % open( from_file ).read() )
|
||||
self.is_dynamic = False
|
||||
self.dynamic_options = None #backwards compatibility with SelectToolParameter's old dynamic options and late validation
|
||||
self.dynamic_options = elem.get( 'dynamic_options' , None )
|
||||
if self.dynamic_options:
|
||||
self.is_dynamic = True
|
||||
self.options = []
|
||||
self.filtered = {}
|
||||
if elem.find( 'filter' ):
|
||||
@@ -1148,12 +1168,23 @@ class DrillDownSelectToolParameter( SelectToolParameter ):
|
||||
if filter.get( 'value' ) not in self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )]:
|
||||
self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )][filter.get( 'value' )] = []
|
||||
recurse_option_elems( self.filtered[filter.get( 'data_ref' )][filter.get( 'meta_key' )][filter.get( 'value' )], filter.find( 'options' ).findall( 'option' ) )
|
||||
else:
|
||||
elif not self.dynamic_options:
|
||||
recurse_option_elems( self.options, elem.find( 'options' ).findall( 'option' ) )
|
||||
|
||||
def _get_options_from_code( self, trans=None, value=None, other_values=None ):
|
||||
assert self.dynamic_options, Exception( "dynamic_options was not specifed" )
|
||||
call_other_values = { '__trans__': trans, '__value__': value }
|
||||
if other_values:
|
||||
call_other_values.update( other_values.dict )
|
||||
return eval( self.dynamic_options, self.tool.code_namespace, call_other_values )
|
||||
|
||||
|
||||
def get_options( self, trans=None, value=None, other_values={} ):
|
||||
if self.is_dynamic:
|
||||
options = []
|
||||
if self.dynamic_options:
|
||||
options = self._get_options_from_code( trans=trans, value=value, other_values=other_values )
|
||||
else:
|
||||
options = []
|
||||
for filter_key, filter_value in self.filtered.iteritems():
|
||||
dataset = other_values[filter_key]
|
||||
if dataset.__class__.__name__.endswith( "DatasetFilenameWrapper" ): #this is a bad way to check for this, but problems importing class ( due to circular imports? )
|
||||
|
||||
@@ -14,7 +14,7 @@ class Base:
|
||||
def cond_plural(self, number_of_records, word) :
|
||||
'''Returns the plural form of a word if first parameter is greater than 1'''
|
||||
|
||||
if number_of_records > 1 :
|
||||
if number_of_records != 1:
|
||||
return self.pluralize(word)
|
||||
else :
|
||||
return word
|
||||
@@ -385,4 +385,4 @@ class Inflector:
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THIS SOFTWARE.
|
||||
# THIS SOFTWARE.
|
||||
|
||||
+37
-40
@@ -11,23 +11,22 @@ class odict(UserDict):
|
||||
This dictionary class extends UserDict to record the order in which items are
|
||||
added. Calling keys(), values(), items(), etc. will return results in this
|
||||
order.
|
||||
|
||||
I've added iterkeys, itervalues, iteritems
|
||||
"""
|
||||
def __init__(self, dict = None):
|
||||
def __init__( self, dict = None ):
|
||||
self._keys = []
|
||||
UserDict.__init__(self, dict)
|
||||
UserDict.__init__( self, dict )
|
||||
|
||||
def __delitem__(self, key):
|
||||
UserDict.__delitem__(self, key)
|
||||
self._keys.remove(key)
|
||||
def __delitem__( self, key ):
|
||||
UserDict.__delitem__( self, key )
|
||||
self._keys.remove( key )
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
UserDict.__setitem__(self, key, item)
|
||||
if key not in self._keys: self._keys.append(key)
|
||||
def __setitem__( self, key, item ):
|
||||
UserDict.__setitem__( self, key, item )
|
||||
if key not in self._keys:
|
||||
self._keys.append( key )
|
||||
|
||||
def clear(self):
|
||||
UserDict.clear(self)
|
||||
def clear( self ):
|
||||
UserDict.clear( self )
|
||||
self._keys = []
|
||||
|
||||
def copy(self):
|
||||
@@ -35,49 +34,43 @@ class odict(UserDict):
|
||||
new.update( self )
|
||||
return new
|
||||
|
||||
def items(self):
|
||||
return zip(self._keys, self.values())
|
||||
def items( self ):
|
||||
return zip( self._keys, self.values() )
|
||||
|
||||
def keys(self):
|
||||
def keys( self ):
|
||||
return self._keys[:]
|
||||
|
||||
def popitem(self):
|
||||
def popitem( self ):
|
||||
try:
|
||||
key = self._keys[-1]
|
||||
except IndexError:
|
||||
raise KeyError('dictionary is empty')
|
||||
raise KeyError( 'dictionary is empty' )
|
||||
val = self[ key ]
|
||||
del self[ key ]
|
||||
return ( key, val )
|
||||
|
||||
val = self[key]
|
||||
del self[key]
|
||||
def setdefault( self, key, failobj=None ):
|
||||
if key not in self._keys:
|
||||
self._keys.append( key )
|
||||
return UserDict.setdefault( self, key, failobj )
|
||||
|
||||
return (key, val)
|
||||
def update( self, dict ):
|
||||
for ( key, val ) in dict.items():
|
||||
self.__setitem__( key, val )
|
||||
|
||||
def setdefault(self, key, failobj = None):
|
||||
if key not in self._keys: self._keys.append(key)
|
||||
return UserDict.setdefault(self, key, failobj)
|
||||
def values( self ):
|
||||
return map( self.get, self._keys )
|
||||
|
||||
def update(self, dict):
|
||||
UserDict.update(self, dict)
|
||||
for key in dict.keys():
|
||||
if key not in self._keys: self._keys.append(key)
|
||||
|
||||
def update(self, dict):
|
||||
for (key,val) in dict.items():
|
||||
self.__setitem__(key,val)
|
||||
|
||||
def values(self):
|
||||
return map(self.get, self._keys)
|
||||
|
||||
def iterkeys(self):
|
||||
def iterkeys( self ):
|
||||
return iter( self._keys )
|
||||
|
||||
def itervalues(self):
|
||||
def itervalues( self ):
|
||||
for key in self._keys:
|
||||
yield self.get(key)
|
||||
yield self.get( key )
|
||||
|
||||
def iteritems(self):
|
||||
def iteritems( self ):
|
||||
for key in self._keys:
|
||||
yield key, self.get(key)
|
||||
yield key, self.get( key )
|
||||
|
||||
def __iter__( self ):
|
||||
for key in self._keys:
|
||||
@@ -86,3 +79,7 @@ class odict(UserDict):
|
||||
def reverse( self ):
|
||||
self._keys.reverse()
|
||||
|
||||
def insert( self, index, key, item ):
|
||||
if key not in self._keys:
|
||||
self._keys.insert( index, key )
|
||||
UserDict.__setitem__( self, key, item )
|
||||
|
||||
+246
-96
@@ -1,18 +1,107 @@
|
||||
import os, tempfile, shutil, subprocess, logging
|
||||
import sys, os, tempfile, shutil, subprocess, logging, string, urllib2
|
||||
from datetime import date, datetime, timedelta
|
||||
from time import strftime
|
||||
from time import strftime, gmtime
|
||||
from galaxy import util
|
||||
from galaxy.datatypes.checkers import *
|
||||
from galaxy.util.json import *
|
||||
from galaxy.tools.search import ToolBoxSearch
|
||||
from galaxy.model.orm import *
|
||||
|
||||
from galaxy import eggs
|
||||
import pkg_resources
|
||||
|
||||
pkg_resources.require( 'mercurial' )
|
||||
from mercurial import ui, commands
|
||||
|
||||
pkg_resources.require( 'elementtree' )
|
||||
from elementtree import ElementTree, ElementInclude
|
||||
from elementtree.ElementTree import Element, SubElement
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
# Characters that must be html escaped
|
||||
MAPPED_CHARS = { '>' :'>',
|
||||
'<' :'<',
|
||||
'"' : '"',
|
||||
'&' : '&',
|
||||
'\'' : ''' }
|
||||
VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
|
||||
|
||||
class ShedCounter( object ):
|
||||
def __init__( self, model ):
|
||||
self.model = model
|
||||
self.generation_time = strftime( "%b %d, %Y", gmtime() )
|
||||
self.repositories = 0
|
||||
self.new_repositories = 0
|
||||
self.deleted_repositories = 0
|
||||
self.invalid_tools = 0
|
||||
self.valid_tools = 0
|
||||
self.workflows = 0
|
||||
self.proprietary_datatypes = 0
|
||||
self.total_clones = 0
|
||||
self.generate_statistics()
|
||||
@property
|
||||
def sa_session( self ):
|
||||
"""Returns a SQLAlchemy session"""
|
||||
return self.model.context
|
||||
def generate_statistics( self ):
|
||||
self.repositories = 0
|
||||
self.new_repositories = 0
|
||||
self.deleted_repositories = 0
|
||||
self.invalid_tools = 0
|
||||
self.valid_tools = 0
|
||||
self.workflows = 0
|
||||
self.proprietary_datatypes = 0
|
||||
self.total_clones = 0
|
||||
for repository in self.sa_session.query( self.model.Repository ):
|
||||
self.repositories += 1
|
||||
self.total_clones += repository.times_downloaded
|
||||
is_deleted = repository.deleted
|
||||
is_new = repository.is_new
|
||||
if is_deleted and is_new:
|
||||
self.deleted_repositories += 1
|
||||
self.new_repositories += 1
|
||||
elif is_deleted:
|
||||
self.deleted_repositories += 1
|
||||
elif is_new:
|
||||
self.new_repositories += 1
|
||||
else:
|
||||
processed_guids = []
|
||||
processed_invalid_tool_configs = []
|
||||
processed_relative_workflow_paths = []
|
||||
processed_datatypes = []
|
||||
for downloadable_revision in repository.downloadable_revisions:
|
||||
metadata = downloadable_revision.metadata
|
||||
if 'tools' in metadata:
|
||||
tool_dicts = metadata[ 'tools' ]
|
||||
for tool_dict in tool_dicts:
|
||||
if 'guid' in tool_dict:
|
||||
guid = tool_dict[ 'guid' ]
|
||||
if guid not in processed_guids:
|
||||
self.valid_tools += 1
|
||||
processed_guids.append( guid )
|
||||
if 'invalid_tools' in metadata:
|
||||
invalid_tool_configs = metadata[ 'invalid_tools' ]
|
||||
for invalid_tool_config in invalid_tool_configs:
|
||||
if invalid_tool_config not in processed_invalid_tool_configs:
|
||||
self.invalid_tools += 1
|
||||
processed_invalid_tool_configs.append( invalid_tool_config )
|
||||
if 'datatypes' in metadata:
|
||||
datatypes = metadata[ 'datatypes' ]
|
||||
for datatypes_dict in datatypes:
|
||||
if 'extension' in datatypes_dict:
|
||||
extension = datatypes_dict[ 'extension' ]
|
||||
if extension not in processed_datatypes:
|
||||
self.proprietary_datatypes += 1
|
||||
processed_datatypes.append( extension )
|
||||
if 'workflows' in metadata:
|
||||
workflows = metadata[ 'workflows' ]
|
||||
for workflow_tup in workflows:
|
||||
relative_path, exported_workflow_dict = workflow_tup
|
||||
if relative_path not in processed_relative_workflow_paths:
|
||||
self.workflows += 1
|
||||
processed_relative_workflow_paths.append( relative_path )
|
||||
self.generation_time = strftime( "%b %d, %Y", gmtime() )
|
||||
def add_to_shed_tool_config( app, shed_tool_conf_dict, elem_list ):
|
||||
# A tool shed repository is being installed so change the shed_tool_conf file. Parse the config file to generate the entire list
|
||||
# of config_elems instead of using the in-memory list since it will be a subset of the entire list if one or more repositories have
|
||||
@@ -129,13 +218,15 @@ def alter_config_and_load_prorietary_datatypes( app, datatypes_config, relative_
|
||||
# The value of proprietary_path must be an absolute path due to job_working_directory.
|
||||
elem.attrib[ 'proprietary_path' ] = os.path.abspath( relative_head )
|
||||
elem.attrib[ 'proprietary_datatype_module' ] = proprietary_datatype_module
|
||||
|
||||
sniffers = datatypes_config_root.find( 'sniffers' )
|
||||
else:
|
||||
sniffers = None
|
||||
fd, proprietary_datatypes_config = tempfile.mkstemp()
|
||||
os.write( fd, '<?xml version="1.0"?>\n' )
|
||||
os.write( fd, '<datatypes>\n' )
|
||||
os.write( fd, '%s' % util.xml_to_string( registration ) )
|
||||
os.write( fd, '%s' % util.xml_to_string( sniffers ) )
|
||||
if sniffers:
|
||||
os.write( fd, '%s' % util.xml_to_string( sniffers ) )
|
||||
os.write( fd, '</datatypes>\n' )
|
||||
os.close( fd )
|
||||
os.chmod( proprietary_datatypes_config, 0644 )
|
||||
@@ -179,31 +270,38 @@ def clean_tool_shed_url( tool_shed_url ):
|
||||
# Eliminate the port, if any, since it will result in an invalid directory name.
|
||||
return tool_shed_url.split( ':' )[ 0 ]
|
||||
return tool_shed_url.rstrip( '/' )
|
||||
def clone_repository( name, clone_dir, current_working_dir, repository_clone_url ):
|
||||
log.debug( "Installing repository '%s'" % name )
|
||||
if not os.path.exists( clone_dir ):
|
||||
os.makedirs( clone_dir )
|
||||
log.debug( 'Cloning %s' % repository_clone_url )
|
||||
cmd = 'hg clone %s' % repository_clone_url
|
||||
tmp_name = tempfile.NamedTemporaryFile().name
|
||||
tmp_stderr = open( tmp_name, 'wb' )
|
||||
os.chdir( clone_dir )
|
||||
proc = subprocess.Popen( args=cmd, shell=True, stderr=tmp_stderr.fileno() )
|
||||
returncode = proc.wait()
|
||||
os.chdir( current_working_dir )
|
||||
tmp_stderr.close()
|
||||
return returncode, tmp_name
|
||||
def copy_sample_loc_file( app, filename ):
|
||||
"""Copy xxx.loc.sample to ~/tool-data/xxx.loc.sample and ~/tool-data/xxx.loc"""
|
||||
head, sample_loc_file = os.path.split( filename )
|
||||
loc_file = sample_loc_file.replace( '.sample', '' )
|
||||
tool_data_path = os.path.abspath( app.config.tool_data_path )
|
||||
def clone_repository( repository_clone_url, repository_file_dir, ctx_rev ):
|
||||
"""
|
||||
Clone the repository up to the specified changeset_revision. No subsequent revisions will be present in the cloned repository.
|
||||
"""
|
||||
commands.clone( get_configured_ui(),
|
||||
repository_clone_url,
|
||||
dest=repository_file_dir,
|
||||
pull=True,
|
||||
noupdate=False,
|
||||
rev=[ ctx_rev ] )
|
||||
def copy_sample_file( app, filename, dest_path=None ):
|
||||
"""
|
||||
Copy xxx.loc.sample to dest_path/xxx.loc.sample and dest_path/xxx.loc. The default value for dest_path is ~/tool-data.
|
||||
"""
|
||||
if dest_path is None:
|
||||
dest_path = os.path.abspath( app.config.tool_data_path )
|
||||
sample_file_path, sample_file_name = os.path.split( filename )
|
||||
copied_file = sample_file_name.replace( '.sample', '' )
|
||||
# It's ok to overwrite the .sample version of the file.
|
||||
shutil.copy( os.path.abspath( filename ), os.path.join( tool_data_path, sample_loc_file ) )
|
||||
# Only create the .loc file if it does not yet exist. We don't
|
||||
# overwrite it in case it contains stuff proprietary to the local instance.
|
||||
if not os.path.exists( os.path.join( tool_data_path, loc_file ) ):
|
||||
shutil.copy( os.path.abspath( filename ), os.path.join( tool_data_path, loc_file ) )
|
||||
shutil.copy( os.path.abspath( filename ), os.path.join( dest_path, sample_file_name ) )
|
||||
# Only create the .loc file if it does not yet exist. We don't overwrite it in case it contains stuff proprietary to the local instance.
|
||||
if not os.path.exists( os.path.join( dest_path, copied_file ) ):
|
||||
shutil.copy( os.path.abspath( filename ), os.path.join( dest_path, copied_file ) )
|
||||
def copy_sample_files( app, sample_files, sample_files_copied=None, dest_path=None ):
|
||||
"""
|
||||
Copy all files to dest_path in the local Galaxy environment that have not already been copied. Those that have been copied
|
||||
are contained in sample_files_copied. The default value for dest_path is ~/tool-data.
|
||||
"""
|
||||
sample_files_copied = util.listify( sample_files_copied )
|
||||
for filename in sample_files:
|
||||
if filename not in sample_files_copied:
|
||||
copy_sample_file( app, filename, dest_path=dest_path )
|
||||
def create_repository_dict_for_proprietary_datatypes( tool_shed, name, owner, installed_changeset_revision, tool_dicts, converter_path=None, display_path=None ):
|
||||
return dict( tool_shed=tool_shed,
|
||||
repository_name=name,
|
||||
@@ -212,7 +310,8 @@ def create_repository_dict_for_proprietary_datatypes( tool_shed, name, owner, in
|
||||
tool_dicts=tool_dicts,
|
||||
converter_path=converter_path,
|
||||
display_path=display_path )
|
||||
def create_or_update_tool_shed_repository( app, name, description, changeset_revision, repository_clone_url, metadata_dict, owner='', dist_to_shed=False ):
|
||||
def create_or_update_tool_shed_repository( app, name, description, changeset_revision, ctx_rev, repository_clone_url, metadata_dict,
|
||||
owner='', dist_to_shed=False ):
|
||||
# The received value for dist_to_shed will be True if the InstallManager is installing a repository that contains tools or datatypes that used
|
||||
# to be in the Galaxy distribution, but have been moved to the main Galaxy tool shed.
|
||||
sa_session = app.model.context.current
|
||||
@@ -225,6 +324,7 @@ def create_or_update_tool_shed_repository( app, name, description, changeset_rev
|
||||
if tool_shed_repository:
|
||||
tool_shed_repository.description = description
|
||||
tool_shed_repository.changeset_revision = changeset_revision
|
||||
tool_shed_repository.ctx_rev = ctx_rev
|
||||
tool_shed_repository.metadata = metadata_dict
|
||||
tool_shed_repository.includes_datatypes = includes_datatypes
|
||||
tool_shed_repository.deleted = False
|
||||
@@ -236,6 +336,7 @@ def create_or_update_tool_shed_repository( app, name, description, changeset_rev
|
||||
owner=owner,
|
||||
installed_changeset_revision=changeset_revision,
|
||||
changeset_revision=changeset_revision,
|
||||
ctx_rev=ctx_rev,
|
||||
metadata=metadata_dict,
|
||||
includes_datatypes=includes_datatypes,
|
||||
dist_to_shed=dist_to_shed )
|
||||
@@ -247,7 +348,7 @@ def generate_clone_url( trans, repository ):
|
||||
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
|
||||
return '%s/repos/%s/%s' % ( tool_shed_url, repository.owner, repository.name )
|
||||
def generate_datatypes_metadata( datatypes_config, metadata_dict ):
|
||||
"""Update the received metadata_dict with changes that have been applied to the received datatypes_config."""
|
||||
"""Update the received metadata_dict with information from the parsed datatypes_config."""
|
||||
tree = ElementTree.parse( datatypes_config )
|
||||
root = tree.getroot()
|
||||
ElementInclude.include( root )
|
||||
@@ -325,12 +426,21 @@ def generate_metadata( toolbox, relative_install_dir, repository_clone_url ):
|
||||
if not ( check_binary( full_path ) or check_image( full_path ) or check_gzip( full_path )[ 0 ]
|
||||
or check_bz2( full_path )[ 0 ] or check_zip( full_path ) ):
|
||||
try:
|
||||
tool = toolbox.load_tool( full_path )
|
||||
# Make sure we're looking at a tool config and not a display application config or something else.
|
||||
element_tree = util.parse_xml( full_path )
|
||||
element_tree_root = element_tree.getroot()
|
||||
is_tool = element_tree_root.tag == 'tool'
|
||||
except Exception, e:
|
||||
tool = None
|
||||
if tool is not None:
|
||||
tool_config = os.path.join( root, name )
|
||||
metadata_dict = generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_dict )
|
||||
log.debug( "Error parsing %s, exception: %s" % ( full_path, str( e ) ) )
|
||||
is_tool = False
|
||||
if is_tool:
|
||||
try:
|
||||
tool = toolbox.load_tool( full_path )
|
||||
except Exception, e:
|
||||
tool = None
|
||||
if tool is not None:
|
||||
tool_config = os.path.join( root, name )
|
||||
metadata_dict = generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_dict )
|
||||
# Find all exported workflows
|
||||
elif name.endswith( '.ga' ):
|
||||
relative_path = os.path.join( root, name )
|
||||
@@ -356,30 +466,30 @@ def generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_di
|
||||
# Handle tool.requirements.
|
||||
tool_requirements = []
|
||||
for tr in tool.requirements:
|
||||
name=tr.name
|
||||
type=tr.type
|
||||
if type == 'fabfile':
|
||||
version = None
|
||||
fabfile = tr.fabfile
|
||||
method = tr.method
|
||||
else:
|
||||
version = tr.version
|
||||
fabfile = None
|
||||
method = None
|
||||
requirement_dict = dict( name=name,
|
||||
type=type,
|
||||
version=version,
|
||||
fabfile=fabfile,
|
||||
method=method )
|
||||
requirement_dict = dict( name=tr.name,
|
||||
type=tr.type,
|
||||
version=tr.version )
|
||||
tool_requirements.append( requirement_dict )
|
||||
# Handle tool.tests.
|
||||
tool_tests = []
|
||||
if tool.tests:
|
||||
for ttb in tool.tests:
|
||||
required_files = []
|
||||
for required_file in ttb.required_files:
|
||||
value, extra = required_file
|
||||
required_files.append( ( value ) )
|
||||
inputs = []
|
||||
for input in ttb.inputs:
|
||||
name, value, extra = input
|
||||
inputs.append( ( name, value ) )
|
||||
outputs = []
|
||||
for output in ttb.outputs:
|
||||
name, file_name, extra = output
|
||||
outputs.append( ( name, os.path.split( file_name )[ 1 ] ) )
|
||||
test_dict = dict( name=ttb.name,
|
||||
required_files=ttb.required_files,
|
||||
inputs=ttb.inputs,
|
||||
outputs=ttb.outputs )
|
||||
required_files=required_files,
|
||||
inputs=inputs,
|
||||
outputs=outputs )
|
||||
tool_tests.append( test_dict )
|
||||
tool_dict = dict( id=tool.id,
|
||||
guid=guid,
|
||||
@@ -570,6 +680,15 @@ def generate_workflow_metadata( relative_path, exported_workflow_dict, metadata_
|
||||
else:
|
||||
metadata_dict[ 'workflows' ] = [ ( relative_path, exported_workflow_dict ) ]
|
||||
return metadata_dict
|
||||
def get_configured_ui():
|
||||
# Configure any desired ui settings.
|
||||
_ui = ui.ui()
|
||||
# The following will suppress all messages. This is
|
||||
# the same as adding the following setting to the repo
|
||||
# hgrc file' [ui] section:
|
||||
# quiet = True
|
||||
_ui.setconfig( 'ui', 'quiet', True )
|
||||
return _ui
|
||||
def get_converter_and_display_paths( registration_elem, relative_install_dir ):
|
||||
"""Find the relative path to data type converters and display applications included in installed tool shed repositories."""
|
||||
converter_path = None
|
||||
@@ -612,6 +731,12 @@ def get_converter_and_display_paths( registration_elem, relative_install_dir ):
|
||||
if converter_path and display_path:
|
||||
break
|
||||
return converter_path, display_path
|
||||
def get_ctx_rev( tool_shed_url, name, owner, changeset_revision ):
|
||||
url = '%s/repository/get_ctx_rev?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % ( tool_shed_url, name, owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
ctx_rev = response.read()
|
||||
response.close()
|
||||
return ctx_rev
|
||||
def get_shed_tool_conf_dict( app, shed_tool_conf ):
|
||||
"""
|
||||
Return the in-memory version of the shed_tool_conf file, which is stored in the config_elems entry
|
||||
@@ -768,27 +893,27 @@ def handle_missing_data_table_entry( app, tool_path, sample_files, repository_to
|
||||
return repository_tools_tups
|
||||
def handle_missing_index_file( app, tool_path, sample_files, repository_tools_tups ):
|
||||
"""Inspect each tool to see if it has any input parameters that are dynamically generated select lists that depend on a .loc file."""
|
||||
missing_files_handled = []
|
||||
sample_files_copied = []
|
||||
for index, repository_tools_tup in enumerate( repository_tools_tups ):
|
||||
tup_path, guid, repository_tool = repository_tools_tup
|
||||
params_with_missing_index_file = repository_tool.params_with_missing_index_file
|
||||
for param in params_with_missing_index_file:
|
||||
options = param.options
|
||||
missing_head, missing_tail = os.path.split( options.missing_index_file )
|
||||
if missing_tail not in missing_files_handled:
|
||||
missing_file_path, missing_file_name = os.path.split( options.missing_index_file )
|
||||
if missing_file_name not in sample_files_copied:
|
||||
# The repository must contain the required xxx.loc.sample file.
|
||||
for sample_file in sample_files:
|
||||
sample_head, sample_tail = os.path.split( sample_file )
|
||||
if sample_tail == '%s.sample' % missing_tail:
|
||||
copy_sample_loc_file( app, sample_file )
|
||||
sample_file_path, sample_file_name = os.path.split( sample_file )
|
||||
if sample_file_name == '%s.sample' % missing_file_name:
|
||||
copy_sample_file( app, sample_file )
|
||||
if options.tool_data_table and options.tool_data_table.missing_index_file:
|
||||
options.tool_data_table.handle_found_index_file( options.missing_index_file )
|
||||
missing_files_handled.append( missing_tail )
|
||||
sample_files_copied.append( options.missing_index_file )
|
||||
break
|
||||
# Reload the tool into the local list of repository_tools_tups.
|
||||
repository_tool = app.toolbox.load_tool( os.path.join( tool_path, tup_path ), guid=guid )
|
||||
repository_tools_tups[ index ] = ( tup_path, guid, repository_tool )
|
||||
return repository_tools_tups
|
||||
return repository_tools_tups, sample_files_copied
|
||||
def handle_sample_tool_data_table_conf_file( app, filename ):
|
||||
"""
|
||||
Parse the incoming filename and add new entries to the in-memory
|
||||
@@ -912,8 +1037,9 @@ def load_datatype_items( app, repository, relative_install_dir, deactivate=False
|
||||
if display_path:
|
||||
# Load or deactivate proprietary datatype display applications
|
||||
app.datatypes_registry.load_display_applications( installed_repository_dict=repository_dict, deactivate=deactivate )
|
||||
def load_repository_contents( trans, repository_name, description, owner, changeset_revision, tool_path, repository_clone_url,
|
||||
relative_install_dir, current_working_dir, tmp_name, tool_shed=None, tool_section=None, shed_tool_conf=None ):
|
||||
def load_repository_contents( trans, repository_name, description, owner, changeset_revision, ctx_rev, tool_path, repository_clone_url,
|
||||
relative_install_dir, current_working_dir, tool_shed=None, tool_section=None, shed_tool_conf=None,
|
||||
install_tool_dependencies=False ):
|
||||
"""Generate the metadata for the installed tool shed repository, among other things."""
|
||||
# It is critical that the installed repository is updated to the desired changeset_revision before metadata is set because the
|
||||
# process for setting metadata uses the repository files on disk. This method is called when an admin is installing a new repository
|
||||
@@ -926,6 +1052,7 @@ def load_repository_contents( trans, repository_name, description, owner, change
|
||||
repository_name,
|
||||
description,
|
||||
changeset_revision,
|
||||
ctx_rev,
|
||||
repository_clone_url,
|
||||
metadata_dict,
|
||||
dist_to_shed=False )
|
||||
@@ -937,8 +1064,9 @@ def load_repository_contents( trans, repository_name, description, owner, change
|
||||
# Handle missing data table entries for tool parameters that are dynamically generated select lists.
|
||||
repository_tools_tups = handle_missing_data_table_entry( trans.app, tool_path, sample_files, repository_tools_tups )
|
||||
# Handle missing index files for tool parameters that are dynamically generated select lists.
|
||||
repository_tools_tups = handle_missing_index_file( trans.app, tool_path, sample_files, repository_tools_tups )
|
||||
# Handle tools that use fabric scripts to install dependencies.
|
||||
repository_tools_tups, sample_files_copied = handle_missing_index_file( trans.app, tool_path, sample_files, repository_tools_tups )
|
||||
# Copy remaining sample files included in the repository to the ~/tool-data directory of the local Galaxy instance.
|
||||
copy_sample_files( trans.app, sample_files, sample_files_copied=sample_files_copied )
|
||||
handle_tool_dependencies( current_working_dir, relative_install_dir, repository_tools_tups )
|
||||
add_to_tool_panel( app=trans.app,
|
||||
repository_name=repository_name,
|
||||
@@ -949,11 +1077,6 @@ def load_repository_contents( trans, repository_name, description, owner, change
|
||||
shed_tool_conf=shed_tool_conf,
|
||||
tool_panel_dict=tool_panel_dict,
|
||||
new_install=True )
|
||||
# Remove the temporary file
|
||||
try:
|
||||
os.unlink( tmp_name )
|
||||
except:
|
||||
pass
|
||||
if 'datatypes_config' in metadata_dict:
|
||||
datatypes_config = os.path.abspath( metadata_dict[ 'datatypes_config' ] )
|
||||
# Load data types required by tools.
|
||||
@@ -987,18 +1110,12 @@ def panel_entry_per_tool( tool_section_dict ):
|
||||
if k not in [ 'id', 'version', 'name' ]:
|
||||
return True
|
||||
return False
|
||||
def pull_repository( current_working_dir, repo_files_dir, name ):
|
||||
# Pull the latest possible contents to the repository.
|
||||
log.debug( "Pulling latest updates to the repository named '%s'" % name )
|
||||
cmd = 'hg pull'
|
||||
tmp_name = tempfile.NamedTemporaryFile().name
|
||||
tmp_stderr = open( tmp_name, 'wb' )
|
||||
os.chdir( repo_files_dir )
|
||||
proc = subprocess.Popen( cmd, shell=True, stderr=tmp_stderr.fileno() )
|
||||
returncode = proc.wait()
|
||||
os.chdir( current_working_dir )
|
||||
tmp_stderr.close()
|
||||
return returncode, tmp_name
|
||||
def pull_repository( repo, repository_clone_url, ctx_rev ):
|
||||
"""Pull changes from a remote repository to a local one."""
|
||||
commands.pull( get_configured_ui(),
|
||||
repo,
|
||||
source=repository_clone_url,
|
||||
rev=ctx_rev )
|
||||
def remove_from_shed_tool_config( trans, shed_tool_conf_dict, guids_to_remove ):
|
||||
# A tool shed repository is being uninstalled so change the shed_tool_conf file. Parse the config file to generate the entire list
|
||||
# of config_elems instead of using the in-memory list since it will be a subset of the entire list if one or more repositories have
|
||||
@@ -1096,17 +1213,50 @@ def remove_from_tool_panel( trans, repository, shed_tool_conf, uninstall ):
|
||||
if uninstall:
|
||||
# Write the current in-memory version of the integrated_tool_panel.xml file to disk.
|
||||
trans.app.toolbox.write_integrated_tool_panel_config_file()
|
||||
def update_repository( current_working_dir, repo_files_dir, changeset_revision ):
|
||||
# Update the cloned repository to changeset_revision. It is imperative that the
|
||||
# installed repository is updated to the desired changeset_revision before metadata
|
||||
# is set because the process for setting metadata uses the repository files on disk.
|
||||
log.debug( 'Updating cloned repository to revision "%s"' % changeset_revision )
|
||||
cmd = 'hg update -r %s' % changeset_revision
|
||||
tmp_name = tempfile.NamedTemporaryFile().name
|
||||
tmp_stderr = open( tmp_name, 'wb' )
|
||||
os.chdir( repo_files_dir )
|
||||
proc = subprocess.Popen( cmd, shell=True, stderr=tmp_stderr.fileno() )
|
||||
returncode = proc.wait()
|
||||
os.chdir( current_working_dir )
|
||||
tmp_stderr.close()
|
||||
return returncode, tmp_name
|
||||
def to_html_escaped( text ):
|
||||
"""Translates the characters in text to html values"""
|
||||
translated = []
|
||||
for c in text:
|
||||
if c in [ '\r\n', '\n', ' ', '\t' ] or c in VALID_CHARS:
|
||||
translated.append( c )
|
||||
elif c in MAPPED_CHARS:
|
||||
translated.append( MAPPED_CHARS[ c ] )
|
||||
else:
|
||||
translated.append( 'X' )
|
||||
return ''.join( translated )
|
||||
def to_html_str( text ):
|
||||
"""Translates the characters in text to sn html string"""
|
||||
translated = []
|
||||
for c in text:
|
||||
if c in VALID_CHARS:
|
||||
translated.append( c )
|
||||
elif c in MAPPED_CHARS:
|
||||
translated.append( MAPPED_CHARS[ c ] )
|
||||
elif c == ' ':
|
||||
translated.append( ' ' )
|
||||
elif c == '\t':
|
||||
translated.append( ' ' )
|
||||
elif c == '\n':
|
||||
translated.append( '<br/>' )
|
||||
elif c not in [ '\r' ]:
|
||||
translated.append( 'X' )
|
||||
return ''.join( translated )
|
||||
def update_repository( repo, ctx_rev=None ):
|
||||
"""
|
||||
Update the cloned repository to changeset_revision. It is critical that the installed repository is updated to the desired
|
||||
changeset_revision before metadata is set because the process for setting metadata uses the repository files on disk.
|
||||
"""
|
||||
# TODO: We may have files on disk in the repo directory that aren't being tracked, so they must be removed.
|
||||
# The codes used to show the status of files are as follows.
|
||||
# M = modified
|
||||
# A = added
|
||||
# R = removed
|
||||
# C = clean
|
||||
# ! = deleted, but still tracked
|
||||
# ? = not tracked
|
||||
# I = ignored
|
||||
# It would be nice if we could use mercurial's purge extension to remove untracked files. The problem is that
|
||||
# purging is not supported by the mercurial API. See the deprecated update_for_browsing() method in common.py.
|
||||
commands.update( get_configured_ui(),
|
||||
repo,
|
||||
rev=ctx_rev )
|
||||
|
||||
@@ -19,7 +19,7 @@ from galaxy.util.lrucache import LRUCache
|
||||
from galaxy.visualization.tracks.summary import *
|
||||
import galaxy_utils.sequence.vcf
|
||||
from galaxy.datatypes.tabular import Vcf
|
||||
from galaxy.datatypes.interval import Bed, Gff, Gtf, ENCODEPeak
|
||||
from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak
|
||||
|
||||
from pysam import csamtools, ctabix
|
||||
|
||||
@@ -67,8 +67,7 @@ class TracksDataProvider( object ):
|
||||
"""
|
||||
Write data in region defined by chrom, start, and end to a file.
|
||||
"""
|
||||
# Override.
|
||||
pass
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
def valid_chroms( self ):
|
||||
"""
|
||||
@@ -81,23 +80,19 @@ class TracksDataProvider( object ):
|
||||
Returns true if dataset has data in the specified genome window, false
|
||||
otherwise.
|
||||
"""
|
||||
# Override.
|
||||
pass
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
def get_iterator( self, chrom, start, end ):
|
||||
"""
|
||||
Returns an iterator that provides data in the region chrom:start-end
|
||||
"""
|
||||
# Override.
|
||||
pass
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
|
||||
"""
|
||||
Process data from an iterator to a format that can be provided to client.
|
||||
"""
|
||||
# Override.
|
||||
pass
|
||||
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
def get_data( self, chrom, start, end, start_val=0, max_vals=sys.maxint, **kwargs ):
|
||||
"""
|
||||
@@ -270,18 +265,96 @@ class TabixDataProvider( FilterableMixin, TracksDataProvider ):
|
||||
out.close()
|
||||
|
||||
#
|
||||
# -- BED data providers --
|
||||
# -- Interval data providers --
|
||||
#
|
||||
|
||||
class BedDataProvider( TracksDataProvider ):
|
||||
class IntervalDataProvider( TracksDataProvider ):
|
||||
"""
|
||||
Abstract class that processes BED data from native format to payload format.
|
||||
Processes BED data from native format to payload format.
|
||||
|
||||
Payload format: [ uid (offset), start, end, name, strand, thick_start, thick_end, blocks ]
|
||||
"""
|
||||
|
||||
def get_iterator( self, chrom, start, end ):
|
||||
raise "Unimplemented Method"
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
|
||||
"""
|
||||
Provides
|
||||
"""
|
||||
# Build data to return. Payload format is:
|
||||
# [ <guid/offset>, <start>, <end>, <name>, <strand> ]
|
||||
#
|
||||
# First three entries are mandatory, others are optional.
|
||||
#
|
||||
filter_cols = from_json_string( kwargs.get( "filter_cols", "[]" ) )
|
||||
no_detail = ( "no_detail" in kwargs )
|
||||
rval = []
|
||||
message = None
|
||||
# Subtract one b/c columns are 1-based but indices are 0-based.
|
||||
col_fn = lambda col: None if col is None else col - 1
|
||||
start_col = self.original_dataset.metadata.startCol - 1
|
||||
end_col = self.original_dataset.metadata.endCol - 1
|
||||
strand_col = col_fn( self.original_dataset.metadata.strandCol )
|
||||
name_col = col_fn( self.original_dataset.metadata.nameCol )
|
||||
for count, line in enumerate( iterator ):
|
||||
if count < start_val:
|
||||
continue
|
||||
if max_vals and count-start_val >= max_vals:
|
||||
message = ERROR_MAX_VALS % ( max_vals, "features" )
|
||||
break
|
||||
|
||||
feature = line.split()
|
||||
length = len(feature)
|
||||
# Unique id is just a hash of the line
|
||||
payload = [ hash(line), int( feature[start_col] ), int( feature [end_col] ) ]
|
||||
|
||||
if no_detail:
|
||||
rval.append( payload )
|
||||
continue
|
||||
|
||||
# Name, strand.
|
||||
if name_col:
|
||||
payload.append( feature[name_col] )
|
||||
if strand_col:
|
||||
# Put empty name as placeholder.
|
||||
if not name_col: payload.append( "" )
|
||||
payload.append( feature[strand_col] )
|
||||
|
||||
# Score (filter data)
|
||||
if length >= 5 and filter_cols and filter_cols[0] == "Score":
|
||||
try:
|
||||
payload.append( float( feature[4] ) )
|
||||
except:
|
||||
payload.append( feature[4] )
|
||||
|
||||
rval.append( payload )
|
||||
|
||||
return { 'data': rval, 'message': message }
|
||||
|
||||
def write_data_to_file( self, chrom, start, end, filename ):
|
||||
raise Exception( "Unimplemented Function" )
|
||||
|
||||
class IntervalTabixDataProvider( TabixDataProvider, IntervalDataProvider ):
|
||||
"""
|
||||
Provides data from a BED file indexed via tabix.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# -- BED data providers --
|
||||
#
|
||||
|
||||
class BedDataProvider( TracksDataProvider ):
|
||||
"""
|
||||
Processes BED data from native format to payload format.
|
||||
|
||||
Payload format: [ uid (offset), start, end, name, strand, thick_start, thick_end, blocks ]
|
||||
"""
|
||||
|
||||
def get_iterator( self, chrom, start, end ):
|
||||
raise Exception( "Unimplemented Method" )
|
||||
|
||||
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
|
||||
"""
|
||||
@@ -359,7 +432,7 @@ class BedTabixDataProvider( TabixDataProvider, BedDataProvider ):
|
||||
Provides data from a BED file indexed via tabix.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RawBedDataProvider( BedDataProvider ):
|
||||
"""
|
||||
Provide data from BED file.
|
||||
@@ -936,9 +1009,9 @@ class IntervalIndexDataProvider( FilterableMixin, TracksDataProvider ):
|
||||
|
||||
return { 'data': results, 'message': message }
|
||||
|
||||
class GFFDataProvider( TracksDataProvider ):
|
||||
class RawGFFDataProvider( TracksDataProvider ):
|
||||
"""
|
||||
Provide data from GFF file.
|
||||
Provide data from GFF file that has not been indexed.
|
||||
|
||||
NOTE: this data provider does not use indices, and hence will be very slow
|
||||
for large datasets.
|
||||
@@ -951,7 +1024,7 @@ class GFFDataProvider( TracksDataProvider ):
|
||||
"""
|
||||
start, end = int( start ), int( end )
|
||||
source = open( self.original_dataset.file_name )
|
||||
|
||||
|
||||
def features_in_region_iter():
|
||||
offset = 0
|
||||
for feature in GFFReaderWrapper( source, fix_strand=True ):
|
||||
@@ -961,7 +1034,7 @@ class GFFDataProvider( TracksDataProvider ):
|
||||
yield feature, offset
|
||||
offset += feature.raw_size
|
||||
return features_in_region_iter()
|
||||
|
||||
|
||||
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
|
||||
"""
|
||||
Process data from an iterator to a format that can be provided to client.
|
||||
@@ -984,6 +1057,48 @@ class GFFDataProvider( TracksDataProvider ):
|
||||
|
||||
|
||||
return { 'data': results, 'message': message }
|
||||
|
||||
class GtfTabixDataProvider( TabixDataProvider ):
|
||||
"""
|
||||
Returns data from GTF datasets that are indexed via tabix.
|
||||
"""
|
||||
|
||||
def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ):
|
||||
# Loop through lines and group by transcript_id; each group is a feature.
|
||||
|
||||
# TODO: extend this code or use code in gff_util to process GFF/3 as well
|
||||
# and then create a generic GFFDataProvider that can be used with both
|
||||
# raw and tabix datasets.
|
||||
features = {}
|
||||
for count, line in enumerate( iterator ):
|
||||
line_attrs = parse_gff_attributes( line.split('\t')[8] )
|
||||
transcript_id = line_attrs[ 'transcript_id' ]
|
||||
if transcript_id in features:
|
||||
feature = features[ transcript_id ]
|
||||
else:
|
||||
feature = []
|
||||
features[ transcript_id ] = feature
|
||||
feature.append( GFFInterval( None, line.split( '\t') ) )
|
||||
|
||||
# Process data.
|
||||
filter_cols = from_json_string( kwargs.get( "filter_cols", "[]" ) )
|
||||
no_detail = ( "no_detail" in kwargs )
|
||||
results = []
|
||||
message = None
|
||||
|
||||
for count, intervals in enumerate( features.values() ):
|
||||
if count < start_val:
|
||||
continue
|
||||
if count-start_val >= max_vals:
|
||||
message = ERROR_MAX_VALS % ( max_vals, "reads" )
|
||||
break
|
||||
|
||||
feature = GFFFeature( None, intervals=intervals )
|
||||
payload = package_gff_feature( feature, no_detail=no_detail, filter_cols=filter_cols )
|
||||
payload.insert( 0, feature.intervals[ 0 ].attributes[ 'transcript_id' ] )
|
||||
results.append( payload )
|
||||
|
||||
return { 'data': results, 'message': message }
|
||||
|
||||
#
|
||||
# -- ENCODE Peak data providers.
|
||||
@@ -1113,7 +1228,13 @@ class ENCODEPeakTabixDataProvider( TabixDataProvider, ENCODEPeakDataProvider ):
|
||||
# type. First key is converted dataset type; if result is another dict, second key
|
||||
# is original dataset type. TODO: This needs to be more flexible.
|
||||
dataset_type_name_to_data_provider = {
|
||||
"tabix": { Vcf: VcfTabixDataProvider, Bed: BedTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, "default" : TabixDataProvider },
|
||||
"tabix": {
|
||||
Vcf: VcfTabixDataProvider,
|
||||
Bed: BedTabixDataProvider,
|
||||
Gtf: GtfTabixDataProvider,
|
||||
ENCODEPeak: ENCODEPeakTabixDataProvider,
|
||||
Interval: IntervalTabixDataProvider,
|
||||
"default" : TabixDataProvider },
|
||||
"interval_index": IntervalIndexDataProvider,
|
||||
"bai": BamDataProvider,
|
||||
"bam": SamDataProvider,
|
||||
@@ -1172,16 +1293,17 @@ def package_gff_feature( feature, no_detail=False, filter_cols=[] ):
|
||||
feature.end
|
||||
]
|
||||
|
||||
# HACK: remove interval with name 'transcript' from feature.
|
||||
# HACK: ignore interval with name 'transcript' from feature.
|
||||
# Cufflinks puts this interval in each of its transcripts,
|
||||
# and they mess up trackster by covering the feature's blocks.
|
||||
# This interval will always be a feature's first interval,
|
||||
# and the GFF's third column is its feature name.
|
||||
# and the GFF's third column is its feature name.
|
||||
feature_intervals = feature.intervals
|
||||
if feature.intervals[0].fields[2] == 'transcript':
|
||||
feature.intervals = feature.intervals[1:]
|
||||
feature_intervals = feature.intervals[1:]
|
||||
# Add blocks.
|
||||
block_sizes = [ (interval.end - interval.start ) for interval in feature.intervals ]
|
||||
block_starts = [ ( interval.start - feature.start ) for interval in feature.intervals ]
|
||||
block_sizes = [ (interval.end - interval.start ) for interval in feature_intervals ]
|
||||
block_starts = [ ( interval.start - feature.start ) for interval in feature_intervals ]
|
||||
blocks = zip( block_sizes, block_starts )
|
||||
payload.append( [ ( feature.start + block[1], feature.start + block[1] + block[0] ) for block in blocks ] )
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
API operations on the contents of a dataset.
|
||||
"""
|
||||
import logging, os, string, shutil, urllib, re, socket
|
||||
from cgi import escape, FieldStorage
|
||||
from galaxy import util, datatypes, jobs, web, util
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.model.orm import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class DatasetsController( BaseAPIController, UsesHistoryDatasetAssociation ):
|
||||
|
||||
@web.expose_api
|
||||
def index( self, trans, hda_id, **kwd ):
|
||||
"""
|
||||
GET /api/datasets
|
||||
Lists datasets.
|
||||
"""
|
||||
pass
|
||||
|
||||
@web.expose_api
|
||||
def show( self, trans, id, deleted='False', **kwd ):
|
||||
"""
|
||||
GET /api/datasets/{encoded_dataset_id}
|
||||
Displays information about and/or content of a dataset.
|
||||
"""
|
||||
|
||||
# Get HDA.
|
||||
try:
|
||||
hda = self.get_dataset( trans, id, check_ownership=True, check_accessible=True )
|
||||
except Exception, e:
|
||||
return str( e )
|
||||
|
||||
# Return information about HDA.
|
||||
rval = None
|
||||
try:
|
||||
rval = hda.get_api_value()
|
||||
except Exception, e:
|
||||
rval = "Error in dataset API at listing contents"
|
||||
log.error( rval + ": %s" % str(e) )
|
||||
trans.response.status = 500
|
||||
return rval
|
||||
@@ -20,28 +20,27 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
GET /api/histories
|
||||
GET /api/histories/deleted
|
||||
Displays a collection (list) of histories.
|
||||
"""
|
||||
"""
|
||||
rval = []
|
||||
deleted = util.string_as_bool( deleted )
|
||||
|
||||
try:
|
||||
query = trans.sa_session.query( trans.app.model.History ).filter_by( user=trans.user, deleted=deleted ).order_by(
|
||||
desc(trans.app.model.History.table.c.update_time)).all()
|
||||
except Exception, e:
|
||||
rval = "Error in history API"
|
||||
log.error( rval + ": %s" % str(e) )
|
||||
trans.response.status = 500
|
||||
|
||||
if not rval:
|
||||
try:
|
||||
if trans.user:
|
||||
query = trans.sa_session.query(trans.app.model.History ).filter_by( user=trans.user, deleted=deleted ).order_by(
|
||||
desc(trans.app.model.History.table.c.update_time)).all()
|
||||
for history in query:
|
||||
item = history.get_api_value(value_mapper={'id':trans.security.encode_id})
|
||||
item['url'] = url_for( 'history', id=trans.security.encode_id( history.id ) )
|
||||
rval.append( item )
|
||||
except Exception, e:
|
||||
rval = "Error in history API at constructing return list"
|
||||
log.error( rval + ": %s" % str(e) )
|
||||
trans.response.status = 500
|
||||
elif trans.galaxy_session.current_history:
|
||||
#No user, this must be session authentication with an anonymous user.
|
||||
history = trans.galaxy_session.current_history
|
||||
item = history.get_api_value(value_mapper={'id':trans.security.encode_id})
|
||||
item['url'] = url_for( 'history', id=trans.security.encode_id( history.id ) )
|
||||
rval.append(item)
|
||||
except Exception, e:
|
||||
rval = "Error in history API"
|
||||
log.error( rval + ": %s" % str(e) )
|
||||
trans.response.status = 500
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
@@ -54,7 +53,7 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
history_id = id
|
||||
params = util.Params( kwd )
|
||||
deleted = util.string_as_bool( deleted )
|
||||
|
||||
|
||||
def traverse( datasets ):
|
||||
rval = {}
|
||||
states = trans.app.model.Dataset.states
|
||||
@@ -65,15 +64,13 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
if not item['deleted']:
|
||||
rval[item['state']] = rval[item['state']] + 1
|
||||
return rval
|
||||
|
||||
try:
|
||||
history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True, deleted=deleted )
|
||||
except Exception, e:
|
||||
return str( e )
|
||||
|
||||
try:
|
||||
item = history.get_api_value(view='element', value_mapper={'id':trans.security.encode_id})
|
||||
num_sets = len( [hda.id for hda in history.datasets if not hda.deleted] )
|
||||
num_sets = len( [hda.id for hda in history.datasets if not hda.deleted] )
|
||||
states = trans.app.model.Dataset.states
|
||||
state = states.ERROR
|
||||
if num_sets == 0:
|
||||
@@ -87,7 +84,7 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
elif summary[states.QUEUED] > 0:
|
||||
state = states.QUEUED
|
||||
elif summary[states.OK] == num_sets:
|
||||
state = states.OK
|
||||
state = states.OK
|
||||
item['contents_url'] = url_for( 'history_contents', history_id=history_id )
|
||||
item['state_details'] = summary
|
||||
item['state'] = state
|
||||
@@ -108,7 +105,7 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
if payload.get( 'name', None ):
|
||||
hist_name = util.restore_text( payload['name'] )
|
||||
new_history = trans.app.model.History( user=trans.user, name=hist_name )
|
||||
|
||||
|
||||
trans.sa_session.add( new_history )
|
||||
trans.sa_session.flush()
|
||||
item = new_history.get_api_value(view='element', value_mapper={'id':trans.security.encode_id})
|
||||
@@ -124,8 +121,8 @@ class HistoriesController( BaseAPIController, UsesHistory ):
|
||||
# a request body is optional here
|
||||
purge = False
|
||||
if kwd.get( 'payload', None ):
|
||||
purge = util.string_as_bool( kwd['payload'].get( 'purge', False ) )
|
||||
|
||||
purge = util.string_as_bool( kwd['payload'].get( 'purge', False ) )
|
||||
|
||||
try:
|
||||
history = self.get_history( trans, history_id, check_ownership=True, check_accessible=False, deleted=True )
|
||||
except Exception, e:
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""
|
||||
API operations on the contents of a history.
|
||||
"""
|
||||
import logging, os, string, shutil, urllib, re, socket
|
||||
from cgi import escape, FieldStorage
|
||||
from galaxy import util, datatypes, jobs, web, util
|
||||
import logging
|
||||
from galaxy import web
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.model.orm import *
|
||||
|
||||
import pkg_resources
|
||||
@@ -21,12 +19,11 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
"""
|
||||
GET /api/histories/{encoded_history_id}/contents
|
||||
Displays a collection (list) of history contents
|
||||
"""
|
||||
"""
|
||||
try:
|
||||
history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True )
|
||||
except Exception, e:
|
||||
return str( e )
|
||||
|
||||
rval = []
|
||||
try:
|
||||
for dataset in history.datasets:
|
||||
@@ -57,6 +54,8 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
return str( e )
|
||||
try:
|
||||
item = content.get_api_value( view='element' )
|
||||
if trans.user_is_admin() or trans.app.config.expose_dataset_path:
|
||||
item['file_name'] = content.file_name
|
||||
if not item['deleted']:
|
||||
# Problem: Method url_for cannot use the dataset controller
|
||||
# Get the environment from DefaultWebTransaction and use default webapp mapper instead of webapp API mapper
|
||||
@@ -67,7 +66,7 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
item = self.encode_all_ids( trans, item )
|
||||
except Exception, e:
|
||||
item = "Error in history API at listing dataset"
|
||||
log.error( item + ": %s" % str(e) )
|
||||
log.error( item + ": %s" % str(e) )
|
||||
trans.response.status = 500
|
||||
return item
|
||||
|
||||
@@ -77,7 +76,6 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
POST /api/libraries/{encoded_history_id}/contents
|
||||
Creates a new history content item (file, aka HistoryDatasetAssociation).
|
||||
"""
|
||||
params = util.Params( payload )
|
||||
from_ld_id = payload.get( 'from_ld_id', None )
|
||||
|
||||
try:
|
||||
@@ -95,7 +93,6 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
except Exception, e:
|
||||
return str( e )
|
||||
hda = ld.library_dataset_dataset_association.to_history_dataset_association( history, add_to_history=True )
|
||||
history.add_dataset( hda )
|
||||
trans.sa_session.flush()
|
||||
return hda.get_api_value()
|
||||
else:
|
||||
|
||||
@@ -7,18 +7,27 @@ from galaxy import util, datatypes, jobs, web, util
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.model.orm import *
|
||||
from paste.httpexceptions import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class LibrariesController( BaseAPIController ):
|
||||
|
||||
@web.expose_api
|
||||
def index( self, trans, **kwd ):
|
||||
def index( self, trans, deleted='False', **kwd ):
|
||||
"""
|
||||
GET /api/libraries
|
||||
GET /api/libraries/deleted
|
||||
Displays a collection (list) of libraries.
|
||||
"""
|
||||
query = trans.sa_session.query( trans.app.model.Library ).filter( trans.app.model.Library.table.c.deleted == False )
|
||||
query = trans.sa_session.query( trans.app.model.Library )
|
||||
deleted = util.string_as_bool( deleted )
|
||||
if deleted:
|
||||
route = 'deleted_library'
|
||||
query = query.filter( trans.app.model.Library.table.c.deleted == True )
|
||||
else:
|
||||
route = 'library'
|
||||
query = query.filter( trans.app.model.Library.table.c.deleted == False )
|
||||
current_user_role_ids = [ role.id for role in trans.get_current_user_roles() ]
|
||||
library_access_action = trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action
|
||||
restricted_library_ids = [ lp.library_id for lp in trans.sa_session.query( trans.model.LibraryPermissions ) \
|
||||
@@ -32,31 +41,32 @@ class LibrariesController( BaseAPIController ):
|
||||
rval = []
|
||||
for library in query:
|
||||
item = library.get_api_value()
|
||||
item['url'] = url_for( 'library', id=trans.security.encode_id( library.id ) )
|
||||
item['url'] = url_for( route, id=trans.security.encode_id( library.id ) )
|
||||
item['id'] = trans.security.encode_id( item['id'] )
|
||||
rval.append( item )
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
def show( self, trans, id, **kwd ):
|
||||
def show( self, trans, id, deleted='False', **kwd ):
|
||||
"""
|
||||
GET /api/libraries/{encoded_library_id}
|
||||
GET /api/libraries/deleted/{encoded_library_id}
|
||||
Displays information about a library.
|
||||
"""
|
||||
library_id = id
|
||||
deleted = util.string_as_bool( deleted )
|
||||
params = util.Params( kwd )
|
||||
try:
|
||||
decoded_library_id = trans.security.decode_id( library_id )
|
||||
except TypeError:
|
||||
trans.response.status = 400
|
||||
return "Malformed library id ( %s ) specified, unable to decode." % str( library_id )
|
||||
raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id )
|
||||
try:
|
||||
library = trans.sa_session.query( trans.app.model.Library ).get( decoded_library_id )
|
||||
assert library.deleted == deleted
|
||||
except:
|
||||
library = None
|
||||
if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ):
|
||||
trans.response.status = 400
|
||||
return "Invalid library id ( %s ) specified." % str( library_id )
|
||||
raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id )
|
||||
item = library.get_api_value( view='element' )
|
||||
#item['contents_url'] = url_for( 'contents', library_id=library_id )
|
||||
item['contents_url'] = url_for( 'library_contents', library_id=library_id )
|
||||
@@ -69,13 +79,11 @@ class LibrariesController( BaseAPIController ):
|
||||
Creates a new library.
|
||||
"""
|
||||
if not trans.user_is_admin():
|
||||
trans.response.status = 403
|
||||
return "You are not authorized to create a new library."
|
||||
raise HTTPForbidden( detail='You are not authorized to create a new library.' )
|
||||
params = util.Params( payload )
|
||||
name = util.restore_text( params.get( 'name', None ) )
|
||||
if not name:
|
||||
trans.response.status = 400
|
||||
return "Missing required parameter 'name'."
|
||||
raise HTTPBadRequest( detail="Missing required parameter 'name'." )
|
||||
description = util.restore_text( params.get( 'description', '' ) )
|
||||
synopsis = util.restore_text( params.get( 'synopsis', '' ) )
|
||||
if synopsis in [ 'None', None ]:
|
||||
@@ -91,3 +99,22 @@ class LibrariesController( BaseAPIController ):
|
||||
rval['name'] = name
|
||||
rval['id'] = encoded_id
|
||||
return [ rval ]
|
||||
|
||||
@web.expose_api
|
||||
def delete( self, trans, id, **kwd ):
|
||||
if not trans.user_is_admin():
|
||||
raise HTTPForbidden( detail='You are not authorized to delete libraries.' )
|
||||
try:
|
||||
decoded_id = trans.security.decode_id( id )
|
||||
except TypeError:
|
||||
raise HTTPBadRequest( detail='Malformed library id ( %s ) specified, unable to decode.' % id )
|
||||
try:
|
||||
library = trans.sa_session.query( trans.app.model.Library ).get( decoded_id )
|
||||
except:
|
||||
library = None
|
||||
if not library:
|
||||
raise HTTPBadRequest( detail='Invalid library id ( %s ) specified.' % id )
|
||||
library.deleted = True
|
||||
trans.sa_session.add( library )
|
||||
trans.sa_session.flush()
|
||||
return library.get_api_value( view='element', value_mapper={ 'id' : trans.security.encode_id } )
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
"""
|
||||
API operations on a sample tracking system.
|
||||
"""
|
||||
import logging, os, string, shutil, urllib, re, socket
|
||||
from cgi import escape, FieldStorage
|
||||
from galaxy import util, datatypes, jobs, web, util
|
||||
import logging
|
||||
from galaxy import util, web
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.util.bunch import Bunch
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class RequestsAPIController( BaseAPIController ):
|
||||
update_types = Bunch( REQUEST = 'request_state' )
|
||||
update_type_values = [v[1] for v in update_types.items()]
|
||||
_update_types = Bunch( REQUEST = 'request_state' )
|
||||
_update_type_values = [v[1] for v in _update_types.items()]
|
||||
@web.expose_api
|
||||
def index( self, trans, **kwd ):
|
||||
"""
|
||||
@@ -77,7 +75,7 @@ class RequestsAPIController( BaseAPIController ):
|
||||
return "Missing required 'update_type' parameter. Please consult the API documentation for help."
|
||||
else:
|
||||
update_type = payload.pop( 'update_type' )
|
||||
if update_type not in self.update_type_values:
|
||||
if update_type not in self._update_type_values:
|
||||
trans.response.status = 400
|
||||
return "Invalid value for 'update_type' parameter ( %s ) specified. Please consult the API documentation for help." % update_type
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
from galaxy import config, tools, web, util
|
||||
from galaxy.web.base.controller import BaseController, BaseAPIController
|
||||
from galaxy.util.bunch import Bunch
|
||||
|
||||
messages = Bunch(
|
||||
NO_TOOL = "no tool"
|
||||
)
|
||||
|
||||
class ToolsController( BaseAPIController ):
|
||||
"""
|
||||
RESTful controller for interactions with tools.
|
||||
"""
|
||||
|
||||
@web.expose_api
|
||||
def index( self, trans, **kwds ):
|
||||
"""
|
||||
GET /api/tools: returns a list of tools defined by parameters
|
||||
parameters:
|
||||
in_panel - if true, tools are returned in panel structure,
|
||||
including sections and labels
|
||||
trackster - if true, only tools that are compatible with
|
||||
Trackster are returned
|
||||
"""
|
||||
|
||||
# Read params.
|
||||
in_panel = util.string_as_bool( kwds.get( 'in_panel', 'True' ) )
|
||||
trackster = util.string_as_bool( kwds.get( 'trackster', 'False' ) )
|
||||
|
||||
# Create return value.
|
||||
return self.app.toolbox.to_dict( trans, in_panel=in_panel, trackster=trackster )
|
||||
|
||||
@web.json
|
||||
def show( self, trans, id, **kwd ):
|
||||
"""
|
||||
GET /api/tools/{tool_id}
|
||||
Returns tool information, including parameters and inputs.
|
||||
"""
|
||||
return self.app.toolbox.tools_by_id[ id ].to_dict( trans, for_display=True )
|
||||
|
||||
@web.expose_api
|
||||
def create( self, trans, payload, **kwd ):
|
||||
"""
|
||||
POST /api/tools
|
||||
Executes tool using specified inputs, creating new history-dataset
|
||||
associations, which are returned.
|
||||
"""
|
||||
|
||||
# TODO: set target history?
|
||||
|
||||
# -- Execute tool. --
|
||||
|
||||
# Get tool.
|
||||
tool_id = payload[ 'id' ]
|
||||
tool = trans.app.toolbox.get_tool( tool_id )
|
||||
if not tool:
|
||||
return { "message": { "type": "error", "text" : messages.NO_TOOL } }
|
||||
|
||||
# Set up inputs.
|
||||
inputs = payload[ 'inputs' ]
|
||||
# HACK: add run button so that tool.handle_input will run tool.
|
||||
inputs['runtool_btn'] = 'Execute'
|
||||
# TODO: encode data ids and decode ids.
|
||||
params = util.Params( inputs, sanitize = False )
|
||||
template, vars = tool.handle_input( trans, params.__dict__ )
|
||||
|
||||
# TODO: check for errors and ensure that output dataset(s) are available.
|
||||
output_datasets = vars[ 'out_data' ].values()
|
||||
rval = {
|
||||
"outputs": []
|
||||
}
|
||||
outputs = rval[ "outputs" ]
|
||||
for output in output_datasets:
|
||||
outputs.append( output.get_api_value() )
|
||||
return rval
|
||||
|
||||
@@ -18,9 +18,9 @@ class WorkflowsAPIController(BaseAPIController):
|
||||
def index(self, trans, **kwd):
|
||||
"""
|
||||
GET /api/workflows
|
||||
Displays a collection of workflows
|
||||
|
||||
Displays a collection of workflows.
|
||||
"""
|
||||
# List parameters of a specific workflow
|
||||
rval = []
|
||||
for wf in trans.sa_session.query(trans.app.model.StoredWorkflow).filter_by(
|
||||
user=trans.user, deleted=False).order_by(
|
||||
@@ -38,10 +38,12 @@ class WorkflowsAPIController(BaseAPIController):
|
||||
item['url'] = url_for('workflow', id=encoded_id)
|
||||
rval.append(item)
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
def show(self, trans, id, **kwd):
|
||||
"""
|
||||
GET /api/workflows/{encoded_workflow_id}
|
||||
|
||||
Displays information needed to run a workflow from the command line.
|
||||
"""
|
||||
workflow_id = id
|
||||
@@ -72,28 +74,29 @@ class WorkflowsAPIController(BaseAPIController):
|
||||
# p = step.get_required_parameters()
|
||||
item['inputs'] = inputs
|
||||
return item
|
||||
|
||||
@web.expose_api
|
||||
def create(self, trans, payload, **kwd):
|
||||
"""
|
||||
POST /api/workflows
|
||||
|
||||
We're not creating workflows from the api. Just execute for now.
|
||||
|
||||
However, we will import them if installed_repository_file is specified
|
||||
"""
|
||||
if 'workflow_id' not in payload:
|
||||
# create new
|
||||
if 'installed_repository_file' in payload:
|
||||
workflow_controller = trans.webapp.controllers[ 'workflow' ]
|
||||
result = workflow_controller.import_workflow( trans=trans,
|
||||
result = workflow_controller.import_workflow( trans=trans,
|
||||
cntrller='api',
|
||||
**payload)
|
||||
return result
|
||||
trans.response.status = 403
|
||||
return "Either workflow_id or installed_repository_file must be specified"
|
||||
|
||||
if 'installed_repository_file' in payload:
|
||||
trans.response.status = 403
|
||||
return "installed_repository_file may not be specified with workflow_id"
|
||||
|
||||
stored_workflow = trans.sa_session.query(self.app.model.StoredWorkflow).get(
|
||||
trans.security.decode_id(payload['workflow_id']))
|
||||
if stored_workflow.user != trans.user and not trans.user_is_admin():
|
||||
@@ -215,3 +218,4 @@ class WorkflowsAPIController(BaseAPIController):
|
||||
trans.sa_session.add( workflow_invocation )
|
||||
trans.sa_session.flush()
|
||||
return rval
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ class UsesVisualization( SharableItemSecurity ):
|
||||
if hda_ldda == "hda":
|
||||
dataset = self.get_dataset( trans, dataset_id, check_ownership=False, check_accessible=True )
|
||||
else:
|
||||
dataset = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id(dataset_id) )
|
||||
dataset = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( dataset_id )
|
||||
|
||||
try:
|
||||
prefs = track_dict['prefs']
|
||||
@@ -1325,8 +1325,7 @@ class Admin( object ):
|
||||
message = kwd.get( 'message', '' )
|
||||
status = kwd.get( 'status', 'done' )
|
||||
if webapp == 'galaxy':
|
||||
cloned_repositories = trans.sa_session.query( trans.model.ToolShedRepository ) \
|
||||
.first()
|
||||
cloned_repositories = trans.sa_session.query( trans.model.ToolShedRepository ).first()
|
||||
return trans.fill_template( '/webapps/galaxy/admin/index.mako',
|
||||
webapp=webapp,
|
||||
cloned_repositories=cloned_repositories,
|
||||
@@ -2381,8 +2380,8 @@ class Admin( object ):
|
||||
deleted = []
|
||||
msg = None
|
||||
status = None
|
||||
if not trans.app.config.get_bool( "enable_job_running", True ):
|
||||
return trans.show_error_message( 'This Galaxy instance is not configured to run jobs. If using multiple servers, please directly access the job running instance to manage jobs.' )
|
||||
if not self.app.config.job_manager != self.app.config.server_name:
|
||||
return trans.show_error_message( 'This Galaxy instance is not the job manager. If using multiple servers, please directly access the job manager instance to manage jobs.' )
|
||||
job_ids = util.listify( stop )
|
||||
if job_ids and stop_msg in [ None, '' ]:
|
||||
msg = 'Please enter an error message to display to the user describing why the job was terminated'
|
||||
|
||||
+32
-30
@@ -103,36 +103,38 @@ def app_factory( global_conf, **kwargs ):
|
||||
webapp.add_route( '/u/:username/h/:slug', controller='history', action='display_by_username_and_slug' )
|
||||
webapp.add_route( '/u/:username/w/:slug', controller='workflow', action='display_by_username_and_slug' )
|
||||
webapp.add_route( '/u/:username/v/:slug', controller='visualization', action='display_by_username_and_slug' )
|
||||
# If enabled, add the web API
|
||||
if asbool( kwargs.get( 'enable_api', False ) ):
|
||||
add_api_controllers( webapp, app )
|
||||
webapp.api_mapper.resource( 'content',
|
||||
'contents',
|
||||
controller='library_contents',
|
||||
name_prefix='library_',
|
||||
path_prefix='/api/libraries/:library_id',
|
||||
parent_resources=dict( member_name='library', collection_name='libraries' ) )
|
||||
webapp.api_mapper.resource( 'content',
|
||||
'contents',
|
||||
controller='history_contents',
|
||||
name_prefix='history_',
|
||||
path_prefix='/api/histories/:history_id',
|
||||
parent_resources=dict( member_name='history', collection_name='histories' ) )
|
||||
webapp.api_mapper.resource( 'permission',
|
||||
'permissions',
|
||||
path_prefix='/api/libraries/:library_id',
|
||||
parent_resources=dict( member_name='library', collection_name='libraries' ) )
|
||||
webapp.api_mapper.resource( 'library', 'libraries', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'sample', 'samples', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'request', 'requests', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'form', 'forms', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'request_type', 'request_types', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'role', 'roles', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'quota', 'quotas', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'user', 'users', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'workflow', 'workflows', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'history', 'histories', path_prefix='/api' )
|
||||
#webapp.api_mapper.connect( 'run_workflow', '/api/workflow/{workflow_id}/library/{library_id}', controller='workflows', action='run', workflow_id=None, library_id=None, conditions=dict(method=["GET"]) )
|
||||
|
||||
# Add the web API
|
||||
add_api_controllers( webapp, app )
|
||||
webapp.api_mapper.resource( 'content',
|
||||
'contents',
|
||||
controller='library_contents',
|
||||
name_prefix='library_',
|
||||
path_prefix='/api/libraries/:library_id',
|
||||
parent_resources=dict( member_name='library', collection_name='libraries' ) )
|
||||
webapp.api_mapper.resource( 'content',
|
||||
'contents',
|
||||
controller='history_contents',
|
||||
name_prefix='history_',
|
||||
path_prefix='/api/histories/:history_id',
|
||||
parent_resources=dict( member_name='history', collection_name='histories' ) )
|
||||
webapp.api_mapper.resource( 'permission',
|
||||
'permissions',
|
||||
path_prefix='/api/libraries/:library_id',
|
||||
parent_resources=dict( member_name='library', collection_name='libraries' ) )
|
||||
webapp.api_mapper.resource( 'dataset', 'datasets', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'library', 'libraries', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'sample', 'samples', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'request', 'requests', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'form', 'forms', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'request_type', 'request_types', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'role', 'roles', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'quota', 'quotas', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'tool', 'tools', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'user', 'users', path_prefix='/api' )
|
||||
webapp.api_mapper.resource( 'workflow', 'workflows', path_prefix='/api' )
|
||||
webapp.api_mapper.resource_with_deleted( 'history', 'histories', path_prefix='/api' )
|
||||
#webapp.api_mapper.connect( 'run_workflow', '/api/workflow/{workflow_id}/library/{library_id}', controller='workflows', action='run', workflow_id=None, library_id=None, conditions=dict(method=["GET"]) )
|
||||
|
||||
webapp.finalize_config()
|
||||
# Wrap the webapp in some useful middleware
|
||||
|
||||
@@ -2,10 +2,15 @@ import urllib2
|
||||
from galaxy.web.controllers.admin import *
|
||||
from galaxy.util.json import from_json_string, to_json_string
|
||||
from galaxy.util.shed_util import *
|
||||
from galaxy import tools
|
||||
from galaxy import eggs, tools
|
||||
|
||||
eggs.require( 'mercurial' )
|
||||
from mercurial import hg
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
MAX_CONTENT_SIZE = 32768
|
||||
|
||||
class RepositoryListGrid( grids.Grid ):
|
||||
class NameColumn( grids.TextColumn ):
|
||||
def get_value( self, trans, grid, tool_shed_repository ):
|
||||
@@ -144,7 +149,7 @@ class AdminToolshed( AdminGalaxy ):
|
||||
def browse_tool_shed( self, trans, **kwd ):
|
||||
tool_shed_url = kwd[ 'tool_shed_url' ]
|
||||
galaxy_url = url_for( '/', qualified=True )
|
||||
url = '%s/repository/browse_valid_repositories?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
|
||||
url = '%srepository/browse_valid_repositories?galaxy_url=%s&webapp=galaxy&no_reset=true' % ( tool_shed_url, galaxy_url )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
@@ -162,7 +167,7 @@ class AdminToolshed( AdminGalaxy ):
|
||||
# Send a request to the relevant tool shed to see if there are any updates.
|
||||
repository = get_repository( trans, kwd[ 'id' ] )
|
||||
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
|
||||
url = '%s/repository/check_for_updates?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
|
||||
url = '%s/repository/check_for_updates?galaxy_url=%s&name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % \
|
||||
( tool_shed_url, url_for( '/', qualified=True ), repository.name, repository.owner, repository.changeset_revision )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
@@ -213,14 +218,14 @@ class AdminToolshed( AdminGalaxy ):
|
||||
def find_tools_in_tool_shed( self, trans, **kwd ):
|
||||
tool_shed_url = kwd[ 'tool_shed_url' ]
|
||||
galaxy_url = url_for( '/', qualified=True )
|
||||
url = '%s/repository/find_tools?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
|
||||
url = '%srepository/find_tools?galaxy_url=%s&webapp=galaxy&no_reset=true' % ( tool_shed_url, galaxy_url )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def find_workflows_in_tool_shed( self, trans, **kwd ):
|
||||
tool_shed_url = kwd[ 'tool_shed_url' ]
|
||||
galaxy_url = url_for( '/', qualified=True )
|
||||
url = '%s/repository/find_workflows?galaxy_url=%s&webapp=galaxy' % ( tool_shed_url, galaxy_url )
|
||||
url = '%srepository/find_workflows?galaxy_url=%s&webapp=galaxy&no_reset=true' % ( tool_shed_url, galaxy_url )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
@@ -288,58 +293,45 @@ class AdminToolshed( AdminGalaxy ):
|
||||
current_working_dir = os.getcwd()
|
||||
installed_repository_names = []
|
||||
for name, repo_info_tuple in repo_info_dict.items():
|
||||
description, repository_clone_url, changeset_revision = repo_info_tuple
|
||||
description, repository_clone_url, changeset_revision, ctx_rev = repo_info_tuple
|
||||
clone_dir = os.path.join( tool_path, self.__generate_tool_path( repository_clone_url, changeset_revision ) )
|
||||
relative_install_dir = os.path.join( clone_dir, name )
|
||||
if os.path.exists( clone_dir ):
|
||||
# Repository and revision has already been cloned.
|
||||
message += 'Revision <b>%s</b> of repository <b>%s</b> was previously installed.<br/>' % ( changeset_revision, name )
|
||||
else:
|
||||
returncode, tmp_name = clone_repository( name, clone_dir, current_working_dir, repository_clone_url )
|
||||
if returncode == 0:
|
||||
returncode, tmp_name = update_repository( current_working_dir, relative_install_dir, changeset_revision )
|
||||
if returncode == 0:
|
||||
owner = get_repository_owner( clean_repository_clone_url( repository_clone_url ) )
|
||||
tool_shed = clean_tool_shed_url( tool_shed_url )
|
||||
tool_shed_repository, metadata_dict = load_repository_contents( trans,
|
||||
repository_name=name,
|
||||
description=description,
|
||||
owner=owner,
|
||||
changeset_revision=changeset_revision,
|
||||
tool_path=tool_path,
|
||||
repository_clone_url=repository_clone_url,
|
||||
relative_install_dir=relative_install_dir,
|
||||
current_working_dir=current_working_dir,
|
||||
tmp_name=tmp_name,
|
||||
tool_shed=tool_shed,
|
||||
tool_section=tool_section,
|
||||
shed_tool_conf=shed_tool_conf )
|
||||
if 'tools' in metadata_dict:
|
||||
# Get the tool_versions from the tool shed for each tool in the installed change set.
|
||||
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
|
||||
( tool_shed_url, name, owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
response.close()
|
||||
if text:
|
||||
tool_version_dicts = from_json_string( text )
|
||||
handle_tool_versions( trans.app, tool_version_dicts, tool_shed_repository )
|
||||
else:
|
||||
message += "Version information for the tools included in the <b>%s</b> repository is missing. " % name
|
||||
message += "Reset all of this repository's metadata in the tool shed, then set the installed tool versions "
|
||||
message += "from the installed repository's <b>Repository Actions</b> menu. "
|
||||
status = 'error'
|
||||
installed_repository_names.append( name )
|
||||
clone_repository( repository_clone_url, os.path.abspath( relative_install_dir ), ctx_rev )
|
||||
owner = get_repository_owner( clean_repository_clone_url( repository_clone_url ) )
|
||||
tool_shed = clean_tool_shed_url( tool_shed_url )
|
||||
tool_shed_repository, metadata_dict = load_repository_contents( trans,
|
||||
repository_name=name,
|
||||
description=description,
|
||||
owner=owner,
|
||||
changeset_revision=changeset_revision,
|
||||
ctx_rev=ctx_rev,
|
||||
tool_path=tool_path,
|
||||
repository_clone_url=repository_clone_url,
|
||||
relative_install_dir=relative_install_dir,
|
||||
current_working_dir=current_working_dir,
|
||||
tool_shed=tool_shed,
|
||||
tool_section=tool_section,
|
||||
shed_tool_conf=shed_tool_conf )
|
||||
if 'tools' in metadata_dict:
|
||||
# Get the tool_versions from the tool shed for each tool in the installed change set.
|
||||
url = '%srepository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % \
|
||||
( tool_shed_url, name, owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
response.close()
|
||||
if text:
|
||||
tool_version_dicts = from_json_string( text )
|
||||
handle_tool_versions( trans.app, tool_version_dicts, tool_shed_repository )
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
message += '%s<br/>' % tmp_stderr.read()
|
||||
tmp_stderr.close()
|
||||
message += "Version information for the tools included in the <b>%s</b> repository is missing. " % name
|
||||
message += "Reset all of this repository's metadata in the tool shed, then set the installed tool versions "
|
||||
message += "from the installed repository's <b>Repository Actions</b> menu. "
|
||||
status = 'error'
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
message += '%s<br/>' % tmp_stderr.read()
|
||||
tmp_stderr.close()
|
||||
status = 'error'
|
||||
installed_repository_names.append( name )
|
||||
if installed_repository_names:
|
||||
installed_repository_names.sort()
|
||||
num_repositories_installed = len( installed_repository_names )
|
||||
@@ -367,6 +359,29 @@ class AdminToolshed( AdminGalaxy ):
|
||||
shed_tool_conf = shed_tool_conf.replace( './', '', 1 )
|
||||
shed_tool_conf_select_field = None
|
||||
tool_panel_section_select_field = build_tool_panel_section_select_field( trans )
|
||||
if includes_tools:
|
||||
# If we're installing a single repository that contains a readme file, get it's contents to display.
|
||||
decoded_repo_info_dict = tool_shed_decode( repo_info_dict )
|
||||
if len( decoded_repo_info_dict ) == 1:
|
||||
name = decoded_repo_info_dict.keys()[ 0 ]
|
||||
repo_info_tuple = decoded_repo_info_dict[ name ]
|
||||
description, repository_clone_url, changeset_revision, ctx_rev = repo_info_tuple
|
||||
owner = get_repository_owner( clean_repository_clone_url( repository_clone_url ) )
|
||||
url = '%srepository/get_readme?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % ( tool_shed_url, name, owner, changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
raw_text = response.read()
|
||||
response.close()
|
||||
readme_text = ''
|
||||
for i, line in enumerate( raw_text ):
|
||||
readme_text = '%s%s' % ( readme_text, to_html_str( line ) )
|
||||
if len( readme_text ) > MAX_CONTENT_SIZE:
|
||||
large_str = '\nFile contents truncated because file size is larger than maximum viewing size of %s\n' % util.nice_size( MAX_CONTENT_SIZE )
|
||||
readme_text = '%s%s' % ( readme_text, to_html_str( large_str ) )
|
||||
break
|
||||
else:
|
||||
readme_text = ''
|
||||
else:
|
||||
readme_text = ''
|
||||
return trans.fill_template( '/admin/tool_shed_repository/select_tool_panel_section.mako',
|
||||
tool_shed_url=tool_shed_url,
|
||||
repo_info_dict=repo_info_dict,
|
||||
@@ -375,6 +390,7 @@ class AdminToolshed( AdminGalaxy ):
|
||||
shed_tool_conf_select_field=shed_tool_conf_select_field,
|
||||
tool_panel_section_select_field=tool_panel_section_select_field,
|
||||
new_tool_panel_section=new_tool_panel_section,
|
||||
readme_text=readme_text,
|
||||
message=message,
|
||||
status=status )
|
||||
@web.expose
|
||||
@@ -418,79 +434,82 @@ class AdminToolshed( AdminGalaxy ):
|
||||
repository_clone_url = generate_clone_url( trans, repository )
|
||||
clone_dir = os.path.join( tool_path, self.__generate_tool_path( repository_clone_url, repository.installed_changeset_revision ) )
|
||||
relative_install_dir = os.path.join( clone_dir, repository.name )
|
||||
returncode, tmp_name = clone_repository( repository.name, clone_dir, current_working_dir, repository_clone_url )
|
||||
if returncode == 0:
|
||||
returncode, tmp_name = update_repository( current_working_dir, relative_install_dir, repository.installed_changeset_revision )
|
||||
if returncode == 0:
|
||||
if repository.includes_tools:
|
||||
# Get the location in the tool panel in which each tool was originally loaded.
|
||||
metadata = repository.metadata
|
||||
if 'tool_panel_section' in metadata:
|
||||
tool_panel_dict = metadata[ 'tool_panel_section' ]
|
||||
if not tool_panel_dict:
|
||||
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
|
||||
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
|
||||
if not repository.ctx_rev:
|
||||
# The ctx_rev column was introduced late, so may be null for some installed ToolShedRepositories.
|
||||
ctx_rev = get_ctx_rev( tool_shed_url, repository.name, repository.owner, repository.installed_changeset_revision )
|
||||
else:
|
||||
ctx_rev = repository.ctx_rev
|
||||
clone_repository( repository_clone_url, os.path.abspath( relative_install_dir ), ctx_rev )
|
||||
if repository.includes_tools:
|
||||
# Get the location in the tool panel in which each tool was originally loaded.
|
||||
metadata = repository.metadata
|
||||
if 'tool_panel_section' in metadata:
|
||||
tool_panel_dict = metadata[ 'tool_panel_section' ]
|
||||
if not tool_panel_dict:
|
||||
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
|
||||
else:
|
||||
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
|
||||
# TODO: Fix this to handle the case where the tools are distributed across in more than 1 ToolSection. The
|
||||
# following assumes everything was loaded into 1 section (or no section) in the tool panel.
|
||||
tool_section_dicts = tool_panel_dict[ tool_panel_dict.keys()[ 0 ] ]
|
||||
tool_section_dict = tool_section_dicts[ 0 ]
|
||||
original_section_id = tool_section_dict[ 'id' ]
|
||||
original_section_name = tool_section_dict[ 'name' ]
|
||||
if no_changes_checked:
|
||||
if original_section_id in [ '' ]:
|
||||
tool_section = None
|
||||
else:
|
||||
section_key = 'section_%s' % str( original_section_id )
|
||||
if section_key in trans.app.toolbox.tool_panel:
|
||||
tool_section = trans.app.toolbox.tool_panel[ section_key ]
|
||||
else:
|
||||
tool_panel_dict = generate_tool_panel_dict_for_new_install( metadata[ 'tools' ] )
|
||||
# TODO: Fix this to handle the case where the tools are distributed across in more than 1 ToolSection. The
|
||||
# following assumes everything was loaded into 1 section (or no section) in the tool panel.
|
||||
tool_section_dicts = tool_panel_dict[ tool_panel_dict.keys()[ 0 ] ]
|
||||
tool_section_dict = tool_section_dicts[ 0 ]
|
||||
original_section_id = tool_section_dict[ 'id' ]
|
||||
original_section_name = tool_section_dict[ 'name' ]
|
||||
if no_changes_checked:
|
||||
if original_section_id in [ '' ]:
|
||||
tool_section = None
|
||||
else:
|
||||
section_key = 'section_%s' % str( original_section_id )
|
||||
if section_key in trans.app.toolbox.tool_panel:
|
||||
tool_section = trans.app.toolbox.tool_panel[ section_key ]
|
||||
else:
|
||||
# The section in which the tool was originally loaded used to be in the tool panel, but no longer is.
|
||||
elem = Element( 'section' )
|
||||
elem.attrib[ 'name' ] = original_section_name
|
||||
elem.attrib[ 'id' ] = original_section_id
|
||||
elem.attrib[ 'version' ] = ''
|
||||
tool_section = tools.ToolSection( elem )
|
||||
trans.app.toolbox.tool_panel[ section_key ] = tool_section
|
||||
# The section in which the tool was originally loaded used to be in the tool panel, but no longer is.
|
||||
elem = Element( 'section' )
|
||||
elem.attrib[ 'name' ] = original_section_name
|
||||
elem.attrib[ 'id' ] = original_section_id
|
||||
elem.attrib[ 'version' ] = ''
|
||||
tool_section = tools.ToolSection( elem )
|
||||
trans.app.toolbox.tool_panel[ section_key ] = tool_section
|
||||
else:
|
||||
# The user elected to change the tool panel section to contain the tools.
|
||||
new_tool_panel_section = kwd.get( 'new_tool_panel_section', '' )
|
||||
tool_panel_section = kwd.get( 'tool_panel_section', '' )
|
||||
if new_tool_panel_section:
|
||||
section_id = new_tool_panel_section.lower().replace( ' ', '_' )
|
||||
new_section_key = 'section_%s' % str( section_id )
|
||||
if new_section_key in trans.app.toolbox.tool_panel:
|
||||
# Appending a tool to an existing section in trans.app.toolbox.tool_panel
|
||||
log.debug( "Appending to tool panel section: %s" % new_tool_panel_section )
|
||||
tool_section = trans.app.toolbox.tool_panel[ new_section_key ]
|
||||
else:
|
||||
# The user elected to change the tool panel section to contain the tools.
|
||||
new_tool_panel_section = kwd.get( 'new_tool_panel_section', '' )
|
||||
tool_panel_section = kwd.get( 'tool_panel_section', '' )
|
||||
if new_tool_panel_section:
|
||||
section_id = new_tool_panel_section.lower().replace( ' ', '_' )
|
||||
new_section_key = 'section_%s' % str( section_id )
|
||||
if new_section_key in trans.app.toolbox.tool_panel:
|
||||
# Appending a tool to an existing section in trans.app.toolbox.tool_panel
|
||||
log.debug( "Appending to tool panel section: %s" % new_tool_panel_section )
|
||||
tool_section = trans.app.toolbox.tool_panel[ new_section_key ]
|
||||
else:
|
||||
# Appending a new section to trans.app.toolbox.tool_panel
|
||||
log.debug( "Loading new tool panel section: %s" % new_tool_panel_section )
|
||||
elem = Element( 'section' )
|
||||
elem.attrib[ 'name' ] = new_tool_panel_section
|
||||
elem.attrib[ 'id' ] = section_id
|
||||
elem.attrib[ 'version' ] = ''
|
||||
tool_section = tools.ToolSection( elem )
|
||||
trans.app.toolbox.tool_panel[ new_section_key ] = tool_section
|
||||
elif tool_panel_section:
|
||||
section_key = 'section_%s' % tool_panel_section
|
||||
tool_section = trans.app.toolbox.tool_panel[ section_key ]
|
||||
else:
|
||||
tool_section = None
|
||||
tool_shed_repository, metadata_dict = load_repository_contents( trans,
|
||||
repository_name=repository.name,
|
||||
description=repository.description,
|
||||
owner=repository.owner,
|
||||
changeset_revision=repository.installed_changeset_revision,
|
||||
tool_path=tool_path,
|
||||
repository_clone_url=repository_clone_url,
|
||||
relative_install_dir=relative_install_dir,
|
||||
current_working_dir=current_working_dir,
|
||||
tmp_name=tmp_name,
|
||||
tool_shed=repository.tool_shed,
|
||||
tool_section=tool_section,
|
||||
shed_tool_conf=shed_tool_conf )
|
||||
repository.uninstalled = False
|
||||
# Appending a new section to trans.app.toolbox.tool_panel
|
||||
log.debug( "Loading new tool panel section: %s" % new_tool_panel_section )
|
||||
elem = Element( 'section' )
|
||||
elem.attrib[ 'name' ] = new_tool_panel_section
|
||||
elem.attrib[ 'id' ] = section_id
|
||||
elem.attrib[ 'version' ] = ''
|
||||
tool_section = tools.ToolSection( elem )
|
||||
trans.app.toolbox.tool_panel[ new_section_key ] = tool_section
|
||||
elif tool_panel_section:
|
||||
section_key = 'section_%s' % tool_panel_section
|
||||
tool_section = trans.app.toolbox.tool_panel[ section_key ]
|
||||
else:
|
||||
tool_section = None
|
||||
tool_shed_repository, metadata_dict = load_repository_contents( trans,
|
||||
repository_name=repository.name,
|
||||
description=repository.description,
|
||||
owner=repository.owner,
|
||||
changeset_revision=repository.installed_changeset_revision,
|
||||
ctx_rev=ctx_rev,
|
||||
tool_path=tool_path,
|
||||
repository_clone_url=repository_clone_url,
|
||||
relative_install_dir=relative_install_dir,
|
||||
current_working_dir=current_working_dir,
|
||||
tool_shed=repository.tool_shed,
|
||||
tool_section=tool_section,
|
||||
shed_tool_conf=shed_tool_conf )
|
||||
repository.uninstalled = False
|
||||
repository.deleted = False
|
||||
trans.sa_session.add( repository )
|
||||
trans.sa_session.flush()
|
||||
@@ -545,7 +564,7 @@ class AdminToolshed( AdminGalaxy ):
|
||||
# Get the tool_versions from the tool shed for each tool in the installed change set.
|
||||
repository = get_repository( trans, kwd[ 'id' ] )
|
||||
tool_shed_url = get_url_from_repository_tool_shed( trans.app, repository )
|
||||
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy' % \
|
||||
url = '%s/repository/get_tool_versions?name=%s&owner=%s&changeset_revision=%s&webapp=galaxy&no_reset=true' % \
|
||||
( tool_shed_url, repository.name, repository.owner, repository.changeset_revision )
|
||||
response = urllib2.urlopen( url )
|
||||
text = response.read()
|
||||
@@ -580,41 +599,32 @@ class AdminToolshed( AdminGalaxy ):
|
||||
owner = params.get( 'owner', None )
|
||||
changeset_revision = params.get( 'changeset_revision', None )
|
||||
latest_changeset_revision = params.get( 'latest_changeset_revision', None )
|
||||
latest_ctx_rev = params.get( 'latest_ctx_rev', None )
|
||||
repository = get_repository_by_shed_name_owner_changeset_revision( trans.app, tool_shed_url, name, owner, changeset_revision )
|
||||
if changeset_revision and latest_changeset_revision:
|
||||
if changeset_revision and latest_changeset_revision and latest_ctx_rev:
|
||||
if changeset_revision == latest_changeset_revision:
|
||||
message = "The cloned tool shed repository named '%s' is current (there are no updates available)." % name
|
||||
else:
|
||||
current_working_dir = os.getcwd()
|
||||
shed_tool_conf, tool_path, relative_install_dir = get_tool_panel_config_tool_path_install_dir( trans.app, repository )
|
||||
if relative_install_dir:
|
||||
repo_files_dir = os.path.join( relative_install_dir, name )
|
||||
returncode, tmp_name = pull_repository( current_working_dir, repo_files_dir, name )
|
||||
if returncode == 0:
|
||||
returncode, tmp_name = update_repository( current_working_dir, repo_files_dir, latest_changeset_revision )
|
||||
if returncode == 0:
|
||||
# Update the repository metadata.
|
||||
repository_clone_url = os.path.join( tool_shed_url, 'repos', owner, name )
|
||||
tool_shed = clean_tool_shed_url( tool_shed_url )
|
||||
metadata_dict = generate_metadata( trans.app.toolbox, relative_install_dir, repository_clone_url )
|
||||
repository.metadata = metadata_dict
|
||||
# Update the repository changeset_revision in the database.
|
||||
repository.changeset_revision = latest_changeset_revision
|
||||
repository.update_available = False
|
||||
trans.sa_session.add( repository )
|
||||
trans.sa_session.flush()
|
||||
message = "The cloned repository named '%s' has been updated to change set revision '%s'." % \
|
||||
( name, latest_changeset_revision )
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
message = tmp_stderr.read()
|
||||
tmp_stderr.close()
|
||||
status = 'error'
|
||||
else:
|
||||
tmp_stderr = open( tmp_name, 'rb' )
|
||||
message = tmp_stderr.read()
|
||||
tmp_stderr.close()
|
||||
status = 'error'
|
||||
repo_files_dir = os.path.abspath( os.path.join( relative_install_dir, name ) )
|
||||
repo = hg.repository( get_configured_ui(), path=repo_files_dir )
|
||||
repository_clone_url = os.path.join( tool_shed_url, 'repos', owner, name )
|
||||
pull_repository( repo, repository_clone_url, latest_ctx_rev )
|
||||
update_repository( repo, latest_ctx_rev )
|
||||
# Update the repository metadata.
|
||||
tool_shed = clean_tool_shed_url( tool_shed_url )
|
||||
metadata_dict = generate_metadata( trans.app.toolbox, relative_install_dir, repository_clone_url )
|
||||
repository.metadata = metadata_dict
|
||||
# Update the repository changeset_revision in the database.
|
||||
repository.changeset_revision = latest_changeset_revision
|
||||
repository.ctx_rev = latest_ctx_rev
|
||||
repository.update_available = False
|
||||
trans.sa_session.add( repository )
|
||||
trans.sa_session.flush()
|
||||
message = "The cloned repository named '%s' has been updated to change set revision '%s'." % \
|
||||
( name, latest_changeset_revision )
|
||||
else:
|
||||
message = "The directory containing the cloned repository named '%s' cannot be found." % name
|
||||
status = 'error'
|
||||
|
||||
@@ -6,10 +6,13 @@ BioCloudCentral Source: https://github.com/chapmanb/biocloudcentral
|
||||
|
||||
"""
|
||||
|
||||
import boto
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
from galaxy import eggs
|
||||
import pkg_resources
|
||||
pkg_resources.require('boto')
|
||||
import boto
|
||||
from galaxy import web
|
||||
from galaxy.web.base.controller import BaseUIController
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import sys, ftplib, json
|
||||
from galaxy import model, util
|
||||
from galaxy.jobs import transfer_manager
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.web.framework.helpers import time_ago, iff, grids
|
||||
from galaxy.model.orm import *
|
||||
from library_common import get_comptypes, lucene_search, whoosh_search
|
||||
|
||||
# Older py compatibility
|
||||
try:
|
||||
set()
|
||||
except:
|
||||
from sets import Set as set
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class DataAdmin( BaseUIController ):
|
||||
jobstyles = dict(
|
||||
done='panel-done-message',
|
||||
waiting='state-color-waiting',
|
||||
running='state-color-running',
|
||||
downloaded='state-color-running',
|
||||
new='state-color-new',
|
||||
ok='panel-done-message',
|
||||
error='panel-error-message'
|
||||
)
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def manage_data( self, trans, **kwd ):
|
||||
dbkeys = trans.db_builds
|
||||
return trans.fill_template( '/admin/data_admin/data_form.mako', dbkeys=dbkeys )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def download_build( self, trans, **kwd ):
|
||||
"""Download a genome from a remote source and add it to the library."""
|
||||
params = util.Params( kwd )
|
||||
source = params.get('source', '')
|
||||
longname = params.get('longname', None)
|
||||
if not isinstance( params.get( 'indexers', None ), list ):
|
||||
indexers = [ params.get( 'indexers', None ) ]
|
||||
else:
|
||||
indexers = params.get( 'indexers', None )
|
||||
if indexers is not None:
|
||||
if indexers == [None]:
|
||||
indexers = None
|
||||
url = None
|
||||
liftover = None
|
||||
newlift = []
|
||||
dbkey = params.get( 'dbkey', None )
|
||||
dbkeys = dict()
|
||||
protocol = 'http'
|
||||
|
||||
if source == 'NCBI':
|
||||
dbkey = params.get('dbkey', '')[0]
|
||||
url = 'http://togows.dbcls.jp/entry/ncbi-nucleotide/%s.fasta' % dbkey
|
||||
elif source == 'Broad':
|
||||
dbkey = params.get('dbkey', '')[0]
|
||||
url = 'ftp://ftp.broadinstitute.org/pub/seq/references/%s.fasta' % dbkey
|
||||
elif source == 'UCSC':
|
||||
longname = None
|
||||
for build in trans.db_builds:
|
||||
if dbkey[1] == build[0]:
|
||||
dbkey = build[0]
|
||||
longname = build[1]
|
||||
break
|
||||
assert dbkey is not '?', 'That build was not found'
|
||||
ftp = ftplib.FTP('hgdownload.cse.ucsc.edu')
|
||||
ftp.login('anonymous', 'user@example.com')
|
||||
checker = []
|
||||
liftover = []
|
||||
newlift = []
|
||||
try:
|
||||
ftp.retrlines('NLST /goldenPath/%s/liftOver/*.chain.gz' % dbkey, liftover.append)
|
||||
for chain in liftover:
|
||||
fname = chain.split( '/' )[-1]
|
||||
target = fname.replace( '.over.chain.gz', '' ).split( 'To' )[1]
|
||||
target = target[0].lower() + target[1:]
|
||||
newlift.append( [ chain, dbkey, target ] )
|
||||
current = dbkey[0].upper() + dbkey[1:]
|
||||
targetfile = '%sTo%s.over.chain.gz' % ( target, current )
|
||||
newlift.append( [ '/goldenPath/%s/liftOver/%s' % ( target, targetfile ), target, dbkey ] )
|
||||
except:
|
||||
newlift = None
|
||||
pass
|
||||
ftp.retrlines('NLST /goldenPath/%s/bigZips/' % dbkey, checker.append)
|
||||
for filename in [ dbkey, 'chromFa' ]:
|
||||
for extension in [ '.tar.gz', '.tar.bz2', '.zip', '.fa.gz', '.fa.bz2' ]:
|
||||
testfile = '/goldenPath/%s/bigZips/%s%s' % ( dbkey, filename, extension )
|
||||
if testfile in checker:
|
||||
url = 'ftp://hgdownload.cse.ucsc.edu%s' % testfile
|
||||
break;
|
||||
else:
|
||||
continue
|
||||
if url is None:
|
||||
message = u'The genome %s was not found on the UCSC server.' % dbkey
|
||||
status = u'error'
|
||||
return trans.fill_template( '/admin/data_admin/data_form.mako',
|
||||
message=message,
|
||||
status=status )
|
||||
elif source == 'Ensembl':
|
||||
section = params.get('ensembl_section', '')
|
||||
release1 = params.get('release_number', '')
|
||||
organism = params.get('organism', '')
|
||||
name = params.get('name', '')
|
||||
longname = organism
|
||||
dbkey = name
|
||||
release2 = params.get('release2', '')
|
||||
release2 = ".%s" % release2 if release2 else ""
|
||||
if section == 'standard':
|
||||
url = 'ftp://ftp.ensembl.org/pub/release-%s/fasta/%s/dna/%s.%s%s.dna.toplevel.fa.gz' % \
|
||||
(release1, organism.lower(), organism, name, release2)
|
||||
else:
|
||||
url = 'ftp://ftp.ensemblgenomes.org/pub/%s/release-%s/fasta/%s/dna/%s.%s%s.dna.toplevel.fa.gz' % \
|
||||
(section, release1, organism.lower(), organism, name, release2)
|
||||
elif source == 'local':
|
||||
url = 'http://127.0.0.1/%s.tar.gz' % dbkey
|
||||
else:
|
||||
raise ValueError
|
||||
params = dict( protocol='http', name=dbkey, datatype='fasta', url=url, user=trans.user.id )
|
||||
jobid = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].create_job( trans, url, dbkey, longname, indexers )
|
||||
chainjob = []
|
||||
if newlift is not None:
|
||||
for chain in newlift:
|
||||
liftover_url = u'ftp://hgdownload.cse.ucsc.edu%s' % chain[0]
|
||||
from_genome = chain[1]
|
||||
to_genome = chain[2]
|
||||
destfile = liftover_url.split('/')[-1].replace('.gz', '')
|
||||
chainjob.append( trans.app.job_manager.deferred_job_queue.plugins['LiftOverTransferPlugin'].create_job( trans, liftover_url, dbkey, from_genome, to_genome, destfile ) )
|
||||
job = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].get_job_status( jobid )
|
||||
job.params['liftover'] = chainjob
|
||||
trans.app.model.context.current.add( job )
|
||||
trans.app.model.context.current.flush()
|
||||
return trans.response.send_redirect( web.url_for( controller='data_admin',
|
||||
action='monitor_status',
|
||||
job=jobid ) )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def monitor_status( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
jobid = params.get( 'job', '' )
|
||||
chains = params.get( 'chains', [] )
|
||||
jobs = self._get_jobs( jobid, trans )
|
||||
return trans.fill_template( '/admin/data_admin/download_status.mako', mainjob=jobid, jobs=jobs )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def ajax_statusupdate( self, trans, **kwd ):
|
||||
sa_session = trans.app.model.context.current
|
||||
jobs = []
|
||||
params = util.Params( kwd )
|
||||
jobid = params.get( 'jobid', '' )
|
||||
jobs = self._get_jobs( jobid, trans )
|
||||
return trans.fill_template( '/admin/data_admin/ajax_statusupdate.mako', mainjob=jobid, jobs=jobs )
|
||||
|
||||
def _get_jobs( self, jobid, trans ):
|
||||
jobs = []
|
||||
job = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].get_job_status( jobid )
|
||||
sa_session = trans.app.model.context.current
|
||||
idxjobs = sa_session.query( model.GenomeIndexToolData ).filter_by( deferred_job_id=job.id, transfer_job_id=job.transfer_job.id ).all()
|
||||
if job.params[ 'liftover' ] is not None:
|
||||
for jobid in job.params[ 'liftover' ]:
|
||||
lo_job = trans.app.job_manager.deferred_job_queue.plugins['LiftOverTransferPlugin'].get_job_status( jobid )
|
||||
jobs.append( dict( jobid=lo_job.id, state=lo_job.state, type='Download liftOver' ) )
|
||||
for idxjob in idxjobs:
|
||||
jobentry = sa_session.query( model.Job ).filter_by( id=idxjob.job_id ).first()
|
||||
jobs.append( dict( jobid=jobentry.id, state=jobentry.state, type='Index Genome' ) )
|
||||
jobs.append( dict ( jobid=job.id, state=job.state, type='Main Job' ) )
|
||||
jobs.append( dict ( jobid=job.transfer_job.id, state=job.transfer_job.state, type='Download Genome' ) )
|
||||
for je in jobs:
|
||||
je[ 'style' ] = self.jobstyles[ je[ 'state' ] ]
|
||||
return jobs
|
||||
@@ -1,17 +1,22 @@
|
||||
import logging, os, string, shutil, re, socket, mimetypes, urllib, tempfile, zipfile, glob, sys
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib
|
||||
import zipfile
|
||||
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.web.framework.helpers import time_ago, iff, grids
|
||||
from galaxy import util, datatypes, jobs, web, model
|
||||
from cgi import escape, FieldStorage
|
||||
from galaxy import util, datatypes, web, model
|
||||
from galaxy.datatypes.display_applications.util import encode_dataset_user, decode_dataset_user
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.util import inflector
|
||||
from galaxy.model.item_attrs import *
|
||||
from galaxy.model import LibraryDatasetDatasetAssociation, HistoryDatasetAssociation
|
||||
from galaxy.web.framework.helpers import to_unicode
|
||||
|
||||
import pkg_resources;
|
||||
import pkg_resources;
|
||||
pkg_resources.require( "Paste" )
|
||||
import paste.httpexceptions
|
||||
|
||||
@@ -32,7 +37,7 @@ try:
|
||||
except RuntimeError:
|
||||
log.exception( "Compression error when testing zip compression. This option will be disabled for library downloads." )
|
||||
except (TypeError, zipfile.LargeZipFile): # ZIP64 is only in Python2.5+. Remove TypeError when 2.4 support is dropped
|
||||
log.warning( 'Max zip file size is 2GB, ZIP64 not supported' )
|
||||
log.warning( 'Max zip file size is 2GB, ZIP64 not supported' )
|
||||
comptypes.append( 'zip' )
|
||||
try:
|
||||
os.unlink( tmpf )
|
||||
@@ -53,7 +58,7 @@ This is in reference to dataset id ${dataset_id} from history id ${history_id}
|
||||
-----------------------------------------------------------------------------
|
||||
You should be able to view the history containing the related history item
|
||||
|
||||
${hid}: ${history_item_name}
|
||||
${hid}: ${history_item_name}
|
||||
|
||||
by logging in as a Galaxy admin user to the Galaxy instance referenced above
|
||||
and pointing your browser to the following link.
|
||||
@@ -90,7 +95,7 @@ class HistoryDatasetAssociationListGrid( grids.Grid ):
|
||||
class HistoryColumn( grids.GridColumn ):
|
||||
def get_value( self, trans, grid, hda):
|
||||
return hda.history.name
|
||||
|
||||
|
||||
class StatusColumn( grids.GridColumn ):
|
||||
def get_value( self, trans, grid, hda ):
|
||||
if hda.deleted:
|
||||
@@ -111,19 +116,19 @@ class HistoryDatasetAssociationListGrid( grids.Grid ):
|
||||
template='/dataset/grid.mako'
|
||||
default_sort_key = "-update_time"
|
||||
columns = [
|
||||
grids.TextColumn( "Name", key="name",
|
||||
grids.TextColumn( "Name", key="name",
|
||||
# Link name to dataset's history.
|
||||
link=( lambda item: iff( item.history.deleted, None, dict( operation="switch", id=item.id ) ) ), filterable="advanced", attach_popup=True ),
|
||||
HistoryColumn( "History", key="history",
|
||||
HistoryColumn( "History", key="history",
|
||||
link=( lambda item: iff( item.history.deleted, None, dict( operation="switch_history", id=item.id ) ) ) ),
|
||||
grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryDatasetAssociationTagAssociation, filterable="advanced", grid_name="HistoryDatasetAssocationListGrid" ),
|
||||
StatusColumn( "Status", key="deleted", attach_popup=False ),
|
||||
grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
|
||||
]
|
||||
columns.append(
|
||||
grids.MulticolFilterColumn(
|
||||
"Search",
|
||||
cols_to_filter=[ columns[0], columns[2] ],
|
||||
columns.append(
|
||||
grids.MulticolFilterColumn(
|
||||
"Search",
|
||||
cols_to_filter=[ columns[0], columns[2] ],
|
||||
key="free-text-search", visible=False, filterable="standard" )
|
||||
)
|
||||
operations = [
|
||||
@@ -136,17 +141,17 @@ class HistoryDatasetAssociationListGrid( grids.Grid ):
|
||||
num_rows_per_page = 50
|
||||
def build_initial_query( self, trans, **kwargs ):
|
||||
# Show user's datasets that are not deleted, not in deleted histories, and not hidden.
|
||||
# To filter HDAs by user, need to join model class/HDA and History table so that it is
|
||||
# possible to filter by user. However, for dictionary-based filtering to work, need a
|
||||
# To filter HDAs by user, need to join model class/HDA and History table so that it is
|
||||
# possible to filter by user. However, for dictionary-based filtering to work, need a
|
||||
# primary table for the query.
|
||||
return trans.sa_session.query( self.model_class ).select_from( self.model_class.table.join( model.History.table ) ) \
|
||||
.filter( model.History.user == trans.user ) \
|
||||
.filter( self.model_class.deleted==False ) \
|
||||
.filter( model.History.deleted==False ) \
|
||||
.filter( self.model_class.visible==True )
|
||||
|
||||
|
||||
class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHistoryDatasetAssociation, UsesItemRatings ):
|
||||
|
||||
|
||||
stored_list_grid = HistoryDatasetAssociationListGrid()
|
||||
|
||||
@web.expose
|
||||
@@ -202,7 +207,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
job_stdout=job.stdout,
|
||||
job_info=job.info,
|
||||
job_traceback=job.traceback,
|
||||
email=email,
|
||||
email=email,
|
||||
message=message )
|
||||
frm = to_address
|
||||
# Check email a bit
|
||||
@@ -219,130 +224,45 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
return trans.show_ok_message( "Your error report has been sent" )
|
||||
except Exception, e:
|
||||
return trans.show_error_message( "An error occurred sending the report by email: %s" % str( e ) )
|
||||
|
||||
|
||||
@web.expose
|
||||
def default(self, trans, dataset_id=None, **kwd):
|
||||
return 'This link may not be followed from within Galaxy.'
|
||||
|
||||
@web.expose
|
||||
def archive_composite_dataset( self, trans, data=None, **kwd ):
|
||||
# save a composite object into a compressed archive for downloading
|
||||
params = util.Params( kwd )
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
outfname = data.name[0:150]
|
||||
outfname = ''.join(c in valid_chars and c or '_' for c in outfname)
|
||||
if (params.do_action == None):
|
||||
params.do_action = 'zip' # default
|
||||
msg = util.restore_text( params.get( 'msg', '' ) )
|
||||
messagetype = params.get( 'messagetype', 'done' )
|
||||
if not data:
|
||||
msg = "You must select at least one dataset"
|
||||
messagetype = 'error'
|
||||
else:
|
||||
error = False
|
||||
try:
|
||||
if (params.do_action == 'zip'):
|
||||
# Can't use mkstemp - the file must not exist first
|
||||
tmpd = tempfile.mkdtemp()
|
||||
tmpf = os.path.join( tmpd, 'library_download.' + params.do_action )
|
||||
if ziptype == '64':
|
||||
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
|
||||
else:
|
||||
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED )
|
||||
archive.add = lambda x, y: archive.write( x, y.encode('CP437') )
|
||||
elif params.do_action == 'tgz':
|
||||
archive = util.streamball.StreamBall( 'w|gz' )
|
||||
elif params.do_action == 'tbz':
|
||||
archive = util.streamball.StreamBall( 'w|bz2' )
|
||||
except (OSError, zipfile.BadZipFile):
|
||||
error = True
|
||||
log.exception( "Unable to create archive for download" )
|
||||
msg = "Unable to create archive for %s for download, please report this error" % outfname
|
||||
messagetype = 'error'
|
||||
if not error:
|
||||
current_user_roles = trans.get_current_user_roles()
|
||||
ext = data.extension
|
||||
path = data.file_name
|
||||
fname = os.path.split(path)[-1]
|
||||
efp = data.extra_files_path
|
||||
htmlname = os.path.splitext(outfname)[0]
|
||||
if not htmlname.endswith(ext):
|
||||
htmlname = '%s_%s' % (htmlname,ext)
|
||||
archname = '%s.html' % htmlname # fake the real nature of the html file
|
||||
try:
|
||||
archive.add(data.file_name,archname)
|
||||
except IOError:
|
||||
error = True
|
||||
log.exception( "Unable to add composite parent %s to temporary library download archive" % data.file_name)
|
||||
msg = "Unable to create archive for download, please report this error"
|
||||
messagetype = 'error'
|
||||
for root, dirs, files in os.walk(efp):
|
||||
for fname in files:
|
||||
fpath = os.path.join(root,fname)
|
||||
rpath = os.path.relpath(fpath,efp)
|
||||
try:
|
||||
archive.add( fpath,rpath )
|
||||
except IOError:
|
||||
error = True
|
||||
log.exception( "Unable to add %s to temporary library download archive" % rpath)
|
||||
msg = "Unable to create archive for download, please report this error"
|
||||
messagetype = 'error'
|
||||
continue
|
||||
if not error:
|
||||
if params.do_action == 'zip':
|
||||
archive.close()
|
||||
tmpfh = open( tmpf )
|
||||
# CANNOT clean up - unlink/rmdir was always failing because file handle retained to return - must rely on a cron job to clean up tmp
|
||||
trans.response.set_content_type( "application/x-zip-compressed" )
|
||||
trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.zip"' % outfname
|
||||
return tmpfh
|
||||
else:
|
||||
trans.response.set_content_type( "application/x-tar" )
|
||||
outext = 'tgz'
|
||||
if params.do_action == 'tbz':
|
||||
outext = 'tbz'
|
||||
trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % (outfname,outext)
|
||||
archive.wsgi_status = trans.response.wsgi_status()
|
||||
archive.wsgi_headeritems = trans.response.wsgi_headeritems()
|
||||
return archive.stream
|
||||
return trans.show_error_message( msg )
|
||||
|
||||
|
||||
@web.expose
|
||||
def get_metadata_file(self, trans, hda_id, metadata_name):
|
||||
""" Allows the downloading of metadata files associated with datasets (eg. bai index for bam files) """
|
||||
data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( hda_id ) )
|
||||
if not data or not trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), data.dataset ):
|
||||
return trans.show_error_message( "You are not allowed to access this dataset" )
|
||||
|
||||
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in data.name)[0:150]
|
||||
|
||||
|
||||
file_ext = data.metadata.spec.get(metadata_name).get("file_ext", metadata_name)
|
||||
trans.response.headers["Content-Type"] = "application/octet-stream"
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (data.hid, fname, file_ext)
|
||||
return open(data.metadata.get(metadata_name).file_name)
|
||||
|
||||
def _check_dataset(self, trans, dataset_id):
|
||||
|
||||
def _check_dataset(self, trans, hda_id):
|
||||
# DEPRECATION: We still support unencoded ids for backward compatibility
|
||||
try:
|
||||
data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( dataset_id ) )
|
||||
data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( hda_id) )
|
||||
if data is None:
|
||||
raise ValueError( 'Invalid reference dataset id: %s.' % dataset_id )
|
||||
raise ValueError( 'Invalid reference dataset id: %s.' % hda_id)
|
||||
except:
|
||||
try:
|
||||
data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( int( dataset_id ) )
|
||||
data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( int( hda_id ) )
|
||||
except:
|
||||
data = None
|
||||
if not data:
|
||||
raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
|
||||
raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( hda_id ) )
|
||||
if not trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), data.dataset ):
|
||||
return trans.show_error_message( "You are not allowed to access this dataset" )
|
||||
|
||||
if data.state == trans.model.Dataset.states.UPLOAD:
|
||||
return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." )
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
def transfer_status(self, trans, dataset_id, filename=None):
|
||||
@@ -352,7 +272,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if isinstance( data, basestring ):
|
||||
return data
|
||||
log.debug( "Checking transfer status for dataset %s..." % data.dataset.id )
|
||||
|
||||
|
||||
# Pulling files in extra_files_path into cache is not handled via this
|
||||
# method but that's primarily because those files are typically linked to
|
||||
# through tool's output page anyhow so tying a JavaScript event that will
|
||||
@@ -361,63 +281,11 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
return True
|
||||
else:
|
||||
return trans.app.object_store.file_ready(data.dataset)
|
||||
|
||||
|
||||
@web.expose
|
||||
def display(self, trans, dataset_id=None, preview=False, filename=None, to_ext=None, **kwd):
|
||||
"""Catches the dataset id and displays file contents as directed"""
|
||||
composite_extensions = trans.app.datatypes_registry.get_composite_extensions( )
|
||||
composite_extensions.append('html') # for archiving composite datatypes
|
||||
def display(self, trans, dataset_id=None, preview=False, filename=None, to_ext=None, chunk=None, **kwd):
|
||||
data = self._check_dataset(trans, dataset_id)
|
||||
if isinstance( data, basestring ):
|
||||
return data
|
||||
|
||||
if filename and filename != "index":
|
||||
# For files in extra_files_path
|
||||
file_path = trans.app.object_store.get_filename(data.dataset, extra_dir='dataset_%s_files' % data.dataset.id, alt_name=filename)
|
||||
if os.path.exists( file_path ):
|
||||
if os.path.isdir( file_path ):
|
||||
return trans.show_error_message( "Directory listing is not allowed." ) #TODO: Reconsider allowing listing of directories?
|
||||
mime, encoding = mimetypes.guess_type( file_path )
|
||||
if not mime:
|
||||
try:
|
||||
mime = trans.app.datatypes_registry.get_mimetype_by_extension( ".".split( file_path )[-1] )
|
||||
except:
|
||||
mime = "text/plain"
|
||||
trans.response.set_content_type( mime )
|
||||
return open( file_path )
|
||||
else:
|
||||
return trans.show_error_message( "Could not find '%s' on the extra files path %s." % ( filename, file_path ) )
|
||||
|
||||
trans.response.set_content_type(data.get_mime())
|
||||
trans.log_event( "Display dataset id: %s" % str( dataset_id ) )
|
||||
|
||||
if to_ext or isinstance(data.datatype, datatypes.binary.Binary): # Saving the file, or binary file
|
||||
if data.extension in composite_extensions:
|
||||
return self.archive_composite_dataset( trans, data, **kwd )
|
||||
else:
|
||||
trans.response.headers['Content-Length'] = int( os.stat( data.file_name ).st_size )
|
||||
if not to_ext:
|
||||
to_ext = data.extension
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in data.name)[0:150]
|
||||
trans.response.set_content_type( "application/octet-stream" ) #force octet-stream so Safari doesn't append mime extensions to filename
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (data.hid, fname, to_ext)
|
||||
return open( data.file_name )
|
||||
if not os.path.exists( data.file_name ):
|
||||
raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % data.file_name )
|
||||
max_peek_size = 1000000 # 1 MB
|
||||
if isinstance(data.datatype, datatypes.images.Html):
|
||||
max_peek_size = 10000000 # 10 MB for html
|
||||
if not preview or isinstance(data.datatype, datatypes.images.Image) or os.stat( data.file_name ).st_size < max_peek_size:
|
||||
if trans.app.config.sanitize_all_html and trans.response.get_content_type() == "text/html":
|
||||
# Sanitize anytime we respond with plain text/html content.
|
||||
return sanitize_html(open( data.file_name ).read())
|
||||
return open( data.file_name )
|
||||
else:
|
||||
trans.response.set_content_type( "text/html" )
|
||||
return trans.stream_template_mako( "/dataset/large_file.mako",
|
||||
truncated_data = open( data.file_name ).read(max_peek_size),
|
||||
data = data )
|
||||
return data.datatype.display_data(trans, data, preview, filename, to_ext, chunk, **kwd)
|
||||
|
||||
@web.expose
|
||||
def edit(self, trans, dataset_id=None, filename=None, hid=None, **kwd):
|
||||
@@ -443,7 +311,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
# TODO: hid handling
|
||||
data = history.datasets[ int( hid ) - 1 ]
|
||||
id = None
|
||||
elif dataset_id is not None:
|
||||
elif dataset_id is not None:
|
||||
id = trans.app.security.decode_id( dataset_id )
|
||||
data = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id )
|
||||
else:
|
||||
@@ -463,7 +331,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
# permission. In this case, we'll reset this permission to the hda user's private role.
|
||||
manage_permissions_action = trans.app.security_agent.get_action( trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action )
|
||||
permissions = { manage_permissions_action : [ trans.app.security_agent.get_private_user_role( data.history.user ) ] }
|
||||
trans.app.security_agent.set_dataset_permission( data.dataset, permissions )
|
||||
trans.app.security_agent.set_dataset_permission( data.dataset, permissions )
|
||||
if trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ):
|
||||
if data.state == trans.model.Dataset.states.UPLOAD:
|
||||
return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to edit its metadata." )
|
||||
@@ -600,7 +468,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
refresh_frames=refresh_frames )
|
||||
else:
|
||||
return trans.show_error_message( "You do not have permission to edit this dataset's ( id: %s ) information." % str( dataset_id ) )
|
||||
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "see all available datasets" )
|
||||
def list( self, trans, **kwargs ):
|
||||
@@ -610,7 +478,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if 'operation' in kwargs:
|
||||
operation = kwargs['operation'].lower()
|
||||
hda_ids = util.listify( kwargs.get( 'id', [] ) )
|
||||
|
||||
|
||||
# Display no message by default
|
||||
status, message = None, None
|
||||
|
||||
@@ -630,15 +498,15 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if hdas:
|
||||
if operation == "switch" or operation == "switch_history":
|
||||
# Switch to a history that the HDA resides in.
|
||||
|
||||
|
||||
# Convert hda to histories.
|
||||
histories = []
|
||||
for hda in hdas:
|
||||
histories.append( hda.history )
|
||||
|
||||
|
||||
# Use history controller to switch the history. TODO: is this reasonable?
|
||||
status, message = trans.webapp.controllers['history']._list_switch( trans, histories )
|
||||
|
||||
|
||||
# Current history changed, refresh history frame; if switching to a dataset, set hda seek.
|
||||
trans.template_context['refresh_frames'] = ['history']
|
||||
if operation == "switch":
|
||||
@@ -648,35 +516,35 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
# Copy a dataset to the current history.
|
||||
target_histories = [ trans.get_history() ]
|
||||
status, message = self._copy_datasets( trans, hda_ids, target_histories )
|
||||
|
||||
|
||||
# Current history changed, refresh history frame.
|
||||
trans.template_context['refresh_frames'] = ['history']
|
||||
|
||||
# Render the list view
|
||||
return self.stored_list_grid( trans, status=status, message=message, **kwargs )
|
||||
|
||||
|
||||
@web.expose
|
||||
def imp( self, trans, dataset_id=None, **kwd ):
|
||||
""" Import another user's dataset via a shared URL; dataset is added to user's current history. """
|
||||
msg = ""
|
||||
|
||||
|
||||
# Set referer message.
|
||||
referer = trans.request.referer
|
||||
if referer is not "":
|
||||
referer_message = "<a href='%s'>return to the previous page</a>" % referer
|
||||
else:
|
||||
referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
|
||||
|
||||
|
||||
# Error checking.
|
||||
if not dataset_id:
|
||||
return trans.show_error_message( "You must specify a dataset to import. You can %s." % referer_message, use_panels=True )
|
||||
|
||||
|
||||
# Do import.
|
||||
cur_history = trans.get_history( create=True )
|
||||
status, message = self._copy_datasets( trans, [ dataset_id ], [ cur_history ], imported=True )
|
||||
message = "Dataset imported. <br>You can <a href='%s'>start using the dataset</a> or %s." % ( url_for('/'), referer_message )
|
||||
return trans.show_message( message, type=status, use_panels=True )
|
||||
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
@web.require_login( "use Galaxy datasets" )
|
||||
@@ -685,7 +553,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
dataset = self.get_dataset( trans, id, False, True )
|
||||
return_dict = { "name" : dataset.name, "link" : url_for( action="display_by_username_and_slug", username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) ) }
|
||||
return return_dict
|
||||
|
||||
|
||||
@web.expose
|
||||
def get_embed_html_async( self, trans, id ):
|
||||
""" Returns HTML for embedding a dataset in a page. """
|
||||
@@ -698,7 +566,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
def set_accessible_async( self, trans, id=None, accessible=False ):
|
||||
""" Does nothing because datasets do not have an importable/accessible attribute. This method could potentially set another attribute. """
|
||||
return
|
||||
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "rate items" )
|
||||
@web.json
|
||||
@@ -713,7 +581,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
dataset_rating = self.rate_item( rate_item, trans.get_user(), dataset, rating )
|
||||
|
||||
return self.get_ave_item_rating_data( trans.sa_session, dataset )
|
||||
|
||||
|
||||
@web.expose
|
||||
def display_by_username_and_slug( self, trans, username, slug, filename=None, preview=True ):
|
||||
""" Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id. """
|
||||
@@ -722,10 +590,10 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
# Filename used for composite types.
|
||||
if filename:
|
||||
return self.display( trans, dataset_id=slug, filename=filename)
|
||||
|
||||
|
||||
truncated, dataset_data = self.get_data( dataset, preview )
|
||||
dataset.annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset )
|
||||
|
||||
|
||||
# If data is binary or an image, stream without template; otherwise, use display template.
|
||||
# TODO: figure out a way to display images in display template.
|
||||
if isinstance(dataset.datatype, datatypes.binary.Binary) or isinstance(dataset.datatype, datatypes.images.Image) or isinstance(dataset.datatype, datatypes.images.Html):
|
||||
@@ -741,12 +609,12 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
else:
|
||||
user_item_rating = 0
|
||||
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, dataset )
|
||||
|
||||
|
||||
return trans.fill_template_mako( "/dataset/display.mako", item=dataset, item_data=dataset_data, truncated=truncated,
|
||||
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
|
||||
else:
|
||||
raise web.httpexceptions.HTTPNotFound()
|
||||
|
||||
|
||||
@web.expose
|
||||
def get_item_content_async( self, trans, id ):
|
||||
""" Returns item content in HTML format. """
|
||||
@@ -758,7 +626,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
# Get annotation.
|
||||
dataset.annotation = self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
|
||||
return trans.stream_template_mako( "/dataset/item_content.mako", item=dataset, item_data=dataset_data, truncated=truncated )
|
||||
|
||||
|
||||
@web.expose
|
||||
def annotate_async( self, trans, id, new_annotation=None, **kwargs ):
|
||||
dataset = self.get_dataset( trans, id, False, True )
|
||||
@@ -770,7 +638,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
self.add_item_annotation( trans.sa_session, trans.get_user(), dataset, new_annotation )
|
||||
trans.sa_session.flush()
|
||||
return new_annotation
|
||||
|
||||
|
||||
@web.expose
|
||||
def get_annotation_async( self, trans, id ):
|
||||
dataset = self.get_dataset( trans, id, False, True )
|
||||
@@ -841,7 +709,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if app_action in [ 'data', 'param' ]:
|
||||
assert action_param, "An action param must be provided for a data or param action"
|
||||
#data is used for things with filenames that could be passed off to a proxy
|
||||
#in case some display app wants all files to be in the same 'directory',
|
||||
#in case some display app wants all files to be in the same 'directory',
|
||||
#data can be forced to param, but not the other way (no filename for other direction)
|
||||
#get param name from url param name
|
||||
try:
|
||||
@@ -904,8 +772,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if job.state in [ self.app.model.Job.states.QUEUED, self.app.model.Job.states.RUNNING, self.app.model.Job.states.NEW ]:
|
||||
# Are *all* of the job's other output datasets deleted?
|
||||
if job.check_if_output_datasets_deleted():
|
||||
job.mark_deleted( self.app.config.get_bool( 'enable_job_running', True ),
|
||||
self.app.config.get_bool( 'track_jobs_in_database', False ) )
|
||||
job.mark_deleted( self.app.config.track_jobs_in_database )
|
||||
self.app.job_manager.job_stop_queue.put( job.id )
|
||||
trans.sa_session.flush()
|
||||
except Exception, e:
|
||||
@@ -961,7 +828,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
trans.log_event( "Dataset id %s has been unhidden" % str(id) )
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _purge( self, trans, dataset_id ):
|
||||
message = None
|
||||
status = 'done'
|
||||
@@ -1038,7 +905,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
return "OK"
|
||||
else:
|
||||
raise Exception( message )
|
||||
|
||||
|
||||
@web.expose
|
||||
def unhide( self, trans, dataset_id, filename ):
|
||||
if self._unhide( trans, dataset_id ):
|
||||
@@ -1071,7 +938,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
"""
|
||||
Show the parameters used for an HDA
|
||||
"""
|
||||
|
||||
|
||||
def source_dataset_chain( dataset, lst ):
|
||||
try:
|
||||
cp_from_ldda = dataset.copied_from_library_dataset_dataset_association
|
||||
@@ -1085,13 +952,13 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
except:
|
||||
pass
|
||||
return lst
|
||||
|
||||
|
||||
hda = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( dataset_id ) )
|
||||
if not hda:
|
||||
raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
|
||||
if not trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset ):
|
||||
return trans.show_error_message( "You are not allowed to access this dataset" )
|
||||
|
||||
|
||||
# Get the associated job, if any. If this hda was copied from another,
|
||||
# we need to find the job that created the origial hda
|
||||
params_objects = None
|
||||
@@ -1103,7 +970,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
job = None
|
||||
for assoc in job_hda.creating_job_associations:
|
||||
job = assoc.job
|
||||
break
|
||||
break
|
||||
if job:
|
||||
# Get the tool object
|
||||
try:
|
||||
@@ -1114,10 +981,10 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
params_objects = job.get_param_values( trans.app )
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
inherit_chain = source_dataset_chain(hda, [])
|
||||
return trans.fill_template( "show_params.mako", inherit_chain=inherit_chain, history=trans.get_history(), hda=hda, tool=tool, params_objects=params_objects )
|
||||
|
||||
|
||||
@web.expose
|
||||
def copy_datasets( self, trans, source_history=None, source_dataset_ids="", target_history_id=None, target_history_ids="", new_history_name="", do_copy=False, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
@@ -1176,7 +1043,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
if history in target_histories:
|
||||
refresh_frames = ['history']
|
||||
trans.sa_session.flush()
|
||||
hist_names_str = ", ".join( ['<a href="%s" target="_top">%s</a>' %
|
||||
hist_names_str = ", ".join( ['<a href="%s" target="_top">%s</a>' %
|
||||
( url_for( controller="history", action="switch_to_history", \
|
||||
hist_id=trans.security.encode_id( hist.id ) ), hist.name ) \
|
||||
for hist in target_histories ] )
|
||||
@@ -1187,7 +1054,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
source_datasets = history.visible_datasets
|
||||
target_histories = [history]
|
||||
if user:
|
||||
target_histories = user.active_histories
|
||||
target_histories = user.active_histories
|
||||
return trans.fill_template( "/dataset/copy_view.mako",
|
||||
source_history = history,
|
||||
current_history = trans.get_history(),
|
||||
@@ -1205,7 +1072,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
""" Helper method for copying datasets. """
|
||||
user = trans.get_user()
|
||||
done_msg = error_msg = ""
|
||||
|
||||
|
||||
invalid_datasets = 0
|
||||
if not dataset_ids or not target_histories:
|
||||
error_msg = "You must provide both source datasets and target histories."
|
||||
@@ -1230,7 +1097,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHist
|
||||
done_msg = "%i dataset%s copied to %i histor%s." % \
|
||||
( num_datasets_copied, iff( num_datasets_copied == 1, "", "s"), len( target_histories ), iff( len ( target_histories ) == 1, "y", "ies") )
|
||||
trans.sa_session.refresh( history )
|
||||
|
||||
|
||||
if error_msg != "":
|
||||
status = ERROR
|
||||
message = error_msg
|
||||
|
||||
@@ -199,6 +199,11 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitions ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
sample_id = params.get( 'sample_id', None )
|
||||
try:
|
||||
sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id ( sample_id ) )
|
||||
except:
|
||||
return invalid_id_redirect( trans, 'requests_admin', sample_id, 'sample' )
|
||||
if 'operation' in kwd:
|
||||
operation = kwd[ 'operation' ].lower()
|
||||
sample_dataset_id = params.get( 'id', None )
|
||||
@@ -269,11 +274,6 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitions ):
|
||||
**kwd ) )
|
||||
|
||||
# Render the grid view
|
||||
sample_id = params.get( 'sample_id', None )
|
||||
try:
|
||||
sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id ( sample_id ) )
|
||||
except:
|
||||
return invalid_id_redirect( trans, 'requests_admin', sample_id, 'sample' )
|
||||
request_id = trans.security.encode_id( sample.request.id )
|
||||
library_id = trans.security.encode_id( sample.library.id )
|
||||
self.datatx_grid.title = 'Manage "%s" datasets' % sample.name
|
||||
@@ -493,6 +493,7 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitions ):
|
||||
return trans.response.send_redirect( web.url_for( controller='requests_admin',
|
||||
action='select_datasets_to_transfer',
|
||||
request_id=trans.security.encode_id( request.id ),
|
||||
external_service_id=trans.security.encode_id( external_service.id ),
|
||||
status=status,
|
||||
message=message ) )
|
||||
def __create_sample_datasets( self, trans, sample, selected_datasets_to_transfer, external_service ):
|
||||
@@ -624,9 +625,6 @@ class RequestsAdmin( BaseUIController, UsesFormDefinitions ):
|
||||
or not scp_configs.get( 'user_name', '' ) \
|
||||
or not scp_configs.get( 'password', '' ):
|
||||
err_msg += "Error in external service login information. "
|
||||
# Make sure web API is enabled and API key exists
|
||||
if not trans.app.config.enable_api:
|
||||
err_msg += "The 'enable_api = True' setting is not correctly set in the Galaxy config file. "
|
||||
if not trans.user.api_keys:
|
||||
err_msg += "Set your API Key in your User Preferences to transfer datasets. "
|
||||
# Check if library_import_dir is set
|
||||
|
||||
@@ -1011,6 +1011,8 @@ class RequestsCommon( BaseUIController, UsesFormDefinitions ):
|
||||
sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
|
||||
except:
|
||||
return invalid_id_redirect( trans, cntrller, sample_id, 'sample' )
|
||||
external_service_id = params.get( 'external_service_id', None )
|
||||
external_service = trans.sa_session.query( trans.model.ExternalService ).get( trans.security.decode_id( external_service_id ) )
|
||||
# See if a library and folder have been set for this sample.
|
||||
if is_admin and not sample.library or not sample.folder:
|
||||
status = 'error'
|
||||
@@ -1043,6 +1045,7 @@ class RequestsCommon( BaseUIController, UsesFormDefinitions ):
|
||||
return trans.fill_template( '/requests/common/view_sample_datasets.mako',
|
||||
cntrller=cntrller,
|
||||
title=title,
|
||||
external_service=external_service,
|
||||
sample=sample,
|
||||
sample_datasets=sample_datasets,
|
||||
transfer_status=transfer_status,
|
||||
|
||||
@@ -250,7 +250,7 @@ class RootController( BaseUIController, UsesHistory, UsesAnnotations ):
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
fname = data.name
|
||||
fname = ''.join(c in valid_chars and c or '_' for c in fname)[0:150]
|
||||
trans.response.headers["Content-Disposition"] = "attachment; filename=GalaxyHistoryItem-%s-[%s]%s" % (data.hid, fname, toext)
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="GalaxyHistoryItem-%s-[%s]%s"' % (data.hid, fname, toext)
|
||||
trans.log_event( "Display dataset id: %s" % str(id) )
|
||||
try:
|
||||
return open( data.file_name )
|
||||
|
||||
@@ -58,6 +58,8 @@ class ToolRunner( BaseUIController ):
|
||||
log.error( "index called with tool id '%s' but no such tool exists", tool_id )
|
||||
trans.log_event( "Tool id '%s' does not exist" % tool_id )
|
||||
return "Tool '%s' does not exist, kwd=%s " % (tool_id, kwd)
|
||||
if tool.require_login and not trans.user:
|
||||
return trans.response.send_redirect( url_for( controller='user', action='login', cntrller='user', message="You must be logged in to use this tool.", status="info", redirect=url_for( controller='/tool_runner', action='index', tool_id=tool_id, **kwd ) ) )
|
||||
params = util.Params( kwd, sanitize = False ) #Sanitize parameters when substituting into command line via input wrappers
|
||||
#do param translation here, used by datasource tools
|
||||
if tool.input_translator:
|
||||
|
||||
@@ -486,7 +486,7 @@ class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetA
|
||||
data = None
|
||||
# TODO: for raw data requests, map dataset type to provider using dict in data_providers.py
|
||||
if isinstance( dataset.datatype, Gff ):
|
||||
data = GFFDataProvider( original_dataset=dataset ).get_data( chrom, low, high, **kwargs )
|
||||
data = RawGFFDataProvider( original_dataset=dataset ).get_data( chrom, low, high, **kwargs )
|
||||
data[ 'dataset_type' ] = 'interval_index'
|
||||
data[ 'extra_info' ] = None
|
||||
elif isinstance( dataset.datatype, Bed ):
|
||||
@@ -932,6 +932,7 @@ class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetA
|
||||
# Set input datasets for tool. If running on region, extract and use subset
|
||||
# when possible.
|
||||
#
|
||||
location = "%s:%i-%i" % ( chrom, low, high )
|
||||
for jida in original_job.input_datasets:
|
||||
# If param set previously by config actions, do nothing.
|
||||
if jida.name in params_set:
|
||||
@@ -943,48 +944,67 @@ class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetA
|
||||
elif run_on_region and hasattr( input_dataset.datatype, 'get_track_type' ):
|
||||
# Dataset is indexed and hence a subset can be extracted and used
|
||||
# as input.
|
||||
track_type, data_sources = input_dataset.datatype.get_track_type()
|
||||
data_source = data_sources[ 'data' ]
|
||||
converted_dataset = input_dataset.get_converted_dataset( trans, data_source )
|
||||
deps = input_dataset.get_converted_dataset_deps( trans, data_source )
|
||||
|
||||
# Create new HDA for input dataset's subset.
|
||||
new_dataset = trans.app.model.HistoryDatasetAssociation( extension=input_dataset.ext, \
|
||||
dbkey=input_dataset.dbkey, \
|
||||
create_dataset=True, \
|
||||
sa_session=trans.sa_session,
|
||||
name="Subset [%s:%i-%i] of data %i" % \
|
||||
( chrom, low, high, input_dataset.hid ),
|
||||
visible=False )
|
||||
target_history.add_dataset( new_dataset )
|
||||
trans.sa_session.add( new_dataset )
|
||||
trans.app.security_agent.set_all_dataset_permissions( new_dataset.dataset, hda_permissions )
|
||||
|
||||
# Write subset of data to new dataset
|
||||
data_provider_class = get_data_provider( original_dataset=input_dataset )
|
||||
data_provider = data_provider_class( original_dataset=input_dataset,
|
||||
converted_dataset=converted_dataset,
|
||||
dependencies=deps )
|
||||
trans.app.object_store.create( new_dataset.dataset )
|
||||
data_provider.write_data_to_file( chrom, low, high, new_dataset.file_name )
|
||||
|
||||
# TODO: (a) size not working; (b) need to set peek.
|
||||
new_dataset.set_size()
|
||||
new_dataset.info = "Data subset for trackster"
|
||||
new_dataset.set_dataset_state( trans.app.model.Dataset.states.OK )
|
||||
|
||||
# Set metadata.
|
||||
if trans.app.config.set_metadata_externally:
|
||||
trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming = { 'input1':new_dataset }, overwrite=False )
|
||||
# Look for subset.
|
||||
subset_dataset_association = trans.sa_session.query( trans.app.model.HistoryDatasetAssociationSubset ) \
|
||||
.filter_by( hda=input_dataset, location=location ) \
|
||||
.first()
|
||||
if subset_dataset_association:
|
||||
# Data subset exists.
|
||||
subset_dataset = subset_dataset_association.subset
|
||||
else:
|
||||
message = 'Attributes updated'
|
||||
new_dataset.set_meta()
|
||||
new_dataset.datatype.after_setting_metadata( new_dataset )
|
||||
# Need to create subset.
|
||||
track_type, data_sources = input_dataset.datatype.get_track_type()
|
||||
data_source = data_sources[ 'data' ]
|
||||
converted_dataset = input_dataset.get_converted_dataset( trans, data_source )
|
||||
deps = input_dataset.get_converted_dataset_deps( trans, data_source )
|
||||
|
||||
# Create new HDA for input dataset's subset.
|
||||
new_dataset = trans.app.model.HistoryDatasetAssociation( extension=input_dataset.ext, \
|
||||
dbkey=input_dataset.dbkey, \
|
||||
create_dataset=True, \
|
||||
sa_session=trans.sa_session,
|
||||
name="Subset [%s] of data %i" % \
|
||||
( location, input_dataset.hid ),
|
||||
visible=False )
|
||||
target_history.add_dataset( new_dataset )
|
||||
trans.sa_session.add( new_dataset )
|
||||
trans.app.security_agent.set_all_dataset_permissions( new_dataset.dataset, hda_permissions )
|
||||
|
||||
# Write subset of data to new dataset
|
||||
data_provider_class = get_data_provider( original_dataset=input_dataset )
|
||||
data_provider = data_provider_class( original_dataset=input_dataset,
|
||||
converted_dataset=converted_dataset,
|
||||
dependencies=deps )
|
||||
trans.app.object_store.create( new_dataset.dataset )
|
||||
data_provider.write_data_to_file( chrom, low, high, new_dataset.file_name )
|
||||
|
||||
# TODO: (a) size not working; (b) need to set peek.
|
||||
new_dataset.set_size()
|
||||
new_dataset.info = "Data subset for trackster"
|
||||
new_dataset.set_dataset_state( trans.app.model.Dataset.states.OK )
|
||||
|
||||
# Set metadata.
|
||||
# TODO: set meta internally if dataset is small enough?
|
||||
if trans.app.config.set_metadata_externally:
|
||||
trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool,
|
||||
trans, incoming = { 'input1':new_dataset },
|
||||
overwrite=False, job_params={ "source" : "trackster" } )
|
||||
else:
|
||||
message = 'Attributes updated'
|
||||
new_dataset.set_meta()
|
||||
new_dataset.datatype.after_setting_metadata( new_dataset )
|
||||
|
||||
# Add HDA subset association.
|
||||
subset_association = trans.app.model.HistoryDatasetAssociationSubset( hda=input_dataset, subset=new_dataset, location=location )
|
||||
trans.sa_session.add( subset_association )
|
||||
|
||||
subset_dataset = new_dataset
|
||||
|
||||
trans.sa_session.flush()
|
||||
|
||||
# Add dataset to tool's parameters.
|
||||
if not set_param_value( tool_params, jida.name, new_dataset ):
|
||||
if not set_param_value( tool_params, jida.name, subset_dataset ):
|
||||
return to_json_string( { "error" : True, "message" : "error setting parameter %s" % jida.name } )
|
||||
|
||||
#
|
||||
|
||||
@@ -10,8 +10,7 @@ from random import choice
|
||||
from galaxy.web.form_builder import *
|
||||
from galaxy.util.json import from_json_string, to_json_string
|
||||
from galaxy.web.framework.helpers import iff
|
||||
from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password
|
||||
from galaxy.openid.providers import OpenIDProvider
|
||||
from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password, transform_publicname
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
@@ -30,7 +29,7 @@ class UserOpenIDGrid( grids.Grid ):
|
||||
default_filter = { "openid" : "All" }
|
||||
default_sort_key = "-create_time"
|
||||
columns = [
|
||||
grids.TextColumn( "OpenID URL", key="openid" ),
|
||||
grids.TextColumn( "OpenID URL", key="openid", link=( lambda x: dict( action='openid_auth', login_button="Login", openid_url=x.openid if not x.provider else '', openid_provider=x.provider, auto_associate=True ) ) ),
|
||||
grids.GridColumn( "Created", key="create_time", format=time_ago ),
|
||||
]
|
||||
operations = [
|
||||
@@ -48,32 +47,30 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
return trans.fill_template( '/user/index.mako', cntrller=cntrller, webapp=webapp )
|
||||
@web.expose
|
||||
def openid_auth( self, trans, webapp='galaxy', **kwd ):
|
||||
'''Handles user request to access an OpenID provider'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
|
||||
message = 'Unspecified failure authenticating via OpenID'
|
||||
status = kwd.get( 'status', 'done' )
|
||||
openid_url = kwd.get( 'openid_url', '' )
|
||||
openid_provider = kwd.get( 'openid_provider', '' )
|
||||
referer = kwd.get( 'referer', trans.request.referer )
|
||||
if not openid_provider or openid_url:
|
||||
openid_provider = trans.app.openid_providers.NO_PROVIDER_ID #empty fields cause validation errors
|
||||
redirect = kwd.get( 'redirect', '' ).strip()
|
||||
auto_associate = util.string_as_bool( kwd.get( 'auto_associate', False ) )
|
||||
use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) )
|
||||
action = 'login'
|
||||
if auto_associate:
|
||||
action = 'openid_manage'
|
||||
if not referer:
|
||||
referer = url_for( '/' )
|
||||
consumer = trans.app.openid_manager.get_consumer( trans )
|
||||
openid_provider_obj = None
|
||||
if not openid_url and openid_provider and trans.app.openid_providers.get( openid_provider ):
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid_provider )
|
||||
elif openid_url:
|
||||
openid_provider_obj = OpenIDProvider( openid_url, openid_url, openid_url ) #for manually entered links use the link for id, name and url
|
||||
elif openid_provider:
|
||||
message = 'Invalid OpenID provider specified: %s' % ( openid_provider )
|
||||
if openid_url:
|
||||
openid_provider_obj = trans.app.openid_providers.new_provider_from_identifier( openid_url )
|
||||
else:
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid_provider )
|
||||
if not openid_url and openid_provider == trans.app.openid_providers.NO_PROVIDER_ID:
|
||||
message = 'An OpenID provider was not specified'
|
||||
process_url = trans.request.base.rstrip( '/' ) + url_for( controller='user', action='openid_process', referer=referer, auto_associate=auto_associate, openid_provider=openid_provider )
|
||||
if openid_provider_obj is not None:
|
||||
elif openid_provider_obj:
|
||||
if not redirect:
|
||||
redirect = ' '
|
||||
process_url = trans.request.base.rstrip( '/' ) + url_for( controller='user', action='openid_process', redirect=redirect, openid_provider=openid_provider, auto_associate=auto_associate ) #None of these values can be empty, or else a verification error will occur
|
||||
request = None
|
||||
try:
|
||||
request = consumer.begin( openid_provider_obj.op_endpoint_url )
|
||||
@@ -87,84 +84,100 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
redirect_url = request.redirectURL(
|
||||
trans.request.base, process_url )
|
||||
trans.app.openid_manager.persist_session( trans, consumer )
|
||||
trans.response.send_redirect( redirect_url )
|
||||
return
|
||||
return trans.response.send_redirect( redirect_url )
|
||||
else:
|
||||
form = request.htmlMarkup( trans.request.base, process_url, form_tag_attrs={'id':'openid_message','target':'_top'} )
|
||||
trans.app.openid_manager.persist_session( trans, consumer )
|
||||
return form
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action=action,
|
||||
redirect=redirect,
|
||||
use_panels=use_panels,
|
||||
message=message,
|
||||
status='error' ) )
|
||||
@web.expose
|
||||
def openid_process( self, trans, webapp='galaxy', **kwd ):
|
||||
'''Handle's response from OpenID Providers'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
|
||||
auto_associate = util.string_as_bool( kwd.get( 'auto_associate', False ) )
|
||||
action = 'login'
|
||||
if auto_associate:
|
||||
if trans.user:
|
||||
action = 'openid_manage'
|
||||
if trans.app.config.support_url is not None:
|
||||
contact = '<a href="%s">support</a>' % trans.app.config.support_url
|
||||
else:
|
||||
contact = 'support'
|
||||
message = 'Verification failed for an unknown reason. Please contact support for assistance.'
|
||||
message = 'Verification failed for an unknown reason. Please contact %s for assistance.' % ( contact )
|
||||
status = 'error'
|
||||
consumer = trans.app.openid_manager.get_consumer( trans )
|
||||
info = consumer.complete( kwd, trans.request.url )
|
||||
display_identifier = info.getDisplayIdentifier()
|
||||
redirect_url = kwd.get( 'referer', url_for( '/' ) )
|
||||
openid_provider = kwd.get( 'openid_provider', '' )
|
||||
redirect = kwd.get( 'redirect', '' ).strip()
|
||||
openid_provider = kwd.get( 'openid_provider', None )
|
||||
if info.status == trans.app.openid_manager.FAILURE and display_identifier:
|
||||
message = "Login via OpenID failed. The technical reason for this follows, please include this message in your email if you need to %s to resolve this problem: %s" % ( contact, info.message )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action=action,
|
||||
use_panels=True,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status='error' ) )
|
||||
elif info.status == trans.app.openid_manager.SUCCESS:
|
||||
if info.endpoint.canonicalID:
|
||||
display_identifier = info.endpoint.canonicalID
|
||||
user_openid = trans.sa_session.query( trans.app.model.UserOpenID ).filter( trans.app.model.UserOpenID.table.c.openid == display_identifier ).first()
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid_provider )
|
||||
user_openid = trans.sa_session.query( trans.app.model.UserOpenID ).filter( trans.app.model.UserOpenID.table.c.openid == display_identifier ).first()
|
||||
if not openid_provider_obj and user_openid and user_openid.provider:
|
||||
openid_provider_obj = trans.app.openid_providers.get( user_openid.provider )
|
||||
if not openid_provider_obj:
|
||||
openid_provider_obj = OpenIDProvider( display_identifier, display_identifier, display_identifier )
|
||||
openid_provider_obj = trans.app.openid_providers.new_provider_from_identifier( display_identifier )
|
||||
if not user_openid:
|
||||
user_openid = trans.app.model.UserOpenID( session=trans.galaxy_session, openid=display_identifier )
|
||||
elif not user_openid.user and user_openid.session.id != trans.galaxy_session.id:
|
||||
if not user_openid.user:
|
||||
user_openid.session = trans.galaxy_session
|
||||
elif user_openid.user and not auto_associate:
|
||||
trans.handle_user_login( user_openid.user, webapp )
|
||||
trans.log_event( "User logged in via OpenID: %s" % display_identifier )
|
||||
openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info )
|
||||
trans.response.send_redirect( redirect_url )
|
||||
return
|
||||
if auto_associate and trans.user:
|
||||
# The user is already logged in and requested association from
|
||||
# the user prefs as opposed to using the OpenID form on the
|
||||
# login page.
|
||||
if not user_openid.provider and openid_provider:
|
||||
user_openid.provider = openid_provider
|
||||
if trans.user:
|
||||
if user_openid.user and user_openid.user.id != trans.user.id:
|
||||
message = "The OpenID <strong>%s</strong> is already associated with another Galaxy account, <strong>%s</strong>. Please disassociate it from that account before attempting to associate it with a new account." % ( display_identifier, user_openid.user.email )
|
||||
status = "error"
|
||||
elif user_openid.user and user_openid.user.id == trans.user.id:
|
||||
message = "The OpenID <strong>%s</strong> is already associated with your Galaxy account, <strong>%s</strong>." % ( display_identifier, trans.user.email )
|
||||
status = "warning"
|
||||
else:
|
||||
user_openid.user_id = trans.user.id
|
||||
trans.sa_session.add( user_openid )
|
||||
trans.sa_session.flush()
|
||||
trans.log_event( "User associated OpenID: %s" % display_identifier )
|
||||
message = "The OpenID <strong>%s</strong> has been associated with your Galaxy account, <strong>%s</strong>." % ( display_identifier, trans.user.email )
|
||||
status = "done"
|
||||
elif not user_openid.user or user_openid.user == trans.user:
|
||||
if openid_provider_obj.id:
|
||||
user_openid.provider = openid_provider_obj.id
|
||||
user_openid.session = trans.galaxy_session
|
||||
if not openid_provider_obj.never_associate_with_user:
|
||||
if not auto_associate and ( user_openid.user and user_openid.user.id == trans.user.id ):
|
||||
message = "The OpenID <strong>%s</strong> is already associated with your Galaxy account, <strong>%s</strong>." % ( display_identifier, trans.user.email )
|
||||
status = "warning"
|
||||
else:
|
||||
message = "The OpenID <strong>%s</strong> has been associated with your Galaxy account, <strong>%s</strong>." % ( display_identifier, trans.user.email )
|
||||
status = "done"
|
||||
user_openid.user = trans.user
|
||||
trans.sa_session.add( user_openid )
|
||||
trans.sa_session.flush()
|
||||
trans.log_event( "User associated OpenID: %s" % display_identifier )
|
||||
else:
|
||||
message = "The OpenID <strong>%s</strong> cannot be used to log into your Galaxy account, but any post authentication actions have been performed." % ( openid_provider_obj.name )
|
||||
status ="info"
|
||||
openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info )
|
||||
trans.response.send_redirect( url_for( controller='user',
|
||||
if redirect:
|
||||
message = '%s<br>Click <a href="%s"><strong>here</strong></a> to return to the page you were previously viewing.' % ( message, redirect )
|
||||
if redirect and status != "error":
|
||||
return trans.response.send_redirect( redirect )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=True,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status=status ) )
|
||||
return
|
||||
elif user_openid.user:
|
||||
trans.handle_user_login( user_openid.user, webapp )
|
||||
trans.log_event( "User logged in via OpenID: %s" % display_identifier )
|
||||
openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info )
|
||||
if not redirect:
|
||||
redirect = url_for( '/' )
|
||||
return trans.response.send_redirect( redirect )
|
||||
trans.sa_session.add( user_openid )
|
||||
trans.sa_session.flush()
|
||||
message = "OpenID authentication was successful, but you need to associate your OpenID with a Galaxy account."
|
||||
@@ -179,10 +192,11 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
email = sreg_resp.get( sreg_email_name, '' )
|
||||
except AttributeError:
|
||||
email = ''
|
||||
trans.response.send_redirect( url_for( controller='user',
|
||||
#OpenID success, but user not logged in, and not previously associated
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_associate',
|
||||
openid_provider=openid_provider,
|
||||
use_panels=True,
|
||||
redirect=redirect,
|
||||
username=username,
|
||||
email=email,
|
||||
message=message,
|
||||
@@ -198,10 +212,12 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action=action,
|
||||
use_panels=True,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status=status ) )
|
||||
@web.expose
|
||||
def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
|
||||
'''Associates a user with an OpenID log in'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
|
||||
use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) )
|
||||
@@ -209,9 +225,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
status = kwd.get( 'status', 'done' )
|
||||
email = kwd.get( 'email', '' )
|
||||
username = kwd.get( 'username', '' )
|
||||
referer = kwd.get( 'referer', trans.request.referer )
|
||||
openid_provider = kwd.get( 'openid_provider', '' )
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid_provider )
|
||||
redirect = kwd.get( 'redirect', '' ).strip()
|
||||
params = util.Params( kwd )
|
||||
is_admin = cntrller == 'admin' and trans.user_is_admin()
|
||||
openids = trans.galaxy_session.openids
|
||||
@@ -223,18 +237,38 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
if kwd.get( 'login_button', False ):
|
||||
message, status, user, success = self.__validate_login( trans, webapp, **kwd )
|
||||
if success:
|
||||
openid_objs = []
|
||||
for openid in openids:
|
||||
openid.user = user
|
||||
trans.sa_session.add( openid )
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid.provider )
|
||||
if not openid_provider_obj or not openid_provider_obj.never_associate_with_user:
|
||||
openid.user = user
|
||||
trans.sa_session.add( openid )
|
||||
trans.log_event( "User associated OpenID: %s" % openid.openid )
|
||||
if openid_provider_obj and openid_provider_obj.has_post_authentication_actions():
|
||||
openid_objs.append( openid_provider_obj )
|
||||
trans.sa_session.flush()
|
||||
for openid in openids:
|
||||
trans.log_event( "User associated OpenID: %s" % openid.openid )
|
||||
redirect_url = referer
|
||||
if not redirect_url:
|
||||
redirect_url = url_for( '/' )
|
||||
if openid_provider_obj:
|
||||
return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_provider, referer=redirect_url ) )
|
||||
return trans.response.send_redirect( redirect_url )
|
||||
if len( openid_objs ) == 1:
|
||||
return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_objs[0].id, redirect=redirect, auto_associate=True ) )
|
||||
elif openid_objs:
|
||||
message = 'You have authenticated with several OpenID providers, please click the following links to execute the post authentication actions. '
|
||||
message = "%s<br/><ul>" % ( message )
|
||||
for openid in openid_objs:
|
||||
message = '%s<li><a href="%s" target="_blank">%s</a></li>' % ( message, url_for( controller='user', action='openid_auth', openid_provider=openid.id, redirect=redirect, auto_associate=True ), openid.name )
|
||||
message = "%s</ul>" % ( message )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=use_panels,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status='info' ) )
|
||||
if redirect:
|
||||
return trans.response.send_redirect( redirect )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=use_panels,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status='info' ) )
|
||||
if kwd.get( 'create_user_button', False ):
|
||||
password = kwd.get( 'password', '' )
|
||||
confirm = kwd.get( 'confirm', '' )
|
||||
@@ -253,21 +287,40 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
subscribe_checked,
|
||||
**kwd )
|
||||
if success:
|
||||
trans.handle_user_login( user, webapp )
|
||||
trans.log_event( "User created a new account" )
|
||||
trans.log_event( "User logged in" )
|
||||
openid_objs = []
|
||||
for openid in openids:
|
||||
openid.user = user
|
||||
trans.sa_session.add( openid )
|
||||
openid_provider_obj = trans.app.openid_providers.get( openid.provider )
|
||||
if not openid_provider_obj:
|
||||
openid_provider_obj = trans.app.openid_providers.new_provider_from_identifier( openid.identifier )
|
||||
if not openid_provider_obj.never_associate_with_user:
|
||||
openid.user = user
|
||||
trans.sa_session.add( openid )
|
||||
trans.log_event( "User associated OpenID: %s" % openid.openid )
|
||||
if openid_provider_obj.has_post_authentication_actions():
|
||||
openid_objs.append( openid_provider_obj )
|
||||
trans.sa_session.flush()
|
||||
for openid in openids:
|
||||
trans.log_event( "User associated OpenID: %s" % openid.openid )
|
||||
redirect_url = referer
|
||||
if not redirect_url:
|
||||
redirect_url = url_for( '/' )
|
||||
if openid_provider_obj:
|
||||
return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_provider, referer=redirect_url ) )
|
||||
return trans.response.send_redirect( redirect_url )
|
||||
if len( openid_objs ) == 1:
|
||||
return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_objs[0].id, redirect=redirect, auto_associate=True ) )
|
||||
elif openid_objs:
|
||||
message = 'You have authenticated with several OpenID providers, please click the following links to execute the post authentication actions. '
|
||||
message = "%s<br/><ul>" % ( message )
|
||||
for openid in openid_objs:
|
||||
message = '%s<li><a href="%s" target="_blank">%s</a></li>' % ( message, url_for( controller='user', action='openid_auth', openid_provider=openid.id, redirect=redirect, auto_associate=True ), openid.name )
|
||||
message = "%s</ul>" % ( message )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=True,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status='info' ) )
|
||||
if redirect:
|
||||
return trans.response.send_redirect( redirect )
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=use_panels,
|
||||
redirect=redirect,
|
||||
message=message,
|
||||
status='info' ) )
|
||||
else:
|
||||
message = error
|
||||
status = 'error'
|
||||
@@ -288,11 +341,10 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
email=email,
|
||||
password='',
|
||||
confirm='',
|
||||
username=username,
|
||||
username=transform_publicname( trans, username ),
|
||||
header='',
|
||||
use_panels=use_panels,
|
||||
redirect_url='',
|
||||
referer='',
|
||||
redirect=redirect,
|
||||
refresh_frames=[],
|
||||
message=message,
|
||||
status=status,
|
||||
@@ -301,11 +353,11 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
user_type_fd_id_select_field=user_type_fd_id_select_field,
|
||||
user_type_form_definition=user_type_form_definition,
|
||||
widgets=widgets,
|
||||
openids=openids,
|
||||
openid_provider=openid_provider )
|
||||
openids=openids )
|
||||
@web.expose
|
||||
@web.require_login( 'manage OpenIDs' )
|
||||
def openid_disassociate( self, trans, webapp='galaxy', **kwd ):
|
||||
'''Disassociates a user with an OpenID'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
|
||||
params = util.Params( kwd )
|
||||
@@ -338,7 +390,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
trans.log_event( "User disassociated OpenID: %s" % deleted_url )
|
||||
message = '%s OpenIDs were disassociated from your Galaxy account.' % len( ids )
|
||||
status = 'done'
|
||||
trans.response.send_redirect( url_for( controller='user',
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_manage',
|
||||
use_panels=use_panels,
|
||||
message=message,
|
||||
@@ -346,29 +398,31 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
@web.expose
|
||||
@web.require_login( 'manage OpenIDs' )
|
||||
def openid_manage( self, trans, webapp='galaxy', **kwd ):
|
||||
'''Manage OpenIDs for user'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message( 'OpenID authentication is not enabled in this instance of Galaxy' )
|
||||
use_panels = kwd.get( 'use_panels', False )
|
||||
if 'operation' in kwd:
|
||||
operation = kwd['operation'].lower()
|
||||
if operation == "delete":
|
||||
trans.response.send_redirect( url_for( controller='user',
|
||||
return trans.response.send_redirect( url_for( controller='user',
|
||||
action='openid_disassociate',
|
||||
use_panels=use_panels,
|
||||
id=kwd['id'] ) )
|
||||
kwd['referer'] = url_for( controller='user', action='openid_manage', use_panels=True )
|
||||
|
||||
kwd['redirect'] = kwd.get( 'redirect', url_for( controller='user', action='openid_manage', use_panels=True ) ).strip()
|
||||
kwd['openid_providers'] = trans.app.openid_providers
|
||||
return self.user_openid_grid( trans, **kwd )
|
||||
@web.expose
|
||||
def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **kwd ):
|
||||
referer = kwd.get( 'referer', trans.request.referer )
|
||||
'''Handle Galaxy Log in'''
|
||||
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
|
||||
use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) )
|
||||
message = kwd.get( 'message', '' )
|
||||
status = kwd.get( 'status', 'done' )
|
||||
header = ''
|
||||
user = None
|
||||
email = kwd.get( 'email', '' )
|
||||
openid_provider = kwd.get( 'openid_provider', '' )
|
||||
if kwd.get( 'login_button', False ):
|
||||
if webapp == 'galaxy' and not refresh_frames:
|
||||
if trans.app.config.require_login:
|
||||
@@ -376,8 +430,8 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
else:
|
||||
refresh_frames = [ 'masthead', 'history' ]
|
||||
message, status, user, success = self.__validate_login( trans, webapp, **kwd )
|
||||
if success and referer and not referer.startswith( trans.request.base + url_for( controller='user', action='logout' ) ):
|
||||
redirect_url = referer
|
||||
if success and redirect and not redirect.startswith( trans.request.base + url_for( controller='user', action='logout' ) ):
|
||||
redirect_url = redirect
|
||||
elif success:
|
||||
redirect_url = url_for( '/' )
|
||||
if not user and trans.app.config.require_login:
|
||||
@@ -399,7 +453,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
header=header,
|
||||
use_panels=use_panels,
|
||||
redirect_url=redirect_url,
|
||||
referer=referer,
|
||||
redirect=redirect,
|
||||
refresh_frames=refresh_frames,
|
||||
message=message,
|
||||
status=status,
|
||||
@@ -410,7 +464,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
status = kwd.get( 'status', 'done' )
|
||||
email = kwd.get( 'email', '' )
|
||||
password = kwd.get( 'password', '' )
|
||||
referer = kwd.get( 'referer', trans.request.referer )
|
||||
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
|
||||
success = False
|
||||
user = trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.email==email ).first()
|
||||
if not user:
|
||||
@@ -430,7 +484,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
if webapp == 'galaxy':
|
||||
trans.log_event( "User logged in" )
|
||||
message = 'You are now logged in as %s.<br>You can <a target="_top" href="%s">go back to the page you were visiting</a> or <a target="_top" href="%s">go to the home page</a>.' % \
|
||||
( user.email, referer, url_for( '/' ) )
|
||||
( user.email, redirect, url_for( '/' ) )
|
||||
if trans.app.config.require_login:
|
||||
message += ' <a target="_top" href="%s">Click here</a> to continue to the home page.' % web.url_for( '/static/welcome.html' )
|
||||
success = True
|
||||
@@ -470,7 +524,7 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
username = util.restore_text( params.get( 'username', '' ) )
|
||||
subscribe = params.get( 'subscribe', '' )
|
||||
subscribe_checked = CheckboxField.is_checked( subscribe )
|
||||
referer = kwd.get( 'referer', trans.request.referer )
|
||||
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
|
||||
is_admin = cntrller == 'admin' and trans.user_is_admin
|
||||
if not trans.app.config.allow_user_creation and not trans.user_is_admin():
|
||||
message = 'User registration is disabled. Please contact your Galaxy administrator for an account.'
|
||||
@@ -527,14 +581,14 @@ class User( BaseUIController, UsesFormDefinitions ):
|
||||
email=email,
|
||||
password=password,
|
||||
confirm=confirm,
|
||||
username=username,
|
||||
username=transform_publicname( trans, username ),
|
||||
subscribe_checked=subscribe_checked,
|
||||
user_type_fd_id_select_field=user_type_fd_id_select_field,
|
||||
user_type_form_definition=user_type_form_definition,
|
||||
widgets=widgets,
|
||||
webapp=webapp,
|
||||
use_panels=use_panels,
|
||||
referer=referer,
|
||||
redirect=redirect,
|
||||
redirect_url=redirect_url,
|
||||
refresh_frames=refresh_frames,
|
||||
message=message,
|
||||
|
||||
@@ -1119,7 +1119,7 @@ class WorkflowController( BaseUIController, Sharable, UsesStoredWorkflow, UsesAn
|
||||
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
sname = stored.name
|
||||
sname = ''.join(c in valid_chars and c or '_' for c in sname)[0:150]
|
||||
trans.response.headers["Content-Disposition"] = "attachment; filename=Galaxy-Workflow-%s.ga" % ( sname )
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.ga"' % ( sname )
|
||||
trans.response.set_content_type( 'application/galaxy-archive' )
|
||||
return stored_dict
|
||||
@web.expose
|
||||
@@ -1160,7 +1160,7 @@ class WorkflowController( BaseUIController, Sharable, UsesStoredWorkflow, UsesAn
|
||||
import_button = True
|
||||
if tool_shed_url and not import_button:
|
||||
# Use urllib (send another request to the tool shed) to retrieve the workflow.
|
||||
workflow_url = '%s/workflow/import_workflow?repository_metadata_id=%s&workflow_name=%s&webapp=%s&open_for_url=true' % \
|
||||
workflow_url = '%s/workflow/import_workflow?repository_metadata_id=%s&workflow_name=%s&webapp=%s&open_for_url=true&no_reset=true' % \
|
||||
( tool_shed_url, repository_metadata_id, tool_shed_encode( workflow_name ), webapp )
|
||||
response = urllib2.urlopen( workflow_url )
|
||||
workflow_text = response.read()
|
||||
@@ -1354,11 +1354,6 @@ class WorkflowController( BaseUIController, Sharable, UsesStoredWorkflow, UsesAn
|
||||
tool = trans.app.toolbox.get_tool( job.tool_id )
|
||||
param_values = job.get_param_values( trans.app )
|
||||
associations = cleanup_param_values( tool.inputs, param_values )
|
||||
# Doing it this way breaks dynamic parameters, backed out temporarily.
|
||||
# def extract_callback( input, value, prefixed_name, prefixed_label ):
|
||||
# if isinstance( value, UnvalidatedValue ):
|
||||
# return str( value )
|
||||
# visit_input_values( tool.inputs, param_values, extract_callback )
|
||||
step = model.WorkflowStep()
|
||||
step.type = 'tool'
|
||||
step.tool_id = job.tool_id
|
||||
@@ -2059,13 +2054,11 @@ def cleanup_param_values( inputs, values ):
|
||||
group_values = values[key]
|
||||
for i, rep_values in enumerate( group_values ):
|
||||
rep_index = rep_values['__index__']
|
||||
prefix = "%s_%d|" % ( key, rep_index )
|
||||
cleanup( prefix, input.inputs, group_values[i] )
|
||||
cleanup( "%s%s_%d|" % (prefix, key, rep_index ), input.inputs, group_values[i] )
|
||||
elif isinstance( input, Conditional ):
|
||||
group_values = values[input.name]
|
||||
current_case = group_values['__current_case__']
|
||||
prefix = "%s|" % ( key )
|
||||
cleanup( prefix, input.cases[current_case].inputs, group_values )
|
||||
cleanup( "%s%s|" % ( prefix, key ), input.cases[current_case].inputs, group_values )
|
||||
cleanup( "", inputs, values )
|
||||
return associations
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging, sys, os, time
|
||||
from cgi import escape
|
||||
from galaxy.util import restore_text, relpath, nice_size
|
||||
from galaxy.web import url_for
|
||||
from binascii import hexlify
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -143,7 +144,7 @@ class FileField(BaseField):
|
||||
def get_html( self, prefix="" ):
|
||||
value_text = ""
|
||||
if self.value:
|
||||
value_text = ' value="%s"' % self.value
|
||||
value_text = ' value="%s"' % escape( str( self.value ), quote=True )
|
||||
ajax_text = ""
|
||||
if self.ajax:
|
||||
ajax_text = ' galaxy-ajax-upload="true"'
|
||||
@@ -278,7 +279,7 @@ class SelectField(BaseField):
|
||||
if self.refresh_on_change:
|
||||
self.refresh_on_change_text = ' refresh_on_change="true"'
|
||||
if self.refresh_on_change_values:
|
||||
self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) )
|
||||
self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, escape( ",".join( self.refresh_on_change_values ), quote=True ) )
|
||||
else:
|
||||
self.refresh_on_change_text = ''
|
||||
def add_option( self, text, value, selected = False ):
|
||||
@@ -305,7 +306,7 @@ class SelectField(BaseField):
|
||||
if selected:
|
||||
selected_text = " checked='checked'"
|
||||
rval.append( '<div%s><input type="checkbox" name="%s%s" value="%s" id="%s"%s%s><label class="inline" for="%s">%s</label></div>' % \
|
||||
( style, prefix, self.name, escaped_value, uniq_id, selected_text, self.get_disabled_str( disabled ), uniq_id, text ) )
|
||||
( style, prefix, self.name, escaped_value, uniq_id, selected_text, self.get_disabled_str( disabled ), uniq_id, escape( str( text ), quote=True ) ) )
|
||||
ctr += 1
|
||||
return "\n".join( rval )
|
||||
def get_html_radio( self, prefix="", disabled=False ):
|
||||
@@ -350,7 +351,7 @@ class SelectField(BaseField):
|
||||
last_selected_value = value
|
||||
else:
|
||||
selected_text = ""
|
||||
rval.append( '<option value="%s"%s>%s</option>' % ( escape( str( value ), quote=True ), selected_text, text ) )
|
||||
rval.append( '<option value="%s"%s>%s</option>' % ( escape( str( value ), quote=True ), selected_text, escape( str( text ), quote=True ) ) )
|
||||
if last_selected_value:
|
||||
last_selected_value = ' last_selected_value="%s"' % escape( str( last_selected_value ), quote=True )
|
||||
rval.insert( 0, '<select name="%s%s"%s%s%s%s%s>' % \
|
||||
@@ -391,48 +392,66 @@ class DrillDownField( BaseField ):
|
||||
|
||||
>>> t = DrillDownField( "foo", multiple=True, display="checkbox", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] )
|
||||
>>> print t.get_html()
|
||||
<div><ul class="toolParameterExpandableCollapsable">
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="foo" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="checkbox" name="foo" value="option1"">Option 1
|
||||
</li>
|
||||
<li><input type="checkbox" name="foo" value="option2"">Option 2
|
||||
</li>
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="checkbox" name="foo" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="checkbox" name="foo" value="option3"">Option 3
|
||||
</li>
|
||||
<li><input type="checkbox" name="foo" value="option4"">Option 4
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><input type="checkbox" name="foo" value="option5"">Option 5
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="form-row drilldown-container" id="drilldown--666f6f">
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-click"></span>
|
||||
<input type="checkbox" name="foo" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--666f6f-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="foo" value="option1" >Option 1
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="foo" value="option2" >Option 2
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-68656164696e6731-click"></span>
|
||||
<input type="checkbox" name="foo" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--666f6f-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="foo" value="option3" >Option 3
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="foo" value="option4" >Option 4
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="foo" value="option5" >Option 5
|
||||
</div>
|
||||
</div>
|
||||
>>> t = DrillDownField( "foo", multiple=False, display="radio", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] )
|
||||
>>> print t.get_html()
|
||||
<div><ul class="toolParameterExpandableCollapsable">
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="foo" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="radio" name="foo" value="option1"">Option 1
|
||||
</li>
|
||||
<li><input type="radio" name="foo" value="option2"">Option 2
|
||||
</li>
|
||||
<li><span class="toolParameterExpandableCollapsable">[+]</span><input type="radio" name="foo" value="heading1"">Heading 1
|
||||
<ul class="toolParameterExpandableCollapsable" default_state="collapsed">
|
||||
<li><input type="radio" name="foo" value="option3"">Option 3
|
||||
</li>
|
||||
<li><input type="radio" name="foo" value="option4"">Option 4
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><input type="radio" name="foo" value="option5"">Option 5
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="form-row drilldown-container" id="drilldown--666f6f">
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-click"></span>
|
||||
<input type="radio" name="foo" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--666f6f-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="foo" value="option1" >Option 1
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="foo" value="option2" >Option 2
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-68656164696e6731-click"></span>
|
||||
<input type="radio" name="foo" value="heading1" >Heading 1
|
||||
<div class="form-row" id="drilldown--666f6f-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="foo" value="option3" >Option 3
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="foo" value="option4" >Option 4
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-input">
|
||||
<input type="radio" name="foo" value="option5" >Option 5
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
def __init__( self, name, multiple=None, display=None, refresh_on_change=False, options = [], value = [], refresh_on_change_values = [] ):
|
||||
self.name = name
|
||||
@@ -440,7 +459,7 @@ class DrillDownField( BaseField ):
|
||||
self.options = options
|
||||
if value and not isinstance( value, list ):
|
||||
value = [ value ]
|
||||
else:
|
||||
elif not value:
|
||||
value = []
|
||||
self.value = value
|
||||
if display == "checkbox":
|
||||
@@ -466,30 +485,34 @@ class DrillDownField( BaseField ):
|
||||
if option['options']:
|
||||
new_parents = list( parent_options ) + [ option['value'] ]
|
||||
find_expanded_options( expanded_options, option['options'], new_parents )
|
||||
def recurse_options( html, options, expanded_options = [] ):
|
||||
def recurse_options( html, options, base_id, expanded_options = [] ):
|
||||
for option in options:
|
||||
escaped_option_value = escape( str( option['value'] ), quote=True )
|
||||
selected = ( option['value'] in self.value )
|
||||
if selected: selected = ' checked'
|
||||
else: selected = ''
|
||||
if option['options']:
|
||||
default_state = 'collapsed'
|
||||
default_icon = '[+]'
|
||||
if option['value'] in expanded_options:
|
||||
default_state = 'expanded'
|
||||
default_icon = '[-]'
|
||||
html.append( '<li><span class="toolParameterExpandableCollapsable">%s</span><input type="%s" name="%s%s" value="%s"%s">%s' % ( default_icon, self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) )
|
||||
html.append( '<ul class="toolParameterExpandableCollapsable" default_state="%s">' % default_state )
|
||||
recurse_options( html, option['options'], expanded_options )
|
||||
html.append( '</ul>')
|
||||
if selected:
|
||||
selected = ' checked'
|
||||
else:
|
||||
html.append( '<li><input type="%s" name="%s%s" value="%s"%s">%s' % ( self.display, prefix, self.name, escape(str(option['value']), quote=True), selected, option['name']) )
|
||||
html.append( '</li>' )
|
||||
selected = ''
|
||||
span_class = 'form-toggle icon-button toggle'
|
||||
if option['value'] not in expanded_options:
|
||||
span_class = "%s-expand" % ( span_class )
|
||||
html.append( '<div class="form-row-input">')
|
||||
drilldown_group_id = "%s-%s" % ( base_id, hexlify( option['value'] ) )
|
||||
if option['options']:
|
||||
html.append( '<span class="%s" id="%s-click"></span>' % ( span_class, drilldown_group_id ) )
|
||||
html.append( '<input type="%s" name="%s%s" value="%s" %s>%s' % ( self.display, prefix, self.name, escaped_option_value, selected, option['name']) )
|
||||
if option['options']:
|
||||
html.append( '<div class="form-row" id="%s-container" style="float: left; margin-left: 1em;">' % ( drilldown_group_id ) )
|
||||
recurse_options( html, option['options'], drilldown_group_id, expanded_options )
|
||||
html.append( '</div>')
|
||||
html.append( '</div>')
|
||||
drilldown_id = "drilldown-%s-%s" % ( hexlify( prefix ), hexlify( self.name ) )
|
||||
rval = []
|
||||
rval.append( '<div><ul class="toolParameterExpandableCollapsable">' )
|
||||
rval.append( '<div class="form-row drilldown-container" id="%s">' % ( drilldown_id ) )
|
||||
expanded_options = []
|
||||
find_expanded_options( expanded_options, self.options )
|
||||
recurse_options( rval, self.options, expanded_options )
|
||||
rval.append( '</ul></div>' )
|
||||
recurse_options( rval, self.options, drilldown_id, expanded_options )
|
||||
rval.append( '</div>' )
|
||||
return '\n'.join( rval )
|
||||
|
||||
class AddressField(BaseField):
|
||||
|
||||
@@ -9,6 +9,7 @@ pkg_resources.require( "Cheetah" )
|
||||
from Cheetah.Template import Template
|
||||
import base
|
||||
import pickle
|
||||
from functools import wraps
|
||||
from galaxy import util
|
||||
from galaxy.exceptions import MessageException
|
||||
from galaxy.util.json import to_json_string, from_json_string
|
||||
@@ -62,6 +63,7 @@ def expose( func ):
|
||||
return func
|
||||
|
||||
def json( func ):
|
||||
@wraps(func)
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
trans.response.set_content_type( "text/javascript" )
|
||||
return simplejson.dumps( func( self, trans, *args, **kwargs ) )
|
||||
@@ -71,6 +73,7 @@ def json( func ):
|
||||
return decorator
|
||||
|
||||
def json_pretty( func ):
|
||||
@wraps(func)
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
trans.response.set_content_type( "text/javascript" )
|
||||
return simplejson.dumps( func( self, trans, *args, **kwargs ), indent=4, sort_keys=True )
|
||||
@@ -81,37 +84,46 @@ def json_pretty( func ):
|
||||
|
||||
def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ):
|
||||
def argcatcher( func ):
|
||||
@wraps(func)
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
if trans.get_user():
|
||||
return func( self, trans, *args, **kwargs )
|
||||
else:
|
||||
return trans.show_error_message(
|
||||
'You must be <a target="galaxy_main" href="%s">logged in</a> to %s.'
|
||||
% ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
|
||||
% ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels )
|
||||
return decorator
|
||||
return argcatcher
|
||||
|
||||
|
||||
def expose_api( func ):
|
||||
@wraps(func)
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
def error( environ, start_response ):
|
||||
start_response( error_status, [('Content-type', 'text/plain')] )
|
||||
return error_message
|
||||
error_status = '403 Forbidden'
|
||||
if 'key' not in kwargs:
|
||||
error_message = 'No API key provided with request, please consult the API documentation.'
|
||||
return error
|
||||
try:
|
||||
provided_key = trans.sa_session.query( trans.app.model.APIKeys ).filter( trans.app.model.APIKeys.table.c.key == kwargs['key'] ).one()
|
||||
except NoResultFound:
|
||||
error_message = 'Provided API key is not valid.'
|
||||
return error
|
||||
if provided_key.user.deleted:
|
||||
error_message = 'User account is deactivated, please contact an administrator.'
|
||||
return error
|
||||
newest_key = provided_key.user.api_keys[0]
|
||||
if newest_key.key != provided_key.key:
|
||||
error_message = 'Provided API key has expired.'
|
||||
return error
|
||||
## If there is a user, we've authenticated a session.
|
||||
if not trans.user and isinstance(trans.galaxy_session, Bunch):
|
||||
# If trans.user is already set, don't check for a key.
|
||||
# This happens when we're authenticating using session instead of an API key.
|
||||
# The Bunch clause is used to prevent the case where there's no user, but there is a real session.
|
||||
# DBTODO: This needs to be fixed when merging transaction types.
|
||||
if 'key' not in kwargs:
|
||||
error_message = 'No API key provided with request, please consult the API documentation.'
|
||||
return error
|
||||
try:
|
||||
provided_key = trans.sa_session.query( trans.app.model.APIKeys ).filter( trans.app.model.APIKeys.table.c.key == kwargs['key'] ).one()
|
||||
except NoResultFound:
|
||||
error_message = 'Provided API key is not valid.'
|
||||
return error
|
||||
if provided_key.user.deleted:
|
||||
error_message = 'User account is deactivated, please contact an administrator.'
|
||||
return error
|
||||
newest_key = provided_key.user.api_keys[0]
|
||||
if newest_key.key != provided_key.key:
|
||||
error_message = 'Provided API key has expired.'
|
||||
return error
|
||||
trans.set_user( provided_key.user )
|
||||
if trans.request.body:
|
||||
try:
|
||||
payload = util.recursively_stringify_dictionary_keys( simplejson.loads( trans.request.body ) )
|
||||
@@ -121,7 +133,6 @@ def expose_api( func ):
|
||||
error_message = 'Your request did not appear to be valid JSON, please consult the API documentation'
|
||||
return error
|
||||
trans.response.set_content_type( "application/json" )
|
||||
trans.set_user( provided_key.user )
|
||||
# Perform api_run_as processing, possibly changing identity
|
||||
if 'run_as' in kwargs:
|
||||
if not trans.user_can_do_run_as():
|
||||
@@ -139,7 +150,6 @@ def expose_api( func ):
|
||||
except:
|
||||
trans.response.status = 400
|
||||
return "That user does not exist."
|
||||
|
||||
try:
|
||||
if trans.debug:
|
||||
return simplejson.dumps( func( self, trans, *args, **kwargs ), indent=4, sort_keys=True )
|
||||
@@ -156,6 +166,7 @@ def expose_api( func ):
|
||||
return decorator
|
||||
|
||||
def require_admin( func ):
|
||||
@wraps(func)
|
||||
def decorator( self, trans, *args, **kwargs ):
|
||||
if not trans.user_is_admin():
|
||||
msg = "You must be an administrator to access this feature."
|
||||
@@ -202,10 +213,10 @@ class WebApplication( base.WebApplication ):
|
||||
return base.WebApplication.make_body_iterable( self, trans, body )
|
||||
def transaction_chooser( self, environ, galaxy_app, session_cookie ):
|
||||
if 'is_api_request' in environ:
|
||||
return GalaxyWebAPITransaction( environ, galaxy_app, self )
|
||||
return GalaxyWebAPITransaction( environ, galaxy_app, self, session_cookie )
|
||||
else:
|
||||
return GalaxyWebUITransaction( environ, galaxy_app, self, session_cookie )
|
||||
|
||||
|
||||
class GalaxyWebTransaction( base.DefaultWebTransaction ):
|
||||
"""
|
||||
Encapsulates web transaction specific state for the Galaxy application
|
||||
@@ -262,7 +273,7 @@ class GalaxyWebTransaction( base.DefaultWebTransaction ):
|
||||
except:
|
||||
action.user = None
|
||||
try:
|
||||
action.session_id = self.galaxy_session.id
|
||||
action.session_id = self.galaxy_session.id
|
||||
except:
|
||||
action.session_id = None
|
||||
self.sa_session.add( action )
|
||||
@@ -317,7 +328,7 @@ class GalaxyWebTransaction( base.DefaultWebTransaction ):
|
||||
tstamp = time.localtime ( time.time() + 3600 * 24 * age )
|
||||
self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
|
||||
self.response.cookies[name]['version'] = version
|
||||
def _ensure_valid_session( self, session_cookie ):
|
||||
def _ensure_valid_session( self, session_cookie, create=True):
|
||||
"""
|
||||
Ensure that a valid Galaxy session exists and is available as
|
||||
trans.session (part of initialization)
|
||||
@@ -822,7 +833,7 @@ class FormBuilder( object ):
|
||||
def add_select( self, name, label, value=None, options=[], error=None, help=None, use_label=True ):
|
||||
self.inputs.append( SelectInput( name, label, value=value, options=options, error=error, help=help, use_label=use_label ) )
|
||||
return self
|
||||
|
||||
|
||||
class FormInput( object ):
|
||||
"""
|
||||
Simple class describing a form input element
|
||||
@@ -837,15 +848,89 @@ class FormInput( object ):
|
||||
self.use_label = use_label
|
||||
|
||||
class GalaxyWebAPITransaction( GalaxyWebTransaction ):
|
||||
def __init__( self, environ, app, webapp ):
|
||||
"""
|
||||
TODO: Unify this with WebUITransaction, since we allow session auth now.
|
||||
Enable functionality of 'create' parameter in parent _ensure_valid_session
|
||||
"""
|
||||
def __init__( self, environ, app, webapp, session_cookie):
|
||||
GalaxyWebTransaction.__init__( self, environ, app, webapp )
|
||||
self.__user = None
|
||||
self._ensure_valid_session( None )
|
||||
self._ensure_valid_session( session_cookie )
|
||||
def _ensure_valid_session( self, session_cookie ):
|
||||
self.galaxy_session = Bunch()
|
||||
self.galaxy_session.history = self.galaxy_session.current_history = Bunch()
|
||||
self.galaxy_session.history.genome_build = None
|
||||
self.galaxy_session.is_api = True
|
||||
#Check to see if there is an existing session. Never create a new one.
|
||||
# Try to load an existing session
|
||||
secure_id = self.get_cookie( name=session_cookie )
|
||||
galaxy_session = None
|
||||
prev_galaxy_session = None
|
||||
user_for_new_session = None
|
||||
invalidate_existing_session = False
|
||||
# Track whether the session has changed so we can avoid calling flush
|
||||
# in the most common case (session exists and is valid).
|
||||
galaxy_session_requires_flush = False
|
||||
if secure_id:
|
||||
# Decode the cookie value to get the session_key
|
||||
session_key = self.security.decode_guid( secure_id )
|
||||
try:
|
||||
# Make sure we have a valid UTF-8 string
|
||||
session_key = session_key.encode( 'utf8' )
|
||||
except UnicodeDecodeError:
|
||||
# We'll end up creating a new galaxy_session
|
||||
session_key = None
|
||||
if session_key:
|
||||
# Retrieve the galaxy_session id via the unique session_key
|
||||
galaxy_session = self.sa_session.query( self.app.model.GalaxySession ) \
|
||||
.filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key,
|
||||
self.app.model.GalaxySession.table.c.is_valid==True ) ) \
|
||||
.first()
|
||||
if galaxy_session:
|
||||
# If remote user is in use it can invalidate the session, so we need to to check some things now.
|
||||
if self.app.config.use_remote_user:
|
||||
assert "HTTP_REMOTE_USER" in self.environ, \
|
||||
"use_remote_user is set but no HTTP_REMOTE_USER variable"
|
||||
remote_user_email = self.environ[ 'HTTP_REMOTE_USER' ]
|
||||
# An existing session, make sure correct association exists
|
||||
if galaxy_session.user is None:
|
||||
# No user, associate
|
||||
galaxy_session.user = self.get_or_create_remote_user( remote_user_email )
|
||||
galaxy_session_requires_flush = True
|
||||
elif galaxy_session.user.email != remote_user_email:
|
||||
# Session exists but is not associated with the correct remote user
|
||||
log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
|
||||
remote_user_email, galaxy_session.user.email )
|
||||
galaxy_session = None
|
||||
else:
|
||||
if galaxy_session.user and galaxy_session.user.external:
|
||||
# Remote user support is not enabled, but there is an existing
|
||||
# session with an external user, invalidate
|
||||
invalidate_existing_session = True
|
||||
log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
|
||||
galaxy_session.user.email )
|
||||
galaxy_session = None
|
||||
elif galaxy_session.user is not None and galaxy_session.user.deleted:
|
||||
invalidate_existing_session = True
|
||||
log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
|
||||
galaxy_session = None
|
||||
# No relevant cookies, or couldn't find, or invalid, so create a new session
|
||||
if galaxy_session:
|
||||
self.galaxy_session = galaxy_session
|
||||
self.user = galaxy_session.user
|
||||
# Do we need to flush the session?
|
||||
if galaxy_session_requires_flush:
|
||||
self.sa_session.add( galaxy_session )
|
||||
# FIXME: If prev_session is a proper relation this would not
|
||||
# be needed.
|
||||
if prev_galaxy_session:
|
||||
self.sa_session.add( prev_galaxy_session )
|
||||
self.sa_session.flush()
|
||||
# If the old session was invalid, get a new history with our new session
|
||||
if not galaxy_session:
|
||||
#Failed to find a session. Set up fake stuff for API transaction
|
||||
self.user = None
|
||||
self.galaxy_session = Bunch()
|
||||
self.galaxy_session.history = self.galaxy_session.current_history = Bunch()
|
||||
self.galaxy_session.history.genome_build = None
|
||||
self.galaxy_session.is_api = True
|
||||
|
||||
def get_user( self ):
|
||||
"""Return the current user (the expose_api decorator ensures that it is set)."""
|
||||
return self.__user
|
||||
|
||||
@@ -45,14 +45,28 @@ def css( *args ):
|
||||
"""
|
||||
return "\n".join( [ stylesheet_link_tag( "/static/style/" + name + ".css?v=%s" % server_starttime ) for name in args ] )
|
||||
|
||||
def js( *args ):
|
||||
def js_helper( prefix, *args ):
|
||||
"""
|
||||
Take a list of javascript names (no extension) and return appropriate
|
||||
Take a prefix and list of javascript names and return appropriate
|
||||
string of script tags.
|
||||
|
||||
Cache-bust with time that server started running on
|
||||
"""
|
||||
return "\n".join( [ javascript_include_tag( "/static/scripts/" + name + ".js?v=%s" % server_starttime ) for name in args ] )
|
||||
return "\n".join( [ javascript_include_tag( prefix + name + ".js?v=%s" % server_starttime ) for name in args ] )
|
||||
|
||||
def js( *args ):
|
||||
"""
|
||||
Take a prefix and list of javascript names and return appropriate
|
||||
string of script tags.
|
||||
"""
|
||||
return js_helper( '/static/scripts/', *args )
|
||||
|
||||
def templates( *args ):
|
||||
"""
|
||||
Take a list of template names (no extension) and return appropriate
|
||||
string of script tags.
|
||||
"""
|
||||
return js_helper( '/static/scripts/templates/compiled/', *args )
|
||||
|
||||
# Hashes
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ class UniverseApplication( object ):
|
||||
# Load security policy
|
||||
self.security_agent = self.model.security_agent
|
||||
self.quota_agent = galaxy.quota.NoQuotaAgent( self.model )
|
||||
self.openid_providers = OpenIDProviders() #TODO: Add OpenID support
|
||||
# TODO: Add OpenID support
|
||||
self.openid_providers = OpenIDProviders()
|
||||
self.shed_counter = self.model.shed_counter
|
||||
def shutdown( self ):
|
||||
pass
|
||||
|
||||
@@ -39,8 +39,6 @@ class Configuration( object ):
|
||||
self.file_path = resolve_path( kwargs.get( "file_path", "database/files" ), self.root )
|
||||
self.new_file_path = resolve_path( kwargs.get( "new_file_path", "database/tmp" ), self.root )
|
||||
self.cookie_path = kwargs.get( "cookie_path", "/" )
|
||||
# web API
|
||||
self.enable_api = string_as_bool( kwargs.get( 'enable_api', False ) )
|
||||
self.enable_quotas = string_as_bool( kwargs.get( 'enable_quotas', False ) )
|
||||
self.datatypes_config = kwargs.get( 'datatypes_config_file', 'datatypes_conf.xml' )
|
||||
self.test_conf = resolve_path( kwargs.get( "test_conf", "" ), self.root )
|
||||
@@ -84,6 +82,12 @@ class Configuration( object ):
|
||||
self.screencasts_url = kwargs.get( 'screencasts_url', None )
|
||||
self.log_events = False
|
||||
self.cloud_controller_instance = False
|
||||
self.server_name = ''
|
||||
self.job_manager = ''
|
||||
self.default_job_handlers = []
|
||||
self.job_handlers = []
|
||||
self.tool_handlers = []
|
||||
self.tool_runners = []
|
||||
# Proxy features
|
||||
self.apache_xsendfile = kwargs.get( 'apache_xsendfile', False )
|
||||
self.nginx_x_accel_redirect_base = kwargs.get( 'nginx_x_accel_redirect_base', False )
|
||||
@@ -94,6 +98,7 @@ class Configuration( object ):
|
||||
self.global_conf_parser = global_conf_parser
|
||||
if global_conf and "__file__" in global_conf:
|
||||
global_conf_parser.read(global_conf['__file__'])
|
||||
self.running_functional_tests = string_as_bool( kwargs.get( 'running_functional_tests', False ) )
|
||||
def get( self, key, default ):
|
||||
return self.config_dict.get( key, default )
|
||||
def get_bool( self, key, default ):
|
||||
|
||||
@@ -3,9 +3,14 @@ from galaxy.webapps.community import model
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.web.framework.helpers import time_ago, iff, grids
|
||||
from galaxy.util import inflector
|
||||
from galaxy.util.shed_util import get_configured_ui
|
||||
from common import *
|
||||
from repository import RepositoryListGrid, CategoryListGrid
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial import hg
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
@@ -321,7 +326,7 @@ class RepositoryMetadataListGrid( grids.Grid ):
|
||||
def get_value( self, trans, grid, repository_metadata ):
|
||||
repository = repository_metadata.repository
|
||||
repo = hg.repository( get_configured_ui(), repository.repo_path )
|
||||
ctx = get_changectx_for_changeset( trans, repo, repository_metadata.changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, repository_metadata.changeset_revision )
|
||||
return "%s:%s" % ( str( ctx.rev() ), repository_metadata.changeset_revision )
|
||||
class ToolsColumn( grids.TextColumn ):
|
||||
def get_value( self, trans, grid, repository_metadata ):
|
||||
@@ -457,6 +462,31 @@ class AdminController( BaseUIController, Admin ):
|
||||
status=status ) )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def reset_all_repository_metadata( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
if 'reset_all_repository_metadata_button' in kwd:
|
||||
count = 0
|
||||
for repository in trans.sa_session.query( trans.model.Repository ) \
|
||||
.filter( trans.model.Repository.table.c.deleted == False ):
|
||||
try:
|
||||
reset_all_repository_metadata( trans, trans.security.encode_id( repository.id ) )
|
||||
log.debug( "Reset metadata on repository %s" % repository.name )
|
||||
count += 1
|
||||
except Exception, e:
|
||||
log.debug( "Error attempting to reset metadata on repository %s: %s" % ( repository.name, str( e ) ) )
|
||||
message = "Reset metadata on %d repositories" % count
|
||||
trans.response.send_redirect( web.url_for( controller='admin',
|
||||
action='browse_repository_metadata',
|
||||
webapp='community',
|
||||
message=util.sanitize_text( message ),
|
||||
status=status ) )
|
||||
return trans.fill_template( '/webapps/community/admin/reset_all_repository_metadata.mako',
|
||||
message=message,
|
||||
status=status )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def browse_repositories( self, trans, **kwd ):
|
||||
# We add params to the keyword dict in this method in order to rename the param
|
||||
# with an "f-" prefix, simulating filtering by clicking a search link. We have
|
||||
@@ -527,6 +557,18 @@ class AdminController( BaseUIController, Admin ):
|
||||
return self.repository_list_grid( trans, **kwd )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def regenerate_statistics( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
if 'regenerate_statistics_button' in kwd:
|
||||
trans.app.shed_counter.generate_statistics()
|
||||
message = "Successfully regenerated statistics"
|
||||
return trans.fill_template( '/webapps/community/admin/statistics.mako',
|
||||
message=message,
|
||||
status=status )
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def delete_repository( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
|
||||
@@ -5,12 +5,15 @@ from galaxy.datatypes.checkers import *
|
||||
from galaxy.tools import *
|
||||
from galaxy.util.json import from_json_string, to_json_string
|
||||
from galaxy.util.hash_util import *
|
||||
from galaxy.util.shed_util import copy_sample_loc_file, generate_datatypes_metadata, generate_tool_metadata, generate_workflow_metadata
|
||||
from galaxy.util.shed_util import handle_sample_tool_data_table_conf_file
|
||||
from galaxy.util.shed_util import copy_sample_file, get_configured_ui, generate_datatypes_metadata, generate_tool_metadata, generate_workflow_metadata
|
||||
from galaxy.util.shed_util import handle_sample_tool_data_table_conf_file, to_html_escaped, to_html_str, update_repository
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.webapps.community import model
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.model.item_attrs import UsesItemRatings
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial import hg, ui, commands
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
@@ -140,7 +143,7 @@ def get_revision_label( trans, repository, changeset_revision ):
|
||||
changeset rev and the changeset revision string.
|
||||
"""
|
||||
repo = hg.repository( get_configured_ui(), repository.repo_path )
|
||||
ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
if ctx:
|
||||
return "%s:%s" % ( str( ctx.rev() ), changeset_revision )
|
||||
else:
|
||||
@@ -201,18 +204,18 @@ def check_tool_input_params( trans, name, tool, sample_files, invalid_files ):
|
||||
can_set_metadata = False
|
||||
correction_msg = "This file requires an entry in the tool_data_table_conf.xml file. "
|
||||
correction_msg += "Upload a file named tool_data_table_conf.xml.sample to the repository "
|
||||
correction_msg += "that includes the required entry to resolve this issue.<br/>"
|
||||
correction_msg += "that includes the required entry to correct this error.<br/>"
|
||||
invalid_files.append( ( name, correction_msg ) )
|
||||
if options.index_file or options.missing_index_file:
|
||||
# Make sure the repository contains the required xxx.loc.sample file.
|
||||
index_file = options.index_file or options.missing_index_file
|
||||
index_head, index_tail = os.path.split( index_file )
|
||||
index_file_path, index_file_name = os.path.split( index_file )
|
||||
sample_found = False
|
||||
for sample_file in sample_files:
|
||||
sample_head, sample_tail = os.path.split( sample_file )
|
||||
if sample_tail == '%s.sample' % index_tail:
|
||||
copy_sample_loc_file( trans.app, sample_file )
|
||||
options.index_file = index_tail
|
||||
sample_file_path, sample_file_name = os.path.split( sample_file )
|
||||
if sample_file_name == '%s.sample' % index_file_name:
|
||||
copy_sample_file( trans.app, sample_file )
|
||||
options.index_file = index_file_name
|
||||
options.missing_index_file = None
|
||||
if options.tool_data_table:
|
||||
options.tool_data_table.missing_index_file = None
|
||||
@@ -221,7 +224,7 @@ def check_tool_input_params( trans, name, tool, sample_files, invalid_files ):
|
||||
if not sample_found:
|
||||
can_set_metadata = False
|
||||
correction_msg = "This file refers to a file named <b>%s</b>. " % str( index_file )
|
||||
correction_msg += "Upload a file named <b>%s.sample</b> to the repository to correct this error." % str( index_tail )
|
||||
correction_msg += "Upload a file named <b>%s.sample</b> to the repository to correct this error." % str( index_file_name )
|
||||
invalid_files.append( ( name, correction_msg ) )
|
||||
return can_set_metadata, invalid_files
|
||||
def new_tool_metadata_required( trans, id, metadata_dict ):
|
||||
@@ -280,16 +283,20 @@ def new_workflow_metadata_required( trans, id, metadata_dict ):
|
||||
else:
|
||||
# There is no saved repository metadata, so we need to create a new repository_metadata table record.
|
||||
return True
|
||||
# The received metadata_dict includes no metadata for workflows, so a new repository_metadata table
|
||||
# record is not needed.
|
||||
# The received metadata_dict includes no metadata for workflows, so a new repository_metadata table record is not needed.
|
||||
return False
|
||||
def generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, repo_dir ):
|
||||
# Browse the repository tip files on disk to generate metadata. This is faster than
|
||||
# the generate_metadata_for_changeset_revision() method below because fctx.data() does
|
||||
# not have to be written to disk to load tools. also, since changeset_revision is the
|
||||
# repository tip, we handle things like .loc.sample files here.
|
||||
def generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, repo, repo_dir ):
|
||||
"""
|
||||
Browse the repository tip files on disk to generate metadata. This is faster than the
|
||||
generate_metadata_for_changeset_revision() method below because fctx.data() does not have
|
||||
to be written to disk to load tools. We also handle things like .loc.sample files and
|
||||
invalid_tool_configs here, while they are ignored in older revisions.
|
||||
"""
|
||||
# If a push from the command line is occurring, update the repository files on disk before setting metadata.
|
||||
update_repository( repo, str( ctx.rev() ) )
|
||||
metadata_dict = {}
|
||||
invalid_files = []
|
||||
invalid_tool_configs = []
|
||||
sample_files = []
|
||||
datatypes_config = None
|
||||
# Find datatypes_conf.xml if it exists.
|
||||
@@ -319,18 +326,30 @@ def generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, re
|
||||
if not ( check_binary( full_path ) or check_image( full_path ) or check_gzip( full_path )[ 0 ]
|
||||
or check_bz2( full_path )[ 0 ] or check_zip( full_path ) ):
|
||||
try:
|
||||
tool = load_tool( trans, full_path )
|
||||
valid = True
|
||||
# Make sure we're looking at a tool config and not a display application config or something else.
|
||||
element_tree = util.parse_xml( full_path )
|
||||
element_tree_root = element_tree.getroot()
|
||||
is_tool = element_tree_root.tag == 'tool'
|
||||
except Exception, e:
|
||||
valid = False
|
||||
invalid_files.append( ( name, str( e ) ) )
|
||||
if valid and tool is not None:
|
||||
can_set_metadata, invalid_files = check_tool_input_params( trans, name, tool, sample_files, invalid_files )
|
||||
if can_set_metadata:
|
||||
# Update the list of metadata dictionaries for tools in metadata_dict.
|
||||
tool_config = os.path.join( root, name )
|
||||
repository_clone_url = generate_clone_url( trans, id )
|
||||
metadata_dict = generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_dict )
|
||||
log.debug( "Error parsing %s, exception: %s" % ( full_path, str( e ) ) )
|
||||
is_tool = False
|
||||
if is_tool:
|
||||
try:
|
||||
tool = load_tool( trans, full_path )
|
||||
valid = True
|
||||
except Exception, e:
|
||||
valid = False
|
||||
invalid_files.append( ( name, str( e ) ) )
|
||||
invalid_tool_configs.append( name )
|
||||
if valid and tool is not None:
|
||||
can_set_metadata, invalid_files = check_tool_input_params( trans, name, tool, sample_files, invalid_files )
|
||||
if can_set_metadata:
|
||||
# Update the list of metadata dictionaries for tools in metadata_dict.
|
||||
tool_config = os.path.join( root, name )
|
||||
repository_clone_url = generate_clone_url( trans, id )
|
||||
metadata_dict = generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_dict )
|
||||
else:
|
||||
invalid_tool_configs.append( name )
|
||||
# Find all exported workflows
|
||||
elif name.endswith( '.ga' ):
|
||||
try:
|
||||
@@ -344,6 +363,8 @@ def generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, re
|
||||
metadata_dict = generate_workflow_metadata( relative_path, exported_workflow_dict, metadata_dict )
|
||||
except Exception, e:
|
||||
invalid_files.append( ( name, str( e ) ) )
|
||||
if invalid_tool_configs:
|
||||
metadata_dict[ 'invalid_tools' ] = invalid_tool_configs
|
||||
return metadata_dict, invalid_files
|
||||
def generate_metadata_for_changeset_revision( trans, id, ctx, changeset_revision, repo_dir ):
|
||||
# Browse repository files within a change set to generate metadata.
|
||||
@@ -388,21 +409,29 @@ def generate_metadata_for_changeset_revision( trans, id, ctx, changeset_revision
|
||||
if not ( check_binary( tmp_filename ) or check_image( tmp_filename ) or check_gzip( tmp_filename )[ 0 ]
|
||||
or check_bz2( tmp_filename )[ 0 ] or check_zip( tmp_filename ) ):
|
||||
try:
|
||||
tool = load_tool( trans, tmp_filename )
|
||||
valid = True
|
||||
except Exception, e:
|
||||
invalid_files.append( ( filename, str( e ) ) )
|
||||
valid = False
|
||||
if valid and tool is not None:
|
||||
# Update the list of metadata dictionaries for tools in metadata_dict. Note that filename
|
||||
# here is the relative path to the config file within the change set context, something
|
||||
# like filtering.xml, but when the change set was the repository tip, the value was
|
||||
# something like database/community_files/000/repo_1/filtering.xml. This shouldn't break
|
||||
# anything, but may result in a bit of confusion when maintaining the code / data over time.
|
||||
# IMPORTANT NOTE: Here we are assuming that since the current change set is not the repository
|
||||
# tip, we do not have to handle any .loc.sample files since they would have been handled previously.
|
||||
repository_clone_url = generate_clone_url( trans, id )
|
||||
metadata_dict = generate_tool_metadata( filename, tool, repository_clone_url, metadata_dict )
|
||||
# Make sure we're looking at a tool config and not a display application config or something else.
|
||||
element_tree = util.parse_xml( tmp_filename )
|
||||
element_tree_root = element_tree.getroot()
|
||||
is_tool = element_tree_root.tag == 'tool'
|
||||
except:
|
||||
is_tool = False
|
||||
if is_tool:
|
||||
try:
|
||||
tool = load_tool( trans, tmp_filename )
|
||||
valid = True
|
||||
except Exception, e:
|
||||
invalid_files.append( ( filename, str( e ) ) )
|
||||
valid = False
|
||||
if valid and tool is not None:
|
||||
# Update the list of metadata dictionaries for tools in metadata_dict. Note that filename
|
||||
# here is the relative path to the config file within the change set context, something
|
||||
# like filtering.xml, but when the change set was the repository tip, the value was
|
||||
# something like database/community_files/000/repo_1/filtering.xml. This shouldn't break
|
||||
# anything, but may result in a bit of confusion when maintaining the code / data over time.
|
||||
# IMPORTANT NOTE: Here we are assuming that since the current change set is not the repository
|
||||
# tip, we do not have to handle any .loc.sample files since they would have been handled previously.
|
||||
repository_clone_url = generate_clone_url( trans, id )
|
||||
metadata_dict = generate_tool_metadata( filename, tool, repository_clone_url, metadata_dict )
|
||||
try:
|
||||
os.unlink( tmp_filename )
|
||||
except:
|
||||
@@ -425,12 +454,12 @@ def set_repository_metadata( trans, id, changeset_revision, content_alert_str=''
|
||||
repository = get_repository( trans, id )
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
metadata_dict = {}
|
||||
invalid_files = []
|
||||
if ctx is not None:
|
||||
if changeset_revision == repository.tip:
|
||||
metadata_dict, invalid_files = generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, repo_dir )
|
||||
metadata_dict, invalid_files = generate_metadata_for_repository_tip( trans, id, ctx, changeset_revision, repo, repo_dir )
|
||||
else:
|
||||
metadata_dict, invalid_files = generate_metadata_for_changeset_revision( trans, id, ctx, changeset_revision, repo_dir )
|
||||
if metadata_dict:
|
||||
@@ -439,10 +468,14 @@ def set_repository_metadata( trans, id, changeset_revision, content_alert_str=''
|
||||
# Create a new repository_metadata table row.
|
||||
repository_metadata = trans.model.RepositoryMetadata( repository.id, changeset_revision, metadata_dict )
|
||||
trans.sa_session.add( repository_metadata )
|
||||
trans.sa_session.flush()
|
||||
# If this is the first record stored for this repository, see if we need to send any email alerts.
|
||||
if len( repository.downloadable_revisions ) == 1:
|
||||
handle_email_alerts( trans, repository, content_alert_str='', new_repo_alert=True, admin_only=False )
|
||||
try:
|
||||
trans.sa_session.flush()
|
||||
# If this is the first record stored for this repository, see if we need to send any email alerts.
|
||||
if len( repository.downloadable_revisions ) == 1:
|
||||
handle_email_alerts( trans, repository, content_alert_str='', new_repo_alert=True, admin_only=False )
|
||||
except TypeError, e:
|
||||
message = "Unable to save metadata for this repository probably due to a tool config file that doesn't conform to the Cheetah template syntax."
|
||||
status = 'error'
|
||||
else:
|
||||
repository_metadata = get_latest_repository_metadata( trans, id )
|
||||
if repository_metadata:
|
||||
@@ -450,9 +483,13 @@ def set_repository_metadata( trans, id, changeset_revision, content_alert_str=''
|
||||
repository_metadata.changeset_revision = changeset_revision
|
||||
repository_metadata.metadata = metadata_dict
|
||||
trans.sa_session.add( repository_metadata )
|
||||
trans.sa_session.flush()
|
||||
try:
|
||||
trans.sa_session.flush()
|
||||
except TypeError, e:
|
||||
message = "Unable to save metadata for this repository probably due to a tool config file that doesn't conform to the Cheetah template syntax."
|
||||
status = 'error'
|
||||
else:
|
||||
# There are no tools in the repository, and we're setting metadat on the repository tip.
|
||||
# There are no tools in the repository, and we're setting metadata on the repository tip.
|
||||
repository_metadata = trans.model.RepositoryMetadata( repository.id, changeset_revision, metadata_dict )
|
||||
trans.sa_session.add( repository_metadata )
|
||||
trans.sa_session.flush()
|
||||
@@ -514,9 +551,9 @@ def reset_all_repository_metadata( trans, id, **kwd ):
|
||||
ancestor_metadata_dict = None
|
||||
for changeset in repo.changelog:
|
||||
current_changeset_revision = str( repo.changectx( changeset ) )
|
||||
ctx = get_changectx_for_changeset( trans, repo, current_changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, current_changeset_revision )
|
||||
if current_changeset_revision == repository.tip:
|
||||
current_metadata_dict, invalid_files = generate_metadata_for_repository_tip( trans, id, ctx, current_changeset_revision, repo_dir )
|
||||
current_metadata_dict, invalid_files = generate_metadata_for_repository_tip( trans, id, ctx, current_changeset_revision, repo, repo_dir )
|
||||
else:
|
||||
current_metadata_dict, invalid_files = generate_metadata_for_changeset_revision( trans, id, ctx, current_changeset_revision, repo_dir )
|
||||
if current_metadata_dict:
|
||||
@@ -538,8 +575,8 @@ def reset_all_repository_metadata( trans, id, **kwd ):
|
||||
create_or_update_repository_metadata( trans, id, repository, ancestor_changeset_revision, ancestor_metadata_dict )
|
||||
# Keep track of the changeset_revisions that we've persisted.
|
||||
changeset_revisions.append( ancestor_changeset_revision )
|
||||
ancestor_changeset_revision = None
|
||||
ancestor_metadata_dict = None
|
||||
ancestor_changeset_revision = current_changeset_revision
|
||||
ancestor_metadata_dict = current_metadata_dict
|
||||
else:
|
||||
# We're either at the first change set in the change log or we have just created or updated
|
||||
# a repository_metadata record. At this point we set the ancestor changeset to the current
|
||||
@@ -722,12 +759,12 @@ def compare_datatypes( ancestor_datatypes, current_datatypes ):
|
||||
# Currently the only way to differentiate datatypes is by name.
|
||||
ancestor_datatype_dtype = ancestor_datatype[ 'dtype' ]
|
||||
ancestor_datatype_extension = ancestor_datatype[ 'extension' ]
|
||||
ancestor_datatype_mimetype = ancestor_datatype[ 'mimetype' ]
|
||||
ancestor_datatype_mimetype = ancestor_datatype.get( 'mimetype', None )
|
||||
found_in_current = False
|
||||
for current_datatype in current_datatypes:
|
||||
if current_datatype[ 'dtype' ] == ancestor_datatype_dtype and \
|
||||
current_datatype[ 'extension' ] == ancestor_datatype_extension and \
|
||||
current_datatype[ 'mimetype' ] == ancestor_datatype_mimetype:
|
||||
current_datatype.get( 'mimetype', None ) == ancestor_datatype_mimetype:
|
||||
found_in_current = True
|
||||
break
|
||||
if not found_in_current:
|
||||
@@ -740,7 +777,7 @@ def compare_datatypes( ancestor_datatypes, current_datatypes ):
|
||||
def get_repository_by_name( trans, name ):
|
||||
"""Get a repository from the database via name"""
|
||||
return trans.sa_session.query( trans.model.Repository ).filter_by( name=name ).one()
|
||||
def get_changectx_for_changeset( trans, repo, changeset_revision, **kwd ):
|
||||
def get_changectx_for_changeset( repo, changeset_revision, **kwd ):
|
||||
"""Retrieve a specified changectx from a repository"""
|
||||
for changeset in repo.changelog:
|
||||
ctx = repo.changectx( changeset )
|
||||
@@ -753,15 +790,6 @@ def change_set_is_malicious( trans, id, changeset_revision, **kwd ):
|
||||
if repository_metadata:
|
||||
return repository_metadata.malicious
|
||||
return False
|
||||
def get_configured_ui():
|
||||
# Configure any desired ui settings.
|
||||
_ui = ui.ui()
|
||||
# The following will suppress all messages. This is
|
||||
# the same as adding the following setting to the repo
|
||||
# hgrc file' [ui] section:
|
||||
# quiet = True
|
||||
_ui.setconfig( 'ui', 'quiet', True )
|
||||
return _ui
|
||||
def get_user( trans, id ):
|
||||
"""Get a user from the database by id"""
|
||||
return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) )
|
||||
@@ -864,9 +892,10 @@ def check_file_contents( trans ):
|
||||
return True
|
||||
return False
|
||||
def update_for_browsing( trans, repository, current_working_dir, commit_message='' ):
|
||||
# Make a copy of a repository's files for browsing, remove from disk all files that
|
||||
# are not tracked, and commit all added, modified or removed files that have not yet
|
||||
# been committed.
|
||||
# This method id deprecated, but we'll keep it around for a while in case we need it. The problem is that hg purge
|
||||
# is not supported by the mercurial API.
|
||||
# Make a copy of a repository's files for browsing, remove from disk all files that are not tracked, and commit all
|
||||
# added, modified or removed files that have not yet been committed.
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
# The following will delete the disk copy of only the files in the repository.
|
||||
@@ -963,7 +992,7 @@ def load_tool_from_changeset_revision( trans, repository_id, changeset_revision,
|
||||
# Get the tool config file name from the hgweb url, something like:
|
||||
# /repos/test/convert_chars1/file/e58dcf0026c7/convert_characters.xml
|
||||
old_tool_config_file_name = tool_config.split( '/' )[ -1 ]
|
||||
ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
fctx = None
|
||||
for filename in ctx:
|
||||
filename_head, filename_tail = os.path.split( filename )
|
||||
@@ -997,12 +1026,24 @@ def build_changeset_revision_select_field( trans, repository, selected_value=Non
|
||||
"""
|
||||
repo = hg.repository( get_configured_ui(), repository.repo_path )
|
||||
options = []
|
||||
changeset_tups = []
|
||||
refresh_on_change_values = []
|
||||
for repository_metadata in repository.downloadable_revisions:
|
||||
changeset_revision = repository_metadata.changeset_revision
|
||||
revision_label = get_revision_label( trans, repository, changeset_revision )
|
||||
options.append( ( revision_label, changeset_revision ) )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
if ctx:
|
||||
rev = '%04d' % ctx.rev()
|
||||
label = "%s:%s" % ( str( ctx.rev() ), changeset_revision )
|
||||
else:
|
||||
rev = '-1'
|
||||
label = "-1:%s" % changeset_revision
|
||||
changeset_tups.append( ( rev, label, changeset_revision ) )
|
||||
refresh_on_change_values.append( changeset_revision )
|
||||
# Sort options by the revision label. Even though the downloadable_revisions query sorts by update_time,
|
||||
# the changeset revisions may not be sorted correctly because setting metadata over time will reset update_time.
|
||||
for changeset_tup in sorted( changeset_tups ):
|
||||
# Display the latest revision first.
|
||||
options.insert( 0, ( changeset_tup[1], changeset_tup[2] ) )
|
||||
if add_id_to_name:
|
||||
name = 'changeset_revision_%d' % repository.id
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import os, logging
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.webapps.community.controllers.common import *
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial.hgweb.hgwebdir_mod import hgwebdir
|
||||
from mercurial.hgweb.request import wsgiapplication
|
||||
|
||||
@@ -10,7 +14,20 @@ class HgController( BaseUIController ):
|
||||
def handle_request( self, trans, **kwd ):
|
||||
# The os command that results in this method being called will look something like
|
||||
# hg clone http://test@127.0.0.1:9009/repos/test/convert_characters1
|
||||
return wsgiapplication( make_web_app )
|
||||
cmd = kwd.get( 'cmd', None )
|
||||
wsgi_app = wsgiapplication( make_web_app )
|
||||
# Hack: Add a parameter to requests for which we do not want all repository metadata reset.
|
||||
reset_metadata = not ( kwd.get( 'no_reset', False ) )
|
||||
if cmd == 'listkeys' and reset_metadata:
|
||||
# This possibly results from an "hg push" from the command line. When doing this, the following 7 commands, in order,
|
||||
# will be retrieved from environ: between -> capabilities -> heads -> branchmap -> unbundle -> unbundle -> listkeys
|
||||
path_info = kwd.get( 'path_info', None )
|
||||
if path_info:
|
||||
owner, name = path_info.split( '/' )
|
||||
repository = get_repository_by_name_and_owner( trans, name, owner )
|
||||
if repository:
|
||||
reset_all_repository_metadata( trans, trans.security.encode_id( repository.id ) )
|
||||
return wsgi_app
|
||||
|
||||
def make_web_app():
|
||||
hgweb_config = "%s/hgweb.config" % os.getcwd()
|
||||
|
||||
@@ -10,19 +10,16 @@ from galaxy.webapps.community.model import directory_hash_id
|
||||
from galaxy.web.framework.helpers import time_ago, iff, grids
|
||||
from galaxy.util.json import from_json_string, to_json_string
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.util.shed_util import get_configured_ui
|
||||
from common import *
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial import hg, ui, patch, commands
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
# Characters that must be html escaped
|
||||
MAPPED_CHARS = { '>' :'>',
|
||||
'<' :'<',
|
||||
'"' : '"',
|
||||
'&' : '&',
|
||||
'\'' : ''' }
|
||||
MAX_CONTENT_SIZE = 32768
|
||||
VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
|
||||
VALID_REPOSITORYNAME_RE = re.compile( "^[a-z0-9\_]+$" )
|
||||
|
||||
class CategoryListGrid( grids.Grid ):
|
||||
@@ -407,6 +404,40 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
# Render the list view
|
||||
return self.valid_repository_list_grid( trans, **kwd )
|
||||
@web.expose
|
||||
def browse_invalid_tools( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
webapp = params.get( 'webapp', 'community' )
|
||||
cntrller = params.get( 'cntrller', 'repository' )
|
||||
is_admin = trans.user_is_admin()
|
||||
invalid_tools_dict = odict()
|
||||
if is_admin and cntrller == 'admin':
|
||||
for repository in trans.sa_session.query( trans.model.Repository ) \
|
||||
.filter( trans.model.Repository.table.c.deleted == False ) \
|
||||
.order_by( trans.model.Repository.table.c.name ):
|
||||
for downloadable_revision in repository.downloadable_revisions:
|
||||
metadata = downloadable_revision.metadata
|
||||
invalid_tools = metadata.get( 'invalid_tools', [] )
|
||||
for invalid_tool_config in invalid_tools:
|
||||
invalid_tools_dict[ invalid_tool_config ] = ( repository.id, repository.name, downloadable_revision.changeset_revision )
|
||||
else:
|
||||
for repository in trans.sa_session.query( trans.model.Repository ) \
|
||||
.filter( and_( trans.model.Repository.table.c.deleted == False,
|
||||
trans.model.Repository.table.c.user_id == trans.user.id ) ) \
|
||||
.order_by( trans.model.Repository.table.c.name ):
|
||||
for downloadable_revision in repository.downloadable_revisions:
|
||||
metadata = downloadable_revision.metadata
|
||||
invalid_tools = metadata.get( 'invalid_tools', [] )
|
||||
for invalid_tool_config in invalid_tools:
|
||||
invalid_tools_dict[ invalid_tool_config ] = ( repository.id, repository.name, downloadable_revision.changeset_revision )
|
||||
return trans.fill_template( '/webapps/community/repository/browse_invalid_tools.mako',
|
||||
cntrller=cntrller,
|
||||
invalid_tools_dict=invalid_tools_dict,
|
||||
webapp=webapp,
|
||||
message=message,
|
||||
status=status )
|
||||
@web.expose
|
||||
def find_workflows( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
@@ -444,13 +475,16 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
# This can only occur when there is a multi-select grid with check boxes and an operation,
|
||||
# and the user clicked the operation button without checking any of the check boxes.
|
||||
return trans.show_error_message( "No items were selected." )
|
||||
workflow_names = [ item.lower() for item in util.listify( kwd.get( 'workflow_name', '' ) ) ]
|
||||
exact_matches = params.get( 'exact_matches', '' )
|
||||
exact_matches_checked = CheckboxField.is_checked( exact_matches )
|
||||
match_tuples = []
|
||||
ok = True
|
||||
if workflow_names:
|
||||
ok, match_tuples = self.__search_repository_metadata( trans, exact_matches_checked, workflow_names=workflow_names )
|
||||
if 'find_workflows_button' in kwd:
|
||||
workflow_names = [ item.lower() for item in util.listify( kwd.get( 'workflow_name', '' ) ) ]
|
||||
exact_matches = params.get( 'exact_matches', '' )
|
||||
exact_matches_checked = CheckboxField.is_checked( exact_matches )
|
||||
match_tuples = []
|
||||
ok = True
|
||||
if workflow_names:
|
||||
ok, match_tuples = self.__search_repository_metadata( trans, exact_matches_checked, workflow_names=workflow_names )
|
||||
else:
|
||||
ok, match_tuples = self.__search_repository_metadata( trans, exact_matches_checked, workflow_names=[], all_workflows=True )
|
||||
if ok:
|
||||
kwd[ 'match_tuples' ] = match_tuples
|
||||
# Render the list view
|
||||
@@ -475,6 +509,9 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
else:
|
||||
message = "No search performed - each field must contain the same number of comma-separated items."
|
||||
status = "error"
|
||||
else:
|
||||
exact_matches_checked = False
|
||||
workflow_names = []
|
||||
exact_matches_check_box = CheckboxField( 'exact_matches', checked=exact_matches_checked )
|
||||
return trans.fill_template( '/webapps/community/repository/find_workflows.mako',
|
||||
webapp=webapp,
|
||||
@@ -562,7 +599,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
exact_matches_check_box=exact_matches_check_box,
|
||||
message=message,
|
||||
status=status )
|
||||
def __search_repository_metadata( self, trans, exact_matches_checked, tool_ids='', tool_names='', tool_versions='', workflow_names='' ):
|
||||
def __search_repository_metadata( self, trans, exact_matches_checked, tool_ids='', tool_names='', tool_versions='', workflow_names='', all_workflows=False ):
|
||||
match_tuples = []
|
||||
ok = True
|
||||
for repository_metadata in trans.sa_session.query( model.RepositoryMetadata ):
|
||||
@@ -630,6 +667,8 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
for workflow_name in workflow_names:
|
||||
if self.__in_workflow_dict( workflow_dict, exact_matches_checked, workflow_name ):
|
||||
match_tuples.append( ( repository_metadata.repository_id, repository_metadata.changeset_revision ) )
|
||||
elif all_workflows and 'workflows' in metadata:
|
||||
match_tuples.append( ( repository_metadata.repository_id, repository_metadata.changeset_revision ) )
|
||||
return ok, match_tuples
|
||||
def __in_workflow_dict( self, workflow_dict, exact_matches_checked, workflow_name ):
|
||||
workflow_dict_workflow_name = workflow_dict[ 'name' ].lower()
|
||||
@@ -716,10 +755,14 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
if not includes_tools and 'tools' in repository_metadata.metadata:
|
||||
includes_tools = True
|
||||
repository = get_repository( trans, trans.security.encode_id( repository_metadata.repository_id ) )
|
||||
repository_id = trans.security.encode_id( repository.id )
|
||||
# Get the changelog rev for this changeset_revision.
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
changeset_revision = repository_metadata.changeset_revision
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
repository_id = trans.security.encode_id( repository.id )
|
||||
repository_clone_url = generate_clone_url( trans, repository_id )
|
||||
repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision )
|
||||
repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision, str( ctx.rev() ) )
|
||||
return encode( repo_info_dict ), includes_tools
|
||||
@web.expose
|
||||
def preview_tools_in_changeset( self, trans, repository_id, **kwd ):
|
||||
@@ -765,14 +808,47 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
# Tell the caller if the repository includes Galaxy tools so the page
|
||||
# enabling selection of the tool panel section can be displayed.
|
||||
includes_tools = 'tools' in repository_metadata.metadata
|
||||
# Get the changelog rev for this changeset_revision.
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
repo_info_dict = {}
|
||||
repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision )
|
||||
repo_info_dict[ repository.name ] = ( repository.description, repository_clone_url, changeset_revision, str( ctx.rev() ) )
|
||||
encoded_repo_info_dict = encode( repo_info_dict )
|
||||
# Redirect back to local Galaxy to perform install.
|
||||
url = '%sadmin_toolshed/install_repository?tool_shed_url=%s&repo_info_dict=%s&includes_tools=%s' % \
|
||||
( galaxy_url, url_for( '/', qualified=True ), encoded_repo_info_dict, str( includes_tools ) )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
def get_ctx_rev( self, trans, **kwd ):
|
||||
"""Given a repository and changeset_revision, return the correct ctx.rev() value."""
|
||||
repository_name = kwd[ 'name' ]
|
||||
repository_owner = kwd[ 'owner' ]
|
||||
changeset_revision = kwd[ 'changeset_revision' ]
|
||||
repository = get_repository_by_name_and_owner( trans, repository_name, repository_owner )
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
if ctx:
|
||||
return str( ctx.rev() )
|
||||
return ''
|
||||
@web.expose
|
||||
def get_readme( self, trans, **kwd ):
|
||||
"""If the received changeset_revision includes a file named readme (case ignored), return it's contents."""
|
||||
repository_name = kwd[ 'name' ]
|
||||
repository_owner = kwd[ 'owner' ]
|
||||
changeset_revision = kwd[ 'changeset_revision' ]
|
||||
repository = get_repository_by_name_and_owner( trans, repository_name, repository_owner )
|
||||
repo_dir = repository.repo_path
|
||||
for root, dirs, files in os.walk( repo_dir ):
|
||||
for name in files:
|
||||
if name.lower() in [ 'readme', 'readme.txt', 'read_me', 'read_me.txt', '%s.txt' % repository_name ]:
|
||||
f = open( os.path.join( root, name ), 'r' )
|
||||
text = f.read()
|
||||
f.close()
|
||||
return str( text )
|
||||
return ''
|
||||
@web.expose
|
||||
def get_tool_versions( self, trans, **kwd ):
|
||||
"""
|
||||
For each valid /downloadable change set (up to the received changeset_revision) in the
|
||||
@@ -793,7 +869,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
tool_version_dicts.append( repository_metadata.tool_versions )
|
||||
if current_changeset_revision == changeset_revision:
|
||||
break
|
||||
if repository_metadata.tool_versions:
|
||||
if tool_version_dicts:
|
||||
return to_json_string( tool_version_dicts )
|
||||
return ''
|
||||
@web.expose
|
||||
@@ -810,6 +886,9 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
changeset_revision = params.get( 'changeset_revision', None )
|
||||
webapp = params.get( 'webapp', 'community' )
|
||||
repository = get_repository_by_name_and_owner( trans, name, owner )
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
latest_ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
from_update_manager = webapp == 'update_manager'
|
||||
if from_update_manager:
|
||||
update = 'true'
|
||||
@@ -820,10 +899,10 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
url += '&name=%s&owner=%s&changeset_revision=%s&latest_changeset_revision=' % \
|
||||
( repository.name, repository.user.username, changeset_revision )
|
||||
if changeset_revision == repository.tip:
|
||||
# If changeset_revision is the repository tip, then
|
||||
# we know there are no additional updates for the tools.
|
||||
# If changeset_revision is the repository tip, we know there are no additional updates for the tools.
|
||||
if from_update_manager:
|
||||
return no_update
|
||||
# Return the same value for changeset_revision and latest_changeset_revision.
|
||||
url += repository.tip
|
||||
else:
|
||||
repository_metadata = get_repository_metadata_by_changeset_revision( trans,
|
||||
@@ -834,15 +913,15 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
# repository, then we know there are no additional updates for the tools.
|
||||
if from_update_manager:
|
||||
return no_update
|
||||
url += changeset_revision
|
||||
else:
|
||||
# Return the same value for changeset_revision and latest_changeset_revision.
|
||||
url += changeset_revision
|
||||
else:
|
||||
# The changeset_revision column in the repository_metadata table has been
|
||||
# updated with a new changeset_revision value since the repository was cloned.
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
# Load each tool in the repository's changeset_revision to generate a list of
|
||||
# tool guids, since guids differentiate tools by id and version.
|
||||
ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
if ctx is not None:
|
||||
tool_guids = []
|
||||
for filename in ctx:
|
||||
@@ -859,12 +938,21 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
if not ( check_binary( tmp_filename ) or check_image( tmp_filename ) or check_gzip( tmp_filename )[ 0 ]
|
||||
or check_bz2( tmp_filename )[ 0 ] or check_zip( tmp_filename ) ):
|
||||
try:
|
||||
tool = load_tool( trans, tmp_filename )
|
||||
valid = True
|
||||
except:
|
||||
valid = False
|
||||
if valid and tool is not None:
|
||||
tool_guids.append( generate_tool_guid( trans, repository, tool ) )
|
||||
# Make sure we're looking at a tool config and not a display application config or something else.
|
||||
element_tree = util.parse_xml( tmp_filename )
|
||||
element_tree_root = element_tree.getroot()
|
||||
is_tool = element_tree_root.tag == 'tool'
|
||||
except Exception, e:
|
||||
log.debug( "Error parsing %s, exception: %s" % ( tmp_filename, str( e ) ) )
|
||||
is_tool = False
|
||||
if is_tool:
|
||||
try:
|
||||
tool = load_tool( trans, tmp_filename )
|
||||
valid = True
|
||||
except:
|
||||
valid = False
|
||||
if valid and tool is not None:
|
||||
tool_guids.append( generate_tool_guid( trans, repository, tool ) )
|
||||
try:
|
||||
os.unlink( tmp_filename )
|
||||
except:
|
||||
@@ -882,11 +970,12 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
metadata_tool_guids.append( tool_dict[ 'guid' ] )
|
||||
metadata_tool_guids.sort()
|
||||
if tool_guids == metadata_tool_guids:
|
||||
# We've found the repository_metadata record whose changeset_revision
|
||||
# value has been updated.
|
||||
# We've found the repository_metadata record whose changeset_revision value has been updated.
|
||||
if from_update_manager:
|
||||
return update
|
||||
url += repository_metadata.changeset_revision
|
||||
# Get the ctx_rev for the discovered changeset_revision.
|
||||
latest_ctx = get_changectx_for_changeset( repo, repository_metadata.changeset_revision )
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
@@ -896,10 +985,11 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
return no_update
|
||||
url += changeset_revision
|
||||
else:
|
||||
# There are not tools in the changeset_revision, so no tool updates are possible.
|
||||
# There are no tools in the changeset_revision, so no tool updates are possible.
|
||||
if from_update_manager:
|
||||
return no_update
|
||||
url += changeset_revision
|
||||
url += '&latest_ctx_rev=%s' % str( latest_ctx.rev() )
|
||||
return trans.response.send_redirect( url )
|
||||
@web.expose
|
||||
def browse_repositories( self, trans, **kwd ):
|
||||
@@ -1123,13 +1213,10 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
new_hgweb_config.flush()
|
||||
shutil.move( tmp_fname, os.path.abspath( hgweb_config ) )
|
||||
def __create_hgrc_file( self, repository ):
|
||||
# At this point, an entry for the repository is required to be in the hgweb.config
|
||||
# file so we can call repository.repo_path.
|
||||
# Since we support both http and https, we set push_ssl to False to override
|
||||
# the default (which is True) in the mercurial api.
|
||||
# The hg purge extension purges all files and directories not being tracked by
|
||||
# mercurial in the current repository. It'll remove unknown files and empty
|
||||
# directories. This is used in the update_for_browsing() method.
|
||||
# At this point, an entry for the repository is required to be in the hgweb.config file so we can call repository.repo_path.
|
||||
# Since we support both http and https, we set push_ssl to False to override the default (which is True) in the mercurial api.
|
||||
# The hg purge extension purges all files and directories not being tracked by mercurial in the current repository. It'll
|
||||
# remove unknown files and empty directories. This is not currently used because it is not supported in the mercurial API.
|
||||
repo = hg.repository( get_configured_ui(), path=repository.repo_path )
|
||||
fp = repo.opener( 'hgrc', 'wb' )
|
||||
fp.write( '[paths]\n' )
|
||||
@@ -1153,7 +1240,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
repo = hg.repository( get_configured_ui(), repository.repo_path )
|
||||
current_working_dir = os.getcwd()
|
||||
# Update repository files for browsing.
|
||||
update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
|
||||
update_repository( repo )
|
||||
is_malicious = change_set_is_malicious( trans, id, repository.tip )
|
||||
return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
|
||||
repo=repo,
|
||||
@@ -1269,7 +1356,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
repo.commit( user=trans.user.username, text=commit_message )
|
||||
handle_email_alerts( trans, repository )
|
||||
# Update the repository files for browsing.
|
||||
update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
|
||||
update_repository( repo )
|
||||
# Get the new repository tip.
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
if tip != repository.tip:
|
||||
@@ -1567,7 +1654,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
status = params.get( 'status', 'done' )
|
||||
repository = get_repository( trans, id )
|
||||
repo = hg.repository( get_configured_ui(), repository.repo_path )
|
||||
ctx = get_changectx_for_changeset( trans, repo, ctx_str )
|
||||
ctx = get_changectx_for_changeset( repo, ctx_str )
|
||||
if ctx is None:
|
||||
message = "Repository does not include changeset revision '%s'." % str( ctx_str )
|
||||
status = 'error'
|
||||
@@ -1581,7 +1668,7 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
anchors = modified + added + removed + deleted + unknown + ignored + clean
|
||||
diffs = []
|
||||
for diff in patch.diff( repo, node1=ctx_parent.node(), node2=ctx.node() ):
|
||||
diffs.append( self.to_html_escaped( diff ) )
|
||||
diffs.append( to_html_escaped( diff ) )
|
||||
is_malicious = change_set_is_malicious( trans, id, repository.tip )
|
||||
return trans.fill_template( '/webapps/community/repository/view_changeset.mako',
|
||||
repository=repository,
|
||||
@@ -1802,6 +1889,82 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
changeset_revision=changeset_revision,
|
||||
message=message,
|
||||
status='error' ) )
|
||||
@web.expose
|
||||
def load_invalid_tool( self, trans, repository_id, tool_config, changeset_revision, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'error' )
|
||||
webapp = params.get( 'webapp', 'community' )
|
||||
repository = get_repository( trans, repository_id )
|
||||
repo_dir = repository.repo_path
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
ctx = get_changectx_for_changeset( repo, changeset_revision )
|
||||
invalid_message = ''
|
||||
if changeset_revision == repository.tip:
|
||||
for root, dirs, files in os.walk( repo_dir ):
|
||||
found = False
|
||||
for name in files:
|
||||
if name == tool_config:
|
||||
tool_config_path = os.path.join( root, name )
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
metadata_dict, invalid_files = generate_metadata_for_repository_tip( trans, repository_id, ctx, changeset_revision, repo, repo_dir )
|
||||
else:
|
||||
for filename in ctx:
|
||||
if filename == tool_config:
|
||||
fctx = ctx[ filename ]
|
||||
# Write the contents of datatypes_config.xml to a temporary file.
|
||||
fh = tempfile.NamedTemporaryFile( 'w' )
|
||||
tool_config_path = fh.name
|
||||
fh.close()
|
||||
fh = open( tool_config_path, 'w' )
|
||||
fh.write( fctx.data() )
|
||||
fh.close()
|
||||
break
|
||||
metadata_dict, invalid_files = generate_metadata_for_changeset_revision( trans, repository_id, ctx, changeset_revision, repo_dir )
|
||||
for invalid_file_tup in invalid_files:
|
||||
invalid_tool_config, invalid_msg = invalid_file_tup
|
||||
if tool_config == invalid_tool_config:
|
||||
invalid_message = invalid_msg
|
||||
break
|
||||
tool, message = load_tool_from_changeset_revision( trans, repository_id, changeset_revision, tool_config_path )
|
||||
tool_state = self.__new_state( trans )
|
||||
is_malicious = change_set_is_malicious( trans, repository_id, repository.tip )
|
||||
if changeset_revision != repository.tip:
|
||||
try:
|
||||
os.unlink( tool_config_path )
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
if invalid_message:
|
||||
message = invalid_message
|
||||
return trans.fill_template( "/webapps/community/repository/tool_form.mako",
|
||||
repository=repository,
|
||||
changeset_revision=changeset_revision,
|
||||
tool=tool,
|
||||
tool_state=tool_state,
|
||||
is_malicious=is_malicious,
|
||||
webapp=webapp,
|
||||
message=message,
|
||||
status='error' )
|
||||
except Exception, e:
|
||||
message = "This tool is invalid because: %s." % str( e )
|
||||
if webapp == 'galaxy':
|
||||
return trans.response.send_redirect( web.url_for( controller='repository',
|
||||
action='preview_tools_in_changeset',
|
||||
repository_id=repository_id,
|
||||
changeset_revision=changeset_revision,
|
||||
message=message,
|
||||
status='error' ) )
|
||||
return trans.response.send_redirect( web.url_for( controller='repository',
|
||||
action='browse_repositories',
|
||||
operation='view_or_manage_repository',
|
||||
id=repository_id,
|
||||
changeset_revision=changeset_revision,
|
||||
message=message,
|
||||
status='error' ) )
|
||||
def __new_state( self, trans, all_pages=False ):
|
||||
"""
|
||||
Create a new `DefaultToolState` for this tool. It will not be initialized
|
||||
@@ -1919,20 +2082,20 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
trans.response.headers['Pragma'] = 'no-cache'
|
||||
trans.response.headers['Expires'] = '0'
|
||||
if is_gzip( file_path ):
|
||||
to_html = self.to_html_str( '\ngzip compressed file\n' )
|
||||
to_html = to_html_str( '\ngzip compressed file\n' )
|
||||
elif is_bz2( file_path ):
|
||||
to_html = self.to_html_str( '\nbz2 compressed file\n' )
|
||||
to_html = to_html_str( '\nbz2 compressed file\n' )
|
||||
elif check_zip( file_path ):
|
||||
to_html = self.to_html_str( '\nzip compressed file\n' )
|
||||
to_html = to_html_str( '\nzip compressed file\n' )
|
||||
elif check_binary( file_path ):
|
||||
to_html = self.to_html_str( '\nBinary file\n' )
|
||||
to_html = to_html_str( '\nBinary file\n' )
|
||||
else:
|
||||
to_html = ''
|
||||
for i, line in enumerate( open( file_path ) ):
|
||||
to_html = '%s%s' % ( to_html, self.to_html_str( line ) )
|
||||
to_html = '%s%s' % ( to_html, to_html_str( line ) )
|
||||
if len( to_html ) > MAX_CONTENT_SIZE:
|
||||
large_str = '\nFile contents truncated because file size is larger than maximum viewing size of %s\n' % util.nice_size( MAX_CONTENT_SIZE )
|
||||
to_html = '%s%s' % ( to_html, self.to_html_str( large_str ) )
|
||||
to_html = '%s%s' % ( to_html, to_html_str( large_str ) )
|
||||
break
|
||||
return to_html
|
||||
@web.expose
|
||||
@@ -1941,34 +2104,6 @@ class RepositoryController( BaseUIController, ItemRatings ):
|
||||
message = util.restore_text( params.get( 'message', '' ) )
|
||||
status = params.get( 'status', 'done' )
|
||||
return trans.fill_template( '/webapps/community/repository/help.mako', message=message, status=status, **kwd )
|
||||
def to_html_escaped( self, text ):
|
||||
"""Translates the characters in text to html values"""
|
||||
translated = []
|
||||
for c in text:
|
||||
if c in [ '\r\n', '\n', ' ', '\t' ] or c in VALID_CHARS:
|
||||
translated.append( c )
|
||||
elif c in MAPPED_CHARS:
|
||||
translated.append( MAPPED_CHARS[ c ] )
|
||||
else:
|
||||
translated.append( 'X' )
|
||||
return ''.join( translated )
|
||||
def to_html_str( self, text ):
|
||||
"""Translates the characters in text to sn html string"""
|
||||
translated = []
|
||||
for c in text:
|
||||
if c in VALID_CHARS:
|
||||
translated.append( c )
|
||||
elif c in MAPPED_CHARS:
|
||||
translated.append( MAPPED_CHARS[ c ] )
|
||||
elif c == ' ':
|
||||
translated.append( ' ' )
|
||||
elif c == '\t':
|
||||
translated.append( ' ' )
|
||||
elif c == '\n':
|
||||
translated.append( '<br/>' )
|
||||
elif c not in [ '\r' ]:
|
||||
translated.append( 'X' )
|
||||
return ''.join( translated )
|
||||
def __build_allow_push_select_field( self, trans, current_push_list, selected_value='none' ):
|
||||
options = []
|
||||
for user in trans.sa_session.query( trans.model.User ):
|
||||
|
||||
@@ -3,7 +3,10 @@ from galaxy.web.base.controller import *
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.datatypes.checkers import *
|
||||
from common import *
|
||||
from galaxy.util.shed_util import handle_sample_tool_data_table_conf_file
|
||||
from galaxy.util.shed_util import get_configured_ui, handle_sample_tool_data_table_conf_file
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial import hg, ui, commands
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
@@ -119,14 +122,9 @@ class UploadController( BaseUIController ):
|
||||
else:
|
||||
content_alert_str = ''
|
||||
commands.add( repo.ui, repo, full_path )
|
||||
try:
|
||||
commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
|
||||
except Exception, e:
|
||||
# I never have a problem with commands.commit on a Mac, but in the test/production
|
||||
# tool shed environment, it occasionally throws a "TypeError: array item must be char"
|
||||
# exception. If this happens, we'll try the following.
|
||||
repo.dirstate.write()
|
||||
repo.commit( user=trans.user.username, text=commit_message )
|
||||
# Convert from unicode to prevent "TypeError: array item must be char"
|
||||
full_path = full_path.encode( 'ascii', 'replace' )
|
||||
commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
|
||||
if full_path.endswith( 'tool_data_table_conf.xml.sample' ):
|
||||
# Handle the special case where a tool_data_table_conf.xml.sample
|
||||
# file is being uploaded by parsing the file and adding new entries
|
||||
@@ -136,15 +134,14 @@ class UploadController( BaseUIController ):
|
||||
if error:
|
||||
message = '%s<br/>%s' % ( message, error_message )
|
||||
if full_path.endswith( '.loc.sample' ):
|
||||
# Handle the special case where a xxx.loc.sample file is
|
||||
# being uploaded by copying it to ~/tool-data/xxx.loc.
|
||||
copy_sample_loc_file( trans.app, full_path )
|
||||
# Handle the special case where a xxx.loc.sample file is being uploaded by copying it to ~/tool-data/xxx.loc.
|
||||
copy_sample_file( trans.app, full_path )
|
||||
# See if the content of the change set was valid.
|
||||
admin_only = len( repository.downloadable_revisions ) != 1
|
||||
handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
|
||||
if ok:
|
||||
# Update the repository files for browsing.
|
||||
update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
|
||||
update_repository( repo )
|
||||
# Get the new repository tip.
|
||||
if tip != repository.tip:
|
||||
if ( isgzip or isbz2 ) and uncompress_file:
|
||||
@@ -201,18 +198,18 @@ class UploadController( BaseUIController ):
|
||||
repo = hg.repository( get_configured_ui(), repo_dir )
|
||||
files_to_remove = []
|
||||
content_alert_str = ''
|
||||
undesirable_dirs_removed = 0
|
||||
undesirable_files_removed = 0
|
||||
ok, message = self.__check_archive( tar )
|
||||
if not ok:
|
||||
tar.close()
|
||||
uploaded_file.close()
|
||||
return ok, message, files_to_remove, content_alert_str
|
||||
return ok, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
|
||||
else:
|
||||
if upload_point is not None:
|
||||
full_path = os.path.abspath( os.path.join( repo_dir, upload_point ) )
|
||||
else:
|
||||
full_path = os.path.abspath( repo_dir )
|
||||
undesirable_dirs_removed = 0
|
||||
undesirable_files_removed = 0
|
||||
filenames_in_archive = []
|
||||
for tarinfo_obj in tar.getmembers():
|
||||
ok = os.path.basename( tarinfo_obj.name ) not in undesirable_files
|
||||
@@ -249,14 +246,11 @@ class UploadController( BaseUIController ):
|
||||
if full_name not in filenames_in_archive:
|
||||
files_to_remove.append( full_name )
|
||||
for repo_file in files_to_remove:
|
||||
# Remove files in the repository (relative to the upload point)
|
||||
# that are not in the uploaded archive.
|
||||
# Remove files in the repository (relative to the upload point) that are not in the uploaded archive.
|
||||
try:
|
||||
commands.remove( repo.ui, repo, repo_file, force=True )
|
||||
except Exception, e:
|
||||
# I never have a problem with commands.remove on a Mac, but in the test/production
|
||||
# tool shed environment, it throws an exception whenever I delete all files from a
|
||||
# repository. If this happens, we'll try the following.
|
||||
log.debug( "Error removing files using the mercurial API, so trying a different approach, the error was: %s" % str( e ))
|
||||
relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
|
||||
repo.dirstate.remove( relative_selected_file )
|
||||
repo.dirstate.write()
|
||||
@@ -290,19 +284,11 @@ class UploadController( BaseUIController ):
|
||||
# appending them to the shed's tool_data_table_conf.xml file on disk.
|
||||
error, message = handle_sample_tool_data_table_conf_file( trans.app, filename_in_archive )
|
||||
if error:
|
||||
return False, message, files_to_remove, content_alert_str
|
||||
return False, message, files_to_remove, content_alert_str, undesirable_dirs_removed, undesirable_files_removed
|
||||
if filename_in_archive.endswith( '.loc.sample' ):
|
||||
# Handle the special case where a xxx.loc.sample file is
|
||||
# being uploaded by copying it to ~/tool-data/xxx.loc.
|
||||
copy_sample_loc_file( trans.app, filename_in_archive )
|
||||
try:
|
||||
commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
|
||||
except Exception, e:
|
||||
# I never have a problem with commands.commit on a Mac, but in the test/production
|
||||
# tool shed environment, it occasionally throws a "TypeError: array item must be char"
|
||||
# exception. If this happens, we'll try the following.
|
||||
repo.dirstate.write()
|
||||
repo.commit( user=trans.user.username, text=commit_message )
|
||||
# Handle the special case where a xxx.loc.sample file is being uploaded by copying it to ~/tool-data/xxx.loc.
|
||||
copy_sample_file( trans.app, filename_in_archive )
|
||||
commands.commit( repo.ui, repo, full_path, user=trans.user.username, message=commit_message )
|
||||
# See if the content of the change set was valid.
|
||||
admin_only = len( repository.downloadable_revisions ) != 1
|
||||
handle_email_alerts( trans, repository, content_alert_str=content_alert_str, new_repo_alert=new_repo_alert, admin_only=admin_only )
|
||||
|
||||
@@ -3,7 +3,6 @@ Middleware for handling hg authentication for users pushing change sets to local
|
||||
"""
|
||||
import os, logging
|
||||
from sqlalchemy import *
|
||||
from mercurial import ui, hg
|
||||
from paste.auth.basic import AuthBasicAuthenticator
|
||||
from paste.httpheaders import REMOTE_USER, AUTH_TYPE
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ from galaxy import util
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util.hash_util import *
|
||||
from galaxy.web.form_builder import *
|
||||
|
||||
from galaxy import eggs
|
||||
eggs.require('mercurial')
|
||||
from mercurial import hg, ui
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class User( object ):
|
||||
|
||||
@@ -13,6 +13,7 @@ from galaxy.model.orm import *
|
||||
from galaxy.model.orm.ext.assignmapper import *
|
||||
from galaxy.model.custom_types import *
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util.shed_util import ShedCounter
|
||||
from galaxy.webapps.community.security import CommunityRBACAgent
|
||||
|
||||
metadata = MetaData()
|
||||
@@ -247,4 +248,5 @@ def init( file_path, url, engine_options={}, create_tables=False ):
|
||||
result.create_tables = create_tables
|
||||
#load local galaxy security policy
|
||||
result.security_agent = CommunityRBACAgent( result )
|
||||
result.shed_counter = ShedCounter( result )
|
||||
return result
|
||||
|
||||
@@ -6,7 +6,6 @@ from datetime import datetime, timedelta
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util import listify
|
||||
from galaxy.model.orm import *
|
||||
from mercurial import hg, ui
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ class ToolModule( WorkflowModule ):
|
||||
# TODO: If workflows are ever enhanced to use tool version
|
||||
# in addition to tool id, enhance the selection process here
|
||||
# to retrieve the correct version of the tool.
|
||||
tool_version = self.__get_tool_version( trans, tool_id )
|
||||
tool_version = Class.__get_tool_version( trans, tool_id )
|
||||
if tool_version:
|
||||
tool_version_ids = tool_version.get_version_ids( trans.app )
|
||||
for tool_version_id in tool_version_ids:
|
||||
@@ -219,7 +219,8 @@ class ToolModule( WorkflowModule ):
|
||||
module.post_job_actions = pjadict
|
||||
return module
|
||||
return None
|
||||
def __get_tool_version( self, trans, tool_id ):
|
||||
@classmethod
|
||||
def __get_tool_version( cls, trans, tool_id ):
|
||||
# Return a ToolVersion if one exists for tool_id.
|
||||
return trans.sa_session.query( trans.app.model.ToolVersion ) \
|
||||
.filter( trans.app.model.ToolVersion.table.c.tool_id == tool_id ) \
|
||||
|
||||
@@ -52,4 +52,17 @@ if [ $FETCH_EGGS -eq 1 ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
python ./scripts/paster.py serve universe_wsgi.ini $@
|
||||
if [ -n "$GALAXY_RUN_ALL" ]; then
|
||||
servers=`sed -n 's/^\[server:\(.*\)\]/\1/ p' universe_wsgi.ini | xargs echo`
|
||||
daemon=`echo "$@" | grep -q daemon`
|
||||
if [ $? -ne 0 ]; then
|
||||
echo 'ERROR: $GALAXY_RUN_ALL cannot be used without the `--daemon` or `--stop-daemon` arguments to run.sh'
|
||||
exit 1
|
||||
fi
|
||||
for server in $servers; do
|
||||
echo "Handling $server with log file $server.log..."
|
||||
python ./scripts/paster.py serve universe_wsgi.ini --server-name=$server --pid-file=$server.pid --log-file=$server.log $@
|
||||
done
|
||||
else
|
||||
python ./scripts/paster.py serve universe_wsgi.ini $@
|
||||
fi
|
||||
|
||||
@@ -20,6 +20,24 @@ elif [ $1 = '-list' ]; then
|
||||
echo "==========================================================================================================================================="
|
||||
echo "'run_functional_tests.sh -id bbb' for testing one tool with id 'bbb' ('bbb' is the tool id)"
|
||||
echo "'run_functional_tests.sh -sid ccc' for testing one section with sid 'ccc' ('ccc' is the string after 'section::')"
|
||||
elif [ $1 = '-migrated' ]; then
|
||||
if [ ! $2 ]; then
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox --with-nosehtml --html-report-file run_functional_tests.html -migrated
|
||||
elif [ $2 = '-id' ]; then
|
||||
# TODO: This option is not tested...
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox:TestForTool_$3 --with-nosehtml --html-report-file run_functional_tests.html -migrated
|
||||
else
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox --with-nosehtml --html-report-file run_functional_tests.html -migrated
|
||||
fi
|
||||
elif [ $1 = '-installed' ]; then
|
||||
if [ ! $2 ]; then
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox --with-nosehtml --html-report-file run_functional_tests.html -installed
|
||||
elif [ $2 = '-id' ]; then
|
||||
# TODO: This option is not tested...
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox:TestForTool_$3 --with-nosehtml --html-report-file run_functional_tests.html -installed
|
||||
else
|
||||
python ./scripts/functional_tests.py -v functional.test_toolbox --with-nosehtml --html-report-file run_functional_tests.html -installed
|
||||
fi
|
||||
else
|
||||
python ./scripts/functional_tests.py -v --with-nosehtml --html-report-file run_functional_tests.html $1
|
||||
fi
|
||||
|
||||
@@ -3,7 +3,6 @@ until the documentation is written.
|
||||
|
||||
Set these options in universe_wsgi.ini and start the server:
|
||||
|
||||
enable_api = True
|
||||
admin_users = you@example.org
|
||||
library_import_dir = /path/to/some/directory
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ class Browser:
|
||||
#return((p.id, p.status))
|
||||
|
||||
def diff(self):
|
||||
self.get("/datasets/%s/display/index" % self.id)
|
||||
self.get("/datasets/%s/display/display?to_ext=bed" % self.id)
|
||||
data = tc.browser.get_html()
|
||||
tmp = tempfile.mkstemp()
|
||||
dprint("tmp file: %s" % tmp[1])
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import os, sys
|
||||
|
||||
msg = """ERROR: Your Python version is: %s
|
||||
Galaxy is currently supported on Python 2.4, 2.5 and 2.6. To run Galaxy,
|
||||
Galaxy is currently supported on Python 2.5, 2.6 and 2.7. To run Galaxy,
|
||||
please download and install a supported version from python.org. If a
|
||||
supported version is installed but is not your default, getgalaxy.org
|
||||
contains instructions on how to force Galaxy to use a different version.""" % sys.version[:3]
|
||||
|
||||
def check_python():
|
||||
try:
|
||||
assert sys.version_info[:2] >= ( 2, 4 ) and sys.version_info[:2] <= ( 2, 7 )
|
||||
assert sys.version_info[:2] >= ( 2, 5 ) and sys.version_info[:2] <= ( 2, 7 )
|
||||
except AssertionError:
|
||||
print >>sys.stderr, msg
|
||||
raise
|
||||
|
||||
@@ -505,7 +505,7 @@ def _purge_folder( folder, app, remove_from_disk, info_only = False ):
|
||||
class CleanupDatasetsApplication( object ):
|
||||
"""Encapsulates the state of a Universe application"""
|
||||
def __init__( self, config ):
|
||||
if config.database_connection is None:
|
||||
if config.database_connection is False:
|
||||
config.database_connection = "sqlite:///%s?isolation_level=IMMEDIATE" % config.database
|
||||
self.object_store = build_object_store_from_config( config )
|
||||
# Setup the database engine and ORM
|
||||
|
||||
+190
-105
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, shutil
|
||||
import os, sys, shutil, tempfile, re
|
||||
|
||||
# Assume we are run from the galaxy root directory, add lib to the python path
|
||||
cwd = os.getcwd()
|
||||
@@ -32,6 +32,13 @@ from galaxy.app import UniverseApplication
|
||||
from galaxy.web import buildapp
|
||||
from galaxy import tools
|
||||
from galaxy.util import bunch
|
||||
from galaxy import util
|
||||
from galaxy.util.json import to_json_string
|
||||
|
||||
import nose.core
|
||||
import nose.config
|
||||
import nose.loader
|
||||
import nose.plugins.manager
|
||||
|
||||
log = logging.getLogger( "functional_tests.py" )
|
||||
|
||||
@@ -40,28 +47,131 @@ default_galaxy_test_port_min = 8000
|
||||
default_galaxy_test_port_max = 9999
|
||||
default_galaxy_locales = 'en'
|
||||
default_galaxy_test_file_dir = "test-data"
|
||||
migrated_tool_panel_config = 'migrated_tools_conf.xml'
|
||||
installed_tool_panel_configs = [ 'shed_tool_conf.xml' ]
|
||||
|
||||
def main():
|
||||
|
||||
def parse_tool_panel_config( config, shed_tools_dict ):
|
||||
"""
|
||||
Parse a shed-related tool panel config to generate the shed_tools_dict. This only happens when testing tools installed from the tool shed.
|
||||
"""
|
||||
last_galaxy_test_file_dir = None
|
||||
last_tested_repository_name = None
|
||||
last_tested_changeset_revision = None
|
||||
tree = util.parse_xml( config )
|
||||
root = tree.getroot()
|
||||
for elem in root:
|
||||
if elem.tag == 'tool':
|
||||
galaxy_test_file_dir, \
|
||||
last_tested_repository_name, \
|
||||
last_tested_changeset_revision = get_installed_repository_info( elem,
|
||||
last_galaxy_test_file_dir,
|
||||
last_tested_repository_name,
|
||||
last_tested_changeset_revision )
|
||||
if galaxy_test_file_dir:
|
||||
if galaxy_test_file_dir != last_galaxy_test_file_dir:
|
||||
if not os.path.isabs( galaxy_test_file_dir ):
|
||||
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
||||
guid = elem.get( 'guid' )
|
||||
shed_tools_dict[ guid ] = galaxy_test_file_dir
|
||||
last_galaxy_test_file_dir = galaxy_test_file_dir
|
||||
elif elem.tag == 'section':
|
||||
for section_elem in elem:
|
||||
if section_elem.tag == 'tool':
|
||||
galaxy_test_file_dir, \
|
||||
last_tested_repository_name, \
|
||||
last_tested_changeset_revision = get_installed_repository_info( section_elem,
|
||||
last_galaxy_test_file_dir,
|
||||
last_tested_repository_name,
|
||||
last_tested_changeset_revision )
|
||||
if galaxy_test_file_dir:
|
||||
if galaxy_test_file_dir != last_galaxy_test_file_dir:
|
||||
if not os.path.isabs( galaxy_test_file_dir ):
|
||||
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
||||
guid = section_elem.get( 'guid' )
|
||||
shed_tools_dict[ guid ] = galaxy_test_file_dir
|
||||
last_galaxy_test_file_dir = galaxy_test_file_dir
|
||||
return shed_tools_dict
|
||||
|
||||
def get_installed_repository_info( elem, last_galaxy_test_file_dir, last_tested_repository_name, last_tested_changeset_revision ):
|
||||
"""
|
||||
Return the GALAXY_TEST_FILE_DIR, the containing repository name and the change set revision for the tool elem.
|
||||
This only happens when testing tools installed from the tool shed.
|
||||
"""
|
||||
tool_config_path = elem.get( 'file' )
|
||||
installed_tool_path_items = tool_config_path.split( '/repos/' )
|
||||
sans_shed = installed_tool_path_items[ 1 ]
|
||||
path_items = sans_shed.split( '/' )
|
||||
repository_owner = path_items[ 0 ]
|
||||
repository_name = path_items[ 1 ]
|
||||
changeset_revision = path_items[ 2 ]
|
||||
if repository_name != last_tested_repository_name or changeset_revision != last_tested_changeset_revision:
|
||||
# Locate the test-data directory.
|
||||
installed_tool_path = os.path.join( installed_tool_path_items[ 0 ], 'repos', repository_owner, repository_name, changeset_revision )
|
||||
for root, dirs, files in os.walk( installed_tool_path ):
|
||||
if 'test-data' in dirs:
|
||||
return os.path.join( root, 'test-data' ), repository_name, changeset_revision
|
||||
return None, repository_name, changeset_revision
|
||||
return last_galaxy_test_file_dir, last_tested_repository_name, last_tested_changeset_revision
|
||||
|
||||
def run_tests( test_config ):
|
||||
loader = nose.loader.TestLoader( config=test_config )
|
||||
plug_loader = test_config.plugins.prepareTestLoader( loader )
|
||||
if plug_loader is not None:
|
||||
loader = plug_loader
|
||||
tests = loader.loadTestsFromNames( test_config.testNames )
|
||||
test_runner = nose.core.TextTestRunner( stream=test_config.stream,
|
||||
verbosity=test_config.verbosity,
|
||||
config=test_config )
|
||||
plug_runner = test_config.plugins.prepareTestRunner( test_runner )
|
||||
if plug_runner is not None:
|
||||
test_runner = plug_runner
|
||||
return test_runner.run( tests )
|
||||
|
||||
def main():
|
||||
# ---- Configuration ------------------------------------------------------
|
||||
|
||||
galaxy_test_host = os.environ.get( 'GALAXY_TEST_HOST', default_galaxy_test_host )
|
||||
galaxy_test_port = os.environ.get( 'GALAXY_TEST_PORT', None )
|
||||
galaxy_test_save = os.environ.get( 'GALAXY_TEST_SAVE', None)
|
||||
|
||||
if 'HTTP_ACCEPT_LANGUAGE' not in os.environ:
|
||||
os.environ['HTTP_ACCEPT_LANGUAGE'] = default_galaxy_locales
|
||||
galaxy_test_file_dir = os.environ.get( 'GALAXY_TEST_FILE_DIR', default_galaxy_test_file_dir )
|
||||
if not os.path.isabs( galaxy_test_file_dir ):
|
||||
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
||||
start_server = 'GALAXY_TEST_EXTERNAL' not in os.environ
|
||||
tool_path = os.environ.get( 'GALAXY_TEST_TOOL_PATH', 'tools' )
|
||||
tool_config_file = os.environ.get( 'GALAXY_TEST_TOOL_CONF', 'tool_conf.xml.sample' )
|
||||
tool_data_table_config_path = 'tool_data_table_conf.xml'
|
||||
tool_dependency_dir = os.environ.get( 'GALAXY_TOOL_DEPENDENCY_DIR', None )
|
||||
use_distributed_object_store = os.environ.get( 'GALAXY_USE_DISTRIBUTED_OBJECT_STORE', False )
|
||||
if 'HTTP_ACCEPT_LANGUAGE' not in os.environ:
|
||||
os.environ[ 'HTTP_ACCEPT_LANGUAGE' ] = default_galaxy_locales
|
||||
testing_migrated_tools = '-migrated' in sys.argv
|
||||
testing_installed_tools = '-installed' in sys.argv
|
||||
|
||||
if testing_migrated_tools or testing_installed_tools:
|
||||
sys.argv.pop()
|
||||
# Store a jsonified dictionary of tool_id : GALAXY_TEST_FILE_DIR pairs.
|
||||
galaxy_tool_shed_test_file = 'shed_tools_dict'
|
||||
# We need the upload tool for functional tests, so we'll create a temporary tool panel config that defines it.
|
||||
fd, tmp_tool_panel_conf = tempfile.mkstemp()
|
||||
os.write( fd, '<?xml version="1.0"?>\n' )
|
||||
os.write( fd, '<toolbox>\n' )
|
||||
os.write( fd, '<tool file="data_source/upload.xml"/>\n' )
|
||||
os.write( fd, '</toolbox>\n' )
|
||||
os.close( fd )
|
||||
tool_config_file = tmp_tool_panel_conf
|
||||
galaxy_test_file_dir = None
|
||||
library_import_dir = None
|
||||
user_library_import_dir = None
|
||||
# Exclude all files except test_toolbox.py.
|
||||
ignore_files = ( re.compile( r'^test_[adghlmsu]*' ), re.compile( r'^test_ta*' ) )
|
||||
else:
|
||||
tool_config_file = os.environ.get( 'GALAXY_TEST_TOOL_CONF', 'tool_conf.xml.sample' )
|
||||
galaxy_test_file_dir = os.environ.get( 'GALAXY_TEST_FILE_DIR', default_galaxy_test_file_dir )
|
||||
if not os.path.isabs( galaxy_test_file_dir ):
|
||||
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
||||
library_import_dir = galaxy_test_file_dir
|
||||
user_library_import_dir = os.path.join( galaxy_test_file_dir, 'users' )
|
||||
ignore_files = ()
|
||||
|
||||
start_server = 'GALAXY_TEST_EXTERNAL' not in os.environ
|
||||
if os.path.exists( 'tool_data_table_conf.test.xml' ):
|
||||
tool_data_table_config_path = 'tool_data_table_conf.test.xml'
|
||||
else:
|
||||
tool_data_table_config_path = 'tool_data_table_conf.xml'
|
||||
tool_dependency_dir = os.environ.get( 'GALAXY_TOOL_DEPENDENCY_DIR', None )
|
||||
use_distributed_object_store = os.environ.get( 'GALAXY_USE_DISTRIBUTED_OBJECT_STORE', False )
|
||||
|
||||
if start_server:
|
||||
psu_production = False
|
||||
galaxy_test_proxy_port = None
|
||||
@@ -126,38 +236,21 @@ def main():
|
||||
try:
|
||||
os.makedirs( dir )
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
pass
|
||||
print "Database connection:", database_connection
|
||||
|
||||
# What requires these?
|
||||
# handy for (eg) functional tests to save outputs?
|
||||
if galaxy_test_save:
|
||||
os.environ['GALAXY_TEST_SAVE'] = galaxy_test_save
|
||||
# pass in through script setenv
|
||||
# will leave a copy of ALL test validate files
|
||||
os.environ['GALAXY_TEST_HOST'] = galaxy_test_host
|
||||
os.environ['GALAXY_TEST_FILE_DIR'] = galaxy_test_file_dir
|
||||
# ---- Build Application --------------------------------------------------
|
||||
|
||||
app = None
|
||||
|
||||
# ---- Build Application --------------------------------------------------
|
||||
app = None
|
||||
if start_server:
|
||||
|
||||
global_conf = { '__file__' : 'universe_wsgi.ini.sample' }
|
||||
if psu_production:
|
||||
global_conf = None
|
||||
|
||||
if not database_connection.startswith( 'sqlite://' ):
|
||||
kwargs['database_engine_option_max_overflow'] = '20'
|
||||
|
||||
kwargs[ 'database_engine_option_max_overflow' ] = '20'
|
||||
if tool_dependency_dir is not None:
|
||||
kwargs['tool_dependency_dir'] = tool_dependency_dir
|
||||
|
||||
kwargs[ 'tool_dependency_dir' ] = tool_dependency_dir
|
||||
if use_distributed_object_store:
|
||||
kwargs['object_store'] = 'distributed'
|
||||
kwargs['distributed_object_store_config_file'] = 'distributed_object_store_conf.xml.sample'
|
||||
|
||||
kwargs[ 'object_store' ] = 'distributed'
|
||||
kwargs[ 'distributed_object_store_config_file' ] = 'distributed_object_store_conf.xml.sample'
|
||||
# Build the Universe Application
|
||||
app = UniverseApplication( job_queue_workers = 5,
|
||||
id_secret = 'changethisinproductiontoo',
|
||||
@@ -179,21 +272,17 @@ def main():
|
||||
allow_user_deletion = True,
|
||||
admin_users = 'test@bx.psu.edu',
|
||||
allow_library_path_paste = True,
|
||||
library_import_dir = galaxy_test_file_dir,
|
||||
user_library_import_dir = os.path.join( galaxy_test_file_dir, 'users' ),
|
||||
library_import_dir = library_import_dir,
|
||||
user_library_import_dir = user_library_import_dir,
|
||||
global_conf = global_conf,
|
||||
running_functional_tests=True,
|
||||
**kwargs )
|
||||
|
||||
log.info( "Embedded Universe application started" );
|
||||
|
||||
log.info( "Embedded Universe application started" )
|
||||
# ---- Run webserver ------------------------------------------------------
|
||||
|
||||
server = None
|
||||
|
||||
if start_server:
|
||||
|
||||
webapp = buildapp.app_factory( dict(), use_translogger = False, static_enabled = False, app=app )
|
||||
|
||||
webapp = buildapp.app_factory( dict(), use_translogger=False, static_enabled=False, app=app )
|
||||
if galaxy_test_port is not None:
|
||||
server = httpserver.serve( webapp, host=galaxy_test_host, port=galaxy_test_port, start_loop=False )
|
||||
else:
|
||||
@@ -217,7 +306,6 @@ def main():
|
||||
|
||||
t = threading.Thread( target=server.serve_forever )
|
||||
t.start()
|
||||
|
||||
# Test if the server is up
|
||||
for i in range( 10 ):
|
||||
conn = httplib.HTTPConnection( galaxy_test_host, galaxy_test_port ) # directly test the app, not the proxy
|
||||
@@ -227,85 +315,83 @@ def main():
|
||||
time.sleep( 0.1 )
|
||||
else:
|
||||
raise Exception( "Test HTTP server did not return '200 OK' after 10 tries" )
|
||||
|
||||
# Test if the proxy server is up
|
||||
if psu_production:
|
||||
conn = httplib.HTTPConnection( galaxy_test_host, galaxy_test_proxy_port ) # directly test the app, not the proxy
|
||||
conn.request( "GET", "/" )
|
||||
if not conn.getresponse().status == 200:
|
||||
raise Exception( "Test HTTP proxy server did not return '200 OK'" )
|
||||
|
||||
log.info( "Embedded web server started" )
|
||||
|
||||
|
||||
# ---- Load toolbox for generated tests -----------------------------------
|
||||
|
||||
# We don't add the tests to the path until everything is up and running
|
||||
new_path = [ os.path.join( cwd, "test" ) ]
|
||||
new_path.extend( sys.path[1:] )
|
||||
sys.path = new_path
|
||||
|
||||
import functional.test_toolbox
|
||||
|
||||
if app:
|
||||
# TODO: provisions for loading toolbox from file when using external server
|
||||
functional.test_toolbox.toolbox = app.toolbox
|
||||
functional.test_toolbox.build_tests()
|
||||
else:
|
||||
# FIXME: This doesn't work at all now that toolbox requires an 'app' instance
|
||||
# (to get at datatypes, might just pass a datatype registry directly)
|
||||
datatypes_registry = galaxy.datatypes.registry.Registry()
|
||||
datatypes_registry.load_datatypes()
|
||||
my_app = bunch.Bunch( datatypes_registry )
|
||||
test_toolbox.toolbox = tools.ToolBox( 'tool_conf.xml.test', 'tools', my_app )
|
||||
|
||||
# ---- Find tests ---------------------------------------------------------
|
||||
|
||||
if galaxy_test_proxy_port:
|
||||
log.info( "Functional tests will be run against %s:%s" % ( galaxy_test_host, galaxy_test_proxy_port ) )
|
||||
else:
|
||||
log.info( "Functional tests will be run against %s:%s" % ( galaxy_test_host, galaxy_test_port ) )
|
||||
|
||||
success = False
|
||||
|
||||
try:
|
||||
|
||||
import nose.core
|
||||
import nose.config
|
||||
import nose.loader
|
||||
import nose.plugins.manager
|
||||
|
||||
test_config = nose.config.Config( env = os.environ, plugins=nose.plugins.manager.DefaultPluginManager() )
|
||||
test_config.configure( sys.argv )
|
||||
|
||||
loader = nose.loader.TestLoader( config = test_config )
|
||||
|
||||
plug_loader = test_config.plugins.prepareTestLoader( loader )
|
||||
if plug_loader is not None:
|
||||
loader = plug_loader
|
||||
|
||||
tests = loader.loadTestsFromNames( test_config.testNames )
|
||||
|
||||
test_runner = nose.core.TextTestRunner(
|
||||
stream = test_config.stream,
|
||||
verbosity = test_config.verbosity,
|
||||
config = test_config)
|
||||
|
||||
plug_runner = test_config.plugins.prepareTestRunner( test_runner )
|
||||
if plug_runner is not None:
|
||||
test_runner = plug_runner
|
||||
|
||||
result = test_runner.run( tests )
|
||||
|
||||
success = result.wasSuccessful()
|
||||
|
||||
# What requires these? Handy for (eg) functional tests to save outputs?
|
||||
if galaxy_test_save:
|
||||
os.environ[ 'GALAXY_TEST_SAVE' ] = galaxy_test_save
|
||||
# Pass in through script setenv, will leave a copy of ALL test validate files
|
||||
os.environ[ 'GALAXY_TEST_HOST' ] = galaxy_test_host
|
||||
if testing_migrated_tools or testing_installed_tools:
|
||||
shed_tools_dict = {}
|
||||
if testing_migrated_tools:
|
||||
shed_tools_dict = parse_tool_panel_config( migrated_tool_panel_config, shed_tools_dict )
|
||||
elif testing_installed_tools:
|
||||
for shed_tool_config in installed_tool_panel_configs:
|
||||
shed_tools_dict = parse_tool_panel_config( shed_tool_config, shed_tools_dict )
|
||||
# Persist the shed_tools_dict to the galaxy_tool_shed_test_file.
|
||||
shed_tools_file = open( galaxy_tool_shed_test_file, 'w' )
|
||||
shed_tools_file.write( to_json_string( shed_tools_dict ) )
|
||||
shed_tools_file.close()
|
||||
if not os.path.isabs( galaxy_tool_shed_test_file ):
|
||||
galaxy_tool_shed_test_file = os.path.join( os.getcwd(), galaxy_tool_shed_test_file )
|
||||
os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_tool_shed_test_file
|
||||
if testing_installed_tools:
|
||||
# Eliminate the migrated_tool_panel_config from the app's tool_configs, append the list of installed_tool_panel_configs,
|
||||
# and reload the app's toolbox.
|
||||
relative_migrated_tool_panel_config = os.path.join( app.config.root, migrated_tool_panel_config )
|
||||
tool_configs = app.config.tool_configs
|
||||
if relative_migrated_tool_panel_config in tool_configs:
|
||||
tool_configs.remove( relative_migrated_tool_panel_config )
|
||||
for installed_tool_panel_config in installed_tool_panel_configs:
|
||||
tool_configs.append( installed_tool_panel_config )
|
||||
app.toolbox = tools.ToolBox( tool_configs, app.config.tool_path, app )
|
||||
functional.test_toolbox.toolbox = app.toolbox
|
||||
functional.test_toolbox.build_tests( testing_shed_tools=True )
|
||||
test_config = nose.config.Config( env=os.environ, ignoreFiles=ignore_files, plugins=nose.plugins.manager.DefaultPluginManager() )
|
||||
test_config.configure( sys.argv )
|
||||
result = run_tests( test_config )
|
||||
success = result.wasSuccessful()
|
||||
try:
|
||||
os.unlink( tmp_tool_panel_conf )
|
||||
except:
|
||||
log.info( "Unable to remove temporary file: %s" % tmp_tool_panel_conf )
|
||||
try:
|
||||
os.unlink( galaxy_tool_shed_test_file )
|
||||
except:
|
||||
log.info( "Unable to remove file: %s" % galaxy_tool_shed_test_file )
|
||||
else:
|
||||
functional.test_toolbox.toolbox = app.toolbox
|
||||
functional.test_toolbox.build_tests()
|
||||
if galaxy_test_file_dir:
|
||||
os.environ[ 'GALAXY_TEST_FILE_DIR' ] = galaxy_test_file_dir
|
||||
test_config = nose.config.Config( env=os.environ, ignoreFiles=ignore_files, plugins=nose.plugins.manager.DefaultPluginManager() )
|
||||
test_config.configure( sys.argv )
|
||||
result = run_tests( test_config )
|
||||
success = result.wasSuccessful()
|
||||
except:
|
||||
log.exception( "Failure running tests" )
|
||||
|
||||
log.info( "Shutting down" )
|
||||
|
||||
# ---- Teardown -----------------------------------------------------------
|
||||
|
||||
# ---- Tear down -----------------------------------------------------------
|
||||
if server:
|
||||
log.info( "Shutting down embedded web server" )
|
||||
server.server_close()
|
||||
@@ -330,7 +416,6 @@ def main():
|
||||
shutil.rmtree( dir )
|
||||
except:
|
||||
pass
|
||||
|
||||
if success:
|
||||
return 0
|
||||
else:
|
||||
|
||||
@@ -177,7 +177,7 @@ class DataTransfer( object ):
|
||||
self.update_status( SampleDataset.transfer_status.ADD_TO_LIBRARY )
|
||||
try:
|
||||
data = {}
|
||||
data[ 'folder_id' ] = api.encode_id( self.config_id_secret, '%s.%s' % ( 'folder', self.folder_id ) )
|
||||
data[ 'folder_id' ] = 'F%s' % api.encode_id( self.config_id_secret, self.folder_id )
|
||||
data[ 'file_type' ] = 'auto'
|
||||
data[ 'server_dir' ] = self.server_dir
|
||||
data[ 'dbkey' ] = ''
|
||||
|
||||
@@ -314,6 +314,11 @@ div.unified-panel-body {
|
||||
-webkit-border-radius: 1em;
|
||||
}
|
||||
|
||||
#dialog-box.dialog-box{
|
||||
min-width: 660px;
|
||||
margin: -250px 0 0 -330px;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
border: solid #999 1px;
|
||||
background: white;
|
||||
@@ -407,14 +412,26 @@ div.unified-panel-body {
|
||||
}
|
||||
|
||||
.title {
|
||||
.navbar.brand();
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
font-family: verdana;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
color: white;
|
||||
// Override margin and padding due to shorter navbar height
|
||||
padding: 5px 20px 12px;
|
||||
margin-left: -15px;
|
||||
z-index: 2000;
|
||||
|
||||
img {
|
||||
display: inline;
|
||||
width: 26px;
|
||||
vertical-align: top;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
@@ -1210,6 +1227,10 @@ a.action-button {
|
||||
.menubutton {
|
||||
.btn();
|
||||
|
||||
&:hover { .btn.hover(); }
|
||||
&:active { .btn.active(); }
|
||||
&:focus { .tab-focus(); }
|
||||
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
@@ -1223,11 +1244,21 @@ a.action-button {
|
||||
// padding: 1px 0.25em;
|
||||
// margin: -1px -0.25em;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
// display: block;
|
||||
display: inline-block;
|
||||
border-right: none;
|
||||
text-decoration: none;
|
||||
text-align: left;
|
||||
// The following properties truncate the text and force the button to have one line
|
||||
max-height: 2*@baseLineHeight;
|
||||
line-height: @baseLineHeight;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.popup .label {
|
||||
@@ -1235,21 +1266,16 @@ a.action-button {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
&.popup {
|
||||
&.popup, &.popup.split {
|
||||
padding-right: 18px;
|
||||
&:after {
|
||||
margin-top: 6px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 6px;
|
||||
.caret();
|
||||
}
|
||||
}
|
||||
|
||||
&.popup.split {
|
||||
&:after {
|
||||
margin-top: 6px;
|
||||
margin-left: 0px;
|
||||
.caret();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// A split menu button, the main button has an action, the arrow causes the
|
||||
@@ -1401,8 +1427,6 @@ div.permissionContainer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@import "base_sprites";
|
||||
|
||||
.tipsy {
|
||||
padding: 5px;
|
||||
font-size: 10px;
|
||||
@@ -1521,19 +1545,19 @@ div.historyItem-failed_metadata {
|
||||
div.historyItem-error {
|
||||
// border-color: @history_error_border;
|
||||
background: @history_error_bg;
|
||||
.state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_error.png;
|
||||
}
|
||||
// .state-icon {
|
||||
// -sprite-group: history-states;
|
||||
// -sprite-image: data_error.png;
|
||||
// }
|
||||
}
|
||||
|
||||
div.historyItem-empty {
|
||||
// border-color: @history_error_border;
|
||||
background: @history_error_bg;
|
||||
.state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_empty.png;
|
||||
}
|
||||
// .state-icon {
|
||||
// -sprite-group: history-states;
|
||||
// -sprite-image: data_empty.png;
|
||||
// }
|
||||
}
|
||||
|
||||
div.historyItem-running {
|
||||
@@ -1561,11 +1585,11 @@ div.historyItem-upload {
|
||||
}
|
||||
|
||||
div.historyItem-queued {
|
||||
// background: @history_queued_bg;
|
||||
.state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_queued.png;
|
||||
}
|
||||
background: @history_queued_bg;
|
||||
// .state-icon {
|
||||
// -sprite-group: history-states;
|
||||
// -sprite-image: data_queued.png;
|
||||
// }
|
||||
}
|
||||
|
||||
div.historyItem-noPermission {
|
||||
@@ -1606,21 +1630,15 @@ pre.peek {
|
||||
|
||||
// ==== Tool menu styles
|
||||
|
||||
body.toolMenuPage
|
||||
.toolMenuContainer
|
||||
{
|
||||
background: white;
|
||||
color: @base_text;
|
||||
background: @menu_bg_over;
|
||||
margin: 5px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
border: none;
|
||||
height: 0px;
|
||||
margin-top: 0px;
|
||||
.unified-panel-body .toolMenu {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.toolSectionPad
|
||||
@@ -1632,7 +1650,7 @@ div.toolSectionPad
|
||||
}
|
||||
|
||||
div.toolSectionWrapper {
|
||||
// margin-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.toolSectionDetailsInner
|
||||
@@ -1648,8 +1666,8 @@ div.toolSectionTitle
|
||||
|
||||
div.toolPanelLabel
|
||||
{
|
||||
//padding-top: 10px;
|
||||
//padding-bottom: 5px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: gray;
|
||||
text-transform: uppercase;
|
||||
@@ -1677,7 +1695,44 @@ div.toolSectionBody div.toolPanelLabel
|
||||
|
||||
div.toolTitleNoSection
|
||||
{
|
||||
padding-bottom: 0px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tool-search {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Dataset Display Styles
|
||||
|
||||
#loading_indicator{
|
||||
position:fixed;
|
||||
right:10px;
|
||||
top:10px;
|
||||
height:32px;
|
||||
width:32px;
|
||||
background:url(largespinner.gif);
|
||||
}
|
||||
|
||||
#content_table td{
|
||||
text-align:right;
|
||||
white-space:nowrap;
|
||||
padding:2px 10px;
|
||||
}
|
||||
|
||||
#content_table td.stringalign{
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
// ==== Integrated tool form styles
|
||||
|
||||
.toolMenuAndView .toolForm
|
||||
{
|
||||
float: left;
|
||||
background-color: white;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
@import "base_sprites";
|
||||
|
||||
@@ -87,6 +87,10 @@
|
||||
-sprite-image: fugue/plus-circle.png;
|
||||
}
|
||||
.icon-button.plus-button {
|
||||
-sprite-group: fugue;
|
||||
-sprite-image: fugue/plus-button-bw.png;
|
||||
}
|
||||
.icon-button.plus-button:hover {
|
||||
-sprite-group: fugue;
|
||||
-sprite-image: fugue/plus-button.png;
|
||||
}
|
||||
@@ -94,9 +98,29 @@
|
||||
-sprite-group: fugue;
|
||||
-sprite-image: fugue/gear.png;
|
||||
}
|
||||
.icon-button.chart_curve {
|
||||
-sprite-group: fugue;
|
||||
-sprite-image: silk/chart_curve.png;
|
||||
}
|
||||
|
||||
.text-and-autocomplete-select {
|
||||
-sprite-group: fugue;
|
||||
-sprite-image: fugue/control-270.png;
|
||||
-sprite-horiz-position: right;
|
||||
}
|
||||
|
||||
|
||||
div.historyItem-error .state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_error.png;
|
||||
}
|
||||
|
||||
div.historyItem-empty .state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_empty.png;
|
||||
}
|
||||
|
||||
div.historyItem-queued .state-icon {
|
||||
-sprite-group: history-states;
|
||||
-sprite-image: data_queued.png;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user