Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 60 lines
 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.

2 comments

Martin Miller 20 years, 9 months ago  # | flag

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.

Moe Aboulkheir 18 years, 2 months ago  # | flag

Bad Date() Marshalling?

$ python
>>> from mx import DateTime
>>> print DateTime.now().ticks()
# prints "1140120746.088465" (today is 2006-02-15)
^D
$ smjs # spidermonkey JS interpreter
// let's parse the value we got from mx.DateTime.ticks()
js> print(new Date(1140120746.088465));
// prints "Wed Jan 14 1970 06:42:00 GMT+0200 (SAST)"
// oops! how about:
js> print(new Date(1140120746.088465 * 1000));
// prints "Thu Feb 16 2006 22:12:26 GMT+0200 (SAST)",
// which is right