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
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# hotdata-core-notebook
# hotdata-runtime

Shared **Hotdata** client and domain types for notebook UIs (Marimo, Jupyter, etc.). UI frameworks depend on this package; they do not belong here.
Shared runtime primitives for Hotdata integrations: workspace/session semantics, execution context, query state, run history, and replayable result handles. Framework packages (Marimo, Jupyter, Streamlit, LangGraph) depend on this package.

Install:

```bash
pip install hotdata-core-notebook
uv pip install hotdata-runtime
# or: pip install hotdata-runtime
```

Development:
Development (uses **uv**; creates `.venv/` in this repo):

```bash
pip install -e ".[dev]"
pytest
uv sync --locked
uv run pytest
```

`uv.lock` is checked in so CI can run `uv sync --locked`. The default **dev** group (pytest) is enabled via `[tool.uv] default-groups`.
26 changes: 0 additions & 26 deletions hotdata_core_notebook/__init__.py

This file was deleted.

36 changes: 36 additions & 0 deletions hotdata_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Hotdata runtime primitives for notebook and app integrations."""

from importlib.metadata import PackageNotFoundError, version

from hotdata_runtime.client import HotdataClient, from_env
from hotdata_runtime.env import (
default_api_key,
default_host,
default_session_id,
explicit_workspace_id,
list_workspaces,
normalize_host,
pick_workspace,
)
from hotdata_runtime.health import workspace_health_lines
from hotdata_runtime.result import QueryResult
Comment on lines +5 to +16
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: imports aren't alphabetical — env is after health. A ruff/isort run would reorder these to client, env, health, result. (not blocking)


try:
__version__ = version("hotdata-runtime")
except PackageNotFoundError:
__version__ = "0.0.0+unknown"

__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",
]
4 changes: 2 additions & 2 deletions hotdata_core_notebook/client.py → hotdata_runtime/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
from hotdata.models.query_response import QueryResponse
from hotdata.models.table_info import TableInfo

from hotdata_core_notebook.env import (
from hotdata_runtime.env import (
default_api_key,
default_host,
default_session_id,
normalize_host,
pick_workspace,
)
from hotdata_core_notebook.result import QueryResult
from hotdata_runtime.result import QueryResult

_TERMINAL = frozenset({"succeeded", "failed", "cancelled"})

Expand Down
File renamed without changes.
27 changes: 27 additions & 0 deletions hotdata_runtime/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

from hotdata.exceptions import ApiException

from hotdata_runtime.client import HotdataClient


def workspace_health_lines(client: HotdataClient) -> tuple[bool, list[str]]:
"""Return ``(ok, parts)`` where ``parts`` are short markdown fragments.

