hotdata-framework 0.7.1__tar.gz → 0.7.2__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.
Files changed (40) hide show
  1. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/CHANGELOG.md +6 -0
  2. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/PKG-INFO +1 -1
  3. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/client.py +6 -14
  4. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/pyproject.toml +1 -1
  5. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_databases.py +43 -5
  6. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/uv.lock +1 -1
  7. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/dependabot.yml +0 -0
  8. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/workflows/check-release.yml +0 -0
  9. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/workflows/ci.yml +0 -0
  10. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/workflows/dependabot-automerge.yml +0 -0
  11. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/workflows/publish.yml +0 -0
  12. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.github/workflows/release.yml +0 -0
  13. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/.gitignore +0 -0
  14. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/CONTRACT.md +0 -0
  15. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/README.md +0 -0
  16. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/RELEASING.md +0 -0
  17. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/examples/basic_usage.py +0 -0
  18. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/__init__.py +0 -0
  19. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/databases.py +0 -0
  20. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/env.py +0 -0
  21. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/errors.py +0 -0
  22. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/health.py +0 -0
  23. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/http.py +0 -0
  24. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/managed_client.py +0 -0
  25. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/py.typed +0 -0
  26. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/hotdata_framework/result.py +0 -0
  27. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/scripts/check-release.py +0 -0
  28. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/scripts/extract-changelog.py +0 -0
  29. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/scripts/publish-workflow.sh +0 -0
  30. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/scripts/release.sh +0 -0
  31. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/scripts/update_changelog.py +0 -0
  32. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_client.py +0 -0
  33. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_contract.py +0 -0
  34. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_errors.py +0 -0
  35. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_health.py +0 -0
  36. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_managed_client.py +0 -0
  37. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_request_timeout.py +0 -0
  38. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_result.py +0 -0
  39. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_update_changelog.py +0 -0
  40. {hotdata_framework-0.7.1 → hotdata_framework-0.7.2}/tests/test_version.py +0 -0
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [0.7.2] - 2026-07-15
12
+
13
+ ### Removed
14
+
15
+ - The `POST /v1/files` fallback in `upload_parquet()`. Presigned upload sessions (`POST /v1/uploads`) are now required; a server that responds 501 raises a clear `RuntimeError` instead of silently falling back to the full-file-in-memory upload path.
16
+
11
17
  ## [0.7.1] - 2026-07-15
12
18
 
13
19
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hotdata-framework
3
- Version: 0.7.1
3
+ Version: 0.7.2
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
@@ -321,7 +321,12 @@ class HotdataClient:
321
321
  )
322
322
  except ApiException as e:
323
323
  if e.status == 501:
324
- return self._upload_parquet_post(path)
324
+ raise RuntimeError(
325
+ "the server does not support presigned uploads (HTTP 501); "
326
+ "managed table loads require a storage backend that can issue "
327
+ "presigned URLs (the POST /v1/files fallback was removed in "
328
+ "hotdata-framework 0.7.2)"
329
+ ) from e
325
330
  raise RuntimeError(api_error_message(e)) from e
326
331
  http = urllib3.PoolManager()
327
332
  parts: list[FinalizeUploadPart] | None = None
@@ -374,19 +379,6 @@ class HotdataClient:
374
379
  raise RuntimeError(api_error_message(e)) from e
375
380
  return finalized.upload_id
376
381
 
377
- def _upload_parquet_post(self, path: str) -> str:
378
- """Fallback for storage backends that do not support presigned URLs (501)."""
379
- with open(path, "rb") as f:
380
- data = f.read()
381
- try:
382
- uploaded = self.uploads().upload_file(
383
- data,
384
- _content_type="application/octet-stream",
385
- )
386
- except ApiException as e:
387
- raise RuntimeError(api_error_message(e)) from e
388
- return uploaded.id
389
-
390
382
  def load_managed_table(
391
383
  self,
392
384
  database: str,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hotdata-framework"
7
- version = "0.7.1"
7
+ version = "0.7.2"
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"
@@ -278,9 +278,8 @@ def test_upload_parquet_single_put():
278
278
  assert call_args.args[1] == "https://s/put"
279
279
 
280
280
 
281
- def test_upload_parquet_fallback_on_501():
281
+ def test_upload_parquet_raises_on_501():
282
282
  client = _client()
283
- uploaded = SimpleNamespace(id="upl_post")
284
283
 
285
284
  with (
286
285
  patch("builtins.open", mock_open(read_data=b"PAR1")),
@@ -289,11 +288,50 @@ def test_upload_parquet_fallback_on_501():
289
288
  ):
290
289
  err = ApiException(status=501)
291
290
  uploads.return_value.create_upload_session_handler.side_effect = err
292
- uploads.return_value.upload_file.return_value = uploaded
293
291
 
294
- upload_id = client.upload_parquet("/tmp/data.parquet")
292
+ with pytest.raises(RuntimeError, match="presigned uploads"):
293
+ client.upload_parquet("/tmp/data.parquet")
294
+
295
+ uploads.return_value.upload_file.assert_not_called()
296
+
297
+
298
+ def test_upload_parquet_multipart_reads_are_part_size_bounded():
299
+ """The OOM fix: multipart mode must never read more than part_size at once."""
300
+ client = _client()
301
+ part_size = 5
302
+ data = b"PAR1" + b"\x00" * 9 # 13 bytes -> parts of 5, 5, 3
303
+ read_sizes: list[int] = []
304
+
305
+ class _TrackingFile(io.BytesIO):
306
+ def read(self, n=-1):
307
+ read_sizes.append(n)
308
+ return super().read(n)
309
+
310
+ bio = _TrackingFile(data)
311
+ handle = MagicMock()
312
+ handle.__enter__ = lambda s: bio
313
+ handle.__exit__ = MagicMock(return_value=False)
314
+ session = _session(
315
+ "multipart", part_size=part_size, part_urls=["https://s/1", "https://s/2", "https://s/3"]
316
+ )
317
+ finalized = SimpleNamespace(upload_id="upl_final")
318
+
319
+ with (
320
+ patch("builtins.open", MagicMock(return_value=handle)),
321
+ patch("os.path.getsize", return_value=len(data)),
322
+ patch.object(client, "uploads") as uploads,
323
+ patch("hotdata_framework.client.urllib3.PoolManager") as MockPool,
324
+ ):
325
+ pool = MockPool.return_value
326
+ pool.request.return_value = _http_resp()
327
+ pool.clear.return_value = None
328
+ uploads.return_value.create_upload_session_handler.return_value = session
329
+ uploads.return_value.finalize_upload_handler.return_value = finalized
330
+
331
+ client.upload_parquet("/tmp/data.parquet")
295
332
 
296
- assert upload_id == "upl_post"
333
+ assert read_sizes, "expected chunked reads"
334
+ assert all(n == part_size for n in read_sizes)
297
335
 
298
336
 
299
337
  def test_load_managed_table_with_upload_id():
@@ -101,7 +101,7 @@ wheels = [
101
101
 
102
102
  [[package]]
103
103
  name = "hotdata-framework"
104
- version = "0.7.1"
104
+ version = "0.7.2"
105
105
  source = { editable = "." }
106
106
  dependencies = [
107
107
  { name = "hotdata" },