hotdata-runtime 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/.github/workflows/release.yml +1 -1
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/CHANGELOG.md +7 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/PKG-INFO +1 -1
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/client.py +16 -4
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/pyproject.toml +1 -1
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_client.py +61 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/uv.lock +1 -1
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/.github/workflows/check-release.yml +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/.github/workflows/ci.yml +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/.github/workflows/publish.yml +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/.gitignore +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/CONTRACT.md +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/README.md +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/RELEASING.md +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/examples/basic_usage.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/__init__.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/databases.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/env.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/health.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/http.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/hotdata_runtime/result.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/scripts/check-release.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/scripts/extract-changelog.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/scripts/publish-workflow.sh +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/scripts/release.sh +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/scripts/update_changelog.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_contract.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_databases.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_health.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_result.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_update_changelog.py +0 -0
- {hotdata_runtime-0.2.0 → hotdata_runtime-0.2.1}/tests/test_version.py +0 -0
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
} >> "$GITHUB_OUTPUT"
|
|
46
46
|
|
|
47
47
|
- name: Create GitHub Release
|
|
48
|
-
uses: softprops/action-gh-release@
|
|
48
|
+
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
|
|
49
49
|
with:
|
|
50
50
|
tag_name: ${{ github.ref_name }}
|
|
51
51
|
name: ${{ steps.meta.outputs.name }} ${{ steps.meta.outputs.version }}
|
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
## [0.2.1] - 2026-05-24
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `execute_sql` accepts an optional `database` keyword argument. When provided, the database name is resolved to an ID and sent as the `X-Database-Id` header so SQL can reference managed database tables as `"default"."<schema>"."<table>"`. Behaviour is unchanged when `database` is omitted.
|
|
16
|
+
|
|
10
17
|
## [0.2.0] - 2026-05-24
|
|
11
18
|
|
|
12
19
|
### Changed
|
|
@@ -475,11 +475,20 @@ class HotdataClient:
|
|
|
475
475
|
f"(last status: {getattr(last, 'status', None)})"
|
|
476
476
|
)
|
|
477
477
|
|
|
478
|
-
def execute_sql(self, sql: str) -> QueryResult:
|
|
478
|
+
def execute_sql(self, sql: str, *, database: str | None = None) -> QueryResult:
|
|
479
|
+
"""Execute SQL and return a :class:`QueryResult`.
|
|
480
|
+
|
|
481
|
+
Pass ``database`` to scope the query to a managed database. The name
|
|
482
|
+
is resolved to a database ID once before the retry loop, and the
|
|
483
|
+
``X-Database-Id`` header is sent with every attempt. Inside a managed
|
|
484
|
+
database the built-in catalog is always ``"default"``, so table
|
|
485
|
+
references should use ``"default"."<schema>"."<table>"``.
|
|
486
|
+
"""
|
|
487
|
+
database_id = self.resolve_managed_database(database).id if database else None
|
|
479
488
|
last_err: BaseException | None = None
|
|
480
489
|
for attempt in range(3):
|
|
481
490
|
try:
|
|
482
|
-
return self._execute_sql_once(sql)
|
|
491
|
+
return self._execute_sql_once(sql, database_id=database_id)
|
|
483
492
|
except (ProtocolError, ConnectionResetError, Urllib3HTTPError) as e:
|
|
484
493
|
last_err = e
|
|
485
494
|
if attempt == 2:
|
|
@@ -487,10 +496,13 @@ class HotdataClient:
|
|
|
487
496
|
time.sleep(0.2 * (2**attempt))
|
|
488
497
|
raise last_err # pragma: no cover
|
|
489
498
|
|
|
490
|
-
def _execute_sql_once(self, sql: str) -> QueryResult:
|
|
499
|
+
def _execute_sql_once(self, sql: str, *, database_id: str | None = None) -> QueryResult:
|
|
491
500
|
q = self._query_api()
|
|
492
501
|
try:
|
|
493
|
-
|
|
502
|
+
if database_id:
|
|
503
|
+
raw = q.query(QueryRequest(sql=sql), x_database_id=database_id)
|
|
504
|
+
else:
|
|
505
|
+
raw = q.query(QueryRequest(sql=sql))
|
|
494
506
|
except ApiException as e:
|
|
495
507
|
raise RuntimeError(e.reason or str(e)) from e
|
|
496
508
|
|
|
@@ -200,6 +200,67 @@ def test_list_recent_results_returns_normalized_summaries():
|
|
|
200
200
|
assert out[0].to_dict()["created_at"] == "2026-01-01T00:00:00Z"
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def test_execute_sql_sends_no_database_id_by_default():
|
|
204
|
+
from hotdata.models.query_response import QueryResponse as _QR
|
|
205
|
+
|
|
206
|
+
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
|
|
207
|
+
|
|
208
|
+
class FakeQueryApi:
|
|
209
|
+
def __init__(self):
|
|
210
|
+
self.calls: list[dict] = []
|
|
211
|
+
|
|
212
|
+
def query(self, request, **kwargs):
|
|
213
|
+
self.calls.append(kwargs)
|
|
214
|
+
return _QR(
|
|
215
|
+
columns=["n"],
|
|
216
|
+
rows=[[1]],
|
|
217
|
+
row_count=1,
|
|
218
|
+
nullable=[False],
|
|
219
|
+
result_id="res_1",
|
|
220
|
+
query_run_id="qrun_1",
|
|
221
|
+
execution_time_ms=1,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
fake_q = FakeQueryApi()
|
|
225
|
+
with patch.object(client, "_query_api", return_value=fake_q):
|
|
226
|
+
client.execute_sql("SELECT 1")
|
|
227
|
+
|
|
228
|
+
assert fake_q.calls == [{}]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_execute_sql_resolves_database_and_sends_x_database_id():
|
|
232
|
+
from hotdata.models.query_response import QueryResponse as _QR
|
|
233
|
+
from types import SimpleNamespace
|
|
234
|
+
|
|
235
|
+
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
|
|
236
|
+
|
|
237
|
+
class FakeQueryApi:
|
|
238
|
+
def __init__(self):
|
|
239
|
+
self.calls: list[dict] = []
|
|
240
|
+
|
|
241
|
+
def query(self, request, **kwargs):
|
|
242
|
+
self.calls.append(kwargs)
|
|
243
|
+
return _QR(
|
|
244
|
+
columns=["n"],
|
|
245
|
+
rows=[[1]],
|
|
246
|
+
row_count=1,
|
|
247
|
+
nullable=[False],
|
|
248
|
+
result_id="res_1",
|
|
249
|
+
query_run_id="qrun_1",
|
|
250
|
+
execution_time_ms=1,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
fake_q = FakeQueryApi()
|
|
254
|
+
fake_db = SimpleNamespace(id="db_abc")
|
|
255
|
+
|
|
256
|
+
with patch.object(client, "_query_api", return_value=fake_q), \
|
|
257
|
+
patch.object(client, "resolve_managed_database", return_value=fake_db) as resolve:
|
|
258
|
+
client.execute_sql('SELECT * FROM "default"."public"."orders"', database="my_db")
|
|
259
|
+
|
|
260
|
+
resolve.assert_called_once_with("my_db")
|
|
261
|
+
assert fake_q.calls == [{"x_database_id": "db_abc"}]
|
|
262
|
+
|
|
263
|
+
|
|
203
264
|
def test_list_run_history_returns_normalized_items():
|
|
204
265
|
client = HotdataClient("k", "ws", host="https://api.hotdata.dev")
|
|
205
266
|
listing = SimpleNamespace(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|