#23 ✓resolved
AnC

support for file I/O (JS_EvaluateScript)

Reported by AnC | June 30th, 2009 @ 12:26 PM

It appears python-spidermonkey does not yet support Spidermonkey's filename argument:
http://stackoverflow.com/questions/1055850/file-i-o-in-spidermonkey...

Apparently, this variant of python-spidermonkey - in contrast to this one - is written almost entirely in C. (Not sure where the difference is between the two variants.)

Since I'm not C-literate, I don't even know where to start to confirm this or even submit a patch.

Any pointers would be welcome.

Comments and changes to this ticket

  • AnC

    AnC June 30th, 2009 @ 12:45 PM

    A grep on the repository reveals occurrences of JS_EvaluateScript in spidermonkey/libjs/jsapi.c - however, there's no hint in the documentation ($ pydoc spidermonkey) on how that might be exposed via the API.

  • Paul J. Davis

    Paul J. Davis June 30th, 2009 @ 01:28 PM

    • Tag set to execfile

    AnC,

    I sure don't provide access to functions like that, but do you really need id? Implementing someting like this should be relatively simple:

    >>> import spidermonkey
    >>> def loadfile(fname):
    ...    return open(fname).read()
    >>> rt = spidermonkey.Runtime()
    >>> cx = rt.new_context()
    >>> cx.add_global("loadfile", loadfile)
    >>> ret = cx.execute('eval(loadfile("foo.js")); f;')
    >>> ret.foo
    True
    

    This assumes that the file foo.js looks like this:

    var f = {"foo": true};
    

    Also of note, the version of python-spidermonkey on Google Code is quite old. Its the version I originally used when I took over development a few months ago. At first I was drawn in by the Pyrex but it turned out to cause more problems than it solved.

  • AnC

    AnC July 1st, 2009 @ 09:05 AM

    Thanks for the quick response!

    You're absolutely correct - I don't actually need JS_EvaluateScript.
    I had no idea you could provide access to Python functions from within Spidermonkey!

    That, I assume, shows a fundamental lack of understanding of python-spidermonkey.
    Sorry about that, I will review the README in more detail.

    Also thanks for the clarification on the different version of python-spidermonkey.
    If the previous maintainer is still around, it would be useful if he added a note to the Google Code page.

  • Paul J. Davis

    Paul J. Davis July 1st, 2009 @ 11:38 AM

    • State changed from “new” to “resolved”

    AnC

    No prob. I tried to make the library interaction as transparent as possible so you should be able to do most anything that comes to mind in terms of passing objects back and forth.

    Paul

  • AnC

    AnC July 6th, 2009 @ 01:23 PM

    I tried to make the library interaction as transparent as possible so you should be able to do most anything that comes to mind in terms of passing objects back and forth.

    I hate to be a pain and abuse this ticket for support - but I can't seem to pass objects between Python and JavaScript:

    import spidermonkey


    myVar1 = { "p1": 123, "p2": "abc" } myVar2 = {}


    rt = spidermonkey.Runtime() cx = rt.new_context()


    cx.add_global("myVar1", myVar1) cx.add_global("myVar2", myVar2)


    cx.execute("myVar1.p2 = 'xxx'"); cx.execute("delete myVar1.p1"); assert myVar1["p2"] == "xxx", "test myVar1 (a)" assert not myVar1.has_key("p1"), "test myVar1 (b)"


    cx.execute("myVar2 = { aaa: 'yyy' }"); assert myVar2.has_key("aaa"), "test myVar2 (a)" assert myVar2["aaa"] == "yyy", "test myVar2 (b)"
    The second set of assertions fails.
    Presumably that's because the assignment on line 17 creates a new object and thus doesn't reference the same memory address as before?
    What's the right way to work around that when working with complex objects?
  • Paul J. Davis

    Paul J. Davis July 6th, 2009 @ 01:44 PM

    AnC,

    If you're wanting to create a variable in JS and return it to Python, the proper method is to make that variable the result of the last expression, like such:

    myvar = cx.execute('var foo = {"foo": "bar"}; foo;')
    assert myvar["foo"] == "bar"
    

    Passing Python variables into JavaScript can be done using the Context.add_global method, or you can create a function and call it with python parameters, like such:

    jsfunc = cx.execute("function(obj) {if(obj.key === null) throw("is null!"); return 2;}")
    assert jsfunc({"key": "bar"}) == 2
    

    There are more examples on passing data in and out of JavaScript in the README and in the unit tests. Though I should probably write up a clearer document that describes the different methods for passing data in and out. And as a matter of fact I had another idea for passing data in and out...

    For this though:

    myVar2 = {"bb": "cc"}
    cx.add_global("myVar2", myVar2)
    cx.execute("myVar2 = { aaa: 'yyy' }");
    assert myVar2.has_key("aaa"), "test myVar2 (a)"
    assert myVar2["aaa"] == "yyy", "test myVar2 (b)"
    

    Assigning a value to the name 'myVar2' in the JavaScript interperter shouldn't affect the value of the Python 'myVar2'. The fact that you're naming them the same in both places does not confer any sort of relation between them. As you show above, you can affect the value that 'myVar1' refers to, but you're not affecting the name->value assignment.

    Paul

  • AnC

    AnC July 6th, 2009 @ 02:52 PM

    Thanks again for your considerate response, Paul!

    I had actually tried using return values early on, but objects are returned as "[object Object]":

    import spidermonkey


    rt = spidermonkey.Runtime() cx = rt.new_context()


    myVar = cx.execute("var obj = { aaa: 'yyy' }; obj;"); print myVar # DEBUG; outputs "[object Object]" assert myVar.has_key("aaa"), "test #1" # fails assert myVar["aaa"] == "yyy", "test #2" # fails
    However, that's not always the case:
    import spidermonkey


    rt = spidermonkey.Runtime() cx = rt.new_context()


    myVar = { "aaa": "yyy" } cx.add_global("obj", myVar) myVar = cx.execute("obj;"); print myVar # DEBUG; outputs "{'aaa': 'yyy'}" myVar = cx.execute("obj.bbb = 'zzz'; obj;"); print myVar # DEBUG; outputs "{'aaa': 'yyy', u'bbb': u'zzz'}" assert myVar.has_key("aaa"), "test #1" # passes assert myVar["aaa"] == "yyy", "test #2" # passes
    Note that I'm just modifying an existing global variable here, not returning a newly-created object.

    Perhaps this is because I'm using the somewhat outdated spidermonkey-bin package from the Ubuntu repositories (version info: JavaScript-C 1.7.0 2007-10-03)?

  • Paul J. Davis

    Paul J. Davis July 7th, 2009 @ 03:20 PM

    AnC,

    Hey, these two cases do seem quite similar, but they're quite different.

    import spidermonkey
    
    rt = spidermonkey.Runtime()
    cx = rt.new_context()
    
    # myVar is a wrapper to a JSObject* in the JavaScript virtual
    # machine. As such, it only has access to the JS functions and
    # a few special cases that are faked for Python.
    myVar = cx.execute("var obj = { aaa: 'yyy' }; obj;");
    
    # Here I could try and do something like pretty printing the object
    # or similar, but I'm not sure its such a good idea as it starts
    # to blur the line a bit too far between what is JS and what is Py
    print myVar # DEBUG; outputs "[object Object]"
    
    # Alternatively for debugging, you can call:
    print myVar.toSource()
    
    # Part of being a JavaScript object means it doesn't have all of
    # the Python dict methods. To test whether the key "aaa" is in the
    # object, you can use the 'in' keyword that has been proxied.
    # assert myVar.has_key("aaa"), "test [#1](/projects/26898/tickets/1 "Ticket #1")" # fails
    assert "aaa" in myVar
    
    # This test passes just fine for me.
    assert myVar["aaa"] == "yyy", "test [#2](/projects/26898/tickets/2 "Ticket #2")" # fails
    

    For the other case you're creating a Python object and modifying that object from JavaScript

    import spidermonkey
    
    rt = spidermonkey.Runtime()
    cx = rt.new_context()
    
    # myVar is a Python dict here
    myVar = { "aaa": "yyy" }
    cx.add_global("obj", myVar)
    
    myVar2 = cx.execute("obj;");
    # You should even be able to go further and say:
    assert id(myVar) == id(myVar2), "myVar2 refers to the same object as myVar1"
    
    # myVar is still a python dict. Using myVar2 above should hopefully make this
    # more clear.
    print myVar # DEBUG; outputs "{'aaa': 'yyy'}"
    
    # To point out that you're editing the original myVar live
    # I'm not returning it so you can see we can assert against
    # the original.
    # myVar = cx.execute("obj.bbb = 'zzz'; obj;");
    cx.execute("obj.bbb = 'zzz';")
    assert myVar["aaa"] == "yyy"
    assert myVar["bbb"] == "zzz"
    
    # and to make it more clear:
    assert myVar2["aaa"] == "yyy"
    assert myVar2["bbb"] == "zzz"
    

    Let me know if that helps.

  • AnC

    AnC July 9th, 2009 @ 04:23 AM

    Right, I shouldn't just assume that a JavaScript object is identical to a Python dictionary - I can see that now.

    In my particular case, I have a complex JS object like this:

    [

    {
        id: 123,
        name: "my item",
        description: "hello world",
        attributes: {
            shape: "square",
            color: "red"
        }
    },
    {
        id: 789,
        [...]
    }
    
    
    
    
    ]
    I guess JSON might be the answer here, returning the object as a string to Python and deserializing it there.
  • Paul J. Davis

    Paul J. Davis July 9th, 2009 @ 11:21 AM

    AnC,

    A thought just occurred to me, you might look into something like
    copy.deepcopy to see if you can use that to convert the object
    recrusively.

  • marc

    marc July 10th, 2009 @ 04:58 PM

    • Assigned user cleared.

    hi anc,

    i had a quite similar problem. i wanted to return a json string that i can return to the user without running a json.dumps(). all i got from spidermonkey was an object.

    you can use the code below and modify it slightly to convert your jsobject to a python dict.

    def convert(obj):
        obj_type = type(obj)
        
        # spidermonkey objects 
        if obj_type == spidermonkey.Object:
            data = []
            for k in obj:
                data.append(
                    ''.join(['"', k, '"', ':', self._convert(obj[k])])
                )
            return ''.join(['{', ','.join(data), '}'])  
        # spidermonkey array
        elif obj_type == spidermonkey.Array:
            data = []
            for i in obj:
                data.append(self._convert(i))
            return ''.join(['[', ','.join(data), ']'])
        # string / unicode
        elif obj_type == unicode or obj_type == str:
            obj = obj.replace('"', '\\"').replace('\n', '\\n') \
                .replace('\r', '\\r').replace('\t', '\\t')
            return ''.join(['"', obj, '"'])
        # everything else
        else:
            return unicode(obj)
    

    in this piece of crap code i don't care about int/long/float. it's just do demonstrate how to recursively convert an jsobj into a python dict.

    hope this helps,

    cheers marc

  • AnC

    AnC September 11th, 2009 @ 02:37 PM

    • Assigned user set to “Paul J. Davis”

    My apologies for the late response - I didn't have much time to attend to this lately.

    I'm now using JSON to exchange objects, as that seemed to be the least complex way.
    (It's a pity python-spidermonkey doesn't support Spidermonkey 1.8's built-in JSON methods yet, so I had to include a separate JavaScript library.)

    you might look into something like copy.deepcopy to see if you can use that to convert the object recrusively

    Not sure what that might look like, to be honest.

    i wanted to return a json string that i can return to the user without running a json.dumps()

    Thanks for sharing the code!
    Any particular reason you don't like json.dumps()?

  • Paul J. Davis

    Paul J. Davis September 11th, 2009 @ 03:08 PM

    AnC,

    Yeah, I was putting off the upgrade to 1.8 because I wanted to go directly to 1.8.1. In fact I don't even know if the native JSON methods are in 1.8. Either way, the Mozilla folks haven't put out a 1.8.1 tarball to use in building which is probably why almost everyone is still using 1.7 when embedding JavaScript.

    JSON is awesome, the reason I'd avoid it in this case though is that python-spidermonkey is trying to be transparent to the caller. Obviously, getting a spidermonkey.Array is a bit different than getting a native array but that's an implementation detail so that your objects are live in the JS VM. Ie, if you edit a spidermonkey.Array object, it's value in the JS vm should be updated as well. (I should probably write a test for that though).

    The deepcopy was to do similar to the function the code that marc posted above. Using a tad bit of python to convert spidermonkey.Object and spidermonkey.Array into Python dicts and arrays should be pretty simple.

    I looked back but I never said anything about not liking json.dumps near as I can tell. I don't like the obj -> JSON -> obj approach because its unnecessary, but then again, a working implementation is always better than a theoretical one :)

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

Python/JavaScript bridge module, making use of Mozilla's spidermonkey JavaScript implementation. Allows implementation of JavaScript classes, objects and functions in Python, and evaluation and calling of JavaScript scripts and functions respectively. Borrows heavily from Claes Jacobssen's Javascript Perl module, in turn based on Mozilla's 'PerlConnect' Perl binding.

People watching this ticket

Tags

Pages