-
Notifications
You must be signed in to change notification settings - Fork 0
Define runtime workspace selection contract #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
6068b56
618f3c3
26acad1
632b689
631e57e
d3a7823
161a142
3e5d531
ba06915
747ca1c
78f8541
466d697
9beb00a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # hotdata-runtime Contract | ||
|
|
||
| `hotdata-runtime` is the framework-agnostic runtime contract for Hotdata integrations. | ||
|
|
||
| ## Scope | ||
|
|
||
| This package provides shared primitives for: | ||
|
|
||
| - Environment and workspace resolution | ||
| - Query execution and polling | ||
| - Normalized tabular result handling | ||
| - Basic workspace health checks | ||
|
|
||
| ## Public Runtime Contract | ||
|
|
||
| The supported import surface is: | ||
|
|
||
| - `HotdataClient` | ||
| - `QueryResult` | ||
| - `from_env` | ||
| - `workspace_health_lines` | ||
| - `default_api_key` | ||
| - `default_host` | ||
| - `default_session_id` | ||
| - `explicit_workspace_id` | ||
| - `list_workspaces` | ||
| - `normalize_host` | ||
| - `pick_workspace` | ||
| - `resolve_workspace_selection` | ||
| - `ResultSummary` | ||
| - `RunHistoryItem` | ||
| - `WorkspaceSelection` | ||
|
|
||
| Adapters should import from `hotdata_runtime` and treat this surface as the stable API. | ||
|
|
||
| ## Semantic Guarantees | ||
|
|
||
| ### `HotdataClient` | ||
|
|
||
| - Represents runtime context: API key, host, workspace, optional session. | ||
| - `from_env()` resolves runtime context from env vars and selected workspace. | ||
| - `execute_sql(sql)` returns `QueryResult` or raises `RuntimeError`/`TimeoutError`. | ||
| - `get_result(result_id)` returns a ready `QueryResult` and waits for readiness when needed. | ||
| - `connections()` returns the connections API wrapper for adapter UI/status features. | ||
| - `query_runs()` returns the query-runs API wrapper for adapter history views. | ||
| - `results()` returns the results API wrapper for adapter result pickers. | ||
| - `list_recent_results(...)` returns normalized `ResultSummary` entries. | ||
| - `list_run_history(...)` returns normalized `RunHistoryItem` entries. | ||
| - `list_qualified_table_names(...)` returns sorted fully qualified table names. | ||
| - `columns_for_qualified(qualified, connection_id=...)` resolves table columns, and | ||
| adapters should pass `connection_id` when known. | ||
|
|
||
| ### `QueryResult` | ||
|
|
||
| - Canonical tabular result model with `columns`, `rows`, and `row_count`. | ||
| - Carries server identifiers and execution metadata when available. | ||
| - `to_pandas()` converts to a DataFrame with stable column ordering. | ||
| - `to_records(max_rows=...)` returns row dicts keyed by column names. | ||
| - `metadata_dict()` returns normalized result metadata for adapter rendering. | ||
|
|
||
| ### Env Resolution | ||
|
|
||
| - `default_api_key()` reads `HOTDATA_API_KEY` then `HOTDATA_TOKEN`. | ||
| - `default_host()` reads `HOTDATA_API_URL` (default: `https://api.hotdata.dev`) and normalizes it. | ||
| - `default_session_id()` reads `HOTDATA_SANDBOX`. | ||
| - `pick_workspace()` prefers explicit env workspace, then active workspace, then first workspace. | ||
| - `resolve_workspace_selection()` is the canonical workspace selection algorithm. It returns `WorkspaceSelection` with selected workspace id, selection source, and discovered workspaces when auto-selected. | ||
|
|
||
| ## Adapter Responsibilities | ||
|
|
||
| Framework packages (Jupyter, Marimo, LangChain, LangGraph, LlamaIndex, Streamlit) own: | ||
|
|
||
| - Framework-native lifecycle and state management | ||
| - Rendering/UI concerns | ||
| - Tool/agent wrappers and callback integration | ||
|
|
||
| They should not duplicate runtime env/workspace/query semantics. | ||
|
|
||
| ## Runtime Non-Goals | ||
|
|
||
| `hotdata-runtime` does not define framework UI primitives and does not require framework dependencies. | ||
|
|
||
| ## Versioning Policy | ||
|
|
||
| - Backward-incompatible contract changes require a major version bump. | ||
| - Additive contract changes are minor versions. | ||
| - Bug fixes that preserve contract semantics are patch versions. | ||
|
|
||
| ## Enforcement | ||
|
|
||
| Contract stability is enforced by tests that verify the public export surface and key behavioral invariants. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from dataclasses import dataclass | ||
| from urllib.parse import urlparse | ||
|
|
||
| from hotdata import ApiClient, Configuration | ||
|
|
@@ -50,13 +51,35 @@ def list_workspaces(api_key: str, host: str, session_id: str | None): | |
| return listing.workspaces | ||
|
|
||
|
|
||
| def pick_workspace(api_key: str, host: str, session_id: str | None) -> str: | ||
| @dataclass(frozen=True) | ||
| class WorkspaceSelection: | ||
| workspace_id: str | ||
| source: str | ||
| workspaces: list | ||
|
Comment on lines
+50
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: (not blocking) Also: the dataclass is |
||
|
|
||
|
|
||
| def resolve_workspace_selection( | ||
| api_key: str, host: str, session_id: str | None | ||
| ) -> WorkspaceSelection: | ||
| explicit = explicit_workspace_id() | ||
| if explicit: | ||
| return explicit | ||
| return WorkspaceSelection( | ||
| workspace_id=explicit, | ||
| source="explicit_env", | ||
| workspaces=[], | ||
| ) | ||
|
Comment on lines
+62
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super nit: (not blocking) returning |
||
| workspaces = list_workspaces(api_key, host, session_id) | ||
| if not workspaces: | ||
| raise RuntimeError("No Hotdata workspaces found for this API key.") | ||
| active = [w for w in workspaces if w.active] | ||
| chosen = active[0] if active else workspaces[0] | ||
| return chosen.public_id | ||
| return WorkspaceSelection( | ||
| workspace_id=chosen.public_id, | ||
| source="active" if active else "first", | ||
| workspaces=workspaces, | ||
| ) | ||
|
|
||
|
|
||
| def pick_workspace(api_key: str, host: str, session_id: str | None) -> str: | ||
| selection = resolve_workspace_selection(api_key, host, session_id) | ||
| return selection.workspace_id | ||
There was a problem hiding this comment.
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_resultsacceptsoffsetbutlist_run_historydoes not, and their defaultlimits differ (50 vs 20). Both wrap paginated server endpoints and adapters will likely want to page through both. Adding a matchingoffset: int = 0tolist_run_history(and considering aligning default limits) would make these helpers consistent and avoid surprise when adapters reuse pagination code across the two.