hotdata-framework 0.7.3__tar.gz → 0.8.0__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_framework-0.7.3 → hotdata_framework-0.8.0}/CHANGELOG.md +9 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/PKG-INFO +2 -2
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/client.py +2 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/managed_client.py +6 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/pyproject.toml +2 -2
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_databases.py +10 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_managed_client.py +38 -1
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/uv.lock +5 -5
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/dependabot.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/check-release.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/ci.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/dependabot-automerge.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/publish.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/release.yml +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.gitignore +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/CONTRACT.md +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/README.md +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/RELEASING.md +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/examples/basic_usage.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/__init__.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/databases.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/env.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/errors.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/health.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/http.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/py.typed +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/hotdata_framework/result.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/scripts/check-release.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/scripts/extract-changelog.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/scripts/publish-workflow.sh +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/scripts/release.sh +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/scripts/update_changelog.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_client.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_contract.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_errors.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_health.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_request_timeout.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_result.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_update_changelog.py +0 -0
- {hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/tests/test_version.py +0 -0
|
@@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [0.8.0] - 2026-07-20
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `load_managed_table` accepts a `key` argument — the merge key columns for
|
|
16
|
+
`delete`/`update`/`upsert` loads, matched per-load instead of requiring a key
|
|
17
|
+
declared at table creation. Omit it to use the table's declared key; ignored
|
|
18
|
+
for `replace`/`append`. Requires `hotdata>=0.8.0`.
|
|
19
|
+
|
|
11
20
|
## [0.7.3] - 2026-07-16
|
|
12
21
|
|
|
13
22
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hotdata-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Python framework for building Hotdata integrations: workspace/session runtime, query execution, and managed databases
|
|
5
5
|
Project-URL: Homepage, https://www.hotdata.dev
|
|
6
6
|
Project-URL: Documentation, https://www.hotdata.dev/docs
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: hotdata>=0.
|
|
24
|
+
Requires-Dist: hotdata>=0.8.0
|
|
25
25
|
Requires-Dist: pandas>=2.0
|
|
26
26
|
Requires-Dist: pyarrow>=14.0
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
@@ -340,6 +340,7 @@ class HotdataClient:
|
|
|
340
340
|
upload_id: str | None = None,
|
|
341
341
|
file: str | None = None,
|
|
342
342
|
mode: ManagedLoadMode = "replace",
|
|
343
|
+
key: list[str] | None = None,
|
|
343
344
|
) -> LoadManagedTableResult:
|
|
344
345
|
if (upload_id is None) == (file is None):
|
|
345
346
|
raise ValueError("Exactly one of upload_id or file is required")
|
|
@@ -352,6 +353,7 @@ class HotdataClient:
|
|
|
352
353
|
request = LoadManagedTableRequest(
|
|
353
354
|
mode=mode,
|
|
354
355
|
upload_id=resolved_upload_id,
|
|
356
|
+
key=key,
|
|
355
357
|
)
|
|
356
358
|
try:
|
|
357
359
|
loaded = self.connections().load_managed_table(
|
|
@@ -205,10 +205,15 @@ class ManagedDatabaseClient:
|
|
|
205
205
|
schema: str,
|
|
206
206
|
upload_id: str,
|
|
207
207
|
mode: ManagedLoadMode = "replace",
|
|
208
|
+
key: list[str] | None = None,
|
|
208
209
|
) -> LoadManagedTableResult:
|
|
209
210
|
# append is the only non-idempotent mode: if the server commits the load
|
|
210
211
|
# but the response is lost, a retry re-appends the same rows. Run it
|
|
211
212
|
# at-most-once; every other mode is safe to retry.
|
|
213
|
+
#
|
|
214
|
+
# `key` is the merge key for delete/update/upsert loads: when set it is
|
|
215
|
+
# matched per-load instead of a key declared at table creation. Omit it
|
|
216
|
+
# to use the table's declared key. Ignored for replace/append.
|
|
212
217
|
return self._request_with_retry(
|
|
213
218
|
lambda: self._runtime.load_managed_table(
|
|
214
219
|
database,
|
|
@@ -216,6 +221,7 @@ class ManagedDatabaseClient:
|
|
|
216
221
|
schema=schema,
|
|
217
222
|
upload_id=upload_id,
|
|
218
223
|
mode=mode,
|
|
224
|
+
key=key,
|
|
219
225
|
),
|
|
220
226
|
retryable=(mode != "append"),
|
|
221
227
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hotdata-framework"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Python framework for building Hotdata integrations: workspace/session runtime, query execution, and managed databases"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
29
|
# 0.7.0 adds `key` to table decls (create_managed_database(keys=) / add_managed_table(key=))
|
|
30
|
-
"hotdata>=0.
|
|
30
|
+
"hotdata>=0.8.0",
|
|
31
31
|
"pandas>=2.0",
|
|
32
32
|
"pyarrow>=14.0",
|
|
33
33
|
]
|
|
@@ -308,6 +308,16 @@ def test_load_managed_table_defaults_to_replace():
|
|
|
308
308
|
assert _load_and_capture_request(_client()).mode == "replace"
|
|
309
309
|
|
|
310
310
|
|
|
311
|
+
def test_load_managed_table_passes_key():
|
|
312
|
+
request = _load_and_capture_request(_client(), mode="delete", key=["id"])
|
|
313
|
+
assert request.mode == "delete"
|
|
314
|
+
assert request.key == ["id"]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_load_managed_table_key_defaults_to_none():
|
|
318
|
+
assert _load_and_capture_request(_client()).key is None
|
|
319
|
+
|
|
320
|
+
|
|
311
321
|
@pytest.mark.parametrize("mode", ["append", "delete", "update", "upsert"])
|
|
312
322
|
def test_load_managed_table_forwards_mode(mode: str):
|
|
313
323
|
assert _load_and_capture_request(_client(), mode=mode).mode == mode
|
|
@@ -163,7 +163,13 @@ def _load_recording_runtime(calls: list[str]) -> SimpleNamespace:
|
|
|
163
163
|
with a transient error, so retry behaviour is observable via ``calls``."""
|
|
164
164
|
|
|
165
165
|
def load_managed_table(
|
|
166
|
-
database: str,
|
|
166
|
+
database: str,
|
|
167
|
+
table: str,
|
|
168
|
+
*,
|
|
169
|
+
schema: str,
|
|
170
|
+
upload_id: str,
|
|
171
|
+
mode: str,
|
|
172
|
+
key: list[str] | None = None,
|
|
167
173
|
) -> SimpleNamespace:
|
|
168
174
|
calls.append(mode)
|
|
169
175
|
raise TimeoutError("commit succeeded but response was lost")
|
|
@@ -209,3 +215,34 @@ def test_idempotent_load_retries_on_transient(monkeypatch: pytest.MonkeyPatch) -
|
|
|
209
215
|
client.load_managed_table("db", "orders", schema="public", upload_id="u1", mode="replace")
|
|
210
216
|
|
|
211
217
|
assert calls == ["replace", "replace", "replace"] # retried up to max_retries
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_load_managed_table_forwards_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
221
|
+
"""A per-load ``key`` is passed straight through to the runtime client."""
|
|
222
|
+
monkeypatch.setattr(mc.time, "sleep", lambda _seconds: None)
|
|
223
|
+
captured: dict[str, Any] = {}
|
|
224
|
+
|
|
225
|
+
def load_managed_table(
|
|
226
|
+
database: str,
|
|
227
|
+
table: str,
|
|
228
|
+
*,
|
|
229
|
+
schema: str,
|
|
230
|
+
upload_id: str,
|
|
231
|
+
mode: str,
|
|
232
|
+
key: list[str] | None = None,
|
|
233
|
+
) -> SimpleNamespace:
|
|
234
|
+
captured["mode"] = mode
|
|
235
|
+
captured["key"] = key
|
|
236
|
+
return SimpleNamespace(
|
|
237
|
+
connection_id="c", schema_name=schema, table_name=table, row_count=0
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
client = _managed_client(max_retries=1)
|
|
241
|
+
runtime = _fake_runtime()
|
|
242
|
+
runtime.load_managed_table = load_managed_table
|
|
243
|
+
client._runtime = runtime
|
|
244
|
+
|
|
245
|
+
client.load_managed_table(
|
|
246
|
+
"db", "orders", schema="public", upload_id="u1", mode="delete", key=["id"]
|
|
247
|
+
)
|
|
248
|
+
assert captured == {"mode": "delete", "key": ["id"]}
|
|
@@ -86,7 +86,7 @@ wheels = [
|
|
|
86
86
|
|
|
87
87
|
[[package]]
|
|
88
88
|
name = "hotdata"
|
|
89
|
-
version = "0.
|
|
89
|
+
version = "0.8.0"
|
|
90
90
|
source = { registry = "https://pypi.org/simple" }
|
|
91
91
|
dependencies = [
|
|
92
92
|
{ name = "pydantic" },
|
|
@@ -94,14 +94,14 @@ dependencies = [
|
|
|
94
94
|
{ name = "typing-extensions" },
|
|
95
95
|
{ name = "urllib3" },
|
|
96
96
|
]
|
|
97
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
97
|
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/38/30ed3d1d99413e7684672fa424baef85a347db465b90c774443320cf1cea/hotdata-0.8.0.tar.gz", hash = "sha256:cdac515ffa193ed028491e4b7abcd1b6404d4e3f96986c9c0a2b2f407db42eb6", size = 216848, upload-time = "2026-07-20T06:30:37.659Z" }
|
|
98
98
|
wheels = [
|
|
99
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/bc/0f/f2ccaeb0f910c3f8cd84a911d934704277f0d6e5a20f8b49cf0eedb2a8aa/hotdata-0.8.0-py3-none-any.whl", hash = "sha256:5d64bfc185a0e7e2bf34ebdcea50787d3418fb250dacc53e00dc74d64e99f753", size = 316923, upload-time = "2026-07-20T06:30:35.778Z" },
|
|
100
100
|
]
|
|
101
101
|
|
|
102
102
|
[[package]]
|
|
103
103
|
name = "hotdata-framework"
|
|
104
|
-
version = "0.
|
|
104
|
+
version = "0.8.0"
|
|
105
105
|
source = { editable = "." }
|
|
106
106
|
dependencies = [
|
|
107
107
|
{ name = "hotdata" },
|
|
@@ -120,7 +120,7 @@ dev = [
|
|
|
120
120
|
|
|
121
121
|
[package.metadata]
|
|
122
122
|
requires-dist = [
|
|
123
|
-
{ name = "hotdata", specifier = ">=0.
|
|
123
|
+
{ name = "hotdata", specifier = ">=0.8.0" },
|
|
124
124
|
{ name = "pandas", specifier = ">=2.0" },
|
|
125
125
|
{ name = "pyarrow", specifier = ">=14.0" },
|
|
126
126
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hotdata_framework-0.7.3 → hotdata_framework-0.8.0}/.github/workflows/dependabot-automerge.yml
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|