Skip to content

Commit e45d2ec

Browse files
committed
Update annotation downloading code
1 parent 4736b23 commit e45d2ec

File tree

6 files changed

+104
-140
lines changed

6 files changed

+104
-140
lines changed

packages/examples/cvat/exchange-oracle/src/crons/cvat/state_trackers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def track_task_creation(logger: logging.Logger, session: Session) -> None:
159159
for upload in uploads:
160160
status, reason = cvat_api.get_task_upload_status(upload.task_id)
161161
project = upload.task.project
162-
if not status or status == cvat_api.UploadStatus.FAILED:
162+
if not status or status == cvat_api.RequestStatus.FAILED:
163163
# TODO: add retries if 5xx
164164
failed.append(upload)
165165

@@ -170,7 +170,7 @@ def track_task_creation(logger: logging.Logger, session: Session) -> None:
170170
type=OracleWebhookTypes.job_launcher,
171171
event=ExchangeOracleEvent_EscrowFailed(reason=reason),
172172
)
173-
elif status == cvat_api.UploadStatus.FINISHED:
173+
elif status == cvat_api.RequestStatus.FINISHED:
174174
try:
175175
cvat_jobs = cvat_api.fetch_task_jobs(upload.task_id)
176176

packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py

Lines changed: 63 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from cvat_sdk.api_client.api_client import Endpoint
1919
from cvat_sdk.core.helpers import get_paginated_collection
2020
from cvat_sdk.core.uploading import AnnotationUploader
21+
from httpx import URL
2122

2223
from src.core.config import Config
2324
from src.utils.enums import BetterEnumMeta
@@ -30,48 +31,46 @@ class CVATException(Exception):
3031
"""Indicates that CVAT API returned unexpected response"""
3132

3233

33-
def _request_annotations(endpoint: Endpoint, cvat_id: int, format_name: str) -> bool:
34+
class RequestStatus(str, Enum, metaclass=BetterEnumMeta):
35+
QUEUED = "Queued"
36+
STARTED = "Started"
37+
FINISHED = "Finished"
38+
FAILED = "Failed"
39+
40+
41+
def _request_annotations(endpoint: Endpoint, cvat_id: int, format_name: str) -> str:
3442
"""
3543
Requests annotations export.
3644
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
3745
38-
while not _request_annotations(...):
39-
# some waiting like
40-
sleep(1)
41-
42-
_get_annotations(...)
46+
request_id = _request_annotations(...)
47+
_get_annotations(request_id, ...)
4348
"""
4449

4550
(_, response) = endpoint.call_with_http_info(
4651
id=cvat_id,
4752
format=format_name,
53+
save_images=False,
4854
_parse_response=False,
4955
)
5056

5157
assert response.status in [HTTPStatus.ACCEPTED, HTTPStatus.CREATED]
52-
return response.status == HTTPStatus.CREATED
58+
return response.json()["rq_id"]
5359

5460

5561
def _get_annotations(
56-
endpoint: Endpoint,
62+
api_client: ApiClient,
63+
request_id: str,
5764
*,
58-
cvat_id: int,
59-
format_name: str,
6065
attempt_interval: int = 5,
6166
timeout: int | None = _NOTSET,
6267
) -> io.RawIOBase:
6368
"""
6469
Downloads annotations.
6570
The dataset preparation can take some time (e.g. 10 min), so it should be used like this:
6671
67-
while not _request_annotations(...):
68-
# some waiting like
69-
sleep(1)
70-
71-
_get_annotations(...)
72-
73-
74-
It still can be used as 1 call, but the result can be unreliable.
72+
request_id = _request_annotations(...)
73+
_get_annotations(request_id, ...)
7574
"""
7675

