Skip to content

Commit 41336aa

Browse files
committed
Add PyProxy set and delete
1 parent b0abc37 commit 41336aa

3 files changed

Lines changed: 102 additions & 3 deletions

File tree

Lib/test/test_pyodide.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,5 +459,16 @@ def test_pyproxy_get(self):
459459

460460
def test_pyproxy_length(self):
461461
f = run_js("(x) => x.length")
462-
for x in [{"x", "y", "z"}, [1,2,3], (1, "x"), {"x":2}]:
462+
for x in [{"x", "y", "z"}, [1, 2, 3], (1, "x"), {"x": 2}]:
463463
self.assertEqual(f(x), len(x))
464+
465+
def test_pyproxy_set_del(self):
466+
d = {"x": 2}
467+
run_js("(d) => d.set('y', 3)")(d)
468+
self.assertEqual(d["x"], 2)
469+
self.assertEqual(d["y"], 3)
470+
run_js("(d) => d.set('x', 7)")(d)
471+
self.assertEqual(d["x"], 7)
472+
self.assertEqual(d["y"], 3)
473+
run_js("(d) => d.delete('x')")(d)
474+
self.assertNotIn("x", d)

Modules/_pyodide_core/pyproxy.c

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define HAS_CONTAINS (1 << 0)
88
#define HAS_GET (1 << 1)
99
#define HAS_LENGTH (1 << 2)
10+
#define HAS_SET (1 << 3)
1011
#define IS_CALLABLE (1 << 4)
1112

1213
EM_JS_VAL(JsVal, pyproxy_new, (PyObject * ptrobj), {
@@ -69,6 +70,7 @@ type_getflags(PyTypeObject* obj_type)
6970
result |= HAS_GET;
7071
}
7172
SET_FLAG_IF(HAS_LENGTH, seq_proto->sq_length || map_proto->mp_length);
73+
SET_FLAG_IF(HAS_SET, map_proto->mp_ass_subscript || seq_proto->sq_ass_item);
7274
SET_FLAG_IF(IS_CALLABLE, obj_type->tp_call);
7375
return result;
7476

@@ -129,7 +131,44 @@ _pyproxy_getitem(PyObject* pyobj,
129131
return JS_ERROR;
130132
}
131133
return result;
132-
};
134+
}
135+
136+
EMSCRIPTEN_KEEPALIVE int
137+
_pyproxy_setitem(PyObject* pyobj, JsVal jskey, JsVal jsval)
138+
{
139+
bool success = false;
140+
PyObject* pykey = NULL;
141+
PyObject* pyval = NULL;
142+
143+
pykey = js2python(jskey);
144+
FAIL_IF_NULL(pykey);
145+
pyval = js2python(jsval);
146+
FAIL_IF_NULL(pyval);
147+
FAIL_IF_MINUS_ONE(PyObject_SetItem(pyobj, pykey, pyval));
148+
149+
success = true;
150+
finally:
151+
Py_CLEAR(pykey);
152+
Py_CLEAR(pyval);
153+
return success ? 0 : -1;
154+
}
155+
156+
EMSCRIPTEN_KEEPALIVE int
157+
_pyproxy_delitem(PyObject* pyobj, JsVal idkey)
158+
{
159+
bool success = false;
160+
PyObject* pykey = NULL;
161+
162+
pykey = js2python(idkey);
163+
FAIL_IF_NULL(pykey);
164+
FAIL_IF_MINUS_ONE(PyObject_DelItem(pyobj, pykey));
165+
166+
success = true;
167+
finally:
168+
Py_CLEAR(pykey);
169+
return success ? 0 : -1;
170+
}
171+
133172

134173
/**
135174
* This sets up a call to _PyObject_Vectorcall. It's a helper function for

Modules/_pyodide_core/pyproxy.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ declare function _pyproxy_getflags(ptr: number): number;
1919

2020
declare function __pyproxy_contains(ptr: number, key: any): number;
2121
declare function __pyproxy_getitem(ptr: number, key: any): any;
22+
declare function __pyproxy_setitem(ptr: number, key: any, val: any): number;
23+
declare function __pyproxy_delitem(ptr: number, key: any): number;
2224
declare function __pyproxy_apply(
2325
ptr: number,
2426
jsargs: any[],
@@ -273,6 +275,7 @@ function getPyProxyClass(flags: number) {
273275
[HAS_CONTAINS, PyContainsMethods],
274276
[HAS_GET, PyGetItemMethods],
275277
[HAS_LENGTH, PyLengthMethods],
278+
[HAS_SET, PySetItemMethods],
276279
[IS_CALLABLE, PyCallableMethods],
277280
];
278281
for (let [feature_flag, methods] of FLAG_TYPE_PAIRS) {
@@ -504,7 +507,7 @@ interface PyProxyWithLength extends PyLengthMethods {}
504507

505508
// Controlled by HAS_LENGTH, appears for any object with __len__ or sq_length
506509
// or mp_length methods
507-
export class PyLengthMethods {
510+
class PyLengthMethods {
508511
/**
509512
* The length of the object.
510513
*/
@@ -526,6 +529,52 @@ export class PyLengthMethods {
526529
}
527530

528531

532+
533+
interface PyProxyWithSet extends PySetItemMethods {}
534+
// Controlled by HAS_SET, appears for any class with __setitem__, __delitem__,
535+
// mp_ass_subscript, or sq_ass_item.
536+
class PySetItemMethods {
537+
/**
538+
* This translates to the Python code ``obj[key] = value``.
539+
*
540+
* @param key The key to set.
541+
* @param value The value to set it to.
542+
*/
543+
set(key: any, value: any) {
544+
let ptrobj = _getPtr(this);
545+
let err;
546+
try {
547+
Py_ENTER();
548+
err = __pyproxy_setitem(ptrobj, key, value);
549+
Py_EXIT();
550+
} catch (e) {
551+
API.fatal_error(e);
552+
}
553+
if (err === -1) {
554+
_pythonexc2js();
555+
}
556+
}
557+
/**
558+
* This translates to the Python code ``del obj[key]``.
559+
*
560+
* @param key The key to delete.
561+
*/
562+
delete(key: any) {
563+
let ptrobj = _getPtr(this);
564+
let err;
565+
try {
566+
Py_ENTER();
567+
err = __pyproxy_delitem(ptrobj, key);
568+
Py_EXIT();
569+
} catch (e) {
570+
API.fatal_error(e);
571+
}
572+
if (err === -1) {
573+
_pythonexc2js();
574+
}
575+
}
576+
}
577+
529578
function _adjustArgs(proxyobj: any, jsthis: any, jsargs: any[]): any[] {
530579
const { captureThis, boundArgs, boundThis, isBound } =
531580
_getAttrs(proxyobj).props;

0 commit comments

Comments
 (0)