Repository navigation
fix: facade calls that fail against the current server - #233
Merged
Merged
Conversation
Handle the operator and Cypher 202 (queued) responses, route write credentials the way reads do, and fix the monitor, cancel, progress, graph-info, table-list and async-facade paths. Add OperatorRunError, create_report periods, list_information_blocks scenario_id, and make dispose_schedule memo/reason optional.
jfrench9
added a commit
that referenced
this pull request
Oct 5, 2026
## Summary `OperationClient.monitor_operation` never applied its `timeout`. `connect()` blocks until the stream ends, so the wait could last forever. `GraphClient.materialize` (default 600 s) and `change_tier` (600 s, documented to raise `TimeoutError`) both pass a timeout expecting it to work. This was a follow-up from #233. ## Changes - **Timeout:** a timer closes the stream when `timeout` passes. The read ends and the call raises `TimeoutError("Operation … timed out after Ns")`, the same message as the async monitor. The timer is cancelled once the call returns, so a run that finishes in time is unaffected. - **Reconnect:** `SSEClient` no longer reopens the stream when `close()` lands during a reconnect backoff. - **Type:** `MonitorOptions.timeout` is now `Optional[float]`, in seconds. This widens the type and removes nothing. The other #233 follow-up, a `mode="stream"` operator call failing, turned out not to be a defect. The server only treats `mode=sync` specially (`routers/graphs/operator/execute.py`). `stream`, `async` and `auto` all answer `202`, which #233 already follows. ## Compatibility Rides a client **patch**. Behaviour change: `materialize()` and `change_tier()` now stop waiting at their timeout (600 s by default) instead of waiting forever. `change_tier` raises `TimeoutError`, as its docstring says. `materialize` returns a failed `MaterializationResult`. The server-side operation keeps running either way. ## Testing - **New tests:** - a silent stream is closed at a 0.1 s timeout and raises; - a run that finishes before its timeout returns its result; - a close during the reconnect backoff doesn't reconnect. - **`just test-all`:** 650 passed, 17 skipped. ruff, format and basedpyright are clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes facade calls that fail against the current server. They came out of a review of the hand-written
clients/layer. The test suite missed them because it answered every call with a mocked 200. The worst one: since server v1.11.10 the operator endpoint answers202unless a sync wait is requested. The generated parser turns a 202 intoNone, so everyOperatorClientrun andoperator_query()call raised "Unexpected response format".Changes
Fixes
202is read from the raw body and followed to completion, over the stream or by polling/status.OperatorOptions.modeis now sent as?mode=. An HTTP error raises with its status instead of "Unexpected response format".202raisesQueuedQueryErroror waits on the stream, as documented. One body classifier is now shared by the sync client, the async client andAuthenticatedClients.execute_cypher_query. That last method also read a dict as an object and always returned empty data.rfs…API keys go inX-API-Key, anything else (a JWT fromtoken_provider) asAuthorization: Bearer. Before, every write sent a JWT asX-API-Keyand got a 401. The graph, file, table and document clients now honourtoken_provider.get_graph_infoalways raised "Graph not found"; fixed.TableClient.listalways returned[], because it read atable_typethe server doesn't send.monitor_operationhung on a cancelled operation; it now settles. A stream that ends with no terminal event raises.cancel_operationalways returnedFalse. It now reads the server'sstatus: "cancelled".progress_percent.AsyncRoboSystemsClients: the async query and operation methods werepassstubs. They are now implemented with the async generated calls.create_report/regenerate_reportcrashed on every call, because the request model expectsdateobjects and the facade passed strings.FileClientno longer leaksUNSETintoFileInfo.Additions (parity with the TypeScript client)
OperatorRunError(message, status, writes, operation_id), raised when a run fails or is cancelled, so a caller can show what the run already wrote. It subclassesException, so existing handlers still catch it.create_report(periods=…).list_information_blocks(scenario_id=…). This adds a variable to its.graphqloperation and regenerates it.dispose_schedule:memoandreasonare now optional, matching the server.Docs: corrected the retry docstring on handler-raised 429s, the
_call_opandGraphClientdocstrings, and removed the uncalled_unwrap.Compatibility
Rides a client minor. The facades are stable tier, but every change is backward compatible: new optional parameters, a new exception that subclasses what was raised before, and a new
TableInfo.s3_locationfield added last with a default. Behaviour an integrator may notice:rfsnow goes out as Bearer on writes. Every real API key starts withrfs.TableInfo.table_typeis alwaysNone, because the server has no such field.Release-note lines:
OperatorClientandoperator_query()failing on every run since server v1.11.10.token_provider; queued Cypher queries;get_graph_info;TableClient.list;cancel_operation; monitoring a cancelled operation;create_report.OperatorRunError,create_report(periods)andlist_information_blocks(scenario_id).Testing
tests/test_facade_wire_shapes.py(30 tests): answers the facades with the server's real status codes and bodies through the real generated parsers. Against the previous HEAD, 25 of the 26 that can run there fail. The 4 monitor tests hang on the old code.just test-all: 647 passed, 17 skipped. ruff, format and basedpyright are clean.Follow-ups (not in this PR)
OperationClient.monitor_operation's synctimeoutis still not enforced, becauseconnect()blocks.mode="stream"operator call fails when the server streams its 200 directly.🤖 Generated with Claude Code