cvat/tests/python/shared/utils/resource_import_export.py

221 lines
6.6 KiB
Python
Raw Normal View History

2025-09-16 01:19:40 +00:00
import functools
import json
from abc import ABC, abstractmethod
from contextlib import ExitStack
from http import HTTPStatus
from time import sleep
from typing import Any, TypeVar
import pytest
T = TypeVar("T")
from shared.utils.config import get_method, post_method
FILENAME_TEMPLATE = "cvat/{}/{}.zip"
EXPORT_FORMAT = "CVAT for images 1.1"
IMPORT_FORMAT = "CVAT 1.1"
def _make_custom_resource_params(resource: str, obj: str, cloud_storage_id: int) -> dict[str, Any]:
return {
"filename": FILENAME_TEMPLATE.format(obj, resource),
"location": "cloud_storage",
"cloud_storage_id": cloud_storage_id,
}
def _make_default_resource_params(resource: str, obj: str) -> dict[str, Any]:
return {
"filename": FILENAME_TEMPLATE.format(obj, resource),
}
def _make_export_resource_params(
resource: str, is_default: bool = True, **kwargs
) -> dict[str, Any]:
func = _make_default_resource_params if is_default else _make_custom_resource_params
params = func(resource, **kwargs)
if resource != "backup":
params["format"] = EXPORT_FORMAT
params["save_images"] = resource == "dataset"
return params
def _make_import_resource_params(
resource: str, is_default: bool = True, **kwargs
) -> dict[str, Any]:
func = _make_default_resource_params if is_default else _make_custom_resource_params
params = func(resource, **kwargs)
if resource != "backup":
params["format"] = IMPORT_FORMAT
return params
# FUTURE-TODO: reuse common logic from rest_api/utils
def _wait_request(
user: str,
request_id: str,
*,
sleep_interval: float = 0.1,
number_of_checks: int = 100,
):
for _ in range(number_of_checks):
sleep(sleep_interval)
response = get_method(user, f"requests/{request_id}")
assert response.status_code == HTTPStatus.OK
request_details = json.loads(response.content)
status = request_details["status"]
assert status in {"started", "queued", "finished", "failed"}
if status in {"finished", "failed"}:
return
class _CloudStorageResourceTest(ABC):
@staticmethod
@abstractmethod
def _make_client():
pass
@pytest.fixture(autouse=True)
def setup(self, admin_user: str):
self.user = admin_user
self.client = self._make_client()
self.exit_stack = ExitStack()
with self.exit_stack:
yield
def _ensure_file_created(self, func: T, storage: dict[str, Any]) -> T:
@functools.wraps(func)
def wrapper(*args, **kwargs):
filename = kwargs["filename"]
bucket = storage["resource"]
# check that file doesn't exist on the bucket
assert not self.client.file_exists(bucket=bucket, filename=filename)
func(*args, **kwargs)
# check that file exists on the bucket
assert self.client.file_exists(bucket=bucket, filename=filename)
return wrapper
def _export_resource_to_cloud_storage(
self,
obj_id: int,
obj: str,
resource: str,
*,
user: str,
_expect_status: HTTPStatus = HTTPStatus.ACCEPTED,
**kwargs,
):
# initialize the export process
response = post_method(
user,
f"{obj}/{obj_id}/{resource if resource != 'annotations' else 'dataset'}/export",
data=None,
**kwargs,
)
assert response.status_code == _expect_status
if _expect_status == HTTPStatus.FORBIDDEN:
return
rq_id = json.loads(response.content).get("rq_id")
assert rq_id, "The rq_id was not found in server request"
_wait_request(user, rq_id)
def _import_resource_from_cloud_storage(
self, url: str, *, user: str, _expect_status: HTTPStatus = HTTPStatus.ACCEPTED, **kwargs
) -> None:
response = post_method(user, url, data=None, **kwargs)
status = response.status_code
assert status == _expect_status, status
if status == HTTPStatus.FORBIDDEN:
return
rq_id = response.json().get("rq_id")
assert rq_id, "The rq_id parameter was not found in the server response"
_wait_request(user, rq_id)
def _import_annotations_from_cloud_storage(
self,
obj_id,
obj,
*,
user,
_expect_status: HTTPStatus = HTTPStatus.ACCEPTED,
_check_uploaded: bool = True,
**kwargs,
):
url = f"{obj}/{obj_id}/annotations"
self._import_resource_from_cloud_storage(
url, user=user, _expect_status=_expect_status, **kwargs
)
if _expect_status == HTTPStatus.FORBIDDEN:
return
if _check_uploaded:
response = get_method(user, url)
assert response.status_code == HTTPStatus.OK
annotations = response.json()
assert len(annotations["shapes"])
def _import_backup_from_cloud_storage(
self, obj, *, user, _expect_status: HTTPStatus = HTTPStatus.ACCEPTED, **kwargs
):
self._import_resource_from_cloud_storage(
f"{obj}/backup", user=user, _expect_status=_expect_status, **kwargs
)
def _import_dataset_from_cloud_storage(
self, obj_id, obj, *, user, _expect_status: HTTPStatus = HTTPStatus.ACCEPTED, **kwargs
):
self._import_resource_from_cloud_storage(
f"{obj}/{obj_id}/dataset", user=user, _expect_status=_expect_status, **kwargs
)
def _import_resource(self, cloud_storage: dict[str, Any], resource_type: str, *args, **kwargs):
methods = {
"annotations": self._import_annotations_from_cloud_storage,
"dataset": self._import_dataset_from_cloud_storage,
"backup": self._import_backup_from_cloud_storage,
}
org_id = cloud_storage["organization"]
if org_id:
kwargs.setdefault("org_id", org_id)
kwargs.setdefault("user", self.user)
return methods[resource_type](*args, **kwargs)
def _export_resource(self, cloud_storage: dict[str, Any], *args, **kwargs):
org_id = cloud_storage["organization"]
if org_id:
kwargs.setdefault("org_id", org_id)
kwargs.setdefault("user", self.user)
export_callback = self._ensure_file_created(
self._export_resource_to_cloud_storage, storage=cloud_storage
)
export_callback(*args, **kwargs)
self.exit_stack.callback(
self.client.remove_file,
bucket=cloud_storage["resource"],
filename=kwargs["filename"],
)