7776
time_begin = utcnow()
@@ -80,20 +79,33 @@ def _get_annotations(
8079
timeout = Config.cvat_config.export_timeout
8180

8281
while True:
83-
(_, response) = endpoint.call_with_http_info(
84-
id=cvat_id,
85-
action="download",
86-
format=format_name,
87-
_parse_response=False,
88-
)
89-
if response.status == HTTPStatus.OK:
82+
request_info = api_client.requests_api.retrieve(request_id)[0]
83+
if request_info.status.value == models.RequestStatus.allowed_values[("value",)]["FAILED"]:
84+
raise Exception(
85+
f"Failed to export annotations for {request_id=}: {request_info.message}"
86+
)
87+
88+
if request_info.status.value == models.RequestStatus.allowed_values[("value",)]["FINISHED"]:
9089
break
9190

9291
if timeout is not None and timedelta(seconds=timeout) < (utcnow() - time_begin):
9392
raise Exception("Failed to retrieve the dataset from CVAT within the timeout interval")
9493

9594
sleep(attempt_interval)
9695

96+
result_url = URL(request_info.result_url)
97+
query_params = result_url.params
98+
headers = api_client.get_common_headers()
99+
api_client.update_params_for_auth(
100+
headers=headers,
101+
queries=query_params,
102+
auth_settings=[""],
103+
resource_path="",
104+
method="GET",
105+
request_auths=list(api_client.configuration.auth_settings().values()),
106+
body="",
107+
)
108+
response = api_client.rest_client.GET(request_info.result_url, headers=headers)
97109
file_buffer = io.BytesIO(response.data)
98110
assert zipfile.is_zipfile(file_buffer)
99111
file_buffer.seek(0)
@@ -231,23 +243,20 @@ def create_project(
231243
raise
232244

233245

234-
def request_project_annotations(cvat_id: int, format_name: str) -> bool:
246+
def request_project_annotations(cvat_id: int, format_name: str) -> str:
235247
"""
236248
Requests annotations export.
237249
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
238250
239-
while not request_project_annotations(...):
240-
# some waiting like
241-
sleep(1)
242-
243-
get_project_annotations(...)
251+
request_id = request_project_annotations(...):
252+
get_project_annotations(request_id, ...)
244253
"""
245254

246255
logger = logging.getLogger("app")
247256
with get_api_client() as api_client:
248257
try:
249258
return _request_annotations(
250-
api_client.projects_api.retrieve_annotations_endpoint,
259+
api_client.projects_api.create_dataset_export_endpoint,
251260
cvat_id=cvat_id,
252261
format_name=format_name,
253262
)
@@ -256,32 +265,19 @@ def request_project_annotations(cvat_id: int, format_name: str) -> bool:
256265
raise
257266

258267

259-
def get_project_annotations(
260-
cvat_id: int, format_name: str, *, timeout: int | None = _NOTSET
261-
) -> io.RawIOBase:
268+
def get_project_annotations(request_id: str, *, timeout: int | None = _NOTSET) -> io.RawIOBase:
262269
"""
263270
Downloads annotations.
264271
The dataset preparation can take some time (e.g. 10 min), so it should be used like this:
265272
266-
while not request_project_annotations(...):
267-
# some waiting like
268-
sleep(1)
269-
270-
get_project_annotations(...)
271-
272-
273-
It still can be used as 1 call, but the result can be unreliable.
273+
request_id = request_project_annotations(...):
274+
get_project_annotations(request_id, ...)
274275
"""
275276

276277
logger = logging.getLogger("app")
277278
with get_api_client() as api_client:
278279
try:
279-
return _get_annotations(
280-
api_client.projects_api.retrieve_annotations_endpoint,
281-
cvat_id=cvat_id,
282-
format_name=format_name,
283-
timeout=timeout,
284-
)
280+
return _get_annotations(api_client, request_id=request_id, timeout=timeout)
285281
except exceptions.ApiException as e:
286282
logger.exception(f"Exception when calling ProjectApi.retrieve_annotations: {e}\n")
287283
raise
@@ -418,23 +414,20 @@ def put_task_data(
418414
raise
419415

420416

421-
def request_task_annotations(cvat_id: int, format_name: str) -> bool:
417+
def request_task_annotations(cvat_id: int, format_name: str) -> str:
422418
"""
423419
Requests annotations export.
424420
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
425421
426-
while not request_task_annotations(...):
427-
# some waiting like
428-
sleep(1)
429-
430-
get_task_annotations(...)
422+
request_id = request_task_annotations(...):
423+
get_task_annotations(request_id, ...)
431424
"""
432425

433426
logger = logging.getLogger("app")
434427
with get_api_client() as api_client:
435428
try:
436429
return _request_annotations(
437-
api_client.tasks_api.retrieve_annotations_endpoint,
430+
api_client.tasks_api.create_dataset_export_endpoint,
438431
cvat_id=cvat_id,
439432
format_name=format_name,
440433
)
@@ -443,32 +436,19 @@ def request_task_annotations(cvat_id: int, format_name: str) -> bool:
443436
raise
444437

445438

446-
def get_task_annotations(
447-
cvat_id: int, format_name: str, *, timeout: int | None = _NOTSET
448-
) -> io.RawIOBase:
439+
def get_task_annotations(request_id: str, *, timeout: int | None = _NOTSET) -> io.RawIOBase:
449440
"""
450441
Downloads annotations.
451442
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
452443
453-
while not request_task_annotations(...):
454-
# some waiting like
455-
sleep(1)
456-
457-
get_task_annotations(...)
458-
459-
460-
It still can be used as 1 call, but the result can be unreliable.
444+
request_id = request_task_annotations(...):
445+
get_task_annotations(request_id, ...)
461446
"""
462447

463448
logger = logging.getLogger("app")
464449
with get_api_client() as api_client:
465450
try:
466-
return _get_annotations(
467-
api_client.tasks_api.retrieve_annotations_endpoint,
468-
cvat_id=cvat_id,
469-
format_name=format_name,
470-
timeout=timeout,
471-
)
451+
return _get_annotations(api_client, request_id=request_id, timeout=timeout)
472452
except exceptions.ApiException as e:
473453
logger.exception(f"Exception when calling TasksApi.retrieve_annotations: {e}\n")
474454
raise
@@ -488,23 +468,20 @@ def fetch_task_jobs(task_id: int) -> list[models.JobRead]:
488468
raise
489469

490470

491-
def request_job_annotations(cvat_id: int, format_name: str) -> bool:
471+
def request_job_annotations(cvat_id: int, format_name: str) -> str:
492472
"""
493473
Requests annotations export.
494474
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
495475
496-
while not request_job_annotations(...):
497-
# some waiting like
498-
sleep(1)
499-
500-
get_job_annotations(...)
476+
request_id = request_job_annotations(...):
477+
get_job_annotations(request_id, ...)
501478
"""
502479

503480
logger = logging.getLogger("app")
504481
with get_api_client() as api_client:
505482
try:
506483
return _request_annotations(
507-
api_client.jobs_api.retrieve_annotations_endpoint,
484+
api_client.jobs_api.create_dataset_export_endpoint,
508485
cvat_id=cvat_id,
509486
format_name=format_name,
510487
)
@@ -513,32 +490,19 @@ def request_job_annotations(cvat_id: int, format_name: str) -> bool:
513490
raise
514491

515492

516-
def get_job_annotations(
517-
cvat_id: int, format_name: str, *, timeout: int | None = _NOTSET
518-
) -> io.RawIOBase:
493+
def get_job_annotations(request_id: str, *, timeout: int | None = _NOTSET) -> io.RawIOBase:
519494
"""
520495
Downloads annotations.
521496
The dataset preparation can take some time (e.g. 10 min), so it must be used like this:
522497
523-
while not request_job_annotations(...):
524-
# some waiting like
525-
sleep(1)
526-
527-
get_job_annotations(...)
528-
529-
530-
It still can be used as 1 call, but the result can be unreliable.
498+
request_id = request_job_annotations(...):
499+
get_job_annotations(request_id, ...)
531500
"""
532501

533502
logger = logging.getLogger("app")
534503
with get_api_client() as api_client:
535504
try:
536-
return _get_annotations(
537-
api_client.jobs_api.retrieve_annotations_endpoint,
538-
cvat_id=cvat_id,
539-
format_name=format_name,
540-
timeout=timeout,
541-
)
505+
return _get_annotations(api_client, request_id=request_id, timeout=timeout)
542506
except exceptions.ApiException as e:
543507
logger.exception(f"Exception when calling JobsApi.retrieve_annotations: {e}\n")
544508
raise
@@ -577,14 +541,7 @@ def fetch_projects(assignee: str = "") -> list[models.ProjectRead]:
577541
raise
578542

579543

580-
class UploadStatus(str, Enum, metaclass=BetterEnumMeta):
581-
QUEUED = "Queued"
582-
STARTED = "Started"
583-
FINISHED = "Finished"
584-
FAILED = "Failed"
585-
586-
587-
def get_task_upload_status(cvat_id: int) -> tuple[UploadStatus | None, str]:
544+
def get_task_upload_status(cvat_id: int) -> tuple[RequestStatus | None, str]:
588545
logger = logging.getLogger("app")
589546

590547
with get_api_client() as api_client:
@@ -596,7 +553,7 @@ def get_task_upload_status(cvat_id: int) -> tuple[UploadStatus | None, str]:
596553
status = None
597554
reason = f"Task #{cvat_id} creation request not found"
598555
else:
599-
status = UploadStatus(results[0].status.value.capitalize())
556+
status = RequestStatus(results[0].status.value.capitalize())
600557
reason = results[0].message
601558

602559
return status, reason

0 commit comments

Comments
 (0)