mirror of https://github.com/pyodide/pyodide.git
* Fix #401: Use PEP 562 to make "import js" work * Add docs * Fix handling of missing attributes * Add CHANGELOG * Fix iodide import
This commit is contained in:
parent
6e561d2b19
commit
c8db5b6433
|
@ -2,6 +2,9 @@
|
|||
|
||||
**User improvements:**
|
||||
|
||||
- Thanks to PEP 562, you can now `import js` from Python and use it to access
|
||||
anything in the global Javascript namespace.
|
||||
|
||||
- Passing a Python object to Javascript always creates the same object in
|
||||
Javascript. This makes APIs like `removeEventListener` usable.
|
||||
|
||||
|
|
|
@ -154,10 +154,42 @@ var sys = pyodide.pyimport('sys');
|
|||
|
||||
## Using Javascript objects from Python
|
||||
|
||||
Javascript objects can be accessed from Python using the `from js import ...`
|
||||
syntax. The object must be in the global (`window`) namespace.
|
||||
Javascript objects can be accessed from Python using the special `js` module.
|
||||
This module looks up attributes of the global (`window`) namespace on the
|
||||
Javascript side.
|
||||
|
||||
```python
|
||||
from js import document
|
||||
document.title = 'New window title'
|
||||
import js
|
||||
js.document.title = 'New window title'
|
||||
```
|
||||
|
||||
### Performance considerations
|
||||
|
||||
Looking up and converting attributes of the `js` module happens dynamically. In
|
||||
most cases, where the value is small or results in a proxy, this is not an
|
||||
issue. However, if the value takes a long time to convert from Javascript to
|
||||
Python, you may want to store it in a Python variable or use the `from js import
|
||||
...` syntax.
|
||||
|
||||
For example, given this large Javascript variable:
|
||||
|
||||
```javascript
|
||||
var x = new Array(1000).fill(0)
|
||||
```
|
||||
|
||||
Use it from Python as follows:
|
||||
|
||||
```python
|
||||
import js
|
||||
x = js.x # conversion happens once here
|
||||
for i in range(len(x)):
|
||||
item = x[i] # we don't pay the conversion price each time here
|
||||
```
|
||||
|
||||
Or alternatively:
|
||||
|
||||
```python
|
||||
from js import x # conversion happens once here
|
||||
for i in range(len(x)):
|
||||
item = x[i] # we don't pay the conversion price each time here
|
||||
```
|
||||
|
|
|
@ -103,10 +103,10 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
|
|||
def create_root_element(self):
|
||||
# Designed to be overridden by subclasses for use in contexts other
|
||||
# than iodide.
|
||||
from js import iodide
|
||||
if iodide is not None:
|
||||
try:
|
||||
from js import iodide
|
||||
return iodide.output.element('div')
|
||||
else:
|
||||
except ImportError:
|
||||
return document.createElement('div')
|
||||
|
||||
def show(self):
|
||||
|
|
|
@ -183,7 +183,11 @@ EM_JS(void, hiwire_push_object_pair, (int idobj, int idkey, int idval), {
|
|||
|
||||
EM_JS(int, hiwire_get_global, (int idname), {
|
||||
var jsname = UTF8ToString(idname);
|
||||
return Module.hiwire_new_value(self[jsname]);
|
||||
if (jsname in self) {
|
||||
return Module.hiwire_new_value(self[jsname]);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
EM_JS(int, hiwire_get_member_string, (int idobj, int idkey), {
|
||||
|
|
151
src/jsimport.c
151
src/jsimport.c
|
@ -5,128 +5,71 @@
|
|||
#include "hiwire.h"
|
||||
#include "js2python.h"
|
||||
|
||||
static PyObject* original__import__;
|
||||
PyObject* globals = NULL;
|
||||
PyObject* original_globals = NULL;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PyObject_HEAD
|
||||
} JsImport;
|
||||
static PyObject* js_module = NULL;
|
||||
|
||||
static PyObject*
|
||||
JsImport_Call(PyObject* self, PyObject* args, PyObject* kwargs)
|
||||
JsImport_GetAttr(PyObject* self, PyObject* attr)
|
||||
{
|
||||
PyObject* name = PyTuple_GET_ITEM(args, 0);
|
||||
if (PyUnicode_CompareWithASCIIString(name, "js") == 0) {
|
||||
PyObject* locals = PyTuple_GET_ITEM(args, 2);
|
||||
PyObject* fromlist = PyTuple_GET_ITEM(args, 3);
|
||||
Py_ssize_t n = PySequence_Size(fromlist);
|
||||
PyObject* jsmod = PyModule_New("js");
|
||||
PyObject* d = PyModule_GetDict(jsmod);
|
||||
|
||||
int is_star = 0;
|
||||
if (n == 1) {
|
||||
PyObject* firstfromlist = PySequence_GetItem(fromlist, 0);
|
||||
if (PyUnicode_CompareWithASCIIString(firstfromlist, "*") == 0) {
|
||||
is_star = 1;
|
||||
}
|
||||
Py_DECREF(firstfromlist);
|
||||
}
|
||||
|
||||
if (is_star) {
|
||||
PyErr_SetString(PyExc_ImportError, "'import *' not supported for js");
|
||||
return NULL;
|
||||
} else {
|
||||
for (Py_ssize_t i = 0; i < n; ++i) {
|
||||
PyObject* key = PySequence_GetItem(fromlist, i);
|
||||
if (key == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
const char* c = PyUnicode_AsUTF8(key);
|
||||
if (c == NULL) {
|
||||
Py_DECREF(key);
|
||||
return NULL;
|
||||
}
|
||||
int jsval = hiwire_get_global((int)c);
|
||||
PyObject* pyval = js2python(jsval);
|
||||
hiwire_decref(jsval);
|
||||
if (PyDict_SetItem(d, key, pyval)) {
|
||||
Py_DECREF(key);
|
||||
Py_DECREF(pyval);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(key);
|
||||
Py_DECREF(pyval);
|
||||
}
|
||||
}
|
||||
|
||||
return jsmod;
|
||||
} else {
|
||||
// Fallback to the standard Python import
|
||||
return PyObject_Call(original__import__, args, kwargs);
|
||||
const char* c = PyUnicode_AsUTF8(attr);
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
int idval = hiwire_get_global((int)c);
|
||||
if (idval == -1) {
|
||||
PyErr_Format(PyExc_AttributeError, "Unknown attribute '%s'", attr);
|
||||
return NULL;
|
||||
}
|
||||
PyObject* result = js2python(idval);
|
||||
hiwire_decref(idval);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyTypeObject JsImportType = {
|
||||
.tp_name = "JsImport",
|
||||
.tp_basicsize = sizeof(JsImport),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_call = JsImport_Call,
|
||||
.tp_doc = "An import hook that imports things from Javascript."
|
||||
static PyObject*
|
||||
JsImport_Dir()
|
||||
{
|
||||
int idwindow = hiwire_get_global((int)"self");
|
||||
int iddir = hiwire_dir(idwindow);
|
||||
hiwire_decref(idwindow);
|
||||
PyObject* pydir = js2python(iddir);
|
||||
hiwire_decref(iddir);
|
||||
return pydir;
|
||||
}
|
||||
|
||||
static PyMethodDef JsModule_Methods[] = {
|
||||
{ "__getattr__",
|
||||
(PyCFunction)JsImport_GetAttr,
|
||||
METH_O,
|
||||
"Get an object from the global Javascript namespace" },
|
||||
{ "__dir__",
|
||||
(PyCFunction)JsImport_Dir,
|
||||
METH_NOARGS,
|
||||
"Returns a list of object name in the global Javascript namespace" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static PyObject*
|
||||
JsImport_New()
|
||||
{
|
||||
JsImport* self;
|
||||
self = (JsImport*)JsImportType.tp_alloc(&JsImportType, 0);
|
||||
return (PyObject*)self;
|
||||
}
|
||||
static struct PyModuleDef JsModule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"js",
|
||||
"Provides access to Javascript global variables from Python",
|
||||
0,
|
||||
JsModule_Methods
|
||||
};
|
||||
|
||||
int
|
||||
JsImport_init()
|
||||
{
|
||||
if (PyType_Ready(&JsImportType)) {
|
||||
PyObject* module_dict = PyImport_GetModuleDict();
|
||||
if (module_dict == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyObject* m = PyImport_AddModule("builtins");
|
||||
if (m == NULL) {
|
||||
js_module = PyModule_Create(&JsModule);
|
||||
if (js_module == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyObject* d = PyModule_GetDict(m);
|
||||
if (d == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
original__import__ = PyDict_GetItemString(d, "__import__");
|
||||
if (original__import__ == NULL) {
|
||||
return 1;
|
||||
}
|
||||
Py_INCREF(original__import__);
|
||||
|
||||
PyObject* importer = JsImport_New();
|
||||
if (importer == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (PyDict_SetItemString(d, "__import__", importer)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
m = PyImport_AddModule("__main__");
|
||||
if (m == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
globals = PyModule_GetDict(m);
|
||||
if (globals == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (PyDict_Update(globals, d)) {
|
||||
if (PyDict_SetItemString(module_dict, "js", js_module)) {
|
||||
Py_DECREF(js_module);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,4 @@
|
|||
int
|
||||
JsImport_init();
|
||||
|
||||
extern PyObject* globals;
|
||||
|
||||
#endif /* JSIMPORT_H */
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "hiwire.h"
|
||||
#include "python2js.h"
|
||||
|
||||
extern PyObject* globals;
|
||||
PyObject* globals;
|
||||
|
||||
PyObject* eval_code;
|
||||
PyObject* find_imports;
|
||||
|
@ -111,6 +111,30 @@ EM_JS(int, runpython_init_js, (), {
|
|||
int
|
||||
runpython_init_py()
|
||||
{
|
||||
PyObject* builtins = PyImport_AddModule("builtins");
|
||||
if (builtins == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyObject* builtins_dict = PyModule_GetDict(builtins);
|
||||
if (builtins_dict == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyObject* __main__ = PyImport_AddModule("__main__");
|
||||
if (__main__ == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
globals = PyModule_GetDict(__main__);
|
||||
if (globals == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (PyDict_Update(globals, builtins_dict)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyObject* m = PyImport_ImportModule("pyodide");
|
||||
if (m == NULL) {
|
||||
return 1;
|
||||
|
@ -131,8 +155,6 @@ runpython_init_py()
|
|||
return 1;
|
||||
}
|
||||
|
||||
Py_DECREF(m);
|
||||
Py_DECREF(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -303,11 +303,18 @@ def test_typed_arrays(selenium, wasm_heap, jstype, pytype):
|
|||
def test_import_js(selenium):
|
||||
result = selenium.run(
|
||||
"""
|
||||
from js import window
|
||||
window.title = 'Foo'
|
||||
window.title
|
||||
import js
|
||||
js.window.title = 'Foo'
|
||||
js.window.title
|
||||
""")
|
||||
assert result == 'Foo'
|
||||
result = selenium.run(
|
||||
"""
|
||||
dir(js)
|
||||
""")
|
||||
assert len(result) > 100
|
||||
assert 'document' in result
|
||||
assert 'window' in result
|
||||
|
||||
|
||||
def test_pyimport_multiple(selenium):
|
||||
|
|
Loading…
Reference in New Issue