8 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/acd6a0ca0b5e/ Changeset: acd6a0ca0b5e User: dannon Date: 2014-09-18 00:46:16+00:00 Summary: Add method for shipping encoded inf/nan in json (as __Infinity__ / __NaN__). We should only be shipping valid JSON now, but it still needs to be parsed and handled on API consumers, of course. Affected #: 2 files diff -r 5a36f30d887ee946c5a2fefb64bd5e5858228d35 -r acd6a0ca0b5eb39cb58043a89aa06527f258c3ef lib/galaxy/util/json.py --- a/lib/galaxy/util/json.py +++ b/lib/galaxy/util/json.py @@ -1,9 +1,12 @@ from __future__ import absolute_import -__all__ = [ "dumps", "loads", "json_fix", "validate_jsonrpc_request", "validate_jsonrpc_response", "jsonrpc_request", "jsonrpc_response" ] +__all__ = [ "dumps", "loads", "safe_dumps", "json_fix", "validate_jsonrpc_request", "validate_jsonrpc_response", "jsonrpc_request", "jsonrpc_response" ] +import copy +import collections import json import logging +import math import random import string @@ -34,6 +37,41 @@ return val +def swap_inf_nan( val ): + """ + This takes an arbitrary object and preps it for jsonifying safely, templating Inf/NaN. + """ + if isinstance(val, basestring): + # basestring first, because it's a sequence and would otherwise get caught below. + return val + elif isinstance( val, collections.Sequence ): + return [ swap_inf_nan( v ) for v in val ] + elif isinstance( val, collections.Mapping ): + return dict( [ ( swap_inf_nan( k ), swap_inf_nan( v ) ) for ( k, v ) in val.iteritems() ] ) + elif isinstance(val, float): + if math.isnan(val): + return "__NaN__" + elif math.isinf(val): + return "__Infinity__" + else: + return val + + +def safe_dumps(*args, **kwargs): + """ + This is a wrapper around dumps that encodes Infinity and NaN values. It's a + fairly rare case (which will be low in request volume). Basically, we tell + json.dumps to blow up if it encounters Infinity/NaN, and we 'fix' it before + re-encoding. + """ + try: + dumped = json.dumps(*args, allow_nan=False, **kwargs) + except ValueError: + obj = swap_inf_nan(copy.deepcopy(args[0])) + dumped = json.dumps(obj, allow_nan=False, **kwargs) + return dumped + + # Methods for handling JSON-RPC def validate_jsonrpc_request( request, regular_methods, notification_methods ): diff -r 5a36f30d887ee946c5a2fefb64bd5e5858228d35 -r acd6a0ca0b5eb39cb58043a89aa06527f258c3ef lib/galaxy/web/framework/decorators.py --- a/lib/galaxy/web/framework/decorators.py +++ b/lib/galaxy/web/framework/decorators.py @@ -10,7 +10,8 @@ from galaxy import util from galaxy.exceptions import error_codes from galaxy.exceptions import MessageException -from galaxy.util.json import loads, dumps +from galaxy.util.json import loads +from galaxy.util.json import safe_dumps as dumps import logging log = logging.getLogger( __name__ ) https://bitbucket.org/galaxy/galaxy-central/commits/2006c39aea16/ Changeset: 2006c39aea16 User: dannon Date: 2014-09-18 00:51:30+00:00 Summary: Might never need it, but, differentiate between and handle +/- inf. Affected #: 1 file diff -r acd6a0ca0b5eb39cb58043a89aa06527f258c3ef -r 2006c39aea16f2468a3098475c8c292cf1b8a121 lib/galaxy/util/json.py --- a/lib/galaxy/util/json.py +++ b/lib/galaxy/util/json.py @@ -51,8 +51,10 @@ elif isinstance(val, float): if math.isnan(val): return "__NaN__" - elif math.isinf(val): + elif val == float("inf"): return "__Infinity__" + elif val == float("-inf"): + return "__-Infinity__" else: return val https://bitbucket.org/galaxy/galaxy-central/commits/c48c2fbc91c3/ Changeset: c48c2fbc91c3 User: dannon Date: 2014-09-18 01:00:39+00:00 Summary: Add TODO in LazyDataLoader (the only place we were mucking about with Infinity/NaN in the client Affected #: 1 file diff -r 2006c39aea16f2468a3098475c8c292cf1b8a121 -r c48c2fbc91c3def7c5896f180b14ec96cf78f3cb client/galaxy/scripts/utils/LazyDataLoader.js --- a/client/galaxy/scripts/utils/LazyDataLoader.js +++ b/client/galaxy/scripts/utils/LazyDataLoader.js @@ -125,6 +125,9 @@ 'text xml' : jQuery.parseXML, // add NaN, inf, -inf handling to jquery json parser (by default) + // TODO: This functionality shouldn't be required as of + // 2006c39aea16, and we should probably assume correct JSON instead + // of stripping out these strings. 'text json' : function( json ){ json = json.replace( /NaN/g, 'null' ); json = json.replace( /-Infinity/g, 'null' ); https://bitbucket.org/galaxy/galaxy-central/commits/8bae0ff6e656/ Changeset: 8bae0ff6e656 User: dannon Date: 2014-09-18 01:03:46+00:00 Summary: Client build. Affected #: 1 file diff -r c48c2fbc91c3def7c5896f180b14ec96cf78f3cb -r 8bae0ff6e656f80f34c0b97361477ead3c6ed9c5 static/scripts/utils/LazyDataLoader.js --- a/static/scripts/utils/LazyDataLoader.js +++ b/static/scripts/utils/LazyDataLoader.js @@ -125,6 +125,9 @@ 'text xml' : jQuery.parseXML, // add NaN, inf, -inf handling to jquery json parser (by default) + // TODO: This functionality shouldn't be required as of + // 2006c39aea16, and we should probably assume correct JSON instead + // of stripping out these strings. 'text json' : function( json ){ json = json.replace( /NaN/g, 'null' ); json = json.replace( /-Infinity/g, 'null' ); https://bitbucket.org/galaxy/galaxy-central/commits/56b27ced7296/ Changeset: 56b27ced7296 User: dannon Date: 2014-09-18 01:11:12+00:00 Summary: Scratch the TODO, just removing those text replacements. Not necessary anymore. Affected #: 1 file diff -r 8bae0ff6e656f80f34c0b97361477ead3c6ed9c5 -r 56b27ced729668522a3f8db623ac8a0a8a4fc33f client/galaxy/scripts/utils/LazyDataLoader.js --- a/client/galaxy/scripts/utils/LazyDataLoader.js +++ b/client/galaxy/scripts/utils/LazyDataLoader.js @@ -123,17 +123,7 @@ '* text' : window.String, 'text html' : true, 'text xml' : jQuery.parseXML, - - // add NaN, inf, -inf handling to jquery json parser (by default) - // TODO: This functionality shouldn't be required as of - // 2006c39aea16, and we should probably assume correct JSON instead - // of stripping out these strings. - 'text json' : function( json ){ - json = json.replace( /NaN/g, 'null' ); - json = json.replace( /-Infinity/g, 'null' ); - json = json.replace( /Infinity/g, 'null' ); - return jQuery.parseJSON( json ); - } + 'text json' : jQuery.parseJSON }, // interface to begin load (and first recursive call) https://bitbucket.org/galaxy/galaxy-central/commits/9f31abd2996c/ Changeset: 9f31abd2996c User: dannon Date: 2014-09-18 01:12:53+00:00 Summary: Fix formatting to prevent potential semicolon insertion. Affected #: 1 file diff -r 56b27ced729668522a3f8db623ac8a0a8a4fc33f -r 9f31abd2996c818e54fcd47227fa3afed4d1c62f client/galaxy/scripts/utils/LazyDataLoader.js --- a/client/galaxy/scripts/utils/LazyDataLoader.js +++ b/client/galaxy/scripts/utils/LazyDataLoader.js @@ -143,8 +143,7 @@ //FIRST RECURSION: start var startingSize = loader.size; - if( ( loader.total !== null ) - && ( loader.total < loader.size ) ){ + if( ( loader.total !== null ) && ( loader.total < loader.size ) ){ startingSize = loader.total; } loader.log( loader + '\t beginning recursion' ); https://bitbucket.org/galaxy/galaxy-central/commits/5d255b770ed8/ Changeset: 5d255b770ed8 User: dannon Date: 2014-09-18 01:17:30+00:00 Summary: Strip trailing whitespace in LazyDataLoader Affected #: 1 file diff -r 9f31abd2996c818e54fcd47227fa3afed4d1c62f -r 5d255b770ed8561db30449a824d2c5c0095fc1e9 client/galaxy/scripts/utils/LazyDataLoader.js --- a/client/galaxy/scripts/utils/LazyDataLoader.js +++ b/client/galaxy/scripts/utils/LazyDataLoader.js @@ -20,11 +20,11 @@ * + '&columns=[10,14]' ), * total : hda.metadata_data_lines, * size : 500, - * + * * initialize : function( config ){ * // ... do some stuff * }, - * + * * buildUrl : function( start, size ){ * // change the formation of start, size in query string * return loader.url + '&' + jQuery.param({ @@ -64,19 +64,19 @@ LOADED_ALL_EVENT = 'complete'; // error from ajax ERROR_EVENT = 'error'; - + jQuery.extend( loader, LoggableMixin ); jQuery.extend( loader, { - + //NOTE: the next two need to be sent in config (required) // total size of data on server total : undefined, // url of service to get the data url : undefined, - + // holds the interval id for the current load delay currentIntervalId : undefined, - + // each load call will add an element to this array // it's the responsibility of the code using this to combine them properly data : [], @@ -86,12 +86,12 @@ start : 0, // size to fetch per load size : 4000, - + // loader init func: extends loader with config and calls config.init if there //@param {object} config : object containing variables to override (or additional) initialize : function( config ){ jQuery.extend( loader, config ); - + // call the custom initialize function if any // only dangerous if the user tries LazyDataLoader.prototype.init if( config.hasOwnProperty( 'initialize' ) ){ @@ -99,7 +99,7 @@ } this.log( this + ' initialized:', loader ); }, - + // returns query string formatted start and size (for the next fetch) appended to the loader.url //OVERRIDE: to change how params are passed, param names, etc. //@param {int} start : the line/row/datum indicating where in the dataset the next load begins @@ -111,7 +111,7 @@ max_vals: size }); }, - + //OVERRIDE: to handle ajax errors differently ajaxErrorFn : function( xhr, status, error ){ console.error( 'ERROR fetching data:', error ); @@ -127,7 +127,7 @@ }, // interface to begin load (and first recursive call) - //@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data + //@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data load : function( callback ){ this.log( this + '.load' ); @@ -140,7 +140,7 @@ this.log( '\t total:', this.total ); } //if( !loader.total ){ throw( loader + ' requires a total (total size of the data)' ); } - + //FIRST RECURSION: start var startingSize = loader.size; if( ( loader.total !== null ) && ( loader.total < loader.size ) ){ @@ -148,13 +148,13 @@ } loader.log( loader + '\t beginning recursion' ); loadHelper( loader.start, startingSize ); - + //FIRST, SUBSEQUENT RECURSION function function loadHelper( start, size ){ loader.log( loader + '.loadHelper, start:', start, 'size:', size ); var url = loader.buildUrl( start, size ); loader.log( '\t url:', url ); - + jQuery.ajax({ url : loader.buildUrl( start, size ), converters : loader.converters, @@ -168,19 +168,19 @@ $( loader ).trigger( ERROR_EVENT, [ status, error ] ); loader.ajaxErrorFn( xhr, status, error ); }, - + success : function( response ){ loader.log( '\t ajax success, response:', response, 'next:', next, 'remainder:', remainder ); - + if( response !== null ){ // store the response as is in a new element //TODO:?? store start, size as well? loader.data.push( response ); - + //TODO: these might not be the best way to split this up // fire the first load event (if this is the first batch) AND partial $( loader ).trigger( LOADED_NEW_EVENT, [ response, start, size ] ); - + //RECURSION: var next = start + size, remainder = loader.size; @@ -197,12 +197,12 @@ loader.delay ); loader.log( '\t currentIntervalId:', loader.currentIntervalId ); - + // otherwise (base-case), don't do anything } else { loadFinished(); } - + } else { //response === null --> base-case, server sez nuthin left loadFinished(); } @@ -217,10 +217,10 @@ if( callback ){ callback( loader.data ); } } }, - + toString : function(){ return 'LazyDataLoader'; } }); - + loader.initialize( config ); return loader; } https://bitbucket.org/galaxy/galaxy-central/commits/76be361f50b2/ Changeset: 76be361f50b2 User: dannon Date: 2014-09-18 01:18:53+00:00 Summary: Client build. Affected #: 2 files diff -r 5d255b770ed8561db30449a824d2c5c0095fc1e9 -r 76be361f50b29515226f94bd2a2e01f0d9ff413b static/scripts/packed/utils/LazyDataLoader.js --- a/static/scripts/packed/utils/LazyDataLoader.js +++ b/static/scripts/packed/utils/LazyDataLoader.js @@ -1,1 +1,1 @@ -function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){console.error("ERROR fetching data:",f)},converters:{"* text":window.String,"text html":true,"text xml":jQuery.parseXML,"text json":function(e){e=e.replace(/NaN/g,"null");e=e.replace(/-Infinity/g,"null");e=e.replace(/Infinity/g,"null");return jQuery.parseJSON(e)}},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),converters:a.converters,dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a}; \ No newline at end of file +function LazyDataLoader(c){var a=this,d="loaded.new",b="complete";ERROR_EVENT="error";jQuery.extend(a,LoggableMixin);jQuery.extend(a,{total:undefined,url:undefined,currentIntervalId:undefined,data:[],delay:4000,start:0,size:4000,initialize:function(e){jQuery.extend(a,e);if(e.hasOwnProperty("initialize")){e.initialize.call(a,e)}this.log(this+" initialized:",a)},buildUrl:function(f,e){return this.url+"&"+jQuery.param({start_val:f,max_vals:e})},ajaxErrorFn:function(g,e,f){console.error("ERROR fetching data:",f)},converters:{"* text":window.String,"text html":true,"text xml":jQuery.parseXML,"text json":jQuery.parseJSON},load:function(h){this.log(this+".load");if(!a.url){throw (a+" requires a url")}if(this.total===null){this.log("\t total is null (will load all)")}else{this.log("\t total:",this.total)}var g=a.size;if((a.total!==null)&&(a.total<a.size)){g=a.total}a.log(a+"\t beginning recursion");f(a.start,g);function f(k,j){a.log(a+".loadHelper, start:",k,"size:",j);var i=a.buildUrl(k,j);a.log("\t url:",i);jQuery.ajax({url:a.buildUrl(k,j),converters:a.converters,dataType:"json",error:function(n,l,m){a.log("\t ajax error, status:",l,"error:",m);if(a.currentIntervalId){clearInterval(a.currentIntervalId)}$(a).trigger(ERROR_EVENT,[l,m]);a.ajaxErrorFn(n,l,m)},success:function(l){a.log("\t ajax success, response:",l,"next:",m,"remainder:",n);if(l!==null){a.data.push(l);$(a).trigger(d,[l,k,j]);var m=k+j,n=a.size;if(a.total!==null){n=Math.min(a.total-m,a.size)}a.log("\t next recursion, start:",m,"size:",n);if(a.total===null||n>0){a.currentIntervalId=setTimeout(function(){f(m,n)},a.delay);a.log("\t currentIntervalId:",a.currentIntervalId)}else{e()}}else{e()}}})}function e(){a.log(a+".loadHelper, has finished:",a.data);$(a).trigger(b,[a.data,a.total]);if(h){h(a.data)}}},toString:function(){return"LazyDataLoader"}});a.initialize(c);return a}; \ No newline at end of file diff -r 5d255b770ed8561db30449a824d2c5c0095fc1e9 -r 76be361f50b29515226f94bd2a2e01f0d9ff413b static/scripts/utils/LazyDataLoader.js --- a/static/scripts/utils/LazyDataLoader.js +++ b/static/scripts/utils/LazyDataLoader.js @@ -20,11 +20,11 @@ * + '&columns=[10,14]' ), * total : hda.metadata_data_lines, * size : 500, - * + * * initialize : function( config ){ * // ... do some stuff * }, - * + * * buildUrl : function( start, size ){ * // change the formation of start, size in query string * return loader.url + '&' + jQuery.param({ @@ -64,19 +64,19 @@ LOADED_ALL_EVENT = 'complete'; // error from ajax ERROR_EVENT = 'error'; - + jQuery.extend( loader, LoggableMixin ); jQuery.extend( loader, { - + //NOTE: the next two need to be sent in config (required) // total size of data on server total : undefined, // url of service to get the data url : undefined, - + // holds the interval id for the current load delay currentIntervalId : undefined, - + // each load call will add an element to this array // it's the responsibility of the code using this to combine them properly data : [], @@ -86,12 +86,12 @@ start : 0, // size to fetch per load size : 4000, - + // loader init func: extends loader with config and calls config.init if there //@param {object} config : object containing variables to override (or additional) initialize : function( config ){ jQuery.extend( loader, config ); - + // call the custom initialize function if any // only dangerous if the user tries LazyDataLoader.prototype.init if( config.hasOwnProperty( 'initialize' ) ){ @@ -99,7 +99,7 @@ } this.log( this + ' initialized:', loader ); }, - + // returns query string formatted start and size (for the next fetch) appended to the loader.url //OVERRIDE: to change how params are passed, param names, etc. //@param {int} start : the line/row/datum indicating where in the dataset the next load begins @@ -111,7 +111,7 @@ max_vals: size }); }, - + //OVERRIDE: to handle ajax errors differently ajaxErrorFn : function( xhr, status, error ){ console.error( 'ERROR fetching data:', error ); @@ -123,21 +123,11 @@ '* text' : window.String, 'text html' : true, 'text xml' : jQuery.parseXML, - - // add NaN, inf, -inf handling to jquery json parser (by default) - // TODO: This functionality shouldn't be required as of - // 2006c39aea16, and we should probably assume correct JSON instead - // of stripping out these strings. - 'text json' : function( json ){ - json = json.replace( /NaN/g, 'null' ); - json = json.replace( /-Infinity/g, 'null' ); - json = json.replace( /Infinity/g, 'null' ); - return jQuery.parseJSON( json ); - } + 'text json' : jQuery.parseJSON }, // interface to begin load (and first recursive call) - //@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data + //@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data load : function( callback ){ this.log( this + '.load' ); @@ -150,22 +140,21 @@ this.log( '\t total:', this.total ); } //if( !loader.total ){ throw( loader + ' requires a total (total size of the data)' ); } - + //FIRST RECURSION: start var startingSize = loader.size; - if( ( loader.total !== null ) - && ( loader.total < loader.size ) ){ + if( ( loader.total !== null ) && ( loader.total < loader.size ) ){ startingSize = loader.total; } loader.log( loader + '\t beginning recursion' ); loadHelper( loader.start, startingSize ); - + //FIRST, SUBSEQUENT RECURSION function function loadHelper( start, size ){ loader.log( loader + '.loadHelper, start:', start, 'size:', size ); var url = loader.buildUrl( start, size ); loader.log( '\t url:', url ); - + jQuery.ajax({ url : loader.buildUrl( start, size ), converters : loader.converters, @@ -179,19 +168,19 @@ $( loader ).trigger( ERROR_EVENT, [ status, error ] ); loader.ajaxErrorFn( xhr, status, error ); }, - + success : function( response ){ loader.log( '\t ajax success, response:', response, 'next:', next, 'remainder:', remainder ); - + if( response !== null ){ // store the response as is in a new element //TODO:?? store start, size as well? loader.data.push( response ); - + //TODO: these might not be the best way to split this up // fire the first load event (if this is the first batch) AND partial $( loader ).trigger( LOADED_NEW_EVENT, [ response, start, size ] ); - + //RECURSION: var next = start + size, remainder = loader.size; @@ -208,12 +197,12 @@ loader.delay ); loader.log( '\t currentIntervalId:', loader.currentIntervalId ); - + // otherwise (base-case), don't do anything } else { loadFinished(); } - + } else { //response === null --> base-case, server sez nuthin left loadFinished(); } @@ -228,10 +217,10 @@ if( callback ){ callback( loader.data ); } } }, - + toString : function(){ return 'LazyDataLoader'; } }); - + loader.initialize( config ); return loader; } 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.