On failure, ``ok`` is False and ``parts`` is a single-element list with the error text.
"""
try:
listing = client.connections().list_connections()
n = len(listing.connections)
lines = [
"**API** reachable",
f"**workspace** `{client.workspace_id}`",
f"**connections** {n}",
]
if client.session_id:
lines.append(f"**sandbox** `{client.session_id}`")
return True, lines
except ApiException as e:
return False, [e.reason or str(e)]
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: only ApiException is caught here, so transport-level failures (e.g., urllib3.exceptions.MaxRetryError from a DNS/connection failure, a TLS error, or a request timeout) will propagate as uncaught exceptions and defeat the (ok, lines) contract. For a "health" probe the most useful behavior is usually to also return (False, [msg]) on those. Consider catching a broader exception type (e.g., Exception with a friendly str(e), or ApiException/OSError/URLError explicitly). (not blocking)

except Exception as e:
return False, [str(e)]
File renamed without changes.
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "hotdata-core-notebook"
name = "hotdata-runtime"
version = "0.1.0"
description = "Hotdata API client and shared models for notebook integrations"
description = "Workspace/session runtime primitives for Hotdata integrations"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
Expand All @@ -14,13 +14,17 @@ dependencies = [
"pandas>=2.0",
]

[project.optional-dependencies]
[dependency-groups]
dev = [
"packaging>=23",
"pytest>=8.0",
]

[tool.uv]
default-groups = ["dev"]

[tool.hatch.build.targets.wheel]
packages = ["hotdata_core_notebook"]
packages = ["hotdata_runtime"]

[tool.pytest.ini_options]
testpaths = ["tests"]
8 changes: 4 additions & 4 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import pytest

from hotdata_core_notebook.env import normalize_host, pick_workspace
from hotdata_core_notebook.client import HotdataClient
from hotdata_runtime.env import normalize_host, pick_workspace
from hotdata_runtime.client import HotdataClient


@pytest.mark.parametrize(
Expand Down Expand Up @@ -47,7 +47,7 @@ def test_pick_workspace_chooses_first_active(monkeypatch: pytest.MonkeyPatch):
]
listing = SimpleNamespace(workspaces=items)

with patch("hotdata_core_notebook.env.WorkspacesApi") as Api:
with patch("hotdata_runtime.env.WorkspacesApi") as Api:
Api.return_value.list_workspaces.return_value = listing
assert pick_workspace("k", "https://api.hotdata.dev", None) == "ws_2"

Expand All @@ -62,7 +62,7 @@ def test_pick_workspace_falls_back_to_first(monkeypatch: pytest.MonkeyPatch):
]
listing = SimpleNamespace(workspaces=items)

with patch("hotdata_core_notebook.env.WorkspacesApi") as Api:
with patch("hotdata_runtime.env.WorkspacesApi") as Api:
Api.return_value.list_workspaces.return_value = listing
assert pick_workspace("k", "https://api.hotdata.dev", None) == "ws_1"

Expand Down
64 changes: 64 additions & 0 deletions tests/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations

from unittest.mock import patch

from hotdata.exceptions import ApiException

from hotdata_runtime.client import HotdataClient
from hotdata_runtime.health import workspace_health_lines


def test_workspace_health_ok():
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
listing = type("L", (), {"connections": [object()]})()

class FakeConnectionsApi:
def list_connections(self):
return listing

with patch.object(client, "connections", return_value=FakeConnectionsApi()):
ok, parts = workspace_health_lines(client)
assert ok is True
assert any("reachable" in p for p in parts)


def test_workspace_health_ok_includes_sandbox_when_session_set():
client = HotdataClient(
"k", "ws", host="https://api.hotdata.dev", session_id="sb_test"
)
listing = type("L", (), {"connections": [object()]})()

class FakeConnectionsApi:
def list_connections(self):
return listing

with patch.object(client, "connections", return_value=FakeConnectionsApi()):
ok, parts = workspace_health_lines(client)
assert ok is True
assert any("sandbox" in p and "sb_test" in p for p in parts)


def test_workspace_health_api_error():
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")

class Boom:
def list_connections(self):
raise ApiException(status=500, reason="nope")

with patch.object(client, "connections", return_value=Boom()):
ok, parts = workspace_health_lines(client)
assert ok is False
assert parts == ["nope"]
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: no test exercises the session_id branch in workspace_health_lines that appends the **sandbox** line. A small case constructing the client with session_id="sb" and asserting that line appears would cover it. (not blocking)



def test_workspace_health_non_api_error():
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")

class Boom:
def list_connections(self):
raise OSError("connection refused")

with patch.object(client, "connections", return_value=Boom()):
ok, parts = workspace_health_lines(client)
assert ok is False
assert parts == ["connection refused"]
13 changes: 13 additions & 0 deletions tests/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from importlib.metadata import version as dist_version

from packaging.version import Version

import hotdata_runtime as hr


def test_version_is_valid_pep440():
Version(hr.__version__)


def test_version_matches_distribution_metadata():
assert dist_version("hotdata-runtime") == hr.__version__
Loading
Loading