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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datalab-api
3
- Version: 0.2.3
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://public.datalab.odbx.science)).
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://public.api.odbx.science") as client:
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.odbx.science --page-limit 2
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://public.datalab.odbx.science)).
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://public.api.odbx.science") as client:
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.odbx.science --page-limit 2
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 = "py310"
83
+ target-version = "py39"
79
84
 
80
85
  [tool.ruff.lint]
81
- select = ["E", "F", "I", "W", "Q"]
82
- ignore = ["E501", "E402"]
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://public.api.odbx.science") as client:
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: Optional[str] = "samples") -> list[dict[str, Any]]:
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: Union[list[str], str] = ["samples", "cells"]
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, item_id: str, item_type: str, item_data: Optional[dict[str, Any]] = None
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(**{"item_id": item_id, "type": item_type})
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 created_item["status"] != "success":
123
- raise RuntimeError(
124
- f"Failed to create item at {create_item_url}: {created_item['status']!r}."
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 RuntimeError(
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: Optional[str] = None,
165
- refcode: Optional[str] = None,
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: Union[Path, str]) -> dict[str, Any]:
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: Optional[Union[str, list[str]]] = None,
310
- file_paths: Optional[Union[Path, list[Path]]] = None,
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: Optional[Union[str, list[str]]] = None
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
- if "bokeh_plot_data" in block_data:
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://public.datalab.odbx.science`
120
- will 'redirect' to `https://public.api.odbx.science`).
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
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://public.datalab.odbx.science)).
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://public.api.odbx.science") as client:
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.odbx.science --page-limit 2
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 ┃
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  src/datalab_api/__init__.py
5
5
  src/datalab_api/_base.py
6
6
  src/datalab_api/cli.py
7
+ src/datalab_api/helpers.py
7
8
  src/datalab_api.egg-info/PKG-INFO
8
9
  src/datalab_api.egg-info/SOURCES.txt
9
10
  src/datalab_api.egg-info/dependency_links.txt
@@ -5,6 +5,10 @@ rich~=13.0
5
5
  [all]
6
6
  datalab-api[cli,dev,docs]
7
7
 
8
+ [cheminventory-helper]
9
+ pandas~=2.2
10
+ openpyxl~=3.0
11
+
8
12
  [cli]
9
13
  typer~=0.9
10
14
  click-shell~=2.1
File without changes
File without changes