Conversation
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Secrets | View in Orca |
Lets the async client run inside Pyodide/WebAssembly, where grpcio has no wheel and sockets do not exist: gRPC goes over grpc-web (fetch) and REST over the browser's fetch, against Weaviate core's native /v1/grpc-web endpoint (1.38.3+). - packages/web: the weaviate-client-web companion distribution (pure-Python grpc shim, GrpcWebChannel, grpc-web framing, pyfetch/httpx senders, httpx-over-fetch transport) - weaviate/__init__.py: a bare `import weaviate` bootstraps the companion under Emscripten; a missing companion raises an install hint - setup.cfg: grpcio is skipped under Emscripten - connect/base.py: grpc_path_prefix on ConnectionParams (port collision allowed with a prefix), fail-fast check at client construction for a sync client or a missing shim, grpc-web.path_prefix channel option for the shim - connect/helpers.py: under Emscripten the async helpers pin gRPC to the REST endpoint under /v1/grpc-web (Con006 when a caller's gRPC endpoint is discarded); unchanged elsewhere - connect/v4.py: sync client rejected at construction under Emscripten; the gRPC ping error is passed through so WeaviateGRPCUnavailableError can name the real cause (grpc-web branch without firewall/port advice; 404 names server < 1.38.3 or a wrong prefix); fetch failures of the pypi version check are ignored - collections/batch/async_.py: batch.stream() fails fast under grpc-web, pointing to insert_many() - embedded.py: explicit error under Emscripten; proto/v1: grpcio version fallback when dist metadata is absent (Emscripten only), drift-pinned by proto_test - CI: grpc-web package tests (3.10-3.14) and a real-Pyodide e2e job under Node Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUNU7QgDr9MmFZnjKY9zFN
0b58414 to
21dc4b5
Compare
6705ab1 to
cc13ad9
Compare
Comment/docstring wording only, no code changes: shorter sentences, plainer words (shim -> replacement, honour -> use, multiplexed -> shares, discarded -> ignored, REST listener -> REST endpoint), same meaning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUNU7QgDr9MmFZnjKY9zFN
There was a problem hiding this comment.
Pull request overview
Adds Pyodide/WASM support to the async client through a companion grpc-web and browser-fetch transport.
Changes:
- Adds the
weaviate-client-webgrpc-web transport and shim. - Integrates automatic WASM routing, diagnostics, and unsupported-feature guards.
- Adds unit and Pyodide end-to-end CI coverage.
Reviewed changes
Copilot reviewed 32 out of 34 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
weaviate/warnings.py |
Adds endpoint-override warning. |
weaviate/proto/v1/__init__.py |
Adds Emscripten grpcio fallback. |
weaviate/exceptions.py |
Adds grpc-web diagnostics. |
weaviate/embedded.py |
Rejects embedded mode under WASM. |
weaviate/connect/v4.py |
Integrates grpc-web lifecycle and errors. |
weaviate/connect/helpers.py |
Routes async helpers through grpc-web. |
weaviate/connect/base.py |
Adds grpc-web connection parameters. |
weaviate/collections/batch/async_.py |
Rejects streaming batches over grpc-web. |
weaviate/__init__.py |
Bootstraps the companion package. |
test/test_wasm_compat.py |
Tests WASM-specific behavior. |
test/test_connection_params.py |
Tests grpc-web connection configuration. |
setup.cfg |
Excludes grpcio under Emscripten. |
pyrightconfig.json |
Type-checks the web package. |
proto_test/test_proto.py |
Tests grpcio fallback compatibility. |
packages/web/tests/test_transport.py |
Tests grpc-web transport behavior. |
packages/web/tests/test_single_import.py |
Tests automatic bootstrap. |
packages/web/tests/test_shim_install.py |
Tests shim installation. |
packages/web/tests/test_httpx_fetch.py |
Tests fetch-based REST transport. |
packages/web/tests/test_framing.py |
Tests grpc-web framing. |
packages/web/tests/conftest.py |
Configures package test imports. |
packages/web/src/weaviate_client_web/py.typed |
Marks the package as typed. |
packages/web/src/weaviate_client_web/_shim.py |
Implements the grpc API shim. |
packages/web/src/weaviate_client_web/_sender.py |
Implements HTTP senders. |
packages/web/src/weaviate_client_web/_httpx_fetch.py |
Implements fetch-backed HTTPX transport. |
packages/web/src/weaviate_client_web/_framing.py |
Implements grpc-web framing. |
packages/web/src/weaviate_client_web/_channel.py |
Implements the grpc-web channel. |
packages/web/src/weaviate_client_web/__init__.py |
Exposes and bootstraps the package. |
packages/web/README.md |
Documents usage and limitations. |
packages/web/pyproject.toml |
Defines companion package metadata. |
ci/pyodide-e2e/run.mjs |
Runs tests inside Pyodide. |
ci/pyodide-e2e/package.json |
Pins the Pyodide runtime. |
ci/pyodide-e2e/e2e.py |
Exercises WASM client workflows. |
.gitignore |
Ignores web-package build artifacts. |
.github/workflows/main.yaml |
Adds grpc-web and Pyodide CI jobs. |
Suppressed comments (1)
weaviate/connect/helpers.py:538
- This value comparison cannot tell an omitted argument from an explicitly supplied
grpc_port=50051(which the helper's own examples use). Under WASM that explicitly requested endpoint is still replaced by the REST port, but Con006 is suppressed, contrary to the documented promise that caller-supplied ports are warned about. Preserve argument presence with a sentinel default, then resolve the omitted value to 50051.
grpc_chosen_by_caller=grpc_port != _LOCAL_GRPC_PORT_DEFAULT,
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…on006 - _encode_timeout/_abort_signal_ms: a huge finite timeout (e.g. 1e308) overflowed to infinity in the millisecond multiplication and raised OverflowError; both now compare against their cap before multiplying (no deadline / capped, as documented) - the "server too old / wrong grpc-web path" diagnosis now also requires the channel's synthetic "HTTP 404"/"HTTP 405" marker in the details, so a genuine UNIMPLEMENTED from a routed endpoint gets the generic message instead; the string contract is noted on both sides - Con006 prints grpc:// / grpcs:// schemes on both endpoints, so a mismatch in grpc_secure alone no longer shows two identical strings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUNU7QgDr9MmFZnjKY9zFN
| async def _fetch_handle_async_request( | ||
| self: httpx.AsyncHTTPTransport, request: httpx.Request | ||
| ) -> httpx.Response: | ||
| from pyodide.http import pyfetch # type: ignore[import-not-found] |
There was a problem hiding this comment.
Why is this import within the function instead of at the top of the file?
| ``pyodide`` does not exist). ``pyfetch`` has no timeout parameter of its own; the | ||
| call deadline is enforced by ``GrpcWebChannel._unary`` via ``asyncio.wait_for``. | ||
| """ | ||
| from pyodide.http import pyfetch # type: ignore[import-not-found] |
There was a problem hiding this comment.
Ah I see it's so that the import is lazy
This makes me wonder, should we instead provide grpc-web functionality using the extras syntax, i.e.
pip install weaviate-client[grpc-web]
so that pyodide and cpython users don't conflict?
The default installation pip install weaviate-client would ship as it is right now and the extras would bundle this extra weaviate_client_web dep into it. wdyt?
There was a problem hiding this comment.
Extras can only add dependencies, so [grpc-web] would just make the extra pull in weaviate-client-web as a dependency, it can't drop grpcio and it can't change behavior in code (that's the check sys.platform == "emscripten" inside the base package). The only new dep for the web packager is anyio, which is already gated with the emscripten check.
The emscripten check already keeps CPython and Pyodide installs from conflicting. Once published, users would install it with micropip.install('weaviate-client-web'). We can theoretically add the extras logic, but functionally it wouldn't add much benefit and the current separation logic in the code would remain the same.
The question is how we want users to install it and if we are managing two packages in the same repo, as I see it the options are:
- Keep the separate package as is and install with
micropip.install('weaviate-client-web') - Keep the separate package as is and install with
micropip.install('weaviate-client[grpc-web]') - Move the web logic to the base package as one package so we have only one that installs with
micropip.install('weaviate-client').
There was a problem hiding this comment.
Agreed, my point here is that weaviate_client_web dep should not be added at all when calling pip install weaviate-client so it should be safe to import from pyodide.http import pyfetch from the top of the file here instead of inside the function body since these files won't be called at all when the dep is absent
Agreed that we need the emscripten check when running in grpc-web-mode but it should be conditional on the presence of the grpc-web dep. This point is in relation to the docstring comment:
"""
Imports ``pyodide.http`` lazily so this module stays importable on CPython (where ``pyodide`` does not exist)
"""The logical implementation of the grpc-web functionality shouldn't have an import hack applied to it because it may be imported into an environment that doesn't support it. Instead, the weavate_client_web package should be defined coherently and consistently to be used in its respective environment
By requiring users do pip install weaviate-client[grpc-web] when they're running in emscripten, they guarantee that their client will have the necessary dependencies installed. Then, if they run pip install weaviate-client, the weaviate_client_web dep is never installed since it is not relevant thereby allowing the package itself to be defined coherently without worrying about whether it can run in cpython
wdyt?
There was a problem hiding this comment.
Agreed and it's implemented now. Just a notice, with the package importable only under Emscripten, its unit tests can't run as CPython pytest anymore.
…yodide unit tests The web package now imports pyodide at module scope and is importable only under Emscripten/Pyodide. The base client gains a grpc-web extra (weaviate-client[grpc-web], marker-gated to Emscripten) as the documented install path. The unit tests stay in packages/web/tests but run inside Pyodide via ci/pyodide-e2e/units.mjs (async-native, no pytest); a conftest keeps CPython pytest from collecting them, and the CPython-only testing seams (install force flags, make_httpx_sender) are removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
…ls_scm The companion's version now derives from the repository's git tags (setuptools_scm with its root at the repo root) instead of a hardcoded 0.0.1.dev0, so every build carries the same version as weaviate-client; the pyodide-e2e job asserts the two built wheels match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
… time packages/web/setup.py injects weaviate-client==<version> into the companion's requirements when the wheel is built, so a mismatched pair can never resolve at install time — the two packages share private contracts (error-string markers, exception constants). Consequence: every tag must publish both packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
pytest executes the packages/web/tests suite inside Pyodide under Node with --experimental-wasm-jspi: async tests run through run_until_complete, which stack-switches when the runner enters via callPromising(). This replaces the hand-rolled runner/harness with standard pytest collection, fixtures and parametrize, and makes empty or partial collection fail the run (pytest exit codes reach JS as a return value, never as an exception across the bridge). The base client's import-hook branches (missing companion, broken companion, grpc-present fall-through) are covered by subprocess tests in test/test_wasm_compat.py on CPython; the bootstrap scenario in units.mjs keeps the one path that needs real Pyodide, micropip and the wheels. Also strengthens assertions the port had weakened (identity restore on uninstall, the literal grpc version pin, install() returning True) and fixes two stale README claims about direct installs and off-Emscripten imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
The release job builds the weaviate-client-web wheel into dist and asserts both packages carry the same version on the artifacts actually uploaded — the companion's weaviate-client==<version> pin makes a base-only release leave the [grpc-web] extra unresolvable. The companion is wheel-only: its setup.py resolves the lockstep version from git tags, which an unpacked sdist would not have. The wheel artifact for the GitHub release includes it too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
pytest-asyncio 0.25.3 declares pytest<9,>=8.2, so pin pytest 8.4.2 instead of the bundled 9.0.2 rather than depending on micropip tolerating the conflict. pytest-asyncio stays on 0.25.x: the asyncio.Runner-based 1.x fails under JSPI stack switching, while 0.25.x's run_until_complete-based execution works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
…nits refactor(grpc-web): top-level pyodide imports, [grpc-web] extra, in-Pyodide unit tests
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
There was a problem hiding this comment.
🟡 Changes recommended
Several transport edge cases can produce malformed requests, incorrect diagnostics, or disabled timeouts, and the stated CI matrix is absent.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 33/35 changed files
- Comments generated: 5
- Review effort level: Balanced
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2142 +/- ##
==========================================
+ Coverage 86.64% 88.77% +2.13%
==========================================
Files 300 306 +6
Lines 23172 24077 +905
==========================================
+ Hits 20077 21374 +1297
+ Misses 3095 2703 -392 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ero timeouts - split_response accepts exactly one uncompressed trailer frame: a second trailer could overwrite the first one's grpc-status (turning an error into a fabricated OK), and flag 0x81 (compressed trailer) was parsed as a plain trailer instead of being rejected. - Call metadata is folded before the grpc-web protocol headers are set, so additional_headers can no longer replace content-type/accept/x-grpc-web/ x-user-agent and break every RPC. - HTTP 405 maps to UNIMPLEMENTED like 404: both mean an HTTP route answered instead of the grpc-web endpoint, and the base client's wrong-path diagnosis keys on UNIMPLEMENTED. - A zero read timeout is an immediate deadline, matching native httpx and the package's own grpc-timeout encoding, instead of disabling the deadline and letting a stalled request hang. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b
What & why
Lets the async client run inside Pyodide/WebAssembly (marimo notebooks, browser, WASM workers), where
grpciohas no wheel and sockets don't exist. gRPC is re-routed over grpc-web (fetch), REST over the browser'sfetch— against Weaviate core's native/v1/grpc-webendpoint (default-on since 1.38.3).This is the trimmed successor of #2056 (kept open for reference): only what the WASM goal actually needs. The general fixes that were bundled there now ship separately:
fix/token-refresh-lifecycle): the async OIDC refresher must be an asyncio task because threads cannot start under Pyodide.Quickstart (Pyodide / marimo)
Under Emscripten all three async helpers (
use_async_with_local/_weaviate_cloud/_custom) pin gRPC to the REST endpoint under/v1/grpc-webthemselves — the same contract as the TS@weaviate/webclient'swebify(). There is nogrpc_path_prefixparameter on the helpers: pass gRPC arguments equal to the HTTP ones; anything else is discarded with aCon006warning. A grpc-web transcoder on a separate endpoint needs hand-builtConnectionParams(..., grpc_path_prefix=...). Off Emscripten nothing changes: the helpers build exactly the params they always did (pinned bytest_helper_params_off_emscripten_are_unchanged).Key pieces
packages/web/— companion distributionweaviate-client-web, defined for its one environment: it importspyodideat module scope and is only importable under Emscripten (no CPython seams — noforce=installs, no CPython sender)._shim.py(pure-Pythongrpcmodule shim),_channel.py(GrpcWebChannel: unary calls, metadata → headers with transport-owned protocol fields, grpc-web status →AioRpcError,grpc-timeoutencoding, 404/405 diagnostics),_framing.py(single uncompressed trailer enforced),_httpx_fetch.py(REST over JSfetch), README. Versioned in lockstep withweaviate-client(setuptools_scm from the same git tag) and pinned to it with==at build time, so mismatched pairs cannot resolve.Base client (no-ops off Emscripten):
weaviate/__init__.py— platform-guarded bootstrap: a bareimport weaviatesoft-imports the companion; a missing companion raises an install hint.setup.cfg—grpcio ; sys_platform != "emscripten"; thegrpc-webextra (weaviate-client-web ; sys_platform == "emscripten"), a no-op on CPython.connect/base.py—grpc_path_prefixonConnectionParams(port collision allowed with a prefix);_check_grpc_web_usableruns once, at client construction (sync client or missing shim fails fast);grpc-web.path_prefixchannel option for the shim.connect/helpers.py—_webify(): the async helpers route gRPC to the REST endpoint under Emscripten; Con006 when a caller's gRPC endpoint is discarded.connect/v4.py— sync client rejected at construction under Emscripten (a 3-line check in_ConnectionBase.__init__);_ping_grpcpasses the gRPC error through so the diagnostics can say what went wrong;_check_package_versionignoresOSError(fetch blocked by a page CSP).exceptions.py—WeaviateGRPCUnavailableErrorgains the gRPC code/details and a grpc-web branch (no firewall/port advice; a 404 names the two real causes: server < 1.38.3 or a wrong prefix).collections/batch/async_.py—batch.stream()fails fast under grpc-web, pointing toinsert_many()(bidi streaming is impossible over fetch).embedded.py— explicit error under Emscripten.proto/v1/__init__.py— grpcio version fallback when dist metadata is absent (Emscripten only), drift-pinned byproto_test.Dropped vs #2056 (and why)
_deadline/_Deadlinesplumbing acrossv4.pyand both batch modules: the web package already treats non-finite timeouts as "no deadline" in both transports (_encode_timeout,_abort_signal_ms); the one CPython effect (Thread.join(inf)in the sync batch-stream shutdown wait) is a separate one-line fix, not included here.ConnectionSync.__init__signature override (→ 3 lines in the base constructor) and the duplicate channel-level_check_grpc_web_usablecall.test_no_helper_takes_a_grpc_path_prefix, the Emscripten__get_timeouttest, the channel-level reject pair, the trivial platform-passes tests, …).CI / tests
The package suite runs as pytest inside Pyodide (
pyodide-e2ejob:node --experimental-wasm-jspi ci/pyodide-e2e/units.mjs— async tests execute via JSPI stack switching; a missing flag fails loudly), followed by the e2e suite against core-native/v1/grpc-web(Weaviate 1.39.0). There is deliberately no CPython matrix for the package: it only ever executes under Pyodide, whose bundle pins the interpreter (currently 3.14).requires-python >= 3.10stays: older Pyodide bundles ship 3.10–3.13 interpreters and the package is pure Python. The base client's import-hook branches run as subprocess tests intest/test_wasm_compat.pyon the full 3.10–3.14 matrix. ruff / flake8 / pyright coverpackages/web;build-and-publishbuilds, version-asserts and publishes both packages (the companion wheel-only). Locally: 139 pytest cases in Pyodide + bootstrap scenario; 36 intest_wasm_compat/test_connection_params; 4 inproto_test; pyright clean for the whole project.Remaining before release
Lockstep versioning, the
[grpc-web]extra and dual publishing are done. One manual check:PYPI_API_TOKENmust be able to create the newweaviate-client-webproject on first publish (a project-scoped token cannot — use an account-scoped token or register a pending publisher on PyPI first).🤖 Generated with Claude Code
https://claude.ai/code/session_01GUNU7QgDr9MmFZnjKY9zFN
https://claude.ai/code/session_01Caze9m6PBSfYkt77mMKj2b