Skip to content

Define runtime workspace selection contract#2

Merged
eddietejeda merged 13 commits into
mainfrom
feat/workspace-selection-contract
May 17, 2026
Merged

Define runtime workspace selection contract#2
eddietejeda merged 13 commits into
mainfrom
feat/workspace-selection-contract

Conversation

@eddietejeda
Copy link
Copy Markdown
Contributor

Summary

  • add WorkspaceSelection and resolve_workspace_selection() as the canonical workspace-selection contract in hotdata-runtime
  • route pick_workspace() through the canonical resolver and export the new symbols from package root
  • document the contract in CONTRACT.md and add tests that lock public API and resolver behavior

Test plan

  • uv run pytest

Introduce a shared resolver API in hotdata-runtime and enforce it with contract tests so adapters can rely on one source of workspace selection semantics.
Comment thread hotdata_runtime/env.py
Comment on lines +54 to +58
@dataclass(frozen=True)
class WorkspaceSelection:
workspace_id: str
source: str
workspaces: list
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (not blocking) workspaces: list is loosely typed and source: str accepts any string. Since source has a small closed set of values ("explicit_env", "active", "first") it would be more contract-friendly to type it as Literal["explicit_env", "active", "first"], and workspaces as list[Any] (or the SDK workspace type). This makes the contract self-documenting and lets type-checkers catch typos in downstream consumers.

Also: the dataclass is frozen=True, but workspaces is a mutable list — consumers could still mutate it in place. Using tuple[...] would make immutability real, though that's a stylistic call.

