datalab-api 0.5.2__tar.gz → 0.5.3__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.2/src/datalab_api.egg-info → datalab_api-0.5.3}/PKG-INFO +26 -1
- {datalab_api-0.5.2 → datalab_api-0.5.3}/README.md +25 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api/__init__.py +6 -1
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api/_base.py +37 -1
- {datalab_api-0.5.2 → datalab_api-0.5.3/src/datalab_api.egg-info}/PKG-INFO +26 -1
- {datalab_api-0.5.2 → datalab_api-0.5.3}/tests/test_client.py +46 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/LICENSE +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/pyproject.toml +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/setup.cfg +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api/cli.py +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api/utils.py +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api.egg-info/SOURCES.txt +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api.egg-info/dependency_links.txt +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api.egg-info/entry_points.txt +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/src/datalab_api.egg-info/requires.txt +0 -0
- {datalab_api-0.5.2 → datalab_api-0.5.3}/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.3
|
|
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
|
|
@@ -120,6 +120,31 @@ with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
|
120
120
|
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
### Elevated (admin) permissions
|
|
124
|
+
|
|
125
|
+
By default a client can only see items its account owns or has been shared on.
|
|
126
|
+
Admins can opt in to datalab's super-user mode to read items belonging to other users, by passing `elevate_permissions`:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
with DatalabClient(
|
|
130
|
+
"https://demo-api.datalab-org.io", elevate_permissions=True
|
|
131
|
+
) as client:
|
|
132
|
+
# Finds the item even if it belongs to another user
|
|
133
|
+
item = client.get_item(item_id="someone-elses-sample")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This sends datalab's `sudo=1` flag on read requests.
|
|
137
|
+
It requires the API key in use to belong to an **admin** account; for any other account the server ignores
|
|
138
|
+
it and normal permissions apply.
|
|
139
|
+
|
|
140
|
+
The attribute can also be toggled at any point in the client's lifetime:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
client.elevate_permissions = True
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Note that objects created while elevated (e.g. uploaded files) are still recorded as belonging to the admin's own account, not the item owner's.
|
|
147
|
+
|
|
123
148
|
### Command-line interface (CLI)
|
|
124
149
|
|
|
125
150
|
There is also an EXPERIMENTAL CLI, exposed via the `datalab` command.
|
|
@@ -75,6 +75,31 @@ with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
|
75
75
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Elevated (admin) permissions
|
|
79
|
+
|
|
80
|
+
By default a client can only see items its account owns or has been shared on.
|
|
81
|
+
Admins can opt in to datalab's super-user mode to read items belonging to other users, by passing `elevate_permissions`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
with DatalabClient(
|
|
85
|
+
"https://demo-api.datalab-org.io", elevate_permissions=True
|
|
86
|
+
) as client:
|
|
87
|
+
# Finds the item even if it belongs to another user
|
|
88
|
+
item = client.get_item(item_id="someone-elses-sample")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This sends datalab's `sudo=1` flag on read requests.
|
|
92
|
+
It requires the API key in use to belong to an **admin** account; for any other account the server ignores
|
|
93
|
+
it and normal permissions apply.
|
|
94
|
+
|
|
95
|
+
The attribute can also be toggled at any point in the client's lifetime:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
client.elevate_permissions = True
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Note that objects created while elevated (e.g. uploaded files) are still recorded as belonging to the admin's own account, not the item owner's.
|
|
102
|
+
|
|
78
103
|
### Command-line interface (CLI)
|
|
79
104
|
|
|
80
105
|
There is also an EXPERIMENTAL CLI, exposed via the `datalab` command.
|
|
@@ -268,8 +268,13 @@ class DatalabClient(BaseDatalabClient):
|
|
|
268
268
|
if Path(f["name"]).exists():
|
|
269
269
|
warnings.warn(f"Will not overwrite existing file {f['name']}")
|
|
270
270
|
continue
|
|
271
|
+
# This streams directly from the session rather than going through
|
|
272
|
+
# `_request`, so the elevation flag has to be applied by hand.
|
|
273
|
+
stream_kwargs = self._apply_elevation("GET", {})
|
|
271
274
|
with open(f["name"], "wb") as file:
|
|
272
|
-
with self.session.stream(
|
|
275
|
+
with self.session.stream(
|
|
276
|
+
"GET", url, follow_redirects=True, **stream_kwargs
|
|
277
|
+
) as response:
|
|
273
278
|
for chunk in response.iter_bytes(chunk_size=1024):
|
|
274
279
|
file.write(chunk)
|
|
275
280
|
|
|
@@ -58,7 +58,22 @@ class BaseDatalabClient(metaclass=AutoPrettyPrint):
|
|
|
58
58
|
min_server_version: tuple[int, int, int] = (0, 1, 0)
|
|
59
59
|
"""The minimum supported server version that this client supports."""
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
elevate_permissions: bool = False
|
|
62
|
+
"""Whether to make requests in datalab's admin super-user mode, i.e., with
|
|
63
|
+
access to items belonging to other users.
|
|
64
|
+
|
|
65
|
+
This only has an effect if the API key in use belongs to an admin account;
|
|
66
|
+
for any other account the server ignores the request and normal permissions
|
|
67
|
+
apply. Can be set at any point in the client's lifetime to toggle the
|
|
68
|
+
behaviour.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
datalab_api_url: str,
|
|
74
|
+
log_level: str = "WARNING",
|
|
75
|
+
elevate_permissions: bool = False,
|
|
76
|
+
):
|
|
62
77
|
"""Creates an authenticated client.
|
|
63
78
|
|
|
64
79
|
An API key is required to authenticate requests. The client will attempt to load it from a
|
|
@@ -72,10 +87,14 @@ class BaseDatalabClient(metaclass=AutoPrettyPrint):
|
|
|
72
87
|
to resolve the underlying API URL (e.g., `https://demo-api.datalab-org.io`
|
|
73
88
|
will 'redirect' to `https://demo-api.datalab-org.io`).
|
|
74
89
|
log_level: The logging level to use for the client. Defaults to "WARNING".
|
|
90
|
+
elevate_permissions: Whether to read items belonging to other users, using
|
|
91
|
+
datalab's admin super-user mode. Requires the API key in use to belong
|
|
92
|
+
to an admin account; ignored by the server otherwise. Defaults to False.
|
|
75
93
|
|
|
76
94
|
|
|
77
95
|
"""
|
|
78
96
|
|
|
97
|
+
self.elevate_permissions = elevate_permissions
|
|
79
98
|
self.datalab_api_url = datalab_api_url
|
|
80
99
|
if not self.datalab_api_url:
|
|
81
100
|
raise ValueError("No Datalab API URL provided.")
|
|
@@ -367,6 +386,7 @@ This is likely a server-side bug. Please report this issue to the datalab develo
|
|
|
367
386
|
The JSON response data
|
|
368
387
|
"""
|
|
369
388
|
timeout = kwargs.pop("timeout", None)
|
|
389
|
+
kwargs = self._apply_elevation(method, kwargs)
|
|
370
390
|
try:
|
|
371
391
|
response = self.session.request(
|
|
372
392
|
method, url, follow_redirects=True, timeout=timeout, **kwargs
|
|
@@ -375,6 +395,22 @@ This is likely a server-side bug. Please report this issue to the datalab develo
|
|
|
375
395
|
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
376
396
|
raise DatalabAPIError(f"Request failed for {url}: {e}")
|
|
377
397
|
|
|
398
|
+
def _apply_elevation(self, method: str, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
399
|
+
"""Add datalab's super-user flag to a read when running elevated.
|
|
400
|
+
|
|
401
|
+
datalab treats an admin as an ordinary user on GET requests unless the
|
|
402
|
+
request opts in with `sudo=1`, but grants them full access on writes
|
|
403
|
+
with no opt-in at all — so only reads need the flag. Any `params` the
|
|
404
|
+
caller supplied are preserved.
|
|
405
|
+
"""
|
|
406
|
+
if not self.elevate_permissions or method.upper() != "GET":
|
|
407
|
+
return kwargs
|
|
408
|
+
|
|
409
|
+
params = dict(kwargs.pop("params", None) or {})
|
|
410
|
+
params.setdefault("sudo", "1")
|
|
411
|
+
kwargs["params"] = params
|
|
412
|
+
return kwargs
|
|
413
|
+
|
|
378
414
|
def _get(self, url: str, **kwargs) -> dict[str, Any]:
|
|
379
415
|
"""Make a GET request with error handling."""
|
|
380
416
|
return self._request("GET", url, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datalab-api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
|
@@ -120,6 +120,31 @@ with DatalabClient("https://demo-api.datalab-org.io") as client:
|
|
|
120
120
|
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
### Elevated (admin) permissions
|
|
124
|
+
|
|
125
|
+
By default a client can only see items its account owns or has been shared on.
|
|
126
|
+
Admins can opt in to datalab's super-user mode to read items belonging to other users, by passing `elevate_permissions`:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
with DatalabClient(
|
|
130
|
+
"https://demo-api.datalab-org.io", elevate_permissions=True
|
|
131
|
+
) as client:
|
|
132
|
+
# Finds the item even if it belongs to another user
|
|
133
|
+
item = client.get_item(item_id="someone-elses-sample")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This sends datalab's `sudo=1` flag on read requests.
|
|
137
|
+
It requires the API key in use to belong to an **admin** account; for any other account the server ignores
|
|
138
|
+
it and normal permissions apply.
|
|
139
|
+
|
|
140
|
+
The attribute can also be toggled at any point in the client's lifetime:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
client.elevate_permissions = True
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Note that objects created while elevated (e.g. uploaded files) are still recorded as belonging to the admin's own account, not the item owner's.
|
|
147
|
+
|
|
123
148
|
### Command-line interface (CLI)
|
|
124
149
|
|
|
125
150
|
There is also an EXPERIMENTAL CLI, exposed via the `datalab` command.
|
|
@@ -123,3 +123,49 @@ def test_upload_file_not_modified(fake_api_url, mocked_api, tmp_path):
|
|
|
123
123
|
assert result["file_id"] == "6810ab6bb2b13a2be6c1e37f"
|
|
124
124
|
assert result["is_update"] is True
|
|
125
125
|
assert result["not_modified"] is True
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_elevate_permissions_adds_sudo_to_reads(mocked_api, fake_api_url):
|
|
129
|
+
"""An elevated client sends `sudo=1` on GETs (and nothing else)."""
|
|
130
|
+
with DatalabClient(fake_api_url, elevate_permissions=True) as client:
|
|
131
|
+
client.get_item("KUVEKJ")
|
|
132
|
+
|
|
133
|
+
request = mocked_api["sample-KUVEKJ"].calls.last.request
|
|
134
|
+
assert request.url.params.get("sudo") == "1"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_elevate_permissions_is_toggleable(mocked_api, fake_api_url):
|
|
138
|
+
"""`elevate_permissions` can be flipped after construction."""
|
|
139
|
+
with DatalabClient(fake_api_url) as client:
|
|
140
|
+
client.get_item("KUVEKJ")
|
|
141
|
+
assert "sudo" not in mocked_api["sample-KUVEKJ"].calls.last.request.url.params
|
|
142
|
+
|
|
143
|
+
client.elevate_permissions = True
|
|
144
|
+
client.get_item("KUVEKJ")
|
|
145
|
+
assert mocked_api["sample-KUVEKJ"].calls.last.request.url.params.get("sudo") == "1"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_elevate_permissions_not_sent_on_writes(mocked_api, fake_api_url):
|
|
149
|
+
"""datalab grants admins full write access with no opt-in, so the flag
|
|
150
|
+
must not leak onto non-GET requests."""
|
|
151
|
+
with DatalabClient(fake_api_url, elevate_permissions=True) as client:
|
|
152
|
+
with pytest.raises(DatalabAPIError):
|
|
153
|
+
# `/save-item/` is mocked as a 500; we only care about the request
|
|
154
|
+
client.update_item("KUVEKJ", {"description": "test"})
|
|
155
|
+
|
|
156
|
+
request = mocked_api["bad-save"].calls.last.request
|
|
157
|
+
assert request.method == "POST"
|
|
158
|
+
assert "sudo" not in request.url.params
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_elevate_permissions_preserves_existing_params(mocked_api, fake_api_url):
|
|
162
|
+
"""A caller-supplied query string must survive elevation."""
|
|
163
|
+
with DatalabClient(fake_api_url, elevate_permissions=True) as client:
|
|
164
|
+
client._get(
|
|
165
|
+
f"{client.datalab_api_url}/get-item-data/KUVEKJ",
|
|
166
|
+
params={"load_blocks": "false"},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
request = mocked_api["sample-KUVEKJ"].calls.last.request
|
|
170
|
+
assert request.url.params.get("load_blocks") == "false"
|
|
171
|
+
assert request.url.params.get("sudo") == "1"
|
|
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
|