datalab-api 0.4.0__tar.gz → 0.5.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.
- {datalab_api-0.4.0/src/datalab_api.egg-info → datalab_api-0.5.1}/PKG-INFO +1 -1
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api/__init__.py +87 -18
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api/_base.py +2 -2
- {datalab_api-0.4.0 → datalab_api-0.5.1/src/datalab_api.egg-info}/PKG-INFO +1 -1
- {datalab_api-0.4.0 → datalab_api-0.5.1}/tests/test_client.py +13 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/LICENSE +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/README.md +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/pyproject.toml +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/setup.cfg +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api/cli.py +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api/utils.py +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api.egg-info/SOURCES.txt +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api.egg-info/dependency_links.txt +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api.egg-info/entry_points.txt +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/src/datalab_api.egg-info/requires.txt +0 -0
- {datalab_api-0.4.0 → datalab_api-0.5.1}/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.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -127,6 +127,8 @@ class DatalabClient(BaseDatalabClient):
|
|
|
127
127
|
item_type: str = "samples",
|
|
128
128
|
item_data: dict[str, Any] | None = None,
|
|
129
129
|
collection_id: str | None = None,
|
|
130
|
+
collection_ids: str | Iterable[str] | None = None, # alias for collection_id
|
|
131
|
+
group_ids: str | Iterable[str] | None = None,
|
|
130
132
|
) -> dict[str, Any]:
|
|
131
133
|
"""Create an item with a given ID and item data.
|
|
132
134
|
|
|
@@ -135,7 +137,9 @@ class DatalabClient(BaseDatalabClient):
|
|
|
135
137
|
item_type: The type of item to create, e.g., 'samples', 'cells'.
|
|
136
138
|
item_data: The data for the item.
|
|
137
139
|
collection_id: The ID of the collection to add the item to (optional).
|
|
138
|
-
If such a collection does not exist, one will be made.
|
|
140
|
+
If such a collection does not exist, one will be made. Deprecated in favor of `collection_ids`.
|
|
141
|
+
collection_ids: The ID or IDs of the collection(s) to add the item to (optional).
|
|
142
|
+
group_ids: The ID or IDs of the group(s) to share the item with (optional).
|
|
139
143
|
|
|
140
144
|
"""
|
|
141
145
|
new_item = {}
|
|
@@ -147,13 +151,34 @@ class DatalabClient(BaseDatalabClient):
|
|
|
147
151
|
new_item.pop("item_id")
|
|
148
152
|
|
|
149
153
|
if collection_id is not None:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
warnings.warn(
|
|
155
|
+
DeprecationWarning(
|
|
156
|
+
"`collection_id` is deprecated, please use `collection_ids` instead."
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if collection_ids is None:
|
|
160
|
+
collection_ids = [collection_id]
|
|
161
|
+
|
|
162
|
+
if collection_ids:
|
|
155
163
|
new_item["collections"] = new_item.get("collections", [])
|
|
156
|
-
|
|
164
|
+
for collection_id in collection_ids:
|
|
165
|
+
try:
|
|
166
|
+
collection_immutable_id = self.get_collection(collection_id)[0]["immutable_id"]
|
|
167
|
+
except (RuntimeError, DatalabAPIError):
|
|
168
|
+
self.create_collection(collection_id)
|
|
169
|
+
collection_immutable_id = self.get_collection(collection_id)[0]["immutable_id"]
|
|
170
|
+
new_item["collections"].append({"immutable_id": collection_immutable_id})
|
|
171
|
+
|
|
172
|
+
if group_ids:
|
|
173
|
+
# For now, we have to use the search groups endpoint
|
|
174
|
+
# until we add a get group by id endpoint that is locked down per user
|
|
175
|
+
new_item["groups"] = new_item.get("groups", [])
|
|
176
|
+
for group_id in group_ids:
|
|
177
|
+
try:
|
|
178
|
+
group_immutable_id = self.get_group(group_id)["immutable_id"]
|
|
179
|
+
new_item["groups"].append({"immutable_id": group_immutable_id})
|
|
180
|
+
except (RuntimeError, DatalabAPIError):
|
|
181
|
+
pass
|
|
157
182
|
|
|
158
183
|
create_item_url = f"{self.datalab_api_url}/new-sample/"
|
|
159
184
|
created_item = self._post(
|
|
@@ -248,7 +273,13 @@ class DatalabClient(BaseDatalabClient):
|
|
|
248
273
|
for chunk in response.iter_bytes(chunk_size=1024):
|
|
249
274
|
file.write(chunk)
|
|
250
275
|
|
|
251
|
-
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]:
|
|
252
283
|
"""Get a block with a given ID and block data.
|
|
253
284
|
Should be used in conjunction with `get_item` to load an existing block.
|
|
254
285
|
|
|
@@ -256,27 +287,39 @@ class DatalabClient(BaseDatalabClient):
|
|
|
256
287
|
item_id: The ID of the item to search for.
|
|
257
288
|
block_id: The ID of the block to search for.
|
|
258
289
|
block_data: Any other block data required by the request.
|
|
290
|
+
event_data: Any block events to trigger.
|
|
259
291
|
|
|
260
292
|
Returns:
|
|
261
293
|
A dictionary of block data for the block with the given ID.
|
|
262
294
|
|
|
263
295
|
"""
|
|
264
|
-
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
|
+
|
|
265
306
|
block_request = {
|
|
266
307
|
"block_data": block_data,
|
|
267
|
-
"
|
|
268
|
-
"block_id": block_id,
|
|
269
|
-
"save_to_db": False,
|
|
308
|
+
"event_data": event_data,
|
|
270
309
|
}
|
|
271
310
|
block = self._post(block_url, json=block_request)
|
|
272
311
|
return block["new_block_data"]
|
|
273
312
|
|
|
274
|
-
def upload_file(
|
|
313
|
+
def upload_file(
|
|
314
|
+
self, item_id: str, file_path: Path | str, replace_file_id: str | None = None
|
|
315
|
+
) -> dict[str, Any]:
|
|
275
316
|
"""Upload a file to an item with a given ID.
|
|
276
317
|
|
|
277
318
|
Parameters:
|
|
278
319
|
item_id: The ID of the item to upload the file to.
|
|
279
320
|
file_path: The path to the file to upload.
|
|
321
|
+
replace_file_id: The ID of an existing file to replace with the uploaded file, if any.
|
|
322
|
+
If provided, the new file will take the place of the old file in any blocks it was attached to.
|
|
280
323
|
|
|
281
324
|
Returns:
|
|
282
325
|
A dictionary of the uploaded file data.
|
|
@@ -297,7 +340,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
297
340
|
upload = self._post(
|
|
298
341
|
upload_url,
|
|
299
342
|
files=files,
|
|
300
|
-
data={"item_id": item_id, "replace_file":
|
|
343
|
+
data={"item_id": item_id, "replace_file": replace_file_id},
|
|
301
344
|
expected_status=201,
|
|
302
345
|
timeout=upload_timeout,
|
|
303
346
|
)
|
|
@@ -395,10 +438,10 @@ class DatalabClient(BaseDatalabClient):
|
|
|
395
438
|
|
|
396
439
|
if file_ids:
|
|
397
440
|
if len(file_ids) > 1:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
)
|
|
401
|
-
|
|
441
|
+
block_data["file_ids"] = file_ids
|
|
442
|
+
else:
|
|
443
|
+
block_data.pop("file_ids", None)
|
|
444
|
+
block_data["file_id"] = file_ids[0]
|
|
402
445
|
|
|
403
446
|
blocks_url = f"{self.datalab_api_url}/update-block/"
|
|
404
447
|
payload = {
|
|
@@ -435,6 +478,32 @@ class DatalabClient(BaseDatalabClient):
|
|
|
435
478
|
collection = self._get(collection_url)
|
|
436
479
|
return collection["data"], collection["child_items"]
|
|
437
480
|
|
|
481
|
+
def get_group(self, group_id: str) -> dict[str, Any]:
|
|
482
|
+
"""Get a group with a given ID.
|
|
483
|
+
|
|
484
|
+
Uses the search groups endpoint under the hood,
|
|
485
|
+
so will only find groups that the user has access
|
|
486
|
+
to and that match the query.
|
|
487
|
+
|
|
488
|
+
Parameters:
|
|
489
|
+
group_id: The human-readable ID of the group to search for.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
A dictionary of group data for the group with the given ID.
|
|
493
|
+
|
|
494
|
+
Raises:
|
|
495
|
+
DatalabAPIError: If no group with the given ID is found.
|
|
496
|
+
|
|
497
|
+
"""
|
|
498
|
+
group_url = f"{self.datalab_api_url}/search/groups?query={group_id}"
|
|
499
|
+
group_resp = self._get(group_url)
|
|
500
|
+
matching_groups = group_resp["data"]
|
|
501
|
+
if matching_groups:
|
|
502
|
+
matching_groups = [g for g in matching_groups if g.get("group_id") == group_id]
|
|
503
|
+
return matching_groups[0]
|
|
504
|
+
|
|
505
|
+
raise DatalabAPIError(f"No group found with ID {group_id!r}")
|
|
506
|
+
|
|
438
507
|
def create_collection(
|
|
439
508
|
self, collection_id: str, collection_data: dict | None = None
|
|
440
509
|
) -> dict[str, Any]:
|
|
@@ -35,7 +35,7 @@ class BaseDatalabClient(metaclass=AutoPrettyPrint):
|
|
|
35
35
|
|
|
36
36
|
_api_key: Optional[str] = None
|
|
37
37
|
_session: Optional[httpx.Client] = None
|
|
38
|
-
_headers: dict[str, str]
|
|
38
|
+
_headers: dict[str, str]
|
|
39
39
|
_timeout: httpx.Timeout = httpx.Timeout(10.0, read=60.0)
|
|
40
40
|
|
|
41
41
|
interactive: bool = True
|
|
@@ -85,7 +85,7 @@ class BaseDatalabClient(metaclass=AutoPrettyPrint):
|
|
|
85
85
|
self.log = logging.getLogger(__name__)
|
|
86
86
|
|
|
87
87
|
self._http_client = httpx.Client
|
|
88
|
-
self._headers
|
|
88
|
+
self._headers = {"User-Agent": f"Datalab Python API/{__version__}"}
|
|
89
89
|
|
|
90
90
|
self._detect_api_url()
|
|
91
91
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -58,3 +58,16 @@ def test_collection_get(fake_api_url, mocked_api):
|
|
|
58
58
|
|
|
59
59
|
collection, children = client.get_collection("test_collection", display=True)
|
|
60
60
|
assert mocked_api["collection-test_collection"].called
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@respx.mock
|
|
64
|
+
def test_group_get(fake_api_url, mocked_api):
|
|
65
|
+
with DatalabClient("https://api.datalab.industries") as client:
|
|
66
|
+
assert mocked_api["api"].called
|
|
67
|
+
assert mocked_api["info"].called
|
|
68
|
+
assert mocked_api["info-blocks"].called
|
|
69
|
+
|
|
70
|
+
group = client.get_group("test_group", display=True)
|
|
71
|
+
assert mocked_api["search-groups"].called
|
|
72
|
+
assert group["immutable_id"] == "67d0b01e03000cdc75134dbd"
|
|
73
|
+
assert group["group_id"] == "test_group"
|
|
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
|