Comment thread hotdata_runtime/env.py
Comment on lines +66 to +70
return WorkspaceSelection(
workspace_id=explicit,
source="explicit_env",
workspaces=[],
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: (not blocking) returning workspaces=[] when the source is "explicit_env" conflates "we didn't list" with "we listed and got nothing." Since the empty-list case in the auto path raises, this is unambiguous today, but workspaces: list | None = None (with None meaning "not discovered") would make the contract clearer to adapters that key behavior off selection.workspaces.

Comment thread tests/test_contract.py
Comment on lines +11 to +27
def test_public_exports_contract():
assert hr.__all__ == [
"__version__",
"HotdataClient",
"QueryResult",
"workspace_health_lines",
"default_api_key",
"default_host",
"default_session_id",
"explicit_workspace_id",
"from_env",
"list_workspaces",
"normalize_host",
"pick_workspace",
"resolve_workspace_selection",
"WorkspaceSelection",
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (not blocking) asserting hr.__all__ == [...] checks both membership and order. Any future reordering of __all__ breaks the test even when the public surface is unchanged. If order isn't part of the contract, compare as sets (set(hr.__all__) == {...}) plus a length check to catch duplicates. If order is intentional, a comment here saying so would help.

Comment thread tests/test_client.py
Comment on lines +84 to +103
def test_resolve_workspace_selection_returns_workspaces_and_source(
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.delenv("HOTDATA_WORKSPACE", raising=False)
monkeypatch.delenv("HOTDATA_WORKSPACE_ID", raising=False)

items = [
SimpleNamespace(public_id="ws_1", active=False),
SimpleNamespace(public_id="ws_2", active=True),
]
listing = SimpleNamespace(workspaces=items)

with patch("hotdata_runtime.env.WorkspacesApi") as Api:
Api.return_value.list_workspaces.return_value = listing
resolved = resolve_workspace_selection(
"k", "https://api.hotdata.dev", None
)
assert resolved.workspace_id == "ws_2"
assert resolved.source == "active"
assert resolved.workspaces == items
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: (not blocking) the new resolver tests cover source="explicit_env" and source="active" but not source="first" (the fallback when no workspace is active). Since source is part of the contract surface, a third case locking that value down would round out coverage.

claude[bot]
claude Bot previously approved these changes May 14, 2026
Treat cancelled result states as immediate runtime errors and add coverage so result polling no longer times out on terminal cancellation.
claude[bot]
claude Bot previously approved these changes May 15, 2026
Document the adapter-facing HotdataClient methods in the runtime contract, add duplicate-connection-name protection, and support explicit connection_id column resolution with regression coverage.
claude[bot]
claude Bot previously approved these changes May 15, 2026
Introduce canonical result/history summary models and QueryResult metadata/record helpers in hotdata-runtime, then lock the expanded contract and behavior with dedicated tests.
Comment thread hotdata_runtime/client.py
Comment on lines +135 to +166
def list_recent_results(
self,
*,
limit: int = 50,
offset: int = 0,
) -> list[ResultSummary]:
listing = self.results().list_results(limit=limit, offset=offset)
return [
ResultSummary(
result_id=r.id,
status=r.status,
created_at=r.created_at,
)
for r in listing.results
]

def list_run_history(
self,
*,
limit: int = 20,
) -> list[RunHistoryItem]:
listing = self.query_runs().list_query_runs(limit=limit)
return [
RunHistoryItem(
query_run_id=r.id,
status=r.status,
created_at=r.created_at,
execution_time_ms=r.execution_time_ms,
result_id=r.result_id,
)
for r in listing.query_runs
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: (not blocking) list_recent_results accepts offset but list_run_history does not, and their default limits differ (50 vs 20). Both wrap paginated server endpoints and adapters will likely want to page through both. Adding a matching offset: int = 0 to list_run_history (and considering aligning default limits) would make these helpers consistent and avoid surprise when adapters reuse pagination code across the two.

claude[bot]
claude Bot previously approved these changes May 15, 2026
Add offset support to list_run_history and extend tests/contracts so runtime pagination semantics stay consistent across normalized helper APIs.
claude[bot]
claude Bot previously approved these changes May 15, 2026
Provide a concrete script demonstrating query execution, metadata, records, and normalized history/result helper usage in hotdata-runtime.
claude[bot]
claude Bot previously approved these changes May 15, 2026
Require HOTDATA_API_KEY as the single environment contract so runtime behavior and docs stay unambiguous.
claude[bot]
claude Bot previously approved these changes May 15, 2026
Drop the legacy HOTDATA_WORKSPACE fallback so runtime workspace
selection matches the canonical env var used across adapters.
Configure urllib3 retries on the SDK client and retry transient
connection errors when running SQL. Also drop the invalid offset
argument from list_run_history.
Revert to the canonical HOTDATA_WORKSPACE name for explicit
workspace selection, matching the CLI and other adapters.
Comment thread examples/basic_usage.py Outdated
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Blocking Issues

  • examples/basic_usage.py:18client.list_run_history(limit=5, offset=0) will raise TypeError. list_run_history in hotdata_runtime/client.py:154-158 only accepts limit as a keyword-only argument. The README now points users at this example, so it needs to actually run.

Action Required

Either drop the offset=0 argument from the call, or add offset: int = 0 to list_run_history (the latter also addresses the prior pagination-parity feedback against list_recent_results).

Keep the runnable usage example aligned with the current list_run_history
helper signature.
Comment thread CONTRACT.md Outdated
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Blocking Issues

  • CONTRACT.md:48 — documents list_run_history(limit=..., offset=...) but the actual method (hotdata_runtime/client.py:154-158) does not accept offset. The example was fixed but the contract doc was not.

Action Required

Update CONTRACT.md:48 to reflect the real list_run_history(limit=...) signature (or add offset to the method if pagination parity with list_recent_results is the intent). Either resolution is fine — they just need to agree.

Summarize the runtime client, workspace, retry, result, and health helpers so downstream adapters can quickly understand the package surface.
Comment thread CONTRACT.md Outdated
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Blocking Issues

  • CONTRACT.md:48 documents list_run_history(limit=..., offset=...) but hotdata_runtime/client.py:154-158 defines def list_run_history(self, *, limit: int = 20) with no offset. This is the same blocking mismatch flagged in cycle 2 — the example was corrected in 78f8541, but the contract doc was not.

Action Required

  • Update CONTRACT.md line 48 to drop offset=... (or, alternatively, add offset: int = 0 to list_run_history to make code match the documented contract and align with list_recent_results).

Document list_run_history without offset so adapter guidance matches the runtime API.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior cycles' blocking issues are resolved — CONTRACT.md, the example, and the client signature for list_run_history are now in agreement.

@eddietejeda eddietejeda merged commit b68ca07 into main May 17, 2026
1 check passed
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.

1 participant