Fix accessing Python reserved word attributes on js objects (#3926)

Removes some of the compatibility break introduced in #3617.
Update faq too.
This commit is contained in:
Hood Chatham 2023-06-14 16:21:52 -07:00 committed by GitHub
parent e3789b8dfa
commit 54540364d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 24 deletions

View File

@ -53,6 +53,11 @@ myst:
`{env: {HOME: whatever_directory}}`.
{pr}`3870`
- {{ Fix }} `getattr(jsproxy, 'python_reserved_word')` works as expected again
(as well as `hasattr` and `setattr`). This fixes a regression introduced in
{pr}`3617`.
{pr}`3926`
### Packages
- OpenBLAS has been added and scipy now uses OpenBLAS rather than CLAPACK

View File

@ -441,30 +441,70 @@ lambda = (x) => {return x + 1};
pyodide.runPython(`from js import lambda; print(lambda(1))`);
```
For JS objects with attributes that are Python reserved keywords, {py:func}`getattr` and {py:func}`setattr` can be used to access the attribute by name:
If you try to access a Python reserved word followed by one or more underscores
on a `JsProxy`, Pyodide will remove a single underscore:
```pyodide
pyodide.runPython(`
from js import Array
print(Array.from_([1,2,3]))
`);
```
If you meant to access the keyword with an underscore at the end, you'll have to
add an extra one:
```pyodide
globalThis.lambda = 7;
globalThis.lambda_ = 8;
pyodide.runPython(`
from js import lambda_, lambda__
print(lambda_, lambda__) # 7, 8
`);
```
Another example:
```pyodide
people = {global: "lots and lots"};
pyodide.runPython(`
from js import people
# the dir contains global_ but not global:
assert "global_" in dir(people)
assert "global" not in dir(people)
people.global_ = 'even more'
print(people.global_)
`);
```
You can also use `getattr`, `setattr`, and `delattr` to access the attribute:
```pyodide
pyodide.runPython(`
from js import Array
fromFunc = getattr(Array, 'from')
print(fromFunc([1,2,3]))
`);
`);
people = {global: "lots and lots"};
pyodide.runPython(`
from js import people
setattr(people, 'global', 'even more')
print(getattr(people, 'global'))
`);
`);
```
For objects whose names are keywords, one can similarly use {py:func}`getattr` on the `js` module itself:
For JavaScript globals whose names are keywords, one can similarly use
{py:func}`getattr` on the `js` module itself:
```pyodide
lambda = (x) => {return x + 1};
globalThis.lambda = 7;
globalThis.lambda_ = 8;
pyodide.runPython(`
import js
js_lambda = getattr(js, 'lambda')
print(js_lambda(1))
`);
js_lambda_ = getattr(js, 'lambda_')
js_lambda__ = getattr(js, 'lambda__')
print(js_lambda, js_lambda_, js_lambda__) # 7, 7, 8
`);
```

View File

@ -947,7 +947,7 @@ EM_JS(bool, isReservedWord, (int word), {
* action: a javascript string, one of get, set, or delete. For error reporting.
* word: a javascript string, the property being accessed
*/
EM_JS(int, normalizeReservedWords, (int action, int word), {
EM_JS(int, normalizeReservedWords, (int word), {
// clang-format off
// 1. if word is not a reserved word followed by 0 or more underscores, return
// it unchanged.
@ -960,19 +960,14 @@ EM_JS(int, normalizeReservedWords, (int action, int word), {
if (noTrailing_ !== word) {
return word.slice(0, -1);
}
// 3. If the word is exactly a reserved word, this is an error.
let action_ptr = stringToNewUTF8(action);
let word_ptr = stringToNewUTF8(word);
_setReservedError(action_ptr, word_ptr);
_free(action_ptr);
_free(word_ptr);
throw new Module._PropagatePythonError();
// 3. If the word is exactly a reserved word, return it unchanged
return word;
// clang-format on
});
EM_JS_REF(JsRef, JsObject_GetString, (JsRef idobj, const char* ptrkey), {
let jsobj = Hiwire.get_value(idobj);
let jskey = normalizeReservedWords("get", UTF8ToString(ptrkey));
let jskey = normalizeReservedWords(UTF8ToString(ptrkey));
if (jskey in jsobj) {
return Hiwire.new_value(jsobj[jskey]);
}
@ -985,7 +980,7 @@ JsObject_SetString,
(JsRef idobj, const char* ptrkey, JsRef idval),
{
let jsobj = Hiwire.get_value(idobj);
let jskey = normalizeReservedWords("set", UTF8ToString(ptrkey));
let jskey = normalizeReservedWords(UTF8ToString(ptrkey));
let jsval = Hiwire.get_value(idval);
jsobj[jskey] = jsval;
});
@ -993,7 +988,7 @@ JsObject_SetString,
EM_JS_NUM(errcode, JsObject_DeleteString, (JsRef idobj, const char* ptrkey), {
let jsobj = Hiwire.get_value(idobj);
let jskey = normalizeReservedWords("delete", UTF8ToString(ptrkey));
let jskey = normalizeReservedWords(UTF8ToString(ptrkey));
delete jsobj[jskey];
});

View File

@ -2409,12 +2409,15 @@ def test_python_reserved_keywords(selenium):
assert o.async___ == 3
assert getattr(o, "async_") == 1 # noqa: B009
assert getattr(o, "async__") == 2 # noqa: B009
with pytest.raises(AttributeError, match="async"):
getattr(o, "async")
with pytest.raises(AttributeError, match="reserved.*set.*'async_'"):
setattr(o, "async", 2)
with pytest.raises(AttributeError, match="reserved.*delete.*'async_'"):
delattr(o, "async")
assert getattr(o, "async") == 1
assert hasattr(o, "async_")
assert hasattr(o, "async")
setattr(o, "async", 2)
assert o.async_ == 2
delattr(o, "async")
assert not hasattr(o, "async_")
assert not hasattr(o, "async")
@run_in_pyodide