Skip to content

Commit c702e52

Browse files
authored
Merge pull request #25 from hotdata-dev/adopt-hotdata-0.4.1
feat: hotdata 0.4.1 + typed error handling + ruff/mypy tooling (v0.3.0)
2 parents 1d08d97 + c1dbbcf commit c702e52

17 files changed

Lines changed: 700 additions & 112 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.0] - 2026-06-22
1011

12+
### Added
13+
14+
- Adopt the `hotdata` 0.4.1 SDK surface.
15+
- New typed error-handling public API: `HotdataError`, `HotdataTerminalError`, `HotdataTransientError`, and `classify_sdk_error` (`hotdata_runtime/errors.py`).
16+
- `ManagedDatabaseClient` for managed database operations (`hotdata_runtime/managed_client.py`).
17+
- `py.typed` marker so downstream consumers pick up inline type information.
18+
19+
### Changed
1120

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

1324

1425
## [0.2.4] - 2026-06-01

hotdata_runtime/__init__.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Hotdata runtime primitives for notebook and app integrations."""
1+
"""Hotdata runtime primitives for notebook, app, and adapter integrations."""
22

33
from importlib.metadata import PackageNotFoundError, version
44

@@ -16,6 +16,7 @@
1616
is_parquet_path,
1717
)
1818
from hotdata_runtime.env import (
19+
WorkspaceSelection,
1920
default_api_key,
2021
default_host,
2122
default_session_id,
@@ -24,9 +25,15 @@
2425
normalize_host,
2526
pick_workspace,
2627
resolve_workspace_selection,
27-
WorkspaceSelection,
28+
)
29+
from hotdata_runtime.errors import (
30+
HotdataError,
31+
HotdataTerminalError,
32+
HotdataTransientError,
33+
classify_sdk_error,
2834
)
2935
from hotdata_runtime.health import workspace_health_lines
36+
from hotdata_runtime.managed_client import ManagedDatabaseClient
3037
from hotdata_runtime.result import QueryResult
3138

3239
try:
@@ -35,25 +42,30 @@
3542
__version__ = "0.0.0+unknown"
3643

3744
__all__ = [
38-
"__version__",
3945
"DEFAULT_SCHEMA",
4046
"HotdataClient",
47+
"HotdataError",
48+
"HotdataTerminalError",
49+
"HotdataTransientError",
4150
"LoadManagedTableResult",
4251
"ManagedDatabase",
52+
"ManagedDatabaseClient",
4353
"ManagedTable",
4454
"QueryResult",
45-
"is_parquet_path",
46-
"workspace_health_lines",
55+
"ResultSummary",
56+
"RunHistoryItem",
57+
"WorkspaceSelection",
58+
"__version__",
59+
"classify_sdk_error",
4760
"default_api_key",
4861
"default_host",
4962
"default_session_id",
5063
"explicit_workspace_id",
5164
"from_env",
65+
"is_parquet_path",
5266
"list_workspaces",
5367
"normalize_host",
5468
"pick_workspace",
5569
"resolve_workspace_selection",
56-
"ResultSummary",
57-
"RunHistoryItem",
58-
"WorkspaceSelection",
70+
"workspace_health_lines",
5971
]

hotdata_runtime/client.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from __future__ import annotations
22

3-
from dataclasses import asdict, dataclass
43
import time
5-
from typing import Any, Iterator
6-
7-
from urllib3.exceptions import HTTPError as Urllib3HTTPError
8-
from urllib3.exceptions import ProtocolError
4+
from collections.abc import Iterator
5+
from dataclasses import asdict, dataclass
6+
from typing import Any
97

108
from hotdata import ApiClient, Configuration
119
from hotdata.api.connections_api import ConnectionsApi
@@ -24,14 +22,9 @@
2422
from hotdata.models.query_request import QueryRequest
2523
from hotdata.models.query_response import QueryResponse
2624
from hotdata.models.table_info import TableInfo
25+
from urllib3.exceptions import HTTPError as Urllib3HTTPError
26+
from urllib3.exceptions import ProtocolError
2727

28-
from hotdata_runtime.env import (
29-
default_api_key,
30-
default_host,
31-
default_session_id,
32-
normalize_host,
33-
pick_workspace,
34-
)
3528
from hotdata_runtime.databases import (
3629
DEFAULT_SCHEMA,
3730
LoadManagedTableResult,
@@ -41,6 +34,13 @@
4134
is_parquet_path,
4235
managed_database_from_detail,
4336
)
37+
from hotdata_runtime.env import (
38+
default_api_key,
39+
default_host,
40+
default_session_id,
41+
normalize_host,
42+
pick_workspace,
43+
)
4444
from hotdata_runtime.http import default_http_retries
4545
from hotdata_runtime.result import QueryResult
4646

@@ -250,9 +250,7 @@ def list_managed_tables(
250250

251251
def upload_parquet(self, path: str) -> str:
252252
if not is_parquet_path(path):
253-
raise ValueError(
254-
f"Managed table loads require a parquet file (got {path!r})"
255-
)
253+
raise ValueError(f"Managed table loads require a parquet file (got {path!r})")
256254
with open(path, "rb") as f:
257255
data = f.read()
258256
try:
@@ -392,8 +390,7 @@ def connection_id_by_name(self) -> dict[str, str]:
392390
if duplicate_names:
393391
names = ", ".join(sorted(duplicate_names))
394392
raise RuntimeError(
395-
f"Duplicate connection names found: {names}. "
396-
"Use an explicit connection_id."
393+
f"Duplicate connection names found: {names}. Use an explicit connection_id."
397394
)
398395
return id_map
399396

@@ -405,9 +402,7 @@ def columns_for_qualified(
405402
) -> list[TableInfo]:
406403
parts = qualified.split(".")
407404
if len(parts) < 3:
408-
raise ValueError(
409-
f"Expected connection.schema.table, got {qualified!r}"
410-
)
405+
raise ValueError(f"Expected connection.schema.table, got {qualified!r}")
411406
conn_name, schema_name, table_name = (
412407
parts[0],
413408
parts[1],
@@ -466,9 +461,7 @@ def _wait_result_ready(
466461
if last.status == "ready":
467462
return last
468463
if last.status in _RESULT_FAILURE:
469-
raise RuntimeError(
470-
last.error_message or f"Result {last.status}"
471-
)
464+
raise RuntimeError(last.error_message or f"Result {last.status}")
472465
time.sleep(interval_s)
473466
raise TimeoutError(
474467
f"Result {result_id} not ready within {timeout_s}s "
@@ -509,9 +502,7 @@ def _execute_sql_once(self, sql: str, *, database_id: str | None = None) -> Quer
509502
if isinstance(raw, AsyncQueryResponse):
510503
run = self._poll_query_run(raw.query_run_id)
511504
if run.status != "succeeded":
512-
raise RuntimeError(
513-
run.error_message or f"Query failed ({run.status})"
514-
)
505+
raise RuntimeError(run.error_message or f"Query failed ({run.status})")
515506
if run.result_id:
516507
persisted = self._wait_result_ready(run.result_id)
517508
return QueryResult.from_get_result(persisted)

hotdata_runtime/errors.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from __future__ import annotations
2+
3+
from hotdata.rest import ApiException
4+
5+
6+
class HotdataError(RuntimeError):
7+
pass
8+
9+
10+
class HotdataTransientError(HotdataError):
11+
pass
12+
13+
14+
class HotdataTerminalError(HotdataError):
15+
pass
16+
17+
18+
def classify_sdk_error(error: Exception) -> HotdataError:
19+
if isinstance(error, TimeoutError):
20+
return HotdataTransientError(str(error))
21+
if isinstance(error, ConnectionError):
22+
return HotdataTransientError(str(error))
23+
if isinstance(error, ApiException):
24+
status_code = int(error.status or 0)
25+
message = f"{status_code}: {error.reason or 'unknown error'}"
26+
if status_code in (408, 409, 425, 429):
27+
return HotdataTransientError(message)
28+
if 500 <= status_code <= 599:
29+
return HotdataTransientError(message)
30+
return HotdataTerminalError(message)
31+
return HotdataTerminalError(str(error))

hotdata_runtime/http.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,5 @@ def default_http_retries() -> Retry:
1313
read=3,
1414
backoff_factor=0.2,
1515
status_forcelist=(502, 503, 504),
16-
allowed_methods=frozenset(
17-
["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]
18-
),
16+
allowed_methods=frozenset(["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]),
1917
)

0 commit comments

Comments
 (0)