|
2 | 2 | import mimetypes |
3 | 3 | import os |
4 | 4 | import urllib |
5 | | -from typing import Dict, List, Optional, Union |
| 5 | +from typing import Any, Dict, List, Optional, Union |
6 | 6 | from urllib.parse import quote |
7 | 7 |
|
8 | 8 | import requests |
@@ -378,6 +378,76 @@ def workspace_delete_images( |
378 | 378 | return response.json() |
379 | 379 |
|
380 | 380 |
|
| 381 | +def update_image_metadata( |
| 382 | + api_key: str, |
| 383 | + workspace_url: str, |
| 384 | + image_id: str, |
| 385 | + *, |
| 386 | + metadata: Optional[Dict] = None, |
| 387 | + remove_metadata: Optional[List[str]] = None, |
| 388 | + add_tags: Optional[List[str]] = None, |
| 389 | + remove_tags: Optional[List[str]] = None, |
| 390 | +) -> dict: |
| 391 | + """Update metadata and tags on a single image (synchronous). |
| 392 | +
|
| 393 | + Args: |
| 394 | + api_key: Roboflow API key. |
| 395 | + workspace_url: Workspace slug/url. |
| 396 | + image_id: Image/source ID. |
| 397 | + metadata: Key-value pairs to set on the image. |
| 398 | + remove_metadata: Metadata keys to delete. |
| 399 | + add_tags: Tags to append. |
| 400 | + remove_tags: Tags to remove. |
| 401 | +
|
| 402 | + Returns: |
| 403 | + Parsed JSON response (``{"success": true}``). |
| 404 | +
|
| 405 | + Raises: |
| 406 | + RoboflowError: On non-200 response. |
| 407 | + """ |
| 408 | + url = f"{API_URL}/{workspace_url}/images/{quote(image_id, safe='')}/metadata" |
| 409 | + body: Dict[str, Any] = {} |
| 410 | + if metadata is not None: |
| 411 | + body["metadata"] = metadata |
| 412 | + if remove_metadata is not None: |
| 413 | + body["removeMetadata"] = remove_metadata |
| 414 | + if add_tags is not None: |
| 415 | + body["addTags"] = add_tags |
| 416 | + if remove_tags is not None: |
| 417 | + body["removeTags"] = remove_tags |
| 418 | + |
| 419 | + response = requests.post(url, params={"api_key": api_key}, json=body) |
| 420 | + if response.status_code != 200: |
| 421 | + raise RoboflowError(response.text) |
| 422 | + return response.json() |
| 423 | + |
| 424 | + |
| 425 | +def batch_update_image_metadata( |
| 426 | + api_key: str, |
| 427 | + workspace_url: str, |
| 428 | + updates: List[Dict], |
| 429 | +) -> dict: |
| 430 | + """Batch-update metadata and tags on multiple images (asynchronous). |
| 431 | +
|
| 432 | + Args: |
| 433 | + api_key: Roboflow API key. |
| 434 | + workspace_url: Workspace slug/url. |
| 435 | + updates: List of update dicts, each containing ``imageId`` and optionally |
| 436 | + ``metadata``, ``removeMetadata``, ``addTags``, ``removeTags``. |
| 437 | +
|
| 438 | + Returns: |
| 439 | + Parsed JSON with ``taskId`` and ``url`` for polling. |
| 440 | +
|
| 441 | + Raises: |
| 442 | + RoboflowError: On non-202 response. |
| 443 | + """ |
| 444 | + url = f"{API_URL}/{workspace_url}/images/metadata" |
| 445 | + response = requests.post(url, params={"api_key": api_key}, json={"updates": updates}) |
| 446 | + if response.status_code != 202: |
| 447 | + raise RoboflowError(response.text) |
| 448 | + return response.json() |
| 449 | + |
| 450 | + |
381 | 451 | def upload_image( |
382 | 452 | api_key, |
383 | 453 | project_url, |
@@ -763,6 +833,28 @@ def delete_folder(api_key, workspace_url, group_id): |
763 | 833 | return response.json() |
764 | 834 |
|
765 | 835 |
|
| 836 | +def add_projects_to_folder(api_key, workspace_url, group_id, project_ids): |
| 837 | + """PATCH /{ws}/groups/{id}/projects — add projects to a folder.""" |
| 838 | + response = requests.patch( |
| 839 | + f"{API_URL}/{workspace_url}/groups/{group_id}/projects", |
| 840 | + params={"api_key": api_key}, |
| 841 | + json={"projects": project_ids}, |
| 842 | + ) |
| 843 | + if response.status_code not in (200, 204): |
| 844 | + raise RoboflowError(response.text) |
| 845 | + |
| 846 | + |
| 847 | +def remove_projects_from_folder(api_key, workspace_url, group_id, project_ids): |
| 848 | + """DELETE /{ws}/groups/{id}/projects — remove projects from a folder.""" |
| 849 | + response = requests.delete( |
| 850 | + f"{API_URL}/{workspace_url}/groups/{group_id}/projects", |
| 851 | + params={"api_key": api_key}, |
| 852 | + json={"projects": project_ids}, |
| 853 | + ) |
| 854 | + if response.status_code not in (200, 204): |
| 855 | + raise RoboflowError(response.text) |
| 856 | + |
| 857 | + |
766 | 858 | # --------------------------------------------------------------------------- |
767 | 859 | # Phase 2: Workflow endpoints |
768 | 860 | # --------------------------------------------------------------------------- |
|
0 commit comments