diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6f38754..ec5df12 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/hotdata-runtime + url: https://pypi.org/p/hotdata-framework permissions: id-token: write steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index b3ef0bc..388abd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **Renamed the distribution from `hotdata-runtime` to `hotdata-framework`** and the import package from `hotdata_runtime` to `hotdata_framework`. Consumers should depend on `hotdata-framework` and use `import hotdata_framework`. The GitHub repository is now `sdk-python-framework`. +- Added PyPI classifiers, keywords, and an updated description identifying the project as a Python framework. + ## [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`). +- New typed error-handling public API: `HotdataError`, `HotdataTerminalError`, `HotdataTransientError`, and `classify_sdk_error` (`hotdata_framework/errors.py`). +- `ManagedDatabaseClient` for managed database operations (`hotdata_framework/managed_client.py`). - `py.typed` marker so downstream consumers pick up inline type information. ### Changed diff --git a/CONTRACT.md b/CONTRACT.md index f5f23c5..4c3c0b7 100644 --- a/CONTRACT.md +++ b/CONTRACT.md @@ -1,6 +1,6 @@ -# hotdata-runtime Contract +# hotdata-framework Contract -`hotdata-runtime` is the framework-agnostic runtime contract for Hotdata integrations. +`hotdata-framework` is the framework-agnostic runtime contract for Hotdata integrations. ## Scope @@ -36,7 +36,7 @@ The supported import surface is: - `DEFAULT_SCHEMA` - `is_parquet_path` -Adapters should import from `hotdata_runtime` and treat this surface as the stable API. +Adapters should import from `hotdata_framework` and treat this surface as the stable API. ## Semantic Guarantees @@ -93,7 +93,7 @@ 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. +`hotdata-framework` does not define framework UI primitives and does not require framework dependencies. ## Versioning Policy diff --git a/README.md b/README.md index 71ffc84..f068ba2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# hotdata-runtime +# hotdata-framework + +**A Python framework for building Hotdata integrations.** 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. @@ -19,8 +21,8 @@ Runtime boundary and guarantees are defined in `CONTRACT.md`. Install: ```bash -uv pip install hotdata-runtime -# or: pip install hotdata-runtime +uv pip install hotdata-framework +# or: pip install hotdata-framework ``` Example: diff --git a/examples/basic_usage.py b/examples/basic_usage.py index 4f93bd0..c3c1445 100644 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -1,6 +1,6 @@ -"""Basic hotdata-runtime usage.""" +"""Basic hotdata-framework usage.""" -from hotdata_runtime import from_env +from hotdata_framework import from_env def main() -> None: diff --git a/hotdata_runtime/__init__.py b/hotdata_framework/__init__.py similarity index 77% rename from hotdata_runtime/__init__.py rename to hotdata_framework/__init__.py index 8169822..4de429d 100644 --- a/hotdata_runtime/__init__.py +++ b/hotdata_framework/__init__.py @@ -2,20 +2,20 @@ from importlib.metadata import PackageNotFoundError, version -from hotdata_runtime.client import ( +from hotdata_framework.client import ( HotdataClient, ResultSummary, RunHistoryItem, from_env, ) -from hotdata_runtime.databases import ( +from hotdata_framework.databases import ( DEFAULT_SCHEMA, LoadManagedTableResult, ManagedDatabase, ManagedTable, is_parquet_path, ) -from hotdata_runtime.env import ( +from hotdata_framework.env import ( WorkspaceSelection, default_api_key, default_host, @@ -26,18 +26,18 @@ pick_workspace, resolve_workspace_selection, ) -from hotdata_runtime.errors import ( +from hotdata_framework.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 +from hotdata_framework.health import workspace_health_lines +from hotdata_framework.managed_client import ManagedDatabaseClient +from hotdata_framework.result import QueryResult try: - __version__ = version("hotdata-runtime") + __version__ = version("hotdata-framework") except PackageNotFoundError: __version__ = "0.0.0+unknown" diff --git a/hotdata_runtime/client.py b/hotdata_framework/client.py similarity index 98% rename from hotdata_runtime/client.py rename to hotdata_framework/client.py index d2a3b93..a789c49 100644 --- a/hotdata_runtime/client.py +++ b/hotdata_framework/client.py @@ -25,7 +25,7 @@ from urllib3.exceptions import HTTPError as Urllib3HTTPError from urllib3.exceptions import ProtocolError -from hotdata_runtime.databases import ( +from hotdata_framework.databases import ( DEFAULT_SCHEMA, LoadManagedTableResult, ManagedDatabase, @@ -34,15 +34,15 @@ is_parquet_path, managed_database_from_detail, ) -from hotdata_runtime.env import ( +from hotdata_framework.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 +from hotdata_framework.http import default_http_retries +from hotdata_framework.result import QueryResult _TERMINAL = frozenset({"succeeded", "failed", "cancelled"}) _RESULT_FAILURE = frozenset({"failed", "cancelled"}) diff --git a/hotdata_runtime/databases.py b/hotdata_framework/databases.py similarity index 100% rename from hotdata_runtime/databases.py rename to hotdata_framework/databases.py diff --git a/hotdata_runtime/env.py b/hotdata_framework/env.py similarity index 100% rename from hotdata_runtime/env.py rename to hotdata_framework/env.py diff --git a/hotdata_runtime/errors.py b/hotdata_framework/errors.py similarity index 100% rename from hotdata_runtime/errors.py rename to hotdata_framework/errors.py diff --git a/hotdata_runtime/health.py b/hotdata_framework/health.py similarity index 94% rename from hotdata_runtime/health.py rename to hotdata_framework/health.py index 14540e4..eca85d6 100644 --- a/hotdata_runtime/health.py +++ b/hotdata_framework/health.py @@ -2,7 +2,7 @@ from hotdata.exceptions import ApiException -from hotdata_runtime.client import HotdataClient +from hotdata_framework.client import HotdataClient def workspace_health_lines(client: HotdataClient) -> tuple[bool, list[str]]: diff --git a/hotdata_runtime/http.py b/hotdata_framework/http.py similarity index 100% rename from hotdata_runtime/http.py rename to hotdata_framework/http.py diff --git a/hotdata_runtime/managed_client.py b/hotdata_framework/managed_client.py similarity index 97% rename from hotdata_runtime/managed_client.py rename to hotdata_framework/managed_client.py index 5030980..1fbad32 100644 --- a/hotdata_runtime/managed_client.py +++ b/hotdata_framework/managed_client.py @@ -20,9 +20,9 @@ from hotdata.models.query_request import QueryRequest from hotdata.models.query_response import QueryResponse -from hotdata_runtime.client import HotdataClient as RuntimeClient -from hotdata_runtime.databases import LoadManagedTableResult, ManagedDatabase -from hotdata_runtime.errors import ( +from hotdata_framework.client import HotdataClient as RuntimeClient +from hotdata_framework.databases import LoadManagedTableResult, ManagedDatabase +from hotdata_framework.errors import ( HotdataTransientError, classify_sdk_error, ) @@ -31,7 +31,7 @@ class ManagedDatabaseClient: - """Managed-database client with bounded retries over hotdata-runtime. + """Managed-database client with bounded retries over hotdata-framework. This is the shared client used by Hotdata adapter packages (Airflow, dlt, etc.). It wraps the lower-level RuntimeClient with retry logic, diff --git a/hotdata_runtime/py.typed b/hotdata_framework/py.typed similarity index 100% rename from hotdata_runtime/py.typed rename to hotdata_framework/py.typed diff --git a/hotdata_runtime/result.py b/hotdata_framework/result.py similarity index 100% rename from hotdata_runtime/result.py rename to hotdata_framework/result.py diff --git a/pyproject.toml b/pyproject.toml index e7bb223..2c3a98b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,18 +3,39 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "hotdata-runtime" +name = "hotdata-framework" version = "0.3.0" -description = "Workspace/session runtime primitives for Hotdata integrations" +description = "Python framework for building Hotdata integrations: workspace/session runtime, query execution, and managed databases" readme = "README.md" requires-python = ">=3.10" license = { text = "MIT" } +keywords = ["hotdata", "python", "framework", "sql", "data", "analytics", "pandas", "arrow"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", +] dependencies = [ "hotdata>=0.4.1", "pandas>=2.0", "pyarrow>=14.0", ] +[project.urls] +Homepage = "https://www.hotdata.dev" +Documentation = "https://www.hotdata.dev/docs" +Repository = "https://github.com/hotdata-dev/sdk-python-framework" + [dependency-groups] dev = [ "packaging>=23", @@ -27,7 +48,7 @@ dev = [ default-groups = ["dev"] [tool.hatch.build.targets.wheel] -packages = ["hotdata_runtime"] +packages = ["hotdata_framework"] [tool.pytest.ini_options] testpaths = ["tests"] @@ -36,7 +57,7 @@ testpaths = ["tests"] python_version = "3.10" strict = true files = [ - "hotdata_runtime", + "hotdata_framework", "tests", ] namespace_packages = true diff --git a/scripts/publish-workflow.sh b/scripts/publish-workflow.sh index 88c9db9..713f872 100755 --- a/scripts/publish-workflow.sh +++ b/scripts/publish-workflow.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Generate publish.yml for a package. Usage: publish-workflow.sh hotdata-runtime +# Generate publish.yml for a package. Usage: publish-workflow.sh hotdata-framework set -euo pipefail pkg="${1:?package name}" cat < None: @@ -37,7 +37,7 @@ def test_resolve_workspace_selection_prefers_env_without_listing( monkeypatch: pytest.MonkeyPatch, ): monkeypatch.setenv("HOTDATA_WORKSPACE", "ws_explicit") - with patch("hotdata_runtime.env.list_workspaces") as listing: + with patch("hotdata_framework.env.list_workspaces") as listing: resolved = resolve_workspace_selection("k", "https://api.hotdata.dev", None) listing.assert_not_called() assert resolved.workspace_id == "ws_explicit" @@ -55,7 +55,7 @@ def test_pick_workspace_chooses_first_active(monkeypatch: pytest.MonkeyPatch): ] listing = SimpleNamespace(workspaces=items) - with patch("hotdata_runtime.env.WorkspacesApi") as Api: + with patch("hotdata_framework.env.WorkspacesApi") as Api: Api.return_value.list_workspaces.return_value = listing assert pick_workspace("k", "https://api.hotdata.dev", None) == "ws_2" @@ -69,7 +69,7 @@ def test_pick_workspace_falls_back_to_first(monkeypatch: pytest.MonkeyPatch): ] listing = SimpleNamespace(workspaces=items) - with patch("hotdata_runtime.env.WorkspacesApi") as Api: + with patch("hotdata_framework.env.WorkspacesApi") as Api: Api.return_value.list_workspaces.return_value = listing assert pick_workspace("k", "https://api.hotdata.dev", None) == "ws_1" @@ -81,7 +81,7 @@ def test_resolve_workspace_selection_source_first(monkeypatch: pytest.MonkeyPatc SimpleNamespace(public_id="ws_2", active=False), ] listing = SimpleNamespace(workspaces=items) - with patch("hotdata_runtime.env.WorkspacesApi") as Api: + with patch("hotdata_framework.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_1" @@ -100,7 +100,7 @@ def test_resolve_workspace_selection_returns_workspaces_and_source( ] listing = SimpleNamespace(workspaces=items) - with patch("hotdata_runtime.env.WorkspacesApi") as Api: + with patch("hotdata_framework.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" diff --git a/tests/test_contract.py b/tests/test_contract.py index f3fa82f..5491cd6 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -3,9 +3,9 @@ from dataclasses import fields from unittest.mock import patch -import hotdata_runtime as hr -from hotdata_runtime.client import HotdataClient -from hotdata_runtime.result import QueryResult +import hotdata_framework as hr +from hotdata_framework.client import HotdataClient +from hotdata_framework.result import QueryResult def test_public_exports_contract(): diff --git a/tests/test_databases.py b/tests/test_databases.py index 5064f37..994b2ce 100644 --- a/tests/test_databases.py +++ b/tests/test_databases.py @@ -6,8 +6,8 @@ import pytest from hotdata.exceptions import ApiException -from hotdata_runtime.client import HotdataClient -from hotdata_runtime.databases import ( +from hotdata_framework.client import HotdataClient +from hotdata_framework.databases import ( is_parquet_path, managed_database_from_detail, ) diff --git a/tests/test_health.py b/tests/test_health.py index 2ac59ae..2f71ad1 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -4,8 +4,8 @@ from hotdata.exceptions import ApiException -from hotdata_runtime.client import HotdataClient -from hotdata_runtime.health import workspace_health_lines +from hotdata_framework.client import HotdataClient +from hotdata_framework.health import workspace_health_lines def test_workspace_health_ok(): diff --git a/tests/test_result.py b/tests/test_result.py index 3c1ebac..2f6ff73 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -1,4 +1,4 @@ -from hotdata_runtime.result import QueryResult +from hotdata_framework.result import QueryResult def _result() -> QueryResult: diff --git a/tests/test_version.py b/tests/test_version.py index 07f0813..64e42f8 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,7 +2,7 @@ from packaging.version import Version -import hotdata_runtime as hr +import hotdata_framework as hr def test_version_is_valid_pep440(): @@ -10,4 +10,4 @@ def test_version_is_valid_pep440(): def test_version_matches_distribution_metadata(): - assert dist_version("hotdata-runtime") == hr.__version__ + assert dist_version("hotdata-framework") == hr.__version__ diff --git a/uv.lock b/uv.lock index 21013e8..314ca25 100644 --- a/uv.lock +++ b/uv.lock @@ -100,7 +100,7 @@ wheels = [ ] [[package]] -name = "hotdata-runtime" +name = "hotdata-framework" version = "0.3.0" source = { editable = "." } dependencies = [