datalab-api 0.5.0__tar.gz → 0.5.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.
- {datalab_api-0.5.0/src/datalab_api.egg-info → datalab_api-0.5.2}/PKG-INFO +1 -1
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api/__init__.py +44 -12
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api/_base.py +6 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2/src/datalab_api.egg-info}/PKG-INFO +1 -1
- {datalab_api-0.5.0 → datalab_api-0.5.2}/tests/test_client.py +52 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/LICENSE +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/README.md +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/pyproject.toml +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/setup.cfg +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api/cli.py +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api/utils.py +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api.egg-info/SOURCES.txt +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api.egg-info/dependency_links.txt +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api.egg-info/entry_points.txt +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api.egg-info/requires.txt +0 -0
- {datalab_api-0.5.0 → datalab_api-0.5.2}/src/datalab_api.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A Python API for instances of the datalab data management platform (https://github.com/datalab-org/datalab).
|
|
5
5
|
Author-email: Matthew Evans <matthew@ml-evs.science>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -273,7 +273,13 @@ class DatalabClient(BaseDatalabClient):
|
|
|
273
273
|
for chunk in response.iter_bytes(chunk_size=1024):
|
|
274
274
|
file.write(chunk)
|
|
275
275
|
|
|
276
|
-
def get_block(
|
|
276
|
+
def get_block(
|
|
277
|
+
self,
|
|
278
|
+
item_id: str,
|
|
279
|
+
block_id: str,
|
|
280
|
+
block_data: dict[str, Any] | None = None,
|
|
281
|
+
event_data: dict[str, Any] | None = None,
|
|
282
|
+
) -> dict[str, Any]:
|
|
277
283
|
"""Get a block with a given ID and block data.
|
|
278
284
|
Should be used in conjunction with `get_item` to load an existing block.
|
|
279
285
|
|
|
@@ -281,17 +287,25 @@ class DatalabClient(BaseDatalabClient):
|
|
|
281
287
|
item_id: The ID of the item to search for.
|
|
282
288
|
block_id: The ID of the block to search for.
|
|
283
289
|
block_data: Any other block data required by the request.
|
|
290
|
+
event_data: Any block events to trigger.
|
|
284
291
|
|
|
285
292
|
Returns:
|
|
286
293
|
A dictionary of block data for the block with the given ID.
|
|
287
294
|
|
|
288
295
|
"""
|
|
289
|
-
block_url = f"{self.datalab_api_url}/
|
|
296
|
+
block_url = f"{self.datalab_api_url}/blocks/"
|
|
297
|
+
|
|
298
|
+
if not block_data:
|
|
299
|
+
block_data = {}
|
|
300
|
+
|
|
301
|
+
block_data["item_id"] = item_id
|
|
302
|
+
block_data["block_id"] = block_id
|
|
303
|
+
if "blocktype" not in block_data:
|
|
304
|
+
raise ValueError("block_data must contain a 'blocktype' key.")
|
|
305
|
+
|
|
290
306
|
block_request = {
|
|
291
307
|
"block_data": block_data,
|
|
292
|
-
"
|
|
293
|
-
"block_id": block_id,
|
|
294
|
-
"save_to_db": False,
|
|
308
|
+
"event_data": event_data,
|
|
295
309
|
}
|
|
296
310
|
block = self._post(block_url, json=block_request)
|
|
297
311
|
return block["new_block_data"]
|
|
@@ -308,7 +322,12 @@ class DatalabClient(BaseDatalabClient):
|
|
|
308
322
|
If provided, the new file will take the place of the old file in any blocks it was attached to.
|
|
309
323
|
|
|
310
324
|
Returns:
|
|
311
|
-
A dictionary of the uploaded file data
|
|
325
|
+
A dictionary of the uploaded file data, with an additional
|
|
326
|
+
`not_modified` key. If `replace_file_id` was provided and the
|
|
327
|
+
server already holds identical content (compared by hash), the
|
|
328
|
+
update is a no-op: the server replies `304 Not Modified` and
|
|
329
|
+
`not_modified` is `True`. Servers older than datalab#1744 never
|
|
330
|
+
report this, so `not_modified` is always `False` against them.
|
|
312
331
|
|
|
313
332
|
"""
|
|
314
333
|
if isinstance(file_path, str):
|
|
@@ -327,10 +346,23 @@ class DatalabClient(BaseDatalabClient):
|
|
|
327
346
|
upload_url,
|
|
328
347
|
files=files,
|
|
329
348
|
data={"item_id": item_id, "replace_file": replace_file_id},
|
|
330
|
-
expected_status=201,
|
|
349
|
+
expected_status=[201, 304],
|
|
331
350
|
timeout=upload_timeout,
|
|
332
351
|
)
|
|
333
|
-
|
|
352
|
+
|
|
353
|
+
# A 201 always carries a body describing the stored file, so an
|
|
354
|
+
# empty dict here can only be the 304: the server compared hashes,
|
|
355
|
+
# found it already holds this content, and made no update. That is
|
|
356
|
+
# a success, so report it rather than raising.
|
|
357
|
+
if not upload:
|
|
358
|
+
return {
|
|
359
|
+
"status": "success",
|
|
360
|
+
"file_id": replace_file_id,
|
|
361
|
+
"is_update": True,
|
|
362
|
+
"not_modified": True,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {**upload, "not_modified": False}
|
|
334
366
|
|
|
335
367
|
def create_data_block(
|
|
336
368
|
self,
|
|
@@ -424,10 +456,10 @@ class DatalabClient(BaseDatalabClient):
|
|
|
424
456
|
|
|
425
457
|
if file_ids:
|
|
426
458
|
if len(file_ids) > 1:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
)
|
|
430
|
-
|
|
459
|
+
block_data["file_ids"] = file_ids
|
|
460
|
+
else:
|
|
461
|
+
block_data.pop("file_ids", None)
|
|
462
|
+
block_data["file_id"] = file_ids[0]
|
|
431
463
|
|
|
432
464
|
blocks_url = f"{self.datalab_api_url}/update-block/"
|
|
433
465
|
payload = {
|
|
@@ -317,6 +317,12 @@ This is likely a server-side bug. Please report this issue to the datalab develo
|
|
|
317
317
|
f"HTTP {response.status_code} error at {url}: {error_message}"
|
|
318
318
|
)
|
|
319
319
|
|
|
320
|
+
# 204 and 304 carry no entity body by definition
|
|
321
|
+
# Where such a status is expected, an empty body
|
|
322
|
+
# is the success case rather than a parse failure.
|
|
323
|
+
if response.status_code in (204, 304):
|
|
324
|
+
return {}
|
|
325
|
+
|
|
320
326
|
# Parse JSON response
|
|
321
327
|
try:
|
|
322
328
|
data = response.json()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A Python API for instances of the datalab data management platform (https://github.com/datalab-org/datalab).
|
|
5
5
|
Author-email: Matthew Evans <matthew@ml-evs.science>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import respx
|
|
3
|
+
from httpx import Response
|
|
3
4
|
|
|
4
5
|
from datalab_api import DatalabClient
|
|
5
6
|
from datalab_api._base import DatalabAPIError
|
|
@@ -71,3 +72,54 @@ def test_group_get(fake_api_url, mocked_api):
|
|
|
71
72
|
assert mocked_api["search-groups"].called
|
|
72
73
|
assert group["immutable_id"] == "67d0b01e03000cdc75134dbd"
|
|
73
74
|
assert group["group_id"] == "test_group"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@respx.mock
|
|
78
|
+
def test_upload_file(fake_api_url, mocked_api, tmp_path):
|
|
79
|
+
"""A new upload is stored by the server and reported as modified."""
|
|
80
|
+
fake_upload = mocked_api.post("/upload-file/", name="upload")
|
|
81
|
+
fake_upload.return_value = Response(
|
|
82
|
+
201,
|
|
83
|
+
json={
|
|
84
|
+
"status": "success",
|
|
85
|
+
"file_id": "6810ab6bb2b13a2be6c1e37f",
|
|
86
|
+
"file_information": {"name": "data.csv"},
|
|
87
|
+
"is_update": False,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
test_file = tmp_path / "data.csv"
|
|
92
|
+
test_file.write_text("a,b\n1,2\n")
|
|
93
|
+
|
|
94
|
+
with DatalabClient(fake_api_url) as client:
|
|
95
|
+
result = client.upload_file(item_id="KUVEKJ", file_path=test_file)
|
|
96
|
+
|
|
97
|
+
assert mocked_api["upload"].called
|
|
98
|
+
assert result["file_id"] == "6810ab6bb2b13a2be6c1e37f"
|
|
99
|
+
assert result["not_modified"] is False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@respx.mock
|
|
103
|
+
def test_upload_file_not_modified(fake_api_url, mocked_api, tmp_path):
|
|
104
|
+
"""When replacing a file whose content the server already holds, it
|
|
105
|
+
replies 304 with an empty body. That is a successful no-op, so the
|
|
106
|
+
existing file ID is returned with `not_modified` set, rather than
|
|
107
|
+
the empty body being treated as an error."""
|
|
108
|
+
fake_upload = mocked_api.post("/upload-file/", name="upload")
|
|
109
|
+
fake_upload.return_value = Response(304, content=b"")
|
|
110
|
+
|
|
111
|
+
test_file = tmp_path / "data.csv"
|
|
112
|
+
test_file.write_text("a,b\n1,2\n")
|
|
113
|
+
|
|
114
|
+
with DatalabClient(fake_api_url) as client:
|
|
115
|
+
result = client.upload_file(
|
|
116
|
+
item_id="KUVEKJ",
|
|
117
|
+
file_path=test_file,
|
|
118
|
+
replace_file_id="6810ab6bb2b13a2be6c1e37f",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert mocked_api["upload"].called
|
|
122
|
+
assert result["status"] == "success"
|
|
123
|
+
assert result["file_id"] == "6810ab6bb2b13a2be6c1e37f"
|
|
124
|
+
assert result["is_update"] is True
|
|
125
|
+
assert result["not_modified"] is True
|
|
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
|