Skip to content

Commit 6de937c

Browse files
committed
feat: switch managed databases to /databases API (hotdata>=0.2.3)
- Use DatabasesApi instead of ConnectionsApi for all managed database ops - Rename create_managed_database first param from name to description (kw-only) - Add expires_at param to create_managed_database - ManagedDatabase: replace name/source_type with description/default_connection_id - resolve_managed_database: ID lookup first, description scan as fallback - list_managed_databases: fetch all databases, no source_type filter - list_managed_tables, load_managed_table, delete_managed_table: use default_connection_id - Remove MANAGED_SOURCE_TYPE, build_managed_config, create_connection_request from public API
1 parent d30cb94 commit 6de937c

8 files changed

Lines changed: 216 additions & 177 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [0.2.0] - 2026-05-24
11+
12+
### Changed
13+
14+
- Switch managed database operations from the connections API to the dedicated `/databases` API (`hotdata>=0.2.3` required).
15+
- `create_managed_database` first parameter renamed from `name` to `description` (keyword-only).
16+
- `ManagedDatabase` dataclass: replace `name`/`source_type` fields with `description`/`default_connection_id`.
17+
- `resolve_managed_database` tries direct ID lookup first, then falls back to a description scan.
18+
- `list_managed_databases` now fetches all databases regardless of source type.
19+
- `list_managed_tables`, `load_managed_table`, and `delete_managed_table` use `default_connection_id` instead of database `id` for connection-scoped operations.
20+
21+
### Added
22+
23+
- `create_managed_database` accepts an optional `expires_at` parameter.
24+
25+
### Removed
26+
27+
- `MANAGED_SOURCE_TYPE`, `build_managed_config`, and `create_connection_request` removed from the public API.
28+
1029
## [0.1.1] - 2026-05-19
1130

1231
### Added

CONTRACT.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ The supported import surface is:
3333
- `ManagedDatabase`
3434
- `ManagedTable`
3535
- `LoadManagedTableResult`
36-
- `MANAGED_SOURCE_TYPE`
3736
- `DEFAULT_SCHEMA`
38-
- `build_managed_config`
39-
- `create_connection_request`
4037
- `is_parquet_path`
4138

4239
Adapters should import from `hotdata_runtime` and treat this surface as the stable API.
@@ -58,10 +55,10 @@ Adapters should import from `hotdata_runtime` and treat this surface as the stab
5855
- `columns_for_qualified(qualified, connection_id=...)` resolves table columns, and
5956
adapters should pass `connection_id` when known.
6057
- `uploads()` returns the uploads API wrapper for parquet staging.
61-
- `list_managed_databases()` returns managed-catalog connections (`source_type: managed`).
62-
- `resolve_managed_database(name_or_id)` resolves a managed database by name or id.
63-
- `create_managed_database(name, schema=..., tables=...)` creates a managed database and optionally declares tables up front.
64-
- `delete_managed_database(name_or_id)` deletes a managed database connection.
58+
- `list_managed_databases()` returns all databases via the `/databases` API.
59+
- `resolve_managed_database(name_or_id)` resolves a database by id (direct lookup) or description (list scan).
60+
- `create_managed_database(description=..., schema=..., tables=..., expires_at=...)` creates a database via the `/databases` API and optionally declares tables up front.
61+
- `delete_managed_database(name_or_id)` deletes a database via the `/databases` API.
6562
- `list_managed_tables(database, schema=...)` lists tables in a managed database.
6663
- `upload_parquet(path)` uploads a local parquet file and returns an upload id.
6764
- `load_managed_table(database, table, schema=..., upload_id=..., file=...)` publishes parquet data into a declared managed table.

hotdata_runtime/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313
LoadManagedTableResult,
1414
ManagedDatabase,
1515
ManagedTable,
16-
MANAGED_SOURCE_TYPE,
17-
build_managed_config,
18-
create_connection_request,
1916
is_parquet_path,
2017
)
2118
from hotdata_runtime.env import (
@@ -42,12 +39,9 @@
4239
"DEFAULT_SCHEMA",
4340
"HotdataClient",
4441
"LoadManagedTableResult",
45-
"MANAGED_SOURCE_TYPE",
4642
"ManagedDatabase",
4743
"ManagedTable",
4844
"QueryResult",
49-
"build_managed_config",
50-
"create_connection_request",
5145
"is_parquet_path",
5246
"workspace_health_lines",
5347
"default_api_key",

hotdata_runtime/client.py

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@
99

1010
from hotdata import ApiClient, Configuration
1111
from hotdata.api.connections_api import ConnectionsApi
12+
from hotdata.api.databases_api import DatabasesApi
1213
from hotdata.api.information_schema_api import InformationSchemaApi
1314
from hotdata.api.query_api import QueryApi
1415
from hotdata.api.query_runs_api import QueryRunsApi
1516
from hotdata.api.results_api import ResultsApi
1617
from hotdata.api.uploads_api import UploadsApi
1718
from hotdata.exceptions import ApiException
1819
from hotdata.models.async_query_response import AsyncQueryResponse
20+
from hotdata.models.create_database_request import CreateDatabaseRequest
21+
from hotdata.models.database_default_schema_decl import DatabaseDefaultSchemaDecl
22+
from hotdata.models.database_default_table_decl import DatabaseDefaultTableDecl
23+
from hotdata.models.load_managed_table_request import LoadManagedTableRequest
1924
from hotdata.models.query_request import QueryRequest
2025
from hotdata.models.query_response import QueryResponse
21-
from hotdata.models.load_managed_table_request import LoadManagedTableRequest
2226
from hotdata.models.table_info import TableInfo
2327

2428
from hotdata_runtime.env import (
@@ -33,11 +37,9 @@
3337
LoadManagedTableResult,
3438
ManagedDatabase,
3539
ManagedTable,
36-
MANAGED_SOURCE_TYPE,
3740
api_error_message,
38-
create_connection_request,
3941
is_parquet_path,
40-
managed_database_from_connection,
42+
managed_database_from_detail,
4143
)
4244
from hotdata_runtime.http import default_http_retries
4345
from hotdata_runtime.result import QueryResult
@@ -130,6 +132,9 @@ def __exit__(self, *args: object) -> None:
130132
def connections(self) -> ConnectionsApi:
131133
return ConnectionsApi(self._api)
132134

135+
def _databases_api(self) -> DatabasesApi:
136+
return DatabasesApi(self._api)
137+
133138
def _information_schema(self) -> InformationSchemaApi:
134139
return InformationSchemaApi(self._api)
135140

@@ -152,47 +157,71 @@ def uploads(self) -> UploadsApi:
152157
return UploadsApi(self._api)
153158

154159
def list_managed_databases(self) -> list[ManagedDatabase]:
155-
listing = self.connections().list_connections()
156-
return [
157-
managed_database_from_connection(c)
158-
for c in listing.connections
159-
if c.source_type == MANAGED_SOURCE_TYPE
160-
]
160+
listing = self._databases_api().list_databases()
161+
result: list[ManagedDatabase] = []
162+
for summary in listing.databases:
163+
try:
164+
detail = self._databases_api().get_database(summary.id)
165+
result.append(managed_database_from_detail(detail))
166+
except ApiException:
167+
pass
168+
return result
161169

162170
def resolve_managed_database(self, name_or_id: str) -> ManagedDatabase:
163-
listing = self.connections().list_connections()
164-
match = None
165-
for c in listing.connections:
166-
if c.id == name_or_id or c.name == name_or_id:
167-
match = c
171+
# Try direct ID lookup first
172+
try:
173+
detail = self._databases_api().get_database(name_or_id)
174+
return managed_database_from_detail(detail)
175+
except ApiException as e:
176+
if e.status != 404:
177+
raise RuntimeError(api_error_message(e)) from e
178+
179+
# Fall back to description-based lookup
180+
listing = self._databases_api().list_databases()
181+
match_id: str | None = None
182+
for db in listing.databases:
183+
if db.description == name_or_id:
184+
match_id = db.id
168185
break
169-
if match is None:
186+
if match_id is None:
170187
raise KeyError(f"No database named or with id {name_or_id!r}")
171-
if match.source_type != MANAGED_SOURCE_TYPE:
172-
raise ValueError(
173-
f"{match.name!r} is not a managed database "
174-
f"(source_type: {match.source_type})"
175-
)
176-
return managed_database_from_connection(match)
188+
try:
189+
detail = self._databases_api().get_database(match_id)
190+
except ApiException as e:
191+
raise RuntimeError(api_error_message(e)) from e
192+
return managed_database_from_detail(detail)
177193

178194
def create_managed_database(
179195
self,
180-
name: str,
196+
description: str | None = None,
181197
*,
182198
schema: str = DEFAULT_SCHEMA,
183199
tables: list[str] | None = None,
200+
expires_at: str | None = None,
184201
) -> ManagedDatabase:
185-
request = create_connection_request(name, schema=schema, tables=tables)
202+
schemas = None
203+
if tables:
204+
schemas = [
205+
DatabaseDefaultSchemaDecl(
206+
name=schema,
207+
tables=[DatabaseDefaultTableDecl(name=t) for t in tables],
208+
)
209+
]
210+
request = CreateDatabaseRequest(
211+
description=description,
212+
schemas=schemas,
213+
expires_at=expires_at,
214+
)
186215
try:
187-
created = self.connections().create_connection(request)
216+
created = self._databases_api().create_database(request)
188217
except ApiException as e:
189218
raise RuntimeError(api_error_message(e)) from e
190-
return managed_database_from_connection(created)
219+
return managed_database_from_detail(created)
191220

192221
def delete_managed_database(self, name_or_id: str) -> None:
193222
db = self.resolve_managed_database(name_or_id)
194223
try:
195-
self.connections().delete_connection(db.id)
224+
self._databases_api().delete_database(db.id)
196225
except ApiException as e:
197226
raise RuntimeError(api_error_message(e)) from e
198227

@@ -204,12 +233,12 @@ def list_managed_tables(
204233
) -> list[ManagedTable]:
205234
db = self.resolve_managed_database(database)
206235
rows: list[ManagedTable] = []
207-
for t in self.iter_tables(connection_id=db.id):
236+
for t in self.iter_tables(connection_id=db.default_connection_id):
208237
if schema is not None and t.var_schema != schema:
209238
continue
210239
rows.append(
211240
ManagedTable(
212-
full_name=f"{db.name}.{t.var_schema}.{t.table}",
241+
full_name=f"{db.id}.{t.var_schema}.{t.table}",
213242
schema=t.var_schema,
214243
table=t.table,
215244
synced=t.synced,
@@ -258,7 +287,7 @@ def load_managed_table(
258287
)
259288
try:
260289
loaded = self.connections().load_managed_table(
261-
db.id,
290+
db.default_connection_id,
262291
schema,
263292
table,
264293
request,
@@ -270,7 +299,7 @@ def load_managed_table(
270299
schema_name=loaded.schema_name,
271300
table_name=loaded.table_name,
272301
row_count=loaded.row_count,
273-
full_name=f"{db.name}.{loaded.schema_name}.{loaded.table_name}",
302+
full_name=f"{db.id}.{loaded.schema_name}.{loaded.table_name}",
274303
)
275304

276305
def delete_managed_table(
@@ -282,7 +311,7 @@ def delete_managed_table(
282311
) -> None:
283312
db = self.resolve_managed_database(database)
284313
try:
285-
self.connections().delete_managed_table(db.id, schema, table)
314+
self.connections().delete_managed_table(db.default_connection_id, schema, table)
286315
except ApiException as e:
287316
raise RuntimeError(api_error_message(e)) from e
288317

hotdata_runtime/databases.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@
77
from typing import Any
88

99
from hotdata.exceptions import ApiException
10-
from hotdata.models.create_connection_request import CreateConnectionRequest
1110

12-
MANAGED_SOURCE_TYPE = "managed"
1311
DEFAULT_SCHEMA = "public"
1412

1513

1614
@dataclass(frozen=True)
1715
class ManagedDatabase:
1816
id: str
19-
name: str
20-
source_type: str
17+
description: str | None
18+
default_connection_id: str
2119

2220
def to_dict(self) -> dict[str, Any]:
2321
return asdict(self)
@@ -51,39 +49,11 @@ def is_parquet_path(path: str) -> bool:
5149
return Path(path).suffix.lower() == ".parquet"
5250

5351

54-
def build_managed_config(schema: str, tables: list[str]) -> dict[str, Any]:
55-
if not tables:
56-
return {}
57-
return {
58-
"schemas": [
59-
{
60-
"name": schema,
61-
"tables": [{"name": table} for table in tables],
62-
}
63-
]
64-
}
65-
66-
67-
def create_connection_request(
68-
name: str,
69-
*,
70-
schema: str = DEFAULT_SCHEMA,
71-
tables: list[str] | None = None,
72-
) -> CreateConnectionRequest:
73-
table_list = tables or []
74-
return CreateConnectionRequest(
75-
name=name,
76-
source_type=MANAGED_SOURCE_TYPE,
77-
config=build_managed_config(schema, table_list),
78-
skip_discovery=True,
79-
)
80-
81-
82-
def managed_database_from_connection(conn: Any) -> ManagedDatabase:
52+
def managed_database_from_detail(detail: Any) -> ManagedDatabase:
8353
return ManagedDatabase(
84-
id=str(conn.id),
85-
name=str(conn.name),
86-
source_type=str(conn.source_type),
54+
id=str(detail.id),
55+
description=detail.description,
56+
default_connection_id=str(detail.default_connection_id),
8757
)
8858

8959

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ readme = "README.md"
1010
requires-python = ">=3.10"
1111
license = { text = "MIT" }
1212
dependencies = [
13-
"hotdata>=0.2.0",
13+
"hotdata>=0.2.3",
1414
"pandas>=2.0",
1515
]
1616

tests/test_contract.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,9 @@ def test_public_exports_contract():
1414
"DEFAULT_SCHEMA",
1515
"HotdataClient",
1616
"LoadManagedTableResult",
17-
"MANAGED_SOURCE_TYPE",
1817
"ManagedDatabase",
1918
"ManagedTable",
2019
"QueryResult",
21-
"build_managed_config",
22-
"create_connection_request",
2320
"is_parquet_path",
2421
"workspace_health_lines",
2522
"default_api_key",

0 commit comments

Comments
 (0)