unitlab 3.0.3__tar.gz → 3.0.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.
- {unitlab-3.0.3 → unitlab-3.0.5}/PKG-INFO +96 -3
- {unitlab-3.0.3 → unitlab-3.0.5}/README.md +95 -2
- {unitlab-3.0.3 → unitlab-3.0.5}/pyproject.toml +1 -1
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/__init__.py +4 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_http.py +5 -0
- unitlab-3.0.5/src/unitlab/_nuscenes.py +531 -0
- unitlab-3.0.5/src/unitlab/_scene_uploader.py +604 -0
- unitlab-3.0.5/src/unitlab/_scenes.py +833 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_uploader.py +217 -19
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/cli.py +223 -1
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/exceptions.py +10 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/assets.py +69 -17
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/projects.py +63 -13
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/releases.py +72 -3
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/workflow.py +104 -8
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/types.py +9 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_cli.py +69 -1
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_client.py +439 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_http.py +27 -1
- unitlab-3.0.5/tests/test_nuscenes.py +484 -0
- unitlab-3.0.5/tests/test_scene_cli.py +250 -0
- unitlab-3.0.5/tests/test_scene_uploads.py +565 -0
- unitlab-3.0.5/tests/test_scenes.py +650 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_tiled_uploads.py +158 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/.gitignore +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/LICENSE.md +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/__main__.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_config.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_downloader.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_grouping.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/_waiter.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/client.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/ontology.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/py.typed +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/__init__.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/_base.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/cloud.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/datasets.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/embeddings.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/src/unitlab/resources/ontologies.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_downloader.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_embeddings.py +0 -0
- {unitlab-3.0.3 → unitlab-3.0.5}/tests/test_uploader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitlab
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.5
|
|
4
4
|
Summary: Python SDK for the Unitlab.ai data annotation platform
|
|
5
5
|
Project-URL: Homepage, https://unitlab.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.unitlab.ai
|
|
@@ -113,7 +113,8 @@ print(batch.uploaded, batch.batch_queue_id)
|
|
|
113
113
|
|
|
114
114
|
The directory may mix images, video, audio, text, PDFs, DICOM, NIfTI, and NRRD.
|
|
115
115
|
One call creates one Batch Queue. Call `batch.wait()` when the next operation
|
|
116
|
-
depends on server-side processing.
|
|
116
|
+
depends on server-side processing. Lidar scenes are uploaded with
|
|
117
|
+
`generic_type="pointcloud"`; see [Lidar scenes](#lidar-scenes).
|
|
117
118
|
|
|
118
119
|
### 2. Curate in Assets, publish a dataset, and attach it
|
|
119
120
|
|
|
@@ -234,6 +235,76 @@ upload directly to storage in bounded parts. Tiled processing can take hours,
|
|
|
234
235
|
so use a longer timeout such as `batch.wait(timeout=25200)` when the next
|
|
235
236
|
operation depends on the generated COG/DZI derivative.
|
|
236
237
|
|
|
238
|
+
Video, medical (NIfTI, NRRD, DICOM), audio and PDF files larger than 64 MiB
|
|
239
|
+
also upload directly to storage, up to 500 MiB each; smaller ones keep the
|
|
240
|
+
ordinary upload. A large medical file becomes one volume on its own and is not
|
|
241
|
+
grouped with other files of the batch into a series.
|
|
242
|
+
|
|
243
|
+
DICOM files named without a usable extension (`IM00001`, or a UID) are found by
|
|
244
|
+
their content when a folder is uploaded and are sent as medical; nothing needs
|
|
245
|
+
to be renamed.
|
|
246
|
+
|
|
247
|
+
### Lidar scenes
|
|
248
|
+
|
|
249
|
+
A lidar scene is one item whatever it is made of. It is uploaded only when the
|
|
250
|
+
type is named; without it `.pcd`, `.ply`, `.bin` and scene `.zip` files are
|
|
251
|
+
skipped. The workspace needs lidar scenes enabled.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from unitlab import detect_scenes
|
|
255
|
+
|
|
256
|
+
# Look first: what would be uploaded, nothing is sent.
|
|
257
|
+
for scene in detect_scenes("./recordings").scenes:
|
|
258
|
+
print(scene.name, scene.frame_count, scene.point_sensors, scene.cameras)
|
|
259
|
+
|
|
260
|
+
batch = project.upload("./recordings", generic_type="pointcloud")
|
|
261
|
+
batch.wait()
|
|
262
|
+
|
|
263
|
+
for item_id in batch.data_item_ids:
|
|
264
|
+
status = project.scene_status(item_id)
|
|
265
|
+
print(status.status, status.frame_count, status.error)
|
|
266
|
+
|
|
267
|
+
project.retry_scene(item_id) # a failed scene, about a minute after it failed
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
What `source` may be:
|
|
271
|
+
|
|
272
|
+
- One file: a `.pcd`, `.ply` or `.bin` cloud, or a `.zip` that contains such
|
|
273
|
+
frames or a `scene.json`. Any other `.zip` is refused.
|
|
274
|
+
- A folder with a `scene.json`, or with the point clouds of one sensor
|
|
275
|
+
(`drive_01/lidar/000.pcd`, ...). Image folders whose file names match the
|
|
276
|
+
frames become its cameras (`drive_01/cam_front/000.jpg`).
|
|
277
|
+
- A folder with one subfolder per sensor that share file names. This is read as
|
|
278
|
+
one scene with several sensors; pass `split_sensor_folders=True` when each
|
|
279
|
+
folder is a recording of its own.
|
|
280
|
+
- A folder that holds several such scenes: each one is uploaded as its own
|
|
281
|
+
item. Scene files left over in the folder are one scene each.
|
|
282
|
+
- A nuScenes dataset (the folder with `samples/` and `v1.0-*/`): one scene per
|
|
283
|
+
recording, key frames only, with the calibration taken from its tables.
|
|
284
|
+
|
|
285
|
+
Other files in the folder are not uploaded by this call; upload the folder once
|
|
286
|
+
more without the type for ordinary media. A scene is limited to 20 GiB and
|
|
287
|
+
1,000 frames, and one data unit is used per frame.
|
|
288
|
+
|
|
289
|
+
`batch.wait()` covers scenes like any other upload. A failed scene is not in
|
|
290
|
+
`project.data_units()`; find it in the Batch Queue (`queue.data()`) and ask
|
|
291
|
+
`project.scene_status(item_id)` for the reason.
|
|
292
|
+
|
|
293
|
+
Scenes go into Assets the same way, and the existing Asset methods apply:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
result = client.assets.upload(
|
|
297
|
+
"./recordings", generic_type="pointcloud", folder="Lidar"
|
|
298
|
+
)
|
|
299
|
+
asset = result.assets[0]
|
|
300
|
+
status = asset.wait() # status.frame_count, status.error
|
|
301
|
+
asset.retry() # a failed scene
|
|
302
|
+
asset = client.assets.asset("ASSET_ID") # a handle by ID, for status and retry
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`release.download_files()` writes a scene that was uploaded as a folder under
|
|
306
|
+
`<item file name>/` with the paths it was uploaded with.
|
|
307
|
+
|
|
237
308
|
### Data Units and project lifecycle
|
|
238
309
|
|
|
239
310
|
```python
|
|
@@ -393,6 +464,18 @@ annotation_path = release.download(split="train", dest="./release-annotations")
|
|
|
393
464
|
files_folder = release.download_files(dest="./release-files")
|
|
394
465
|
```
|
|
395
466
|
|
|
467
|
+
`client.releases.create()` waits until the Release is ready. To return immediately
|
|
468
|
+
and wait later:
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
release = client.releases.create(project, wait=False)
|
|
472
|
+
release.wait(timeout=7200) # Wait before downloading.
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Check progress with `release.refresh()`, then read `release.status` and
|
|
476
|
+
`release.progress`. Lists show ready Releases; use `client.releases.get(release_id)`
|
|
477
|
+
to check one still being prepared.
|
|
478
|
+
|
|
396
479
|
Release item download tokens are persistent Unitlab URLs. For files stored in
|
|
397
480
|
customer cloud storage, the URL redirects to a signed target URL; only that target
|
|
398
481
|
URL is temporary.
|
|
@@ -411,6 +494,12 @@ unitlab project sources PROJECT_ID
|
|
|
411
494
|
unitlab project detach-source PROJECT_ID SOURCE_LINK_ID --preview
|
|
412
495
|
unitlab project upload PROJECT_ID --source ./data
|
|
413
496
|
unitlab project upload PROJECT_ID --source ./maps
|
|
497
|
+
unitlab project upload PROJECT_ID --source ./recordings --type pointcloud --dry-run
|
|
498
|
+
unitlab project upload PROJECT_ID --source ./recordings --type pointcloud
|
|
499
|
+
unitlab project upload PROJECT_ID --source ./rig --type pointcloud \
|
|
500
|
+
--split-sensor-folders
|
|
501
|
+
unitlab project scene-status PROJECT_ID ITEM_ID
|
|
502
|
+
unitlab project scene-retry PROJECT_ID ITEM_ID
|
|
414
503
|
unitlab batch-queue list PROJECT_ID
|
|
415
504
|
unitlab batch-queue detail PROJECT_ID BATCH_QUEUE_ID
|
|
416
505
|
unitlab batch-queue status PROJECT_ID BATCH_QUEUE_ID
|
|
@@ -426,6 +515,9 @@ unitlab project attach PROJECT_ID --dataset-version DATASET_ID:2
|
|
|
426
515
|
# Assets
|
|
427
516
|
unitlab assets upload ./data --folder "Raw data" --tag incoming
|
|
428
517
|
unitlab assets upload ./slides --folder "Slides"
|
|
518
|
+
unitlab assets upload ./recordings --folder "Lidar" --type pointcloud
|
|
519
|
+
unitlab assets status ASSET_ID
|
|
520
|
+
unitlab assets retry ASSET_ID
|
|
429
521
|
|
|
430
522
|
# Folders
|
|
431
523
|
unitlab folders create "Raw data"
|
|
@@ -454,8 +546,9 @@ unitlab dataset unpublished-changes DATASET_ID
|
|
|
454
546
|
unitlab dataset publish DATASET_ID --title "Initial snapshot"
|
|
455
547
|
unitlab dataset versions DATASET_ID
|
|
456
548
|
|
|
457
|
-
# Releases
|
|
549
|
+
# Releases (create returns immediately; add --wait to wait until ready)
|
|
458
550
|
unitlab release create PROJECT_ID --format UUEF --splits train=100
|
|
551
|
+
unitlab release wait RELEASE_ID --timeout 7200
|
|
459
552
|
unitlab release list
|
|
460
553
|
unitlab release detail RELEASE_ID
|
|
461
554
|
unitlab release download RELEASE_ID --split-type train --dest ./release-annotations
|
|
@@ -80,7 +80,8 @@ print(batch.uploaded, batch.batch_queue_id)
|
|
|
80
80
|
|
|
81
81
|
The directory may mix images, video, audio, text, PDFs, DICOM, NIfTI, and NRRD.
|
|
82
82
|
One call creates one Batch Queue. Call `batch.wait()` when the next operation
|
|
83
|
-
depends on server-side processing.
|
|
83
|
+
depends on server-side processing. Lidar scenes are uploaded with
|
|
84
|
+
`generic_type="pointcloud"`; see [Lidar scenes](#lidar-scenes).
|
|
84
85
|
|
|
85
86
|
### 2. Curate in Assets, publish a dataset, and attach it
|
|
86
87
|
|
|
@@ -201,6 +202,76 @@ upload directly to storage in bounded parts. Tiled processing can take hours,
|
|
|
201
202
|
so use a longer timeout such as `batch.wait(timeout=25200)` when the next
|
|
202
203
|
operation depends on the generated COG/DZI derivative.
|
|
203
204
|
|
|
205
|
+
Video, medical (NIfTI, NRRD, DICOM), audio and PDF files larger than 64 MiB
|
|
206
|
+
also upload directly to storage, up to 500 MiB each; smaller ones keep the
|
|
207
|
+
ordinary upload. A large medical file becomes one volume on its own and is not
|
|
208
|
+
grouped with other files of the batch into a series.
|
|
209
|
+
|
|
210
|
+
DICOM files named without a usable extension (`IM00001`, or a UID) are found by
|
|
211
|
+
their content when a folder is uploaded and are sent as medical; nothing needs
|
|
212
|
+
to be renamed.
|
|
213
|
+
|
|
214
|
+
### Lidar scenes
|
|
215
|
+
|
|
216
|
+
A lidar scene is one item whatever it is made of. It is uploaded only when the
|
|
217
|
+
type is named; without it `.pcd`, `.ply`, `.bin` and scene `.zip` files are
|
|
218
|
+
skipped. The workspace needs lidar scenes enabled.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from unitlab import detect_scenes
|
|
222
|
+
|
|
223
|
+
# Look first: what would be uploaded, nothing is sent.
|
|
224
|
+
for scene in detect_scenes("./recordings").scenes:
|
|
225
|
+
print(scene.name, scene.frame_count, scene.point_sensors, scene.cameras)
|
|
226
|
+
|
|
227
|
+
batch = project.upload("./recordings", generic_type="pointcloud")
|
|
228
|
+
batch.wait()
|
|
229
|
+
|
|
230
|
+
for item_id in batch.data_item_ids:
|
|
231
|
+
status = project.scene_status(item_id)
|
|
232
|
+
print(status.status, status.frame_count, status.error)
|
|
233
|
+
|
|
234
|
+
project.retry_scene(item_id) # a failed scene, about a minute after it failed
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
What `source` may be:
|
|
238
|
+
|
|
239
|
+
- One file: a `.pcd`, `.ply` or `.bin` cloud, or a `.zip` that contains such
|
|
240
|
+
frames or a `scene.json`. Any other `.zip` is refused.
|
|
241
|
+
- A folder with a `scene.json`, or with the point clouds of one sensor
|
|
242
|
+
(`drive_01/lidar/000.pcd`, ...). Image folders whose file names match the
|
|
243
|
+
frames become its cameras (`drive_01/cam_front/000.jpg`).
|
|
244
|
+
- A folder with one subfolder per sensor that share file names. This is read as
|
|
245
|
+
one scene with several sensors; pass `split_sensor_folders=True` when each
|
|
246
|
+
folder is a recording of its own.
|
|
247
|
+
- A folder that holds several such scenes: each one is uploaded as its own
|
|
248
|
+
item. Scene files left over in the folder are one scene each.
|
|
249
|
+
- A nuScenes dataset (the folder with `samples/` and `v1.0-*/`): one scene per
|
|
250
|
+
recording, key frames only, with the calibration taken from its tables.
|
|
251
|
+
|
|
252
|
+
Other files in the folder are not uploaded by this call; upload the folder once
|
|
253
|
+
more without the type for ordinary media. A scene is limited to 20 GiB and
|
|
254
|
+
1,000 frames, and one data unit is used per frame.
|
|
255
|
+
|
|
256
|
+
`batch.wait()` covers scenes like any other upload. A failed scene is not in
|
|
257
|
+
`project.data_units()`; find it in the Batch Queue (`queue.data()`) and ask
|
|
258
|
+
`project.scene_status(item_id)` for the reason.
|
|
259
|
+
|
|
260
|
+
Scenes go into Assets the same way, and the existing Asset methods apply:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
result = client.assets.upload(
|
|
264
|
+
"./recordings", generic_type="pointcloud", folder="Lidar"
|
|
265
|
+
)
|
|
266
|
+
asset = result.assets[0]
|
|
267
|
+
status = asset.wait() # status.frame_count, status.error
|
|
268
|
+
asset.retry() # a failed scene
|
|
269
|
+
asset = client.assets.asset("ASSET_ID") # a handle by ID, for status and retry
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`release.download_files()` writes a scene that was uploaded as a folder under
|
|
273
|
+
`<item file name>/` with the paths it was uploaded with.
|
|
274
|
+
|
|
204
275
|
### Data Units and project lifecycle
|
|
205
276
|
|
|
206
277
|
```python
|
|
@@ -360,6 +431,18 @@ annotation_path = release.download(split="train", dest="./release-annotations")
|
|
|
360
431
|
files_folder = release.download_files(dest="./release-files")
|
|
361
432
|
```
|
|
362
433
|
|
|
434
|
+
`client.releases.create()` waits until the Release is ready. To return immediately
|
|
435
|
+
and wait later:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
release = client.releases.create(project, wait=False)
|
|
439
|
+
release.wait(timeout=7200) # Wait before downloading.
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Check progress with `release.refresh()`, then read `release.status` and
|
|
443
|
+
`release.progress`. Lists show ready Releases; use `client.releases.get(release_id)`
|
|
444
|
+
to check one still being prepared.
|
|
445
|
+
|
|
363
446
|
Release item download tokens are persistent Unitlab URLs. For files stored in
|
|
364
447
|
customer cloud storage, the URL redirects to a signed target URL; only that target
|
|
365
448
|
URL is temporary.
|
|
@@ -378,6 +461,12 @@ unitlab project sources PROJECT_ID
|
|
|
378
461
|
unitlab project detach-source PROJECT_ID SOURCE_LINK_ID --preview
|
|
379
462
|
unitlab project upload PROJECT_ID --source ./data
|
|
380
463
|
unitlab project upload PROJECT_ID --source ./maps
|
|
464
|
+
unitlab project upload PROJECT_ID --source ./recordings --type pointcloud --dry-run
|
|
465
|
+
unitlab project upload PROJECT_ID --source ./recordings --type pointcloud
|
|
466
|
+
unitlab project upload PROJECT_ID --source ./rig --type pointcloud \
|
|
467
|
+
--split-sensor-folders
|
|
468
|
+
unitlab project scene-status PROJECT_ID ITEM_ID
|
|
469
|
+
unitlab project scene-retry PROJECT_ID ITEM_ID
|
|
381
470
|
unitlab batch-queue list PROJECT_ID
|
|
382
471
|
unitlab batch-queue detail PROJECT_ID BATCH_QUEUE_ID
|
|
383
472
|
unitlab batch-queue status PROJECT_ID BATCH_QUEUE_ID
|
|
@@ -393,6 +482,9 @@ unitlab project attach PROJECT_ID --dataset-version DATASET_ID:2
|
|
|
393
482
|
# Assets
|
|
394
483
|
unitlab assets upload ./data --folder "Raw data" --tag incoming
|
|
395
484
|
unitlab assets upload ./slides --folder "Slides"
|
|
485
|
+
unitlab assets upload ./recordings --folder "Lidar" --type pointcloud
|
|
486
|
+
unitlab assets status ASSET_ID
|
|
487
|
+
unitlab assets retry ASSET_ID
|
|
396
488
|
|
|
397
489
|
# Folders
|
|
398
490
|
unitlab folders create "Raw data"
|
|
@@ -421,8 +513,9 @@ unitlab dataset unpublished-changes DATASET_ID
|
|
|
421
513
|
unitlab dataset publish DATASET_ID --title "Initial snapshot"
|
|
422
514
|
unitlab dataset versions DATASET_ID
|
|
423
515
|
|
|
424
|
-
# Releases
|
|
516
|
+
# Releases (create returns immediately; add --wait to wait until ready)
|
|
425
517
|
unitlab release create PROJECT_ID --format UUEF --splits train=100
|
|
518
|
+
unitlab release wait RELEASE_ID --timeout 7200
|
|
426
519
|
unitlab release list
|
|
427
520
|
unitlab release detail RELEASE_ID
|
|
428
521
|
unitlab release download RELEASE_ID --split-type train --dest ./release-annotations
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from importlib.metadata import version
|
|
2
2
|
|
|
3
3
|
from ._grouping import tiles_from_template
|
|
4
|
+
from ._scene_uploader import detect_scenes
|
|
4
5
|
from .client import UnitlabClient
|
|
5
6
|
from .exceptions import (
|
|
6
7
|
AmbiguousUploadCompletionError,
|
|
7
8
|
AuthenticationError,
|
|
9
|
+
ConflictError,
|
|
8
10
|
NetworkError,
|
|
9
11
|
NotFoundError,
|
|
10
12
|
PermissionDeniedError,
|
|
@@ -67,6 +69,7 @@ __all__ = [
|
|
|
67
69
|
"ChecklistAttribute",
|
|
68
70
|
"CloudEntry",
|
|
69
71
|
"CloudStorage",
|
|
72
|
+
"ConflictError",
|
|
70
73
|
"DataUnit",
|
|
71
74
|
"Dataset",
|
|
72
75
|
"DatasetItem",
|
|
@@ -100,5 +103,6 @@ __all__ = [
|
|
|
100
103
|
"WorkflowStage",
|
|
101
104
|
"WorkflowTask",
|
|
102
105
|
"__version__",
|
|
106
|
+
"detect_scenes",
|
|
103
107
|
"tiles_from_template",
|
|
104
108
|
]
|
|
@@ -7,6 +7,7 @@ import httpx
|
|
|
7
7
|
|
|
8
8
|
from .exceptions import (
|
|
9
9
|
AuthenticationError,
|
|
10
|
+
ConflictError,
|
|
10
11
|
NetworkError,
|
|
11
12
|
NotFoundError,
|
|
12
13
|
PermissionDeniedError,
|
|
@@ -65,6 +66,10 @@ def raise_for_response(response: httpx.Response) -> None:
|
|
|
65
66
|
if code == "permission_denied":
|
|
66
67
|
raise PermissionDeniedError(message or "Forbidden", error, code)
|
|
67
68
|
raise SubscriptionError(message or "Forbidden", error, code)
|
|
69
|
+
# Must stay above the message-based NotFound fallback below: a 409 detail
|
|
70
|
+
# can mention "not found" and still has to surface as a conflict.
|
|
71
|
+
if response.status_code == 409:
|
|
72
|
+
raise ConflictError(message, error, code)
|
|
68
73
|
if response.status_code == 404 or "not found" in message.lower():
|
|
69
74
|
raise NotFoundError(message, error, code)
|
|
70
75
|
raise NetworkError(message, error, code)
|