Skip to content

fix: make iterable returned from rpc call work correctly #127

Merged
ryanking13 merged 2 commits into
mainfrom
gyeongjae/binding-wrapper-sequence
Jun 18, 2026
Merged

fix: make iterable returned from rpc call work correctly #127
ryanking13 merged 2 commits into
mainfrom
gyeongjae/binding-wrapper-sequence

Conversation

@ryanking13

Copy link
Copy Markdown
Contributor

Fixes another bug reported in #125

Durable Objects Storage returns a cursor when ctx.storage.sql.exec("...") is called. This cursor is an iterable that users can do for row in cursor.

However, our _BindingWrapper does not handle iterable correctly, hence users were getting the following error message.

attribute name must be string, not 'int'

@ask-bonk

ask-bonk Bot commented Jun 16, 2026

Copy link
Copy Markdown

Good, the regex match works because re.search does substring matching. Now let me think through the review carefully.

Analysis of the changes:

  1. _real_name property (lines 1108-1113): Gets the JS constructor name for better error messages. Uses a bare except Exception as a fallback, which is reasonable here.

  2. __getitem__ for int keys (lines 1151-1154): When key is an int, it uses bracket indexing on the JS binding instead of getattr. This is the core fix for the toArray() result where arr[0] was failing with "attribute name must be string, not 'int'". Looks correct.

  3. __iter__ (lines 1156-1161): Delegates iteration to the underlying binding, converting each item. Raises a descriptive TypeError if the binding isn't iterable. This is the core fix for for row in cursor.

  4. __len__ (lines 1163-1167): Checks for JS length property and returns it. Raises TypeError if not available.

Potential issues to evaluate:

  • __len__ returning binding.length: This returns the raw JS value. For a JsProxy, binding.length should return a Python int, so this should be fine. No conversion needed.

  • __iter__ and _convert_result: Each iterated item goes through _convert_result, which calls python_from_rpc. For SQL cursor rows, these would be JsProxy objects representing row objects. If python_from_rpc can't fully convert them (they remain JsProxy), they'll be re-wrapped as _BindingWrapper. This is actually the correct behavior since the test then accesses row["id"] and row["val"] via __getitem__, which delegates to getattr on the binding.

  • __getitem__ for int vs str: The int branch does self._binding[key] while the str branch does getattr(self._binding, key). This is correct because JS array-like indexing uses brackets while JS property access uses dot notation (mapped to getattr in Pyodide).

  • Test coverage: Tests cover iteration (test_sql_cursor_iter), integer indexing via toArray() (test_sql_cursor_toarray_getitem_int), negative tests for non-iterable bindings (test_binding_not_iterable), and non-lennable bindings (test_binding_no_len). Good coverage.

  • Edge case: __iter__ on something with __iter__ that isn't really iterable? The hasattr(binding, "__iter__") check is reasonable. Pyodide JsProxy objects get __iter__ if the JS object is iterable (has Symbol.iterator).

I don't see any actionable bugs, security issues, backward compatibility problems, or logic errors. The change is well-scoped and the tests are thorough.

LGTM

github run

Comment thread packages/runtime-sdk/src/workers/_workers.py Outdated
@ryanking13 ryanking13 merged commit 36dc659 into main Jun 18, 2026
12 checks passed
@ryanking13 ryanking13 deleted the gyeongjae/binding-wrapper-sequence branch June 18, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants