Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.0] - 2026-06-22

### Added

- Adopt the `hotdata` 0.4.1 SDK surface.
- New typed error-handling public API: `HotdataError`, `HotdataTerminalError`, `HotdataTransientError`, and `classify_sdk_error` (`hotdata_runtime/errors.py`).
- `ManagedDatabaseClient` for managed database operations (`hotdata_runtime/managed_client.py`).
- `py.typed` marker so downstream consumers pick up inline type information.

### Changed

- Bump the `hotdata` dependency pin to `>=0.4.1`.
- Add ruff and mypy tooling configuration and dev dependencies (`ruff>=0.5`, `mypy>=1.5`); apply ruff lint/format cleanup across the package.


## [0.2.4] - 2026-06-01
Expand Down
28 changes: 20 additions & 8 deletions hotdata_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Hotdata runtime primitives for notebook and app integrations."""
"""Hotdata runtime primitives for notebook, app, and adapter integrations."""

from importlib.metadata import PackageNotFoundError, version

Expand All @@ -16,6 +16,7 @@
is_parquet_path,
)
from hotdata_runtime.env import (
WorkspaceSelection,
default_api_key,
default_host,
default_session_id,
Expand All @@ -24,9 +25,15 @@
normalize_host,
pick_workspace,
resolve_workspace_selection,
WorkspaceSelection,
)
from hotdata_runtime.errors import (
HotdataError,
HotdataTerminalError,
HotdataTransientError,
classify_sdk_error,
)
from hotdata_runtime.health import workspace_health_lines
from hotdata_runtime.managed_client import ManagedDatabaseClient
from hotdata_runtime.result import QueryResult

try:
Expand All @@ -35,25 +42,30 @@
__version__ = "0.0.0+unknown"

__all__ = [
"__version__",
"DEFAULT_SCHEMA",
"HotdataClient",
"HotdataError",
"HotdataTerminalError",
"HotdataTransientError",
"LoadManagedTableResult",
"ManagedDatabase",
"ManagedDatabaseClient",
"ManagedTable",
"QueryResult",
"is_parquet_path",
"workspace_health_lines",
"ResultSummary",
"RunHistoryItem",
"WorkspaceSelection",
"__version__",
"classify_sdk_error",
"default_api_key",
"default_host",
"default_session_id",
"explicit_workspace_id",
"from_env",
"is_parquet_path",
"list_workspaces",
"normalize_host",
"pick_workspace",
"resolve_workspace_selection",
"ResultSummary",
"RunHistoryItem",
"WorkspaceSelection",
"workspace_health_lines",
]
43 changes: 17 additions & 26 deletions hotdata_runtime/client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from __future__ import annotations

from dataclasses import asdict, dataclass
import time
from typing import Any, Iterator

from urllib3.exceptions import HTTPError as Urllib3HTTPError
from urllib3.exceptions import ProtocolError
from collections.abc import Iterator
from dataclasses import asdict, dataclass
from typing import Any

from hotdata import ApiClient, Configuration
from hotdata.api.connections_api import ConnectionsApi
Expand All @@ -24,14 +22,9 @@
from hotdata.models.query_request import QueryRequest
from hotdata.models.query_response import QueryResponse
from hotdata.models.table_info import TableInfo
from urllib3.exceptions import HTTPError as Urllib3HTTPError
from urllib3.exceptions import ProtocolError

from hotdata_runtime.env import (
default_api_key,
default_host,
default_session_id,
normalize_host,
pick_workspace,
)
from hotdata_runtime.databases import (
DEFAULT_SCHEMA,
LoadManagedTableResult,
Expand All @@ -41,6 +34,13 @@
is_parquet_path,
managed_database_from_detail,
)
from hotdata_runtime.env import (
default_api_key,
default_host,
default_session_id,
normalize_host,
pick_workspace,
)
from hotdata_runtime.http import default_http_retries
from hotdata_runtime.result import QueryResult

Expand Down Expand Up @@ -250,9 +250,7 @@ def list_managed_tables(

def upload_parquet(self, path: str) -> str:
if not is_parquet_path(path):
raise ValueError(
f"Managed table loads require a parquet file (got {path!r})"
)
raise ValueError(f"Managed table loads require a parquet file (got {path!r})")
with open(path, "rb") as f:
data = f.read()
try:
Expand Down Expand Up @@ -392,8 +390,7 @@ def connection_id_by_name(self) -> dict[str, str]:
if duplicate_names:
names = ", ".join(sorted(duplicate_names))
raise RuntimeError(
f"Duplicate connection names found: {names}. "
"Use an explicit connection_id."
f"Duplicate connection names found: {names}. Use an explicit connection_id."
)
return id_map

Expand All @@ -405,9 +402,7 @@ def columns_for_qualified(
) -> list[TableInfo]:
parts = qualified.split(".")
if len(parts) < 3:
raise ValueError(
f"Expected connection.schema.table, got {qualified!r}"
)
raise ValueError(f"Expected connection.schema.table, got {qualified!r}")
conn_name, schema_name, table_name = (
parts[0],
parts[1],
Expand Down Expand Up @@ -466,9 +461,7 @@ def _wait_result_ready(
if last.status == "ready":
return last
if last.status in _RESULT_FAILURE:
raise RuntimeError(
last.error_message or f"Result {last.status}"
)
raise RuntimeError(last.error_message or f"Result {last.status}")
time.sleep(interval_s)
raise TimeoutError(
f"Result {result_id} not ready within {timeout_s}s "
Expand Down Expand Up @@ -509,9 +502,7 @@ def _execute_sql_once(self, sql: str, *, database_id: str | None = None) -> Quer
if isinstance(raw, AsyncQueryResponse):
run = self._poll_query_run(raw.query_run_id)
if run.status != "succeeded":
raise RuntimeError(
run.error_message or f"Query failed ({run.status})"
)
raise RuntimeError(run.error_message or f"Query failed ({run.status})")
if run.result_id:
persisted = self._wait_result_ready(run.result_id)
return QueryResult.from_get_result(persisted)
Expand Down
31 changes: 31 additions & 0 deletions hotdata_runtime/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from hotdata.rest import ApiException

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) client.py imports ApiException from hotdata.exceptions, while here it's imported from hotdata.rest. They resolve to the same class today (rest re-exports it), but using one canonical import path across the package avoids confusion if the SDK ever changes the re-export.



class HotdataError(RuntimeError):
pass


class HotdataTransientError(HotdataError):
pass


class HotdataTerminalError(HotdataError):
pass


def classify_sdk_error(error: Exception) -> HotdataError:
if isinstance(error, TimeoutError):
return HotdataTransientError(str(error))
if isinstance(error, ConnectionError):
return HotdataTransientError(str(error))
if isinstance(error, ApiException):
status_code = int(error.status or 0)
message = f"{status_code}: {error.reason or 'unknown error'}"
if status_code in (408, 409, 425, 429):
return HotdataTransientError(message)
if 500 <= status_code <= 599:
return HotdataTransientError(message)
return HotdataTerminalError(message)
return HotdataTerminalError(str(error))
4 changes: 1 addition & 3 deletions hotdata_runtime/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ def default_http_retries() -> Retry:
read=3,
backoff_factor=0.2,
status_forcelist=(502, 503, 504),
allowed_methods=frozenset(
["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]
),
allowed_methods=frozenset(["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]),
)
Loading
Loading