datalab-api 0.2.3__tar.gz → 0.2.5__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.2.3/src/datalab_api.egg-info → datalab_api-0.2.5}/PKG-INFO +7 -4
- {datalab_api-0.2.3 → datalab_api-0.2.5}/README.md +3 -3
- {datalab_api-0.2.3 → datalab_api-0.2.5}/pyproject.toml +92 -4
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api/__init__.py +97 -20
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api/_base.py +3 -6
- datalab_api-0.2.5/src/datalab_api/helpers.py +53 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5/src/datalab_api.egg-info}/PKG-INFO +7 -4
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api.egg-info/SOURCES.txt +1 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api.egg-info/requires.txt +4 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/LICENSE +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/setup.cfg +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api/cli.py +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api.egg-info/dependency_links.txt +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api.egg-info/entry_points.txt +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/src/datalab_api.egg-info/top_level.txt +0 -0
- {datalab_api-0.2.3 → datalab_api-0.2.5}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: A Python API for instances of the datalab data management platform (https://github.com/the-grey-group/datalab).
|
|
5
5
|
Author-email: Matthew Evans <matthew@ml-evs.science>
|
|
6
6
|
License: MIT
|
|
@@ -38,6 +38,9 @@ Requires-Dist: mkdocs-jupyter; extra == "docs"
|
|
|
38
38
|
Provides-Extra: cli
|
|
39
39
|
Requires-Dist: typer~=0.9; extra == "cli"
|
|
40
40
|
Requires-Dist: click-shell~=2.1; extra == "cli"
|
|
41
|
+
Provides-Extra: cheminventory-helper
|
|
42
|
+
Requires-Dist: pandas~=2.2; extra == "cheminventory-helper"
|
|
43
|
+
Requires-Dist: openpyxl~=3.0; extra == "cheminventory-helper"
|
|
41
44
|
Provides-Extra: all
|
|
42
45
|
Requires-Dist: datalab-api[cli,dev,docs]; extra == "all"
|
|
43
46
|
|
|
@@ -96,7 +99,7 @@ You can generate one for your account for a given *datalab* instance by visiting
|
|
|
96
99
|
|
|
97
100
|
This API key can be set via the environment variable `DATALAB_API_KEY`.
|
|
98
101
|
To suport the use case of needing to interact with multiple datalab instances, the client will also check prefixed environment variables that use the [`IDENTIFIER_PREFIX` of the chosen datalab instance](https://the-datalab.readthedocs.io/en/latest/config/#mandatory-settings), e.g., `GREY_DATALAB_API_KEY` or `PUBLIC_DATALAB_API_KEY`.
|
|
99
|
-
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://
|
|
102
|
+
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://demo.datalab-org.io)).
|
|
100
103
|
|
|
101
104
|
### Python API
|
|
102
105
|
|
|
@@ -105,7 +108,7 @@ This package implements basic functionality for displaying and manipulating entr
|
|
|
105
108
|
```python
|
|
106
109
|
from datalab_api import DatalabClient
|
|
107
110
|
|
|
108
|
-
with DatalabClient("https://
|
|
111
|
+
with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
109
112
|
|
|
110
113
|
# List all items of a given type
|
|
111
114
|
items = client.get_items()
|
|
@@ -164,7 +167,7 @@ datalab > help get
|
|
|
164
167
|
│ --help Show this message and exit. │
|
|
165
168
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
166
169
|
|
|
167
|
-
datalab > get samples api.
|
|
170
|
+
datalab > get samples demo-api.datalab-org.io --page-limit 2
|
|
168
171
|
/samples/
|
|
169
172
|
┏━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
|
|
170
173
|
┃ type ┃ ID ┃ refcode ┃ name ┃ nblocks ┃ collections ┃ creators ┃
|
|
@@ -53,7 +53,7 @@ You can generate one for your account for a given *datalab* instance by visiting
|
|
|
53
53
|
|
|
54
54
|
This API key can be set via the environment variable `DATALAB_API_KEY`.
|
|
55
55
|
To suport the use case of needing to interact with multiple datalab instances, the client will also check prefixed environment variables that use the [`IDENTIFIER_PREFIX` of the chosen datalab instance](https://the-datalab.readthedocs.io/en/latest/config/#mandatory-settings), e.g., `GREY_DATALAB_API_KEY` or `PUBLIC_DATALAB_API_KEY`.
|
|
56
|
-
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://
|
|
56
|
+
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://demo.datalab-org.io)).
|
|
57
57
|
|
|
58
58
|
### Python API
|
|
59
59
|
|
|
@@ -62,7 +62,7 @@ This package implements basic functionality for displaying and manipulating entr
|
|
|
62
62
|
```python
|
|
63
63
|
from datalab_api import DatalabClient
|
|
64
64
|
|
|
65
|
-
with DatalabClient("https://
|
|
65
|
+
with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
66
66
|
|
|
67
67
|
# List all items of a given type
|
|
68
68
|
items = client.get_items()
|
|
@@ -121,7 +121,7 @@ datalab > help get
|
|
|
121
121
|
│ --help Show this message and exit. │
|
|
122
122
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
123
123
|
|
|
124
|
-
datalab > get samples api.
|
|
124
|
+
datalab > get samples demo-api.datalab-org.io --page-limit 2
|
|
125
125
|
/samples/
|
|
126
126
|
┏━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
|
|
127
127
|
┃ type ┃ ID ┃ refcode ┃ name ┃ nblocks ┃ collections ┃ creators ┃
|
|
@@ -50,6 +50,11 @@ cli = [
|
|
|
50
50
|
"click-shell ~= 2.1", # REPL-like interface
|
|
51
51
|
]
|
|
52
52
|
|
|
53
|
+
cheminventory-helper = [
|
|
54
|
+
"pandas ~= 2.2",
|
|
55
|
+
"openpyxl ~= 3.0"
|
|
56
|
+
]
|
|
57
|
+
|
|
53
58
|
all = [
|
|
54
59
|
"datalab-api[dev,docs,cli]"
|
|
55
60
|
]
|
|
@@ -75,13 +80,96 @@ follow_imports = "skip"
|
|
|
75
80
|
|
|
76
81
|
[tool.ruff]
|
|
77
82
|
line-length = 100
|
|
78
|
-
target-version = "
|
|
83
|
+
target-version = "py39"
|
|
79
84
|
|
|
80
85
|
[tool.ruff.lint]
|
|
81
|
-
select = ["
|
|
82
|
-
ignore = [
|
|
86
|
+
select = ["ALL"]
|
|
87
|
+
ignore = [
|
|
88
|
+
"A001",
|
|
89
|
+
"A002",
|
|
90
|
+
"ANN", # TODO fix all ANN errors
|
|
91
|
+
"ARG", # TODO fix unused method argument
|
|
92
|
+
"B007",
|
|
93
|
+
"B028",
|
|
94
|
+
"B904",
|
|
95
|
+
"BLE001",
|
|
96
|
+
"C408", # Unnecessary (dict/list/tuple) call - remove call
|
|
97
|
+
"C416",
|
|
98
|
+
"C419",
|
|
99
|
+
"C901", # function too complex
|
|
100
|
+
"COM812", # trailing comma missing
|
|
101
|
+
"D",
|
|
102
|
+
"D205",
|
|
103
|
+
"DTZ", # datetime-tz-now
|
|
104
|
+
"E501", # TODO fix line too long
|
|
105
|
+
"EM", # exception message must not use f-string literal
|
|
106
|
+
"ERA001", # found commented out code
|
|
107
|
+
"FA100", # TODO fix FA errors
|
|
108
|
+
"FBT001",
|
|
109
|
+
"FBT002",
|
|
110
|
+
"FBT003",
|
|
111
|
+
"FIX002",
|
|
112
|
+
"G004", # logging uses fstring
|
|
113
|
+
"G201",
|
|
114
|
+
"INP001",
|
|
115
|
+
"ISC001",
|
|
116
|
+
"N802", # TODO maybe fix these
|
|
117
|
+
"N805",
|
|
118
|
+
"N818",
|
|
119
|
+
"PD011", # pandas-use-of-dot-values
|
|
120
|
+
"PERF102",
|
|
121
|
+
"PERF203", # try-except-in-loop
|
|
122
|
+
"PERF401",
|
|
123
|
+
"PGH003",
|
|
124
|
+
"PGH004",
|
|
125
|
+
"PLR", # pylint-refactor
|
|
126
|
+
"PLW0602",
|
|
127
|
+
"PLW0603",
|
|
128
|
+
"PLW2901",
|
|
129
|
+
"PT003",
|
|
130
|
+
"PT004", # pytest-missing-fixture-name-underscore
|
|
131
|
+
"PT006", # pytest-parametrize-names-wrong-type
|
|
132
|
+
"PT013", # pytest-incorrect-pytest-import
|
|
133
|
+
"PTH", # prefer Pathlib to os.path
|
|
134
|
+
"PYI024",
|
|
135
|
+
"RET",
|
|
136
|
+
"RET504",
|
|
137
|
+
"RUF005",
|
|
138
|
+
"RUF012",
|
|
139
|
+
"RUF013", # implicit-optional
|
|
140
|
+
"RUF015",
|
|
141
|
+
"S106",
|
|
142
|
+
"S110",
|
|
143
|
+
"S112",
|
|
144
|
+
"S311",
|
|
145
|
+
"S324", # use of insecure hash function
|
|
146
|
+
"S507", # paramiko auto trust
|
|
147
|
+
"S602",
|
|
148
|
+
"S603",
|
|
149
|
+
"S607",
|
|
150
|
+
"SIM102",
|
|
151
|
+
"SIM105",
|
|
152
|
+
"SIM108",
|
|
153
|
+
"SIM117",
|
|
154
|
+
"SLF", # private member accessed outside class
|
|
155
|
+
"SLOT000",
|
|
156
|
+
"T201",
|
|
157
|
+
"TCH",
|
|
158
|
+
"TD", # TODOs
|
|
159
|
+
"TRY", # long message outside exception class
|
|
160
|
+
"UP031",
|
|
161
|
+
]
|
|
162
|
+
pydocstyle.convention = "numpy"
|
|
163
|
+
isort.known-first-party = ["jobflow_remote"]
|
|
164
|
+
isort.split-on-trailing-comma = false
|
|
83
165
|
fixable = ["A", "B", "C", "D", "E", "F", "I"]
|
|
84
166
|
unfixable = []
|
|
85
|
-
per-file-ignores = {}
|
|
86
167
|
# Allow unused variables when underscore-prefixed.
|
|
87
168
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
169
|
+
|
|
170
|
+
[tool.ruff.format]
|
|
171
|
+
docstring-code-format = true
|
|
172
|
+
|
|
173
|
+
[tool.ruff.lint.per-file-ignores]
|
|
174
|
+
"__init__.py" = ["F401"]
|
|
175
|
+
"**/tests/*" = ["INP001", "S101"]
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import warnings
|
|
4
|
+
from collections.abc import Iterable
|
|
2
5
|
from pathlib import Path
|
|
3
6
|
from typing import Any, Optional, Union
|
|
4
7
|
|
|
@@ -7,6 +10,10 @@ from ._base import BaseDatalabClient, __version__
|
|
|
7
10
|
__all__ = ("__version__", "DatalabClient")
|
|
8
11
|
|
|
9
12
|
|
|
13
|
+
class DuplicateItemError(ValueError):
|
|
14
|
+
"""Raised when the API operation would create a duplicate item."""
|
|
15
|
+
|
|
16
|
+
|
|
10
17
|
class DatalabClient(BaseDatalabClient):
|
|
11
18
|
"""A client for the Datalab API.
|
|
12
19
|
|
|
@@ -14,7 +21,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
14
21
|
amenable for use as a context manager, e.g.,
|
|
15
22
|
|
|
16
23
|
```python
|
|
17
|
-
with DatalabClient("https://
|
|
24
|
+
with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
18
25
|
client.get_items()
|
|
19
26
|
```
|
|
20
27
|
|
|
@@ -41,7 +48,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
41
48
|
)
|
|
42
49
|
return user_resp.json()
|
|
43
50
|
|
|
44
|
-
def get_items(self, item_type:
|
|
51
|
+
def get_items(self, item_type: str | None = "samples") -> list[dict[str, Any]]:
|
|
45
52
|
"""List items of the given type available to the authenticated user.
|
|
46
53
|
|
|
47
54
|
Parameters:
|
|
@@ -66,7 +73,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
66
73
|
return items[item_type]
|
|
67
74
|
|
|
68
75
|
def search_items(
|
|
69
|
-
self, query: str, item_types:
|
|
76
|
+
self, query: str, item_types: Iterable[str] | str = ("samples", "cells")
|
|
70
77
|
) -> list[dict[str, Any]]:
|
|
71
78
|
"""Search for items of the given types that match the query.
|
|
72
79
|
|
|
@@ -96,39 +103,55 @@ class DatalabClient(BaseDatalabClient):
|
|
|
96
103
|
return items["items"]
|
|
97
104
|
|
|
98
105
|
def create_item(
|
|
99
|
-
self,
|
|
106
|
+
self,
|
|
107
|
+
item_id: str | None = None,
|
|
108
|
+
item_type: str = "samples",
|
|
109
|
+
item_data: dict[str, Any] | None = None,
|
|
110
|
+
collection_id: str | None = None,
|
|
100
111
|
) -> dict[str, Any]:
|
|
101
112
|
"""Create an item with a given ID and item data.
|
|
102
113
|
|
|
103
114
|
Parameters:
|
|
104
|
-
item_id: The ID of the item to create.
|
|
115
|
+
item_id: The ID of the item to create, if set to `None`, the server will generate one.
|
|
105
116
|
item_type: The type of item to create, e.g., 'samples', 'cells'.
|
|
106
117
|
item_data: The data for the item.
|
|
118
|
+
collection_id: The ID of the collection to add the item to (optional).
|
|
119
|
+
If such a collection does not exist, one will be made.
|
|
107
120
|
|
|
108
121
|
"""
|
|
109
122
|
new_item = {}
|
|
110
123
|
if item_data is not None:
|
|
111
124
|
new_item = item_data
|
|
112
|
-
new_item.update(
|
|
125
|
+
new_item.update({"item_id": item_id, "type": item_type})
|
|
126
|
+
|
|
127
|
+
if collection_id is not None:
|
|
128
|
+
try:
|
|
129
|
+
collection_immutable_id = self.get_collection(collection_id)["immutable_id"]
|
|
130
|
+
except RuntimeError:
|
|
131
|
+
collection_immutable_id = self.create_collection(collection_id)["immutable_id"]
|
|
132
|
+
new_item["collections"] = new_item.get("collections", [])
|
|
133
|
+
new_item["collections"].append({"immutable_id": collection_immutable_id})
|
|
113
134
|
|
|
114
135
|
create_item_url = f"{self.datalab_api_url}/new-sample/"
|
|
115
136
|
create_item_resp = self.session.post(
|
|
116
137
|
create_item_url,
|
|
117
|
-
json=new_item,
|
|
138
|
+
json={"new_sample_data": new_item, "generate_id_automatically": item_id is None},
|
|
118
139
|
follow_redirects=True,
|
|
119
140
|
)
|
|
120
141
|
try:
|
|
121
142
|
created_item = create_item_resp.json()
|
|
122
|
-
if
|
|
123
|
-
raise
|
|
124
|
-
f"
|
|
143
|
+
if create_item_resp.status_code == 409:
|
|
144
|
+
raise DuplicateItemError(
|
|
145
|
+
f"Item {item_id=} already exists at {create_item_url}: {created_item['status']!r}."
|
|
125
146
|
)
|
|
147
|
+
if created_item["status"] != "success":
|
|
148
|
+
raise RuntimeError(f"Failed to create item at {create_item_url}: {created_item}.")
|
|
126
149
|
return created_item["sample_list_entry"]
|
|
127
150
|
|
|
128
|
-
except Exception:
|
|
129
|
-
raise
|
|
130
|
-
f"Failed to create item {item_id=} with data {item_data=} at {create_item_url}: {create_item_resp.status_code=}. Check the item information is correct."
|
|
131
|
-
)
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
raise exc.__class__(
|
|
153
|
+
f"Failed to create item {item_id=} with data {item_data=} at {create_item_url}: {create_item_resp.status_code=}, {create_item_resp.content}. Check the item information is correct."
|
|
154
|
+
) from exc
|
|
132
155
|
|
|
133
156
|
def update_item(self, item_id: str, item_data: dict[str, Any]) -> dict[str, Any]:
|
|
134
157
|
"""Update an item with the given item data.
|
|
@@ -161,8 +184,8 @@ class DatalabClient(BaseDatalabClient):
|
|
|
161
184
|
|
|
162
185
|
def get_item(
|
|
163
186
|
self,
|
|
164
|
-
item_id:
|
|
165
|
-
refcode:
|
|
187
|
+
item_id: str | None = None,
|
|
188
|
+
refcode: str | None = None,
|
|
166
189
|
load_blocks: bool = False,
|
|
167
190
|
) -> dict[str, Any]:
|
|
168
191
|
"""Get an item with a given ID or refcode.
|
|
@@ -266,7 +289,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
266
289
|
raise RuntimeError(f"Failed to get block at {block_url}: {block['status']!r}.")
|
|
267
290
|
return block["new_block_data"]
|
|
268
291
|
|
|
269
|
-
def upload_file(self, item_id: str, file_path:
|
|
292
|
+
def upload_file(self, item_id: str, file_path: Path | str) -> dict[str, Any]:
|
|
270
293
|
"""Upload a file to an item with a given ID.
|
|
271
294
|
|
|
272
295
|
Parameters:
|
|
@@ -306,8 +329,8 @@ class DatalabClient(BaseDatalabClient):
|
|
|
306
329
|
self,
|
|
307
330
|
item_id: str,
|
|
308
331
|
block_type: str,
|
|
309
|
-
file_ids:
|
|
310
|
-
file_paths:
|
|
332
|
+
file_ids: str | list[str] | None = None,
|
|
333
|
+
file_paths: Path | list[Path] | None = None,
|
|
311
334
|
) -> dict[str, Any]:
|
|
312
335
|
"""Creates a data block for an item with the given block type and file ID.
|
|
313
336
|
|
|
@@ -364,7 +387,7 @@ class DatalabClient(BaseDatalabClient):
|
|
|
364
387
|
return block_data
|
|
365
388
|
|
|
366
389
|
def _update_data_block(
|
|
367
|
-
self, block_type: str, block_data: dict, file_ids:
|
|
390
|
+
self, block_type: str, block_data: dict, file_ids: str | list[str] | None = None
|
|
368
391
|
) -> dict[str, Any]:
|
|
369
392
|
"""Attaches files to blocks: should only be used via wrapper methods."""
|
|
370
393
|
if file_ids and isinstance(file_ids, str):
|
|
@@ -390,3 +413,57 @@ class DatalabClient(BaseDatalabClient):
|
|
|
390
413
|
raise RuntimeError(f"Failed to update block {block_type=}:\n{resp.text}")
|
|
391
414
|
|
|
392
415
|
return resp.json()["new_block_data"]
|
|
416
|
+
|
|
417
|
+
def get_collection(self, collection_id: str) -> dict[str, Any]:
|
|
418
|
+
"""Get a collection with a given ID.
|
|
419
|
+
|
|
420
|
+
Parameters:
|
|
421
|
+
collection_id: The ID of the collection to search for.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
A dictionary of collection data for the collection with the given ID.
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
collection_url = f"{self.datalab_api_url}/collections/{collection_id}"
|
|
428
|
+
collection_resp = self.session.get(collection_url, follow_redirects=True)
|
|
429
|
+
if collection_resp.status_code != 200:
|
|
430
|
+
raise RuntimeError(
|
|
431
|
+
f"Failed to find collection {collection_id=} at {collection_url}: {collection_resp.status_code=}. Check the collection ID is correct."
|
|
432
|
+
)
|
|
433
|
+
collection = collection_resp.json()
|
|
434
|
+
if collection["status"] != "success":
|
|
435
|
+
raise RuntimeError(
|
|
436
|
+
f"Failed to get collection at {collection_url}: {collection['status']!r}."
|
|
437
|
+
)
|
|
438
|
+
return collection["data"]
|
|
439
|
+
|
|
440
|
+
def create_collection(
|
|
441
|
+
self, collection_id: str, collection_data: dict | None = None
|
|
442
|
+
) -> dict[str, Any]:
|
|
443
|
+
"""Create a collection with a given ID and collection data.
|
|
444
|
+
|
|
445
|
+
Parameters:
|
|
446
|
+
collection_id: The ID of the collection to create.
|
|
447
|
+
collection_data: The data for the collection.
|
|
448
|
+
|
|
449
|
+
"""
|
|
450
|
+
collection_url = f"{self.datalab_api_url}/collections"
|
|
451
|
+
|
|
452
|
+
new_collection = {}
|
|
453
|
+
if collection_data is not None:
|
|
454
|
+
new_collection = collection_data
|
|
455
|
+
new_collection.update({"collection_id": collection_id, "type": "collections"})
|
|
456
|
+
|
|
457
|
+
collection_resp = self.session.put(
|
|
458
|
+
collection_url,
|
|
459
|
+
json={"data": new_collection},
|
|
460
|
+
follow_redirects=True,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if collection_resp.status_code != 201 or collection_resp.json()["status"] != "success":
|
|
464
|
+
raise RuntimeError(
|
|
465
|
+
f"Failed to create collection {collection_id=} at {collection_url}: {collection_resp.status_code=}. Check the collection information is correct: {collection_resp.content}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
created_collection = collection_resp.json()["data"]
|
|
469
|
+
return created_collection
|
|
@@ -76,10 +76,7 @@ def bokeh_from_json(block_data, show=True):
|
|
|
76
76
|
from bokeh.io import curdoc
|
|
77
77
|
from bokeh.plotting import show as bokeh_show
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
bokeh_plot_data = block_data["bokeh_plot_data"]
|
|
81
|
-
else:
|
|
82
|
-
bokeh_plot_data = block_data
|
|
79
|
+
bokeh_plot_data = block_data.get("bokeh_plot_data", block_data)
|
|
83
80
|
curdoc().replace_with_json(bokeh_plot_data["doc"])
|
|
84
81
|
if show:
|
|
85
82
|
bokeh_show(curdoc().roots[0])
|
|
@@ -116,8 +113,8 @@ class BaseDatalabClient(metaclass=AutoPrettyPrint):
|
|
|
116
113
|
Parameters:
|
|
117
114
|
datalab_api_url: The URL of the Datalab API.
|
|
118
115
|
TODO: If the URL of a datalab *UI* is provided, a request will be made to attempt
|
|
119
|
-
to resolve the underlying API URL (e.g., `https://
|
|
120
|
-
will 'redirect' to `https://
|
|
116
|
+
to resolve the underlying API URL (e.g., `https://demo-api.datalab-org.io`
|
|
117
|
+
will 'redirect' to `https://demo-api.datalab-org.io`).
|
|
121
118
|
log_level: The logging level to use for the client. Defaults to "WARNING".
|
|
122
119
|
|
|
123
120
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from datalab_api import DatalabClient, DuplicateItemError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def import_cheminventory(filename: Path, client: DatalabClient):
|
|
8
|
+
"""Helper function to import a ChemInventory xlsx export into datalab, using the
|
|
9
|
+
'starting_materials' item type.
|
|
10
|
+
|
|
11
|
+
Migrated from the original version in the main datalab repository:
|
|
12
|
+
https://github.com/the-grey-group/datalab/blob/43764fb494c2cc1bf9f7dc90c25594aeb79d5767/pydatalab/tasks.py#L350-L413
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def _generate_random_startingmaterial_id():
|
|
17
|
+
"""Generate 'XX' + a random 15-length string for use as an id for starting materials
|
|
18
|
+
that don't have a barcode.
|
|
19
|
+
"""
|
|
20
|
+
yield "".join(["XX"] + random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=15))
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import pandas as pd
|
|
24
|
+
|
|
25
|
+
inventory_df = pd.read_excel(filename)
|
|
26
|
+
except ImportError as exc:
|
|
27
|
+
raise ImportError(
|
|
28
|
+
"Please install pandas + openpyxl to use this helper function, via `pip install datalab-api[cheminventory-helper]`"
|
|
29
|
+
) from exc
|
|
30
|
+
inventory_df["type"] = "starting_materials"
|
|
31
|
+
inventory_df["item_id"] = inventory_df["Barcode"]
|
|
32
|
+
# Fill missing barcodes
|
|
33
|
+
inventory_df["item_id"] = inventory_df["item_id"].fillna(
|
|
34
|
+
inventory_df["item_id"].apply(lambda _: next(_generate_random_startingmaterial_id()))
|
|
35
|
+
)
|
|
36
|
+
inventory_df["Molecular Weight"] = inventory_df["Molecular Weight"].replace(" ", float("nan"))
|
|
37
|
+
|
|
38
|
+
counts = {"success": 0, "duplicate": 0, "failed": 0}
|
|
39
|
+
|
|
40
|
+
for item in inventory_df.to_dict(orient="records"):
|
|
41
|
+
try:
|
|
42
|
+
client.create_item(
|
|
43
|
+
item["item_id"], item["type"], item, collection_id="jul24-inventory-import"
|
|
44
|
+
)
|
|
45
|
+
counts["success"] += 1
|
|
46
|
+
except DuplicateItemError:
|
|
47
|
+
counts["duplicate"] += 1
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
counts["failed"] += 1
|
|
50
|
+
print(f"Failed to import item: {item}. Error: {exc}")
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
print(f"Done: {counts=}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: A Python API for instances of the datalab data management platform (https://github.com/the-grey-group/datalab).
|
|
5
5
|
Author-email: Matthew Evans <matthew@ml-evs.science>
|
|
6
6
|
License: MIT
|
|
@@ -38,6 +38,9 @@ Requires-Dist: mkdocs-jupyter; extra == "docs"
|
|
|
38
38
|
Provides-Extra: cli
|
|
39
39
|
Requires-Dist: typer~=0.9; extra == "cli"
|
|
40
40
|
Requires-Dist: click-shell~=2.1; extra == "cli"
|
|
41
|
+
Provides-Extra: cheminventory-helper
|
|
42
|
+
Requires-Dist: pandas~=2.2; extra == "cheminventory-helper"
|
|
43
|
+
Requires-Dist: openpyxl~=3.0; extra == "cheminventory-helper"
|
|
41
44
|
Provides-Extra: all
|
|
42
45
|
Requires-Dist: datalab-api[cli,dev,docs]; extra == "all"
|
|
43
46
|
|
|
@@ -96,7 +99,7 @@ You can generate one for your account for a given *datalab* instance by visiting
|
|
|
96
99
|
|
|
97
100
|
This API key can be set via the environment variable `DATALAB_API_KEY`.
|
|
98
101
|
To suport the use case of needing to interact with multiple datalab instances, the client will also check prefixed environment variables that use the [`IDENTIFIER_PREFIX` of the chosen datalab instance](https://the-datalab.readthedocs.io/en/latest/config/#mandatory-settings), e.g., `GREY_DATALAB_API_KEY` or `PUBLIC_DATALAB_API_KEY`.
|
|
99
|
-
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://
|
|
102
|
+
Only keys that match will be read (e.g., other environment variables starting with `PUBLIC_` will be ignored, when connecting to the [public demo datalab](https://demo.datalab-org.io)).
|
|
100
103
|
|
|
101
104
|
### Python API
|
|
102
105
|
|
|
@@ -105,7 +108,7 @@ This package implements basic functionality for displaying and manipulating entr
|
|
|
105
108
|
```python
|
|
106
109
|
from datalab_api import DatalabClient
|
|
107
110
|
|
|
108
|
-
with DatalabClient("https://
|
|
111
|
+
with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
109
112
|
|
|
110
113
|
# List all items of a given type
|
|
111
114
|
items = client.get_items()
|
|
@@ -164,7 +167,7 @@ datalab > help get
|
|
|
164
167
|
│ --help Show this message and exit. │
|
|
165
168
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
166
169
|
|
|
167
|
-
datalab > get samples api.
|
|
170
|
+
datalab > get samples demo-api.datalab-org.io --page-limit 2
|
|
168
171
|
/samples/
|
|
169
172
|
┏━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
|
|
170
173
|
┃ type ┃ ID ┃ refcode ┃ name ┃ nblocks ┃ collections ┃ creators ┃
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|