1 | #Contains objects for using external display applications |
---|
2 | from galaxy.util import parse_xml, string_as_bool |
---|
3 | from galaxy.util.odict import odict |
---|
4 | from galaxy.util.template import fill_template |
---|
5 | from galaxy.web import url_for |
---|
6 | from parameters import DisplayApplicationParameter, DEFAULT_DATASET_NAME |
---|
7 | from urllib import quote_plus |
---|
8 | from util import encode_dataset_user |
---|
9 | from copy import deepcopy |
---|
10 | |
---|
11 | #Any basic functions that we want to provide as a basic part of parameter dict should be added to this dict |
---|
12 | BASE_PARAMS = { 'qp': quote_plus, 'url_for':url_for } #url_for has route memory... |
---|
13 | |
---|
14 | class DisplayApplicationLink( object ): |
---|
15 | @classmethod |
---|
16 | def from_elem( cls, elem, display_application, other_values = None ): |
---|
17 | rval = DisplayApplicationLink( display_application ) |
---|
18 | rval.id = elem.get( 'id', None ) |
---|
19 | assert rval.id, 'Link elements require a id.' |
---|
20 | rval.name = elem.get( 'name', rval.id ) |
---|
21 | rval.url = elem.find( 'url' ) |
---|
22 | assert rval.url is not None, 'A url element must be provided for link elements.' |
---|
23 | rval.other_values = other_values |
---|
24 | rval.filters = elem.findall( 'filter' ) |
---|
25 | for param_elem in elem.findall( 'param' ): |
---|
26 | param = DisplayApplicationParameter.from_elem( param_elem, rval ) |
---|
27 | assert param, 'Unable to load parameter from element: %s' % param_elem |
---|
28 | rval.parameters[ param.name ] = param |
---|
29 | rval.url_param_name_map[ param.url ] = param.name |
---|
30 | return rval |
---|
31 | def __init__( self, display_application ): |
---|
32 | self.display_application = display_application |
---|
33 | self.parameters = odict() #parameters are populated in order, allowing lower listed ones to have values of higher listed ones |
---|
34 | self.url_param_name_map = {} |
---|
35 | self.url = None |
---|
36 | self.id = None |
---|
37 | self.name = None |
---|
38 | def get_display_url( self, data, trans ): |
---|
39 | dataset_hash, user_hash = encode_dataset_user( trans, data, None ) |
---|
40 | return url_for( controller = '/dataset', action = "display_application", dataset_id = dataset_hash, user_id = user_hash, app_name = self.display_application.id, link_name = self.id, app_action = None ) |
---|
41 | def get_inital_values( self, data, trans ): |
---|
42 | if self.other_values: |
---|
43 | rval = odict( self.other_values ) |
---|
44 | else: |
---|
45 | rval = odict() |
---|
46 | rval.update( { 'BASE_URL': trans.request.base, 'APP': trans.app } ) #trans automatically appears as a response, need to add properties of trans that we want here |
---|
47 | for key, value in BASE_PARAMS.iteritems(): #add helper functions/variables |
---|
48 | rval[ key ] = value |
---|
49 | rval[ DEFAULT_DATASET_NAME ] = data #always have the display dataset name available |
---|
50 | return rval |
---|
51 | def build_parameter_dict( self, data, dataset_hash, user_hash, trans ): |
---|
52 | other_values = self.get_inital_values( data, trans ) |
---|
53 | other_values[ 'DATASET_HASH' ] = dataset_hash |
---|
54 | other_values[ 'USER_HASH' ] = user_hash |
---|
55 | for name, param in self.parameters.iteritems(): |
---|
56 | assert name not in other_values, "The display parameter '%s' has been defined more than once." % name |
---|
57 | if param.ready( other_values ): |
---|
58 | other_values[ name ] = param.get_value( other_values, dataset_hash, user_hash, trans )#subsequent params can rely on this value |
---|
59 | else: |
---|
60 | other_values[ name ] = None |
---|
61 | return False, other_values #need to stop here, next params may need this value |
---|
62 | return True, other_values #we built other_values, lets provide it as well, or else we will likely regenerate it in the next step |
---|
63 | def filter_by_dataset( self, data, trans ): |
---|
64 | context = self.get_inital_values( data, trans ) |
---|
65 | for filter_elem in self.filters: |
---|
66 | if fill_template( filter_elem.text, context = context ) != filter_elem.get( 'value', 'True' ): |
---|
67 | return False |
---|
68 | return True |
---|
69 | |
---|
70 | class DynamicDisplayApplicationBuilder( object ): |
---|
71 | @classmethod |
---|
72 | def __init__( self, elem, display_application ): |
---|
73 | rval = [] |
---|
74 | filename = elem.get( 'from_file', None ) |
---|
75 | assert filename is not None, 'Filename and id attributes required for dynamic_links' |
---|
76 | skip_startswith = elem.get( 'skip_startswith', None ) |
---|
77 | separator = elem.get( 'separator', '\t' ) |
---|
78 | id_col = int( elem.get( 'id', None ) ) |
---|
79 | name_col = int( elem.get( 'name', id_col ) ) |
---|
80 | dynamic_params = {} |
---|
81 | max_col = max( id_col, name_col ) |
---|
82 | for dynamic_param in elem.findall( 'dynamic_param' ): |
---|
83 | name = dynamic_param.get( 'name' ) |
---|
84 | value = int( dynamic_param.get( 'value' ) ) |
---|
85 | split = string_as_bool( dynamic_param.get( 'split', False ) ) |
---|
86 | param_separator = dynamic_param.get( 'separator', ',' ) |
---|
87 | max_col = max( max_col, value ) |
---|
88 | dynamic_params[name] = { 'column': value, 'split': split, 'separator': param_separator } |
---|
89 | for line in open( filename ): |
---|
90 | if not skip_startswith or not line.startswith( skip_startswith ): |
---|
91 | line = line.rstrip( '\n\r' ) |
---|
92 | fields = line.split( separator ) |
---|
93 | if len( fields ) >= max_col: |
---|
94 | new_elem = deepcopy( elem ) |
---|
95 | new_elem.set( 'id', fields[id_col] ) |
---|
96 | new_elem.set( 'name', fields[name_col] ) |
---|
97 | dynamic_values = {} |
---|
98 | for key, attributes in dynamic_params.iteritems(): |
---|
99 | value = fields[ attributes[ 'column' ] ] |
---|
100 | if attributes['split']: |
---|
101 | value = value.split( attributes['separator'] ) |
---|
102 | dynamic_values[key] = value |
---|
103 | #now populate |
---|
104 | rval.append( DisplayApplicationLink.from_elem( new_elem, display_application, other_values = dynamic_values ) ) |
---|
105 | self.links = rval |
---|
106 | def __iter__( self ): |
---|
107 | return iter( self.links ) |
---|
108 | |
---|
109 | class PopulatedDisplayApplicationLink( object ): |
---|
110 | def __init__( self, display_application_link, data, dataset_hash, user_hash, trans ): |
---|
111 | self.link = display_application_link |
---|
112 | self.data = data |
---|
113 | self.dataset_hash = dataset_hash |
---|
114 | self.user_hash = user_hash |
---|
115 | self.trans = trans |
---|
116 | self.ready, self.parameters = self.link.build_parameter_dict( self.data, self.dataset_hash, self.user_hash, trans ) |
---|
117 | def display_ready( self ): |
---|
118 | return self.ready |
---|
119 | def get_param_value( self, name ): |
---|
120 | value = None |
---|
121 | if self.ready: |
---|
122 | value = self.parameters.get( name, None ) |
---|
123 | assert value, 'Unknown parameter requested' |
---|
124 | return value |
---|
125 | def preparing_display( self ): |
---|
126 | if not self.ready: |
---|
127 | return self.link.parameters[ self.parameters.keys()[ -1 ] ].is_preparing( self.parameters ) |
---|
128 | return False |
---|
129 | def prepare_display( self ): |
---|
130 | if not self.ready and not self.preparing_display(): |
---|
131 | other_values = self.parameters |
---|
132 | for name, param in self.link.parameters.iteritems(): |
---|
133 | if other_values.keys()[ -1 ] == name: #found last parameter to be populated |
---|
134 | value = param.prepare( other_values, self.dataset_hash, self.user_hash, self.trans ) |
---|
135 | if value is None: |
---|
136 | return #we can go no further until we have a value for this parameter |
---|
137 | other_values[ name ] = value |
---|
138 | def display_url( self ): |
---|
139 | assert self.display_ready(), 'Display is not yet ready, cannot generate display link' |
---|
140 | return fill_template( self.link.url.text, context = self.parameters ) |
---|
141 | def get_param_name_by_url( self, url ): |
---|
142 | for name, parameter in self.link.parameters.iteritems(): |
---|
143 | if parameter.build_url( self.parameters ) == url: |
---|
144 | return name |
---|
145 | raise ValueError( "Unknown URL parameter name provided: %s" % url ) |
---|
146 | |
---|
147 | class DisplayApplication( object ): |
---|
148 | @classmethod |
---|
149 | def from_file( cls, filename, datatypes_registry ): |
---|
150 | return cls.from_elem( parse_xml( filename ).getroot(), datatypes_registry ) |
---|
151 | @classmethod |
---|
152 | def from_elem( cls, elem, datatypes_registry ): |
---|
153 | display_id = elem.get( 'id', None ) |
---|
154 | assert display_id, "ID tag is required for a Display Application" |
---|
155 | name = elem.get( 'name', display_id ) |
---|
156 | version = elem.get( 'version', None ) |
---|
157 | rval = DisplayApplication( display_id, name, datatypes_registry, version ) |
---|
158 | for link_elem in elem.findall( 'link' ): |
---|
159 | link = DisplayApplicationLink.from_elem( link_elem, rval ) |
---|
160 | if link: |
---|
161 | rval.links[ link.id ] = link |
---|
162 | for dynamic_links in elem.findall( 'dynamic_links' ): |
---|
163 | for link in DynamicDisplayApplicationBuilder( dynamic_links, rval ): |
---|
164 | rval.links[ link.id ] = link |
---|
165 | return rval |
---|
166 | def __init__( self, display_id, name, datatypes_registry, version = None ): |
---|
167 | self.id = display_id |
---|
168 | self.name = name |
---|
169 | self.datatypes_registry = datatypes_registry |
---|
170 | if version is None: |
---|
171 | version = "1.0.0" |
---|
172 | self.version = version |
---|
173 | self.links = odict() |
---|
174 | def get_link( self, link_name, data, dataset_hash, user_hash, trans ): |
---|
175 | #returns a link object with data knowledge to generate links |
---|
176 | return PopulatedDisplayApplicationLink( self.links[ link_name ], data, dataset_hash, user_hash, trans ) |
---|
177 | def filter_by_dataset( self, data, trans ): |
---|
178 | filtered = DisplayApplication( self.id, self.name, self.datatypes_registry, version = self.version ) |
---|
179 | for link_name, link_value in self.links.iteritems(): |
---|
180 | if link_value.filter_by_dataset( data, trans ): |
---|
181 | filtered.links[link_name] = link_value |
---|
182 | return filtered |
---|