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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `HotdataClient.add_managed_table(database, table, *, schema)` declares a new table on an existing managed database (wrapping the SDK `add_database_table` endpoint). This allows additive schema evolution without recreating the database.


## [0.5.0] - 2026-06-28

Expand Down
28 changes: 28 additions & 0 deletions hotdata_framework/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from hotdata.api.results_api import ResultsApi
from hotdata.api.uploads_api import UploadsApi
from hotdata.exceptions import ApiException
from hotdata.models.add_managed_table_request import AddManagedTableRequest
from hotdata.models.async_query_response import AsyncQueryResponse
from hotdata.models.create_database_request import CreateDatabaseRequest
from hotdata.models.database_default_schema_decl import DatabaseDefaultSchemaDecl
Expand Down Expand Up @@ -300,6 +301,33 @@ def load_managed_table(
full_name=f"{db.id}.{loaded.schema_name}.{loaded.table_name}",
)

def add_managed_table(
self,
database: str,
table: str,
*,
schema: str = DEFAULT_SCHEMA,
) -> ManagedTable:
"""Declare a new table on an existing managed database.

The table is added empty (declared-but-unloaded); populate it with
:meth:`load_managed_table`. Use this to evolve a managed database's
schema after creation without recreating it.
"""
db = self.resolve_managed_database(database)
request = AddManagedTableRequest(name=table)
try:
self._databases_api().add_database_table(db.id, schema, request)
except ApiException as e:
raise RuntimeError(api_error_message(e)) from e
return ManagedTable(
full_name=f"{db.id}.{schema}.{table}",
schema=schema,
table=table,
synced=False,
last_sync=None,
)

def delete_managed_table(
self,
database: str,
Expand Down
29 changes: 29 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,35 @@ def information_schema(self, **kwargs):
assert fake_api.kwargs["connection_id"] == "conn_explicit"


def test_add_managed_table_declares_table_on_existing_database():
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
fake_db = SimpleNamespace(id="db_1", default_connection_id="conn")

class FakeDatabasesApi:
def __init__(self):
self.calls: list[tuple[str, str, str]] = []

def add_database_table(self, database_id, var_schema, request):
self.calls.append((database_id, var_schema, request.name))
return SimpleNamespace(
connection_id="conn", var_schema=var_schema, table=request.name
)

fake_api = FakeDatabasesApi()
with (
patch.object(client, "resolve_managed_database", return_value=fake_db),
patch.object(client, "_databases_api", return_value=fake_api),
):
result = client.add_managed_table("mydb", "orders", schema="public")

assert fake_api.calls == [("db_1", "public", "orders")]
assert result.full_name == "db_1.public.orders"
assert result.schema == "public"
assert result.table == "orders"
assert result.synced is False
assert result.last_sync is None


def test_list_recent_results_returns_normalized_summaries():
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
listing = SimpleNamespace(
Expand Down
Loading