Skip to content

Commit dabcd59

Browse files
authored
feat(managed): add add_managed_table for additive schema evolution (#32)
Wraps the SDK add_database_table endpoint so callers can declare a new table on an existing managed database without recreating it. The table is added empty (declared-but-unloaded) and populated via load_managed_table. Co-authored-by: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com>
1 parent 3fde042 commit dabcd59

3 files changed

Lines changed: 61 additions & 0 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- `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.
13+
1014

1115
## [0.5.0] - 2026-06-28
1216

hotdata_framework/client.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from hotdata.api.results_api import ResultsApi
1515
from hotdata.api.uploads_api import UploadsApi
1616
from hotdata.exceptions import ApiException
17+
from hotdata.models.add_managed_table_request import AddManagedTableRequest
1718
from hotdata.models.async_query_response import AsyncQueryResponse
1819
from hotdata.models.create_database_request import CreateDatabaseRequest
1920
from hotdata.models.database_default_schema_decl import DatabaseDefaultSchemaDecl
@@ -300,6 +301,33 @@ def load_managed_table(
300301
full_name=f"{db.id}.{loaded.schema_name}.{loaded.table_name}",
301302
)
302303

304+
def add_managed_table(
305+
self,
306+
database: str,
307+
table: str,
308+
*,
309+
schema: str = DEFAULT_SCHEMA,
310+
) -> ManagedTable:
311+
"""Declare a new table on an existing managed database.
312+
313+
The table is added empty (declared-but-unloaded); populate it with
314+
:meth:`load_managed_table`. Use this to evolve a managed database's
315+
schema after creation without recreating it.
316+
"""
317+
db = self.resolve_managed_database(database)
318+
request = AddManagedTableRequest(name=table)
319+
try:
320+
self._databases_api().add_database_table(db.id, schema, request)
321+
except ApiException as e:
322+
raise RuntimeError(api_error_message(e)) from e
323+
return ManagedTable(
324+
full_name=f"{db.id}.{schema}.{table}",
325+
schema=schema,
326+
table=table,
327+
synced=False,
328+
last_sync=None,
329+
)
330+
303331
def delete_managed_table(
304332
self,
305333
database: str,

tests/test_client.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,35 @@ def information_schema(self, **kwargs):
178178
assert fake_api.kwargs["connection_id"] == "conn_explicit"
179179

180180

181+
def test_add_managed_table_declares_table_on_existing_database():
182+
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
183+
fake_db = SimpleNamespace(id="db_1", default_connection_id="conn")
184+
185+
class FakeDatabasesApi:
186+
def __init__(self):
187+
self.calls: list[tuple[str, str, str]] = []
188+
189+
def add_database_table(self, database_id, var_schema, request):
190+
self.calls.append((database_id, var_schema, request.name))
191+
return SimpleNamespace(
192+
connection_id="conn", var_schema=var_schema, table=request.name
193+
)
194+
195+
fake_api = FakeDatabasesApi()
196+
with (
197+
patch.object(client, "resolve_managed_database", return_value=fake_db),
198+
patch.object(client, "_databases_api", return_value=fake_api),
199+
):
200+
result = client.add_managed_table("mydb", "orders", schema="public")
201+
202+
assert fake_api.calls == [("db_1", "public", "orders")]
203+
assert result.full_name == "db_1.public.orders"
204+
assert result.schema == "public"
205+
assert result.table == "orders"
206+
assert result.synced is False
207+
assert result.last_sync is None
208+
209+
181210
def test_list_recent_results_returns_normalized_summaries():
182211
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
183212
listing = SimpleNamespace(

0 commit comments

Comments
 (0)