When writing web applications with an application framework like SkunkWeb, it is sometimes desirable to make Python data available to client-side Javascript. This recipe creates a function, to_js, that marshals Python data into a Javascript string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | """
contains a function for marshalling literal (and only literal) Python
data into Javascript. Supports Python None, strings, ints and floats,
dates, lists, tuples and dictionaries.
"""
import re
_jsidentifierRE=re.compile(r'[a-zA-Z_\$][a-zA-Z_\$0-9]*$')
def is_valid_js_identifier(s):
try:
return bool(_jsidentifierRE.match(s))
except TypeError:
return 0
class MarshalException(ValueError):
pass
class InvalidIdentifierException(MarshalException):
pass
def get_identifier(s):
if is_valid_js_identifier(s):
return s
raise InvalidIdentifierException, \
"not a valid Javascript identifier: %s" % s
_marshalRegistry={str: repr,
int: repr,
float: repr,
type(None): lambda x: 'null'}
def _seq_to_js(s):
return "[%s]" % ', '.join([to_js(y) for y in s])
_marshalRegistry[list]=_seq_to_js
_marshalRegistry[tuple]=_seq_to_js
def _dict_to_js(d):
s=', '.join(["%s: %s" % (get_identifier(k), to_js(v)) \
for k, v in d.items()])
return "{%s}" % s
_marshalRegistry[dict]=_dict_to_js
try:
import mx.DateTime as M
except ImportError:
pass
else:
def _date_to_js(dt):
return "new Date(%s)" % int(dt.ticks())
_marshalRegistry[type(M.now())]=_date_to_js
def to_js(obj):
# the isinstance test permits type subclasses
for k in _marshalRegistry:
if isinstance(obj, k):
return _marshalRegistry[k](obj)
raise MarshalException, obj
|
The to_js function supports Python integers, floats, mx.DateTime dates (if mx.DateTime can be imported), strings, None, sequences, and dictionaries. It will complain loudly if you try to pass another type. The reliance on type rather than interface is rather unfortunate, in that Python classes that emulate the behavior of the builtin containers may not be their subclasses; with some database drivers, for instance, the result set returned for a row behaves a bit like a list and a bit like a dictionary, but is an instance of neither. To marshal such an object with this code, you have to cast it first. The recipe could conceivably be refined by replacing the type checks for those containers with some other heuristic for determining whether an object is "close enough" to a list/tuple/dict, but no way of doing that that wasn't ugly and potentially error-prone has occurred to me.
Interface Checking. Idea: To avoid type checks, it might be possible to adapt Raymond Hettinger's "Metaclass for Interface Checking" recipe described at:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204349
to determine if something was "close enough" to a list/tuple/dict etc.
Bad Date() Marshalling?