7 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/9ef633f09b00/ Changeset: 9ef633f09b00 User: guerler Date: 2015-02-23 22:56:38+00:00 Summary: Allow text selection in tool form Affected #: 2 files diff -r b18fce698ca0b29aaca7733cf9ba95b65c56ca48 -r 9ef633f09b006774c402120fced862cf6b8effed static/style/blue/base.css --- a/static/style/blue/base.css +++ b/static/style/blue/base.css @@ -1410,7 +1410,7 @@ .upload-header .header-selection{width:200px;min-width:200px;font-size:11px;padding-right:10px} .upload-header .select2-choice{max-height:20px;line-height:18px;background:transparent;text-align:center;font-weight:normal}.upload-header .select2-choice .select2-arrow b{background-position:0 -3px} .upload-top{height:5%;text-align:center}.upload-top .upload-info{margin-top:0px;font-weight:normal;text-align:center} -.no-highlight,.ui-button-icon,.ui-portlet,.ui-portlet-repeat,.ui-portlet-limited,.ui-portlet-section,.ui-portlet-narrow{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none;} +.no-highlight,.ui-button-icon{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none;} .no-padding{padding:0px !important} .ui-margin-top{padding-top:5px !important} .ui-margin-bottom{margin-bottom:5px !important} @@ -1434,7 +1434,7 @@ .ui-table-form-separator{font-weight:bold;font-size:0.9em} .ui-table-form-info{clear:both !important} .ui-table-form-error{display:none}.ui-table-form-error .ui-table-form-error-text{padding-left:5px} -.ui-portlet,.ui-portlet-repeat,.ui-portlet-limited,.ui-portlet-section,.ui-portlet-narrow,.ui-portlet-repeat,.ui-portlet-limited,.ui-portlet-section,.ui-portlet-narrow{border:solid #d6b161 1px;border-radius:3px;position:relative;clear:both;width:auto;height:100%}.ui-portlet .portlet-header,.ui-portlet-limited .portlet-header,.ui-portlet-narrow .portlet-header{background:#ebd9b2;border-bottom:solid #d6b161 1px;padding:2px 8px;overflow:visible;float:right;width:100%}.ui-portlet .portlet-header .portlet-title,.ui-portlet-limited .portlet-header .portlet-title,.ui-portlet-narrow .portlet-header .portlet-title{display:inline;vertical-align:middle}.ui-portlet .portlet-header .portlet-title .portlet-title-text,.ui-portlet-limited .portlet-header .portlet-title .portlet-title-text,.ui-portlet-narrow .portlet-header .portlet-title .portlet-title-text{vertical-align:middle;line-height:22px} +.ui-portlet,.ui-portlet-repeat,.ui-portlet-limited,.ui-portlet-section,.ui-portlet-narrow{border:solid #d6b161 1px;border-radius:3px;position:relative;clear:both;width:auto;height:100%}.ui-portlet .portlet-header,.ui-portlet-limited .portlet-header,.ui-portlet-narrow .portlet-header{background:#ebd9b2;border-bottom:solid #d6b161 1px;padding:2px 8px;overflow:visible;float:right;width:100%}.ui-portlet .portlet-header .portlet-title,.ui-portlet-limited .portlet-header .portlet-title,.ui-portlet-narrow .portlet-header .portlet-title{display:inline;vertical-align:middle}.ui-portlet .portlet-header .portlet-title .portlet-title-text,.ui-portlet-limited .portlet-header .portlet-title .portlet-title-text,.ui-portlet-narrow .portlet-header .portlet-title .portlet-title-text{vertical-align:middle;line-height:22px} .ui-portlet .portlet-header .portlet-title .icon,.ui-portlet-limited .portlet-header .portlet-title .icon,.ui-portlet-narrow .portlet-header .portlet-title .icon{font-size:1.2em;vertical-align:middle} .ui-portlet .portlet-header .portlet-operations .ui-button-icon,.ui-portlet-limited .portlet-header .portlet-operations .ui-button-icon,.ui-portlet-narrow .portlet-header .portlet-operations .ui-button-icon{margin-left:5px} .ui-portlet .portlet-content,.ui-portlet-limited .portlet-content,.ui-portlet-narrow .portlet-content{height:inherit;padding-left:10px;padding-right:10px;clear:both}.ui-portlet .portlet-content .content,.ui-portlet-limited .portlet-content .content,.ui-portlet-narrow .portlet-content .content{padding:0px;height:100%;width:100%} diff -r b18fce698ca0b29aaca7733cf9ba95b65c56ca48 -r 9ef633f09b006774c402120fced862cf6b8effed static/style/src/less/ui.less --- a/static/style/src/less/ui.less +++ b/static/style/src/less/ui.less @@ -182,7 +182,6 @@ // portlets .ui-portlet { - &:extend(.no-highlight); border: solid @form-border 1px; border-radius: @border-radius-base; https://bitbucket.org/galaxy/galaxy-central/commits/0a95eaf36a88/ Changeset: 0a95eaf36a88 User: guerler Date: 2015-02-23 23:04:02+00:00 Summary: Fix job ids in success message Affected #: 3 files diff -r 9ef633f09b006774c402120fced862cf6b8effed -r 0a95eaf36a88e1a18bea59461c906b48a214b943 client/galaxy/scripts/mvc/tools/tools-template.js --- a/client/galaxy/scripts/mvc/tools/tools-template.js +++ b/client/galaxy/scripts/mvc/tools/tools-template.js @@ -10,17 +10,17 @@ '</div>' + '</div>'; }, - + success: function(response) { // check if (!response.jobs || !response.jobs.length) { console.debug('tools-template::success() - Failed jobs.'); return; } - + // number of jobs var njobs = response.jobs.length; - + // job count info text var njobs_text = ''; if (njobs == 1) { @@ -28,20 +28,20 @@ } else { njobs_text = njobs + ' jobs have'; } - + // create template string var tmpl = '<div class="donemessagelarge">' + '<p>' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:</p>'; for (var i in response.outputs) { - tmpl += '<p style="padding: 10px 20px;"><b>' + (parseInt(i)+1) + ': ' + response.outputs[i].name + '</b></p>'; + tmpl += '<p style="padding: 10px 20px;"><b>' + response.outputs[i].hid + ': ' + response.outputs[i].name + '</b></p>'; } tmpl += '<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \'running\' to \'finished\' if completed successfully or \'error\' if problems were encountered.</p>' + '</div>'; - + // return success message element return tmpl; }, - + error: function(job_def) { return '<div>' + '<p>' + @@ -52,14 +52,14 @@ '</textarea>' + '</div>'; }, - + batchMode: function() { return '<div class="ui-table-form-info">' + '<i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>' + 'This is a batch mode input field. A separate job will be triggered for each dataset.' + '</div>'; }, - + requirements: function(options) { var requirements_message = 'This tool requires '; for (var i in options.requirements) { diff -r 9ef633f09b006774c402120fced862cf6b8effed -r 0a95eaf36a88e1a18bea59461c906b48a214b943 static/scripts/mvc/tools/tools-template.js --- a/static/scripts/mvc/tools/tools-template.js +++ b/static/scripts/mvc/tools/tools-template.js @@ -10,17 +10,17 @@ '</div>' + '</div>'; }, - + success: function(response) { // check if (!response.jobs || !response.jobs.length) { console.debug('tools-template::success() - Failed jobs.'); return; } - + // number of jobs var njobs = response.jobs.length; - + // job count info text var njobs_text = ''; if (njobs == 1) { @@ -28,20 +28,20 @@ } else { njobs_text = njobs + ' jobs have'; } - + // create template string var tmpl = '<div class="donemessagelarge">' + '<p>' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:</p>'; for (var i in response.outputs) { - tmpl += '<p style="padding: 10px 20px;"><b>' + (parseInt(i)+1) + ': ' + response.outputs[i].name + '</b></p>'; + tmpl += '<p style="padding: 10px 20px;"><b>' + response.outputs[i].hid + ': ' + response.outputs[i].name + '</b></p>'; } tmpl += '<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \'running\' to \'finished\' if completed successfully or \'error\' if problems were encountered.</p>' + '</div>'; - + // return success message element return tmpl; }, - + error: function(job_def) { return '<div>' + '<p>' + @@ -52,14 +52,14 @@ '</textarea>' + '</div>'; }, - + batchMode: function() { return '<div class="ui-table-form-info">' + '<i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>' + 'This is a batch mode input field. A separate job will be triggered for each dataset.' + '</div>'; }, - + requirements: function(options) { var requirements_message = 'This tool requires '; for (var i in options.requirements) { diff -r 9ef633f09b006774c402120fced862cf6b8effed -r 0a95eaf36a88e1a18bea59461c906b48a214b943 static/scripts/packed/mvc/tools/tools-template.js --- a/static/scripts/packed/mvc/tools/tools-template.js +++ b/static/scripts/packed/mvc/tools/tools-template.js @@ -1,1 +1,1 @@ -define([],function(){return{help:function(a){return'<div class="toolHelp" style="overflow: auto;"><div class="toolHelpBody">'+a+"</div></div>"},success:function(c){if(!c.jobs||!c.jobs.length){console.debug("tools-template::success() - Failed jobs.");return}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='<div class="donemessagelarge"><p>'+d+" been successfully added to the queue - resulting in the following datasets:</p>";for(var e in c.outputs){b+='<p style="padding: 10px 20px;"><b>'+(parseInt(e)+1)+": "+c.outputs[e].name+"</b></p>"}b+="<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.</p></div>";return b},error:function(a){return'<div><p>The server could not complete the request. Please contact the Galaxy Team if this error persists.</p><textarea class="ui-textarea" disabled style="color: black;" rows="6">'+JSON.stringify(a,undefined,4)+"</textarea></div>"},batchMode:function(){return'<div class="ui-table-form-info"><i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>This is a batch mode input field. A separate job will be triggered for each dataset.</div>'},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(b<a.requirements.length-2){d+=", "}if(b==a.requirements.length-2){d+=" and "}}return d+'. Click <a target="_blank" href="https://wiki.galaxyproject.org/Tools/Requirements">here</a> for more information.'}}}); \ No newline at end of file +define([],function(){return{help:function(a){return'<div class="toolHelp" style="overflow: auto;"><div class="toolHelpBody">'+a+"</div></div>"},success:function(c){if(!c.jobs||!c.jobs.length){console.debug("tools-template::success() - Failed jobs.");return}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='<div class="donemessagelarge"><p>'+d+" been successfully added to the queue - resulting in the following datasets:</p>";for(var e in c.outputs){b+='<p style="padding: 10px 20px;"><b>'+c.outputs[e].hid+": "+c.outputs[e].name+"</b></p>"}b+="<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.</p></div>";return b},error:function(a){return'<div><p>The server could not complete the request. Please contact the Galaxy Team if this error persists.</p><textarea class="ui-textarea" disabled style="color: black;" rows="6">'+JSON.stringify(a,undefined,4)+"</textarea></div>"},batchMode:function(){return'<div class="ui-table-form-info"><i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>This is a batch mode input field. A separate job will be triggered for each dataset.</div>'},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(b<a.requirements.length-2){d+=", "}if(b==a.requirements.length-2){d+=" and "}}return d+'. Click <a target="_blank" href="https://wiki.galaxyproject.org/Tools/Requirements">here</a> for more information.'}}}); \ No newline at end of file https://bitbucket.org/galaxy/galaxy-central/commits/c18c22d74ce2/ Changeset: c18c22d74ce2 User: jmchilton Date: 2015-02-24 00:52:17+00:00 Summary: Improved tool diagnostics API. Affected #: 1 file diff -r 0a95eaf36a88e1a18bea59461c906b48a214b943 -r c18c22d74ce26f484b50baa61eab27bc160f8b43 lib/galaxy/webapps/galaxy/api/tools.py --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -102,6 +102,7 @@ Return diagnostic information to help debug panel and dependency related problems. """ + # TODO: Move this into tool. to_dict = lambda x: x.to_dict() tool = self._get_tool( id, user=trans.user ) if hasattr( tool, 'lineage' ): @@ -113,7 +114,6 @@ tool_shed_dependencies_dict = map(to_dict, tool_shed_dependencies) else: tool_shed_dependencies_dict = None - tool = self._get_tool( id, user=trans.user ) return { "tool_id": tool.id, "tool_version": tool.version, @@ -122,6 +122,11 @@ "requirements": map(to_dict, tool.requirements), "installed_tool_shed_dependencies": tool_shed_dependencies_dict, "tool_dir": tool.tool_dir, + "tool_shed": tool.tool_shed, + "repository_name": tool.repository_name, + "repository_owner": tool.repository_owner, + "installed_changeset_revision": None, + "guid": tool.guid, } @_future_expose_api_anonymous https://bitbucket.org/galaxy/galaxy-central/commits/08153b7c614a/ Changeset: 08153b7c614a User: jmchilton Date: 2015-02-22 05:32:46+00:00 Summary: Fix for watching tools on some more interesting file systems. Affected #: 3 files diff -r c18c22d74ce26f484b50baa61eab27bc160f8b43 -r 08153b7c614a26b19ee3130acb0a2a9a1bd6763c config/galaxy.ini.sample --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -176,11 +176,15 @@ # install from in the admin interface (.sample used if default does not exist). #tool_sheds_config_file = config/tool_sheds_conf.xml -# Enable monitoring of tools and tool directories listed in any tool config file -# specified in tool_config_file option. If changes are found, tools are -# automatically reloaded. -# Watchdog ( https://pypi.python.org/pypi/watchdog ) must be installed and -# available to Galaxy to use this option. +# Set to True to Enable monitoring of tools and tool directories +# listed in any tool config file specified in tool_config_file option. +# If changes are found, tools are automatically reloaded. Watchdog ( +# https://pypi.python.org/pypi/watchdog ) must be installed and +# available to Galaxy to use this option. Other options include 'auto' +# which will attempt to watch tools if the watchdog library is available +# but won't fail to load Galaxy if it is not and 'polling' which will use +# a less efficient monitoring scheme that may work in wider range of scenarios +# then the watchdog default montiory. #watch_tools = False # Enable automatic polling of relative tool sheds to see if any updates diff -r c18c22d74ce26f484b50baa61eab27bc160f8b43 -r 08153b7c614a26b19ee3130acb0a2a9a1bd6763c lib/galaxy/config.py --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -272,7 +272,7 @@ self.ftp_upload_site = kwargs.get( 'ftp_upload_site', None ) self.allow_library_path_paste = kwargs.get( 'allow_library_path_paste', False ) self.disable_library_comptypes = kwargs.get( 'disable_library_comptypes', '' ).lower().split( ',' ) - self.watch_tools = string_as_bool( kwargs.get( 'watch_tools', False ) ) + self.watch_tools = kwargs.get( 'watch_tools', 'false' ) # On can mildly speed up Galaxy startup time by disabling index of help, # not needed on production systems but useful if running many functional tests. self.index_tool_help = string_as_bool( kwargs.get( "index_tool_help", True ) ) diff -r c18c22d74ce26f484b50baa61eab27bc160f8b43 -r 08153b7c614a26b19ee3130acb0a2a9a1bd6763c lib/galaxy/tools/toolbox/watcher.py --- a/lib/galaxy/tools/toolbox/watcher.py +++ b/lib/galaxy/tools/toolbox/watcher.py @@ -2,9 +2,11 @@ try: from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer + from watchdog.observers.polling import PollingObserver can_watch = True except ImportError: FileSystemEventHandler = object + PollingObserver = object can_watch = False import logging @@ -12,23 +14,34 @@ def get_watcher(toolbox, config): - watch_tools = getattr(config, "watch_tools", False) - if watch_tools: + watch_tools_val = str( getattr(config, "watch_tools", False) ).lower() + if watch_tools_val in ( 'true', 'yes', 'on' ): return ToolWatcher(toolbox) + elif watch_tools_val == "auto": + try: + return ToolWatcher(toolbox) + except Exception: + log.info("Failed to load ToolWatcher (watchdog is likely unavailable) - proceeding without tool monitoring.") + return NullWatcher() + elif watch_tools_val == "polling": + log.info("Using less ineffecient polling toolbox watcher.") + return ToolWatcher(toolbox, observer_class=PollingObserver) else: return NullWatcher() class ToolWatcher(object): - def __init__(self, toolbox): + def __init__(self, toolbox, observer_class=None): if not can_watch: raise Exception("Watchdog library unavailble, cannot watch tools.") + if observer_class is None: + observer_class = Observer self.toolbox = toolbox self.tool_file_ids = {} self.tool_dir_callbacks = {} self.monitored_dirs = {} - self.observer = Observer() + self.observer = observer_class() self.event_handler = ToolFileEventHandler(self) self.start() https://bitbucket.org/galaxy/galaxy-central/commits/872867495129/ Changeset: 872867495129 User: jmchilton Date: 2015-02-24 01:33:05+00:00 Summary: Allow setting job_conf params via environment variables & galaxy.ini. Affected #: 2 files diff -r 08153b7c614a26b19ee3130acb0a2a9a1bd6763c -r 8728674951297e85894b9e191ceb4d26a85bde4d config/job_conf.xml.sample_advanced --- a/config/job_conf.xml.sample_advanced +++ b/config/job_conf.xml.sample_advanced @@ -427,6 +427,15 @@ <param id="nativeSpecification">--mem-per-cpu=512</param><resubmit condition="memory_limit_reached" destination="bigmem" /></destination> + <!-- Any tag param in this file can be set using an enviornment variable or using + values from galaxy.ini using the from_environ and from_config attributes + repectively. The text of the param will still be used if that enviornment variable + or config value isn't set. + --> + <destination id="params_from_environment" runner="slurm"> + <param id="nativeSpecification" from_environ="NATIVE_SPECIFICATION">--time=00:05:00 --nodes=1</param> + <param id="docker_enabled" from_config="use_docker">false</param> + </destination></destinations><resources default="default"> diff -r 08153b7c614a26b19ee3130acb0a2a9a1bd6763c -r 8728674951297e85894b9e191ceb4d26a85bde4d lib/galaxy/jobs/__init__.py --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -445,7 +445,15 @@ """ rval = {} for param in parent.findall('param'): - rval[param.get('id')] = param.text + key = param.get('id') + param_value = param.text + if 'from_environ' in param.attrib: + environ_var = param.attrib['from_environ'] + param_value = os.environ.get(environ_var, param_value) + elif 'from_config' in param.attrib: + config_val = param.attrib['from_config'] + param_value = self.app.config.config_dict.get(config_val, param_value) + rval[key] = param_value return rval def __get_envs(self, parent): https://bitbucket.org/galaxy/galaxy-central/commits/678bb1ba4d89/ Changeset: 678bb1ba4d89 User: jmchilton Date: 2015-02-24 16:03:42+00:00 Summary: Docfixes - grammar and spelling problems caught by Nicola. Affected #: 2 files diff -r 8728674951297e85894b9e191ceb4d26a85bde4d -r 678bb1ba4d89b3043dd75d6b2d0c36663f693e62 config/galaxy.ini.sample --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -176,7 +176,7 @@ # install from in the admin interface (.sample used if default does not exist). #tool_sheds_config_file = config/tool_sheds_conf.xml -# Set to True to Enable monitoring of tools and tool directories +# Set to True to enable monitoring of tools and tool directories # listed in any tool config file specified in tool_config_file option. # If changes are found, tools are automatically reloaded. Watchdog ( # https://pypi.python.org/pypi/watchdog ) must be installed and @@ -184,7 +184,7 @@ # which will attempt to watch tools if the watchdog library is available # but won't fail to load Galaxy if it is not and 'polling' which will use # a less efficient monitoring scheme that may work in wider range of scenarios -# then the watchdog default montiory. +# than the watchdog default. #watch_tools = False # Enable automatic polling of relative tool sheds to see if any updates diff -r 8728674951297e85894b9e191ceb4d26a85bde4d -r 678bb1ba4d89b3043dd75d6b2d0c36663f693e62 config/job_conf.xml.sample_advanced --- a/config/job_conf.xml.sample_advanced +++ b/config/job_conf.xml.sample_advanced @@ -427,9 +427,9 @@ <param id="nativeSpecification">--mem-per-cpu=512</param><resubmit condition="memory_limit_reached" destination="bigmem" /></destination> - <!-- Any tag param in this file can be set using an enviornment variable or using + <!-- Any tag param in this file can be set using an environment variable or using values from galaxy.ini using the from_environ and from_config attributes - repectively. The text of the param will still be used if that enviornment variable + repectively. The text of the param will still be used if that environment variable or config value isn't set. --><destination id="params_from_environment" runner="slurm"> https://bitbucket.org/galaxy/galaxy-central/commits/476c79d7b00a/ Changeset: 476c79d7b00a User: dannon Date: 2015-02-24 16:30:43+00:00 Summary: Improved Mutable Handling w/ JSONType and MetadataType -- should resolve the outstanding sqlalchemy coerce/list bug Affected #: 1 file diff -r 678bb1ba4d89b3043dd75d6b2d0c36663f693e62 -r 476c79d7b00a5edde57847ca1882d7e7781030b6 lib/galaxy/model/custom_types.py --- a/lib/galaxy/model/custom_types.py +++ b/lib/galaxy/model/custom_types.py @@ -1,5 +1,4 @@ import binascii -import copy import json import logging import uuid @@ -34,67 +33,33 @@ return value -class MutableDict(Mutable, dict): - # MutableDict following http://docs.sqlalchemy.org/en/latest/orm/extensions/mutable.html - @classmethod - def coerce(cls, key, value): - "Convert plain dictionaries to MutableDict." +# Mutable JSONType for SQLAlchemy from original gist: +# https://gist.github.com/dbarnett/1730610 +# +# Also using minor changes from this fork of the gist: +# https://gist.github.com/miracle2k/52a031cced285ba9b8cd +# +# And other minor changes to make it work for us. - if not isinstance(value, MutableDict): - if isinstance(value, dict): - return MutableDict(value) - # this call will raise ValueError - return Mutable.coerce(key, value) - else: - return value +class JSONType(sqlalchemy.types.TypeDecorator): + """Represents an immutable structure as a json-encoded string. - def __setitem__(self, key, value): - "Detect dictionary set events and emit change events." + If default is, for example, a dict, then a NULL value in the + database will be exposed as an empty dict. + """ - dict.__setitem__(self, key, value) - self.changed() - - def __delitem__(self, key): - "Detect dictionary del events and emit change events." - - dict.__delitem__(self, key) - self.changed() - - def __getstate__(self): - return dict(self) - - def __setstate__(self, state): - self.update(state) - - -class JSONType( TypeDecorator ): - """ - Defines a JSONType for SQLAlchemy. Takes a primitive as input and - JSONifies it. This should replace PickleType throughout Galaxy. - """ impl = LargeBinary - def process_bind_param( self, value, dialect ): - if value is None: - return None - return json_encoder.encode( value ) + def process_bind_param(self, value, dialect): + if value is not None: + value = json_encoder.encode(value) + return value - def process_result_value( self, value, dialect ): + def process_result_value(self, value, dialect): if value is not None: - try: - return json_decoder.decode( str( _sniffnfix_pg9_hex( value ) ) ) - except Exception, e: - log.error( 'Failed to decode JSON (%s): %s', value, e ) - return None - - def copy_value( self, value ): - # return json_decoder.decode( json_encoder.encode( value ) ) - return copy.deepcopy( value ) - - def compare_values( self, x, y ): - # return json_encoder.encode( x ) == json_encoder.encode( y ) - return ( x == y ) + value = json_decoder.decode( str( _sniffnfix_pg9_hex( value ) ) ) + return value def load_dialect_impl(self, dialect): if dialect.name == "mysql": @@ -103,8 +68,136 @@ return self.impl -MutableDict.associate_with(JSONType) +class MutationObj(Mutable): + @classmethod + def coerce(cls, key, value): + if isinstance(value, dict) and not isinstance(value, MutationDict): + return MutationDict.coerce(key, value) + if isinstance(value, list) and not isinstance(value, MutationList): + return MutationList.coerce(key, value) + return value + @classmethod + def _listen_on_attribute(cls, attribute, coerce, parent_cls): + key = attribute.key + if parent_cls is not attribute.class_: + return + + # rely on "propagate" here + parent_cls = attribute.class_ + + def load(state, *args): + val = state.dict.get(key, None) + if coerce: + val = cls.coerce(key, val) + state.dict[key] = val + if isinstance(val, cls): + val._parents[state.obj()] = key + + def set(target, value, oldvalue, initiator): + if not isinstance(value, cls): + value = cls.coerce(key, value) + if isinstance(value, cls): + value._parents[target.obj()] = key + if isinstance(oldvalue, cls): + oldvalue._parents.pop(target.obj(), None) + return value + + def pickle(state, state_dict): + val = state.dict.get(key, None) + if isinstance(val, cls): + if 'ext.mutable.values' not in state_dict: + state_dict['ext.mutable.values'] = [] + state_dict['ext.mutable.values'].append(val) + + def unpickle(state, state_dict): + if 'ext.mutable.values' in state_dict: + for val in state_dict['ext.mutable.values']: + val._parents[state.obj()] = key + + sqlalchemy.event.listen(parent_cls, 'load', load, raw=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True, propagate=True) + sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True, propagate=True) + + +class MutationDict(MutationObj, dict): + @classmethod + def coerce(cls, key, value): + """Convert plain dictionary to MutationDict""" + self = MutationDict((k, MutationObj.coerce(key, v)) for (k, v) in value.items()) + self._key = key + return self + + def __setitem__(self, key, value): + # Due to the way OrderedDict works, this is called during __init__. + # At this time we don't have a key set, but what is more, the value + # being set has already been coerced. So special case this and skip. + if hasattr(self, '_key'): + value = MutationObj.coerce(self._key, value) + dict.__setitem__(self, key, value) + self.changed() + + def __delitem__(self, key): + dict.__delitem__(self, key) + self.changed() + + +class MutationList(MutationObj, list): + @classmethod + def coerce(cls, key, value): + """Convert plain list to MutationList""" + self = MutationList((MutationObj.coerce(key, v) for v in value)) + self._key = key + return self + + def __setitem__(self, idx, value): + list.__setitem__(self, idx, MutationObj.coerce(self._key, value)) + self.changed() + + def __setslice__(self, start, stop, values): + list.__setslice__(self, start, stop, (MutationObj.coerce(self._key, v) for v in values)) + self.changed() + + def __delitem__(self, idx): + list.__delitem__(self, idx) + self.changed() + + def __delslice__(self, start, stop): + list.__delslice__(self, start, stop) + self.changed() + + def append(self, value): + list.append(self, MutationObj.coerce(self._key, value)) + self.changed() + + def insert(self, idx, value): + list.insert(self, idx, MutationObj.coerce(self._key, value)) + self.changed() + + def extend(self, values): + list.extend(self, (MutationObj.coerce(self._key, v) for v in values)) + self.changed() + + def pop(self, *args, **kw): + value = list.pop(self, *args, **kw) + self.changed() + return value + + def remove(self, value): + list.remove(self, value) + self.changed() + + +MutationObj.associate_with(JSONType) + +"""A type to encode/decode JSON on the fly + +sqltype is the string type for the underlying DB column:: + + Column(JSON) +""" metadata_pickler = AliasPickleModule( { ( "cookbook.patterns", "Bunch" ): ( "galaxy.util.bunch", "Bunch" ) Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.