chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import cvat_sdk.auto_annotation as cvataa
|
||||
import cvat_sdk.models as models
|
||||
import PIL.Image
|
||||
|
||||
spec = cvataa.DetectionFunctionSpec(
|
||||
labels=[
|
||||
cvataa.label_spec("car", 0),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def detect(
|
||||
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
|
||||
) -> list[models.LabeledShapeRequest]:
|
||||
if context.conv_mask_to_poly:
|
||||
return [cvataa.polygon(0, [0, 0, 0, 1, 1, 1])]
|
||||
else:
|
||||
return [cvataa.mask(0, [1, 0, 0, 0, 0])]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import cvat_sdk.auto_annotation as cvataa
|
||||
import cvat_sdk.models as models
|
||||
import PIL.Image
|
||||
|
||||
spec = cvataa.DetectionFunctionSpec(
|
||||
labels=[
|
||||
cvataa.label_spec("car", 0),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def detect(
|
||||
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
|
||||
) -> list[models.LabeledShapeRequest]:
|
||||
return [
|
||||
cvataa.rectangle(0, [context.conf_threshold, 1, 1, 1]),
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Force execution of fixture definitions
|
||||
from sdk.fixtures import * # pylint: disable=wildcard-import
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import cvat_sdk.auto_annotation as cvataa
|
||||
import cvat_sdk.models as models
|
||||
import PIL.Image
|
||||
|
||||
spec = cvataa.DetectionFunctionSpec(
|
||||
labels=[
|
||||
cvataa.label_spec("car", 0),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def detect(
|
||||
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
|
||||
) -> list[models.LabeledShapeRequest]:
|
||||
return [
|
||||
cvataa.rectangle(0, [1, 2, 3, 4]),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from types import SimpleNamespace as namespace
|
||||
|
||||
import cvat_sdk.auto_annotation as cvataa
|
||||
import cvat_sdk.models as models
|
||||
import PIL.Image
|
||||
|
||||
|
||||
def create(s: str, i: int, f: float, b: bool) -> cvataa.DetectionFunction:
|
||||
assert s == "string"
|
||||
assert i == 123
|
||||
assert f == 5.5
|
||||
assert b is False
|
||||
|
||||
spec = cvataa.DetectionFunctionSpec(
|
||||
labels=[
|
||||
cvataa.label_spec("car", 0),
|
||||
],
|
||||
)
|
||||
|
||||
def detect(
|
||||
context: cvataa.DetectionFunctionContext, image: PIL.Image.Image
|
||||
) -> list[models.LabeledShapeRequest]:
|
||||
return [
|
||||
cvataa.rectangle(0, [1, 2, 3, 4]),
|
||||
]
|
||||
|
||||
return namespace(spec=spec, detect=detect)
|
||||
@@ -0,0 +1,240 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import packaging.version as pv
|
||||
import pytest
|
||||
from cvat_cli._internal.agent import (
|
||||
_Event,
|
||||
_NewReconnectionDelay,
|
||||
_parse_event_stream,
|
||||
_TaskCacheLimiter,
|
||||
)
|
||||
from cvat_sdk import Client
|
||||
from cvat_sdk.api_client import models
|
||||
from cvat_sdk.core.proxies.tasks import ResourceType
|
||||
|
||||
from sdk.util import https_reverse_proxy
|
||||
|
||||
from .util import TestCliBase, generate_images, run_cli
|
||||
|
||||
|
||||
class TestCliMisc(TestCliBase):
|
||||
def test_can_warn_on_mismatching_server_version(self, monkeypatch, caplog):
|
||||
def mocked_version(_):
|
||||
return pv.Version("0")
|
||||
|
||||
# We don't actually run a separate process in the tests here, so it works
|
||||
monkeypatch.setattr(Client, "get_server_version", mocked_version)
|
||||
|
||||
self.run_cli("task", "ls")
|
||||
|
||||
assert "Server version '0' is not compatible with SDK version" in caplog.text
|
||||
|
||||
@pytest.mark.parametrize("verify", [True, False])
|
||||
def test_can_control_ssl_verification_with_arg(self, verify: bool):
|
||||
with https_reverse_proxy() as proxy_url:
|
||||
if verify:
|
||||
insecure_args = []
|
||||
else:
|
||||
insecure_args = ["--insecure"]
|
||||
|
||||
run_cli(
|
||||
self,
|
||||
f"--auth={self.user}:{self.password}",
|
||||
f"--server-host={proxy_url}",
|
||||
*insecure_args,
|
||||
"task",
|
||||
"ls",
|
||||
expected_code=1 if verify else 0,
|
||||
)
|
||||
stdout = self.stdout.getvalue()
|
||||
|
||||
if not verify:
|
||||
for line in stdout.splitlines():
|
||||
int(line)
|
||||
|
||||
def test_can_control_organization_context(self):
|
||||
org = "cli-test-org"
|
||||
self.client.organizations.create(models.OrganizationWriteRequest(org))
|
||||
|
||||
files = generate_images(self.tmp_path, 1)
|
||||
|
||||
stdout = self.run_cli(
|
||||
"task",
|
||||
"create",
|
||||
"personal_task",
|
||||
ResourceType.LOCAL.name,
|
||||
*map(os.fspath, files),
|
||||
"--completion_verification_period=0.01",
|
||||
organization="",
|
||||
)
|
||||
|
||||
personal_task_id = int(stdout.split()[-1])
|
||||
|
||||
stdout = self.run_cli(
|
||||
"task",
|
||||
"create",
|
||||
"org_task",
|
||||
ResourceType.LOCAL.name,
|
||||
*map(os.fspath, files),
|
||||
"--completion_verification_period=0.01",
|
||||
organization=org,
|
||||
)
|
||||
|
||||
org_task_id = int(stdout.split()[-1])
|
||||
|
||||
personal_task_ids = list(map(int, self.run_cli("task", "ls", organization="").split()))
|
||||
assert personal_task_id in personal_task_ids
|
||||
assert org_task_id not in personal_task_ids
|
||||
|
||||
org_task_ids = list(map(int, self.run_cli("task", "ls", organization=org).split()))
|
||||
assert personal_task_id not in org_task_ids
|
||||
assert org_task_id in org_task_ids
|
||||
|
||||
all_task_ids = list(map(int, self.run_cli("task", "ls").split()))
|
||||
assert personal_task_id in all_task_ids
|
||||
assert org_task_id in all_task_ids
|
||||
|
||||
def test_can_use_access_token_env_variable(
|
||||
self, monkeypatch: pytest.MonkeyPatch, access_tokens
|
||||
):
|
||||
token = next(t for t in access_tokens)["private_key"]
|
||||
|
||||
from cvat_sdk.api_client.rest import RESTClientObject
|
||||
|
||||
original_request = RESTClientObject.request
|
||||
|
||||
calls = 0
|
||||
|
||||
def patched_request(self, *args, **kwargs):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
|
||||
assert kwargs["headers"].get("Authorization") == f"Bearer {token}"
|
||||
return original_request(self, *args, **kwargs)
|
||||
|
||||
monkeypatch.setenv("CVAT_ACCESS_TOKEN", token)
|
||||
monkeypatch.setattr(RESTClientObject, "request", patched_request)
|
||||
self.run_cli("task", "ls", authenticate=False)
|
||||
|
||||
assert calls
|
||||
|
||||
def test_can_use_current_user_env_variable(self, monkeypatch: pytest.MonkeyPatch):
|
||||
# set all user env vars supported by getuser()
|
||||
for env_var in ("LOGNAME", "USER", "LNAME", "USERNAME"):
|
||||
monkeypatch.setenv(env_var, self.user)
|
||||
|
||||
from getpass import getuser as original_getuser
|
||||
|
||||
from cvat_sdk.core.auth import default_auth_factory
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"cvat_sdk.core.auth.default_auth_factory", wraps=default_auth_factory
|
||||
) as mock_auth_factory,
|
||||
mock.patch("getpass.getuser", wraps=original_getuser) as mock_getuser,
|
||||
mock.patch("getpass.getpass", return_value=self.password) as mock_getpass,
|
||||
):
|
||||
self.run_cli("task", "ls", authenticate=False)
|
||||
|
||||
mock_auth_factory.assert_called_once()
|
||||
mock_getuser.assert_called()
|
||||
mock_getpass.assert_called_once()
|
||||
|
||||
def test_can_use_pass_env_variable(self, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("PASS", self.password)
|
||||
|
||||
from cvat_sdk.core.auth import get_auth_factory
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"cvat_sdk.core.auth.get_auth_factory", wraps=get_auth_factory
|
||||
) as mock_auth_factory,
|
||||
mock.patch("getpass.getpass") as mock_getpass,
|
||||
):
|
||||
self.run_cli(f"--auth={self.user}", "task", "ls", authenticate=False)
|
||||
|
||||
mock_auth_factory.assert_called_once()
|
||||
mock_getpass.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["lines", "messages"],
|
||||
[
|
||||
# empty
|
||||
([], []),
|
||||
([""], [_Event("", "")]),
|
||||
# event only
|
||||
(["event: test", ""], [_Event("test", "")]),
|
||||
(["event: foo", "event: bar", ""], [_Event("bar", "")]),
|
||||
# data only
|
||||
(["data: test", ""], [_Event("", "test")]),
|
||||
(["data: foo", "data: bar", ""], [_Event("", "foo\nbar")]),
|
||||
# event and data
|
||||
(["event: test", "data: foo", "data: bar", ""], [_Event("test", "foo\nbar")]),
|
||||
(["data: foo", "event: test", "data: bar", ""], [_Event("test", "foo\nbar")]),
|
||||
(["data: foo", "data: bar", "event: test", ""], [_Event("test", "foo\nbar")]),
|
||||
# fields without values
|
||||
(["event: test", "event", ""], [_Event("", "")]),
|
||||
(["data: test", "data", ""], [_Event("", "test\n")]),
|
||||
# incomplete event
|
||||
(["event: test", "data: foo"], []),
|
||||
# multiple events
|
||||
(
|
||||
["event: test1", "data: foo", "", "event: test2", "data: bar", ""],
|
||||
[_Event("test1", "foo"), _Event("test2", "bar")],
|
||||
),
|
||||
# comments
|
||||
([":"], []),
|
||||
([":1", "event: test", ":2", "data: foo", ":3", ""], [_Event("test", "foo")]),
|
||||
# retry
|
||||
(["retry: 1234"], [_NewReconnectionDelay(timedelta(milliseconds=1234))]),
|
||||
(["retry", "retry:", "retry: a"], []),
|
||||
# no space
|
||||
(["event:test", "data:foo", ""], [_Event("test", "foo")]),
|
||||
# two spaces
|
||||
(["event: test", "data: foo", ""], [_Event(" test", " foo")]),
|
||||
# carriage return
|
||||
(["event: test\r", "data: foo\r", "\r"], [_Event("test", "foo")]),
|
||||
],
|
||||
)
|
||||
def test_parse_event_stream(lines, messages):
|
||||
stream = BytesIO(b"".join(line.encode() + b"\n" for line in lines))
|
||||
assert list(_parse_event_stream(stream)) == messages
|
||||
|
||||
|
||||
def test_task_cache_limiter_keeps_last_10_tasks(
|
||||
tmp_path: Path,
|
||||
fxt_login: tuple[Client, str],
|
||||
fxt_logger,
|
||||
):
|
||||
client = fxt_login[0]
|
||||
client.logger = fxt_logger[0]
|
||||
client.config.cache_dir = tmp_path / "cache"
|
||||
|
||||
limiter = _TaskCacheLimiter(client)
|
||||
|
||||
for task_id in range(1, 13):
|
||||
limiter._cache_manager.task_dir(task_id).mkdir(parents=True)
|
||||
with limiter.using_cache_for_task(task_id):
|
||||
pass
|
||||
|
||||
if task_id <= 10:
|
||||
for cached_task_id in range(1, task_id + 1):
|
||||
assert limiter._cache_manager.task_dir(cached_task_id).exists()
|
||||
elif task_id == 11:
|
||||
assert not limiter._cache_manager.task_dir(1).exists()
|
||||
for cached_task_id in range(2, 12):
|
||||
assert limiter._cache_manager.task_dir(cached_task_id).exists()
|
||||
elif task_id == 12:
|
||||
assert not limiter._cache_manager.task_dir(1).exists()
|
||||
assert not limiter._cache_manager.task_dir(2).exists()
|
||||
for cached_task_id in range(3, 13):
|
||||
assert limiter._cache_manager.task_dir(cached_task_id).exists()
|
||||
@@ -0,0 +1,196 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
import pytest
|
||||
from cvat_sdk.api_client import exceptions
|
||||
from cvat_sdk.core.proxies.projects import Project
|
||||
from cvat_sdk.core.proxies.tasks import ResourceType
|
||||
|
||||
from .util import TestCliBase, generate_images
|
||||
|
||||
|
||||
class TestCliProjects(TestCliBase):
|
||||
@pytest.fixture
|
||||
def fxt_new_project(self):
|
||||
project = self.client.projects.create(
|
||||
spec={
|
||||
"name": "test_project",
|
||||
"labels": [{"name": "car"}, {"name": "person"}],
|
||||
},
|
||||
)
|
||||
|
||||
return project
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_project_with_task(self, fxt_new_project: Project):
|
||||
files = generate_images(self.tmp_path, 5)
|
||||
|
||||
self.client.tasks.create_from_data(
|
||||
spec={
|
||||
"name": "test_task",
|
||||
"project_id": fxt_new_project.id,
|
||||
},
|
||||
resource_type=ResourceType.LOCAL,
|
||||
resources=files,
|
||||
)
|
||||
fxt_new_project.fetch()
|
||||
|
||||
return fxt_new_project
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_backup_file(self, fxt_project_with_task: Project):
|
||||
backup_path = self.tmp_path / "backup.zip"
|
||||
|
||||
fxt_project_with_task.download_backup(backup_path)
|
||||
|
||||
yield backup_path
|
||||
|
||||
def test_can_create_project(self):
|
||||
stdout = self.run_cli(
|
||||
"project",
|
||||
"create",
|
||||
"new_project",
|
||||
"--labels",
|
||||
json.dumps([{"name": "car"}, {"name": "person"}]),
|
||||
"--bug_tracker",
|
||||
"https://bugs.example/",
|
||||
)
|
||||
|
||||
project_id = int(stdout.rstrip("\n"))
|
||||
created_project = self.client.projects.retrieve(project_id)
|
||||
assert created_project.name == "new_project"
|
||||
assert created_project.bug_tracker == "https://bugs.example/"
|
||||
assert {label.name for label in created_project.get_labels()} == {"car", "person"}
|
||||
|
||||
def test_can_create_project_from_dataset(self, fxt_coco_dataset):
|
||||
stdout = self.run_cli(
|
||||
"project",
|
||||
"create",
|
||||
"new_project",
|
||||
"--dataset_path",
|
||||
os.fspath(fxt_coco_dataset),
|
||||
"--dataset_format",
|
||||
"COCO 1.0",
|
||||
)
|
||||
|
||||
project_id = int(stdout.rstrip("\n"))
|
||||
created_project = self.client.projects.retrieve(project_id)
|
||||
assert created_project.name == "new_project"
|
||||
assert {label.name for label in created_project.get_labels()} == {"car", "person"}
|
||||
assert created_project.tasks.count == 1
|
||||
|
||||
def test_can_list_projects_in_simple_format(self, fxt_new_project: Project):
|
||||
output = self.run_cli("project", "ls")
|
||||
|
||||
results = output.split("\n")
|
||||
assert any(str(fxt_new_project.id) in r for r in results)
|
||||
|
||||
def test_can_list_project_in_json_format(self, fxt_new_project: Project):
|
||||
output = self.run_cli("project", "ls", "--json")
|
||||
|
||||
results = json.loads(output)
|
||||
assert any(r["id"] == fxt_new_project.id for r in results)
|
||||
|
||||
def test_can_delete_project(self, fxt_new_project: Project):
|
||||
self.run_cli("project", "delete", str(fxt_new_project.id))
|
||||
|
||||
with pytest.raises(exceptions.NotFoundException):
|
||||
fxt_new_project.fetch()
|
||||
|
||||
def test_can_download_project_backup(self, fxt_project_with_task: Project):
|
||||
filename = self.tmp_path / f"project_{fxt_project_with_task.id}-cvat.zip"
|
||||
self.run_cli(
|
||||
"project",
|
||||
"backup",
|
||||
str(fxt_project_with_task.id),
|
||||
str(filename),
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
assert 0 < filename.stat().st_size
|
||||
with zipfile.ZipFile(filename) as backup_zip:
|
||||
assert "project.json" in backup_zip.namelist()
|
||||
|
||||
def test_can_download_project_backup_with_server_filename(self, fxt_project_with_task: Project):
|
||||
output_dir = str(self.tmp_path / "save_dir") + os.path.sep
|
||||
self.run_cli(
|
||||
"project",
|
||||
"backup",
|
||||
str(fxt_project_with_task.id),
|
||||
output_dir,
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
output_dir_files = os.listdir(output_dir)
|
||||
assert len(output_dir_files) == 1
|
||||
assert os.stat(output_dir + output_dir_files[0]).st_size > 0
|
||||
|
||||
def test_can_create_project_from_backup(self, fxt_project_with_task: Project, fxt_backup_file):
|
||||
stdout = self.run_cli("project", "create-from-backup", str(fxt_backup_file))
|
||||
|
||||
project_id = int(stdout.rstrip("\n"))
|
||||
assert project_id
|
||||
assert project_id != fxt_project_with_task.id
|
||||
|
||||
restored_project = self.client.projects.retrieve(project_id)
|
||||
restored_tasks = restored_project.get_tasks()
|
||||
assert len(restored_tasks) == 1
|
||||
assert restored_tasks[0].size == fxt_project_with_task.get_tasks()[0].size
|
||||
|
||||
def test_can_download_project_annotations(self, fxt_project_with_task: Project):
|
||||
filename = self.tmp_path / f"project_{fxt_project_with_task.id}-cvat.zip"
|
||||
self.run_cli(
|
||||
"project",
|
||||
"export-dataset",
|
||||
str(fxt_project_with_task.id),
|
||||
str(filename),
|
||||
"--format",
|
||||
"CVAT for images 1.1",
|
||||
"--with-images",
|
||||
"no",
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
assert 0 < filename.stat().st_size
|
||||
|
||||
def test_can_download_project_annotations_with_server_filename(
|
||||
self, fxt_project_with_task: Project
|
||||
):
|
||||
output_dir = str(self.tmp_path / "save_dir") + os.path.sep
|
||||
self.run_cli(
|
||||
"project",
|
||||
"export-dataset",
|
||||
str(fxt_project_with_task.id),
|
||||
output_dir,
|
||||
"--format",
|
||||
"CVAT for images 1.1",
|
||||
"--with-images",
|
||||
"yes",
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
output_dir_files = os.listdir(output_dir)
|
||||
assert len(output_dir_files) == 1
|
||||
assert os.stat(output_dir + output_dir_files[0]).st_size > 0
|
||||
|
||||
def test_can_upload_project_annotations(self, fxt_new_project: Project, fxt_coco_dataset):
|
||||
self.run_cli(
|
||||
"project",
|
||||
"import-dataset",
|
||||
str(fxt_new_project.id),
|
||||
os.fspath(fxt_coco_dataset),
|
||||
"--format",
|
||||
"COCO 1.0",
|
||||
)
|
||||
|
||||
fxt_new_project.fetch()
|
||||
assert fxt_new_project.tasks.count == 1
|
||||
@@ -0,0 +1,350 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from cvat_sdk.api_client import exceptions
|
||||
from cvat_sdk.core.proxies.tasks import ResourceType, Task
|
||||
from PIL import Image
|
||||
|
||||
from sdk.util import generate_coco_json
|
||||
from shared.utils.helpers import generate_image_file
|
||||
|
||||
from .util import TestCliBase, generate_images
|
||||
|
||||
|
||||
class TestCliTasks(TestCliBase):
|
||||
@pytest.fixture
|
||||
def fxt_image_file(self):
|
||||
img_path = self.tmp_path / "img_0.png"
|
||||
with img_path.open("wb") as f:
|
||||
f.write(generate_image_file(filename=str(img_path)).getvalue())
|
||||
|
||||
return img_path
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_coco_file(self, fxt_image_file: Path):
|
||||
img_filename = fxt_image_file
|
||||
img_size = Image.open(img_filename).size
|
||||
ann_filename = self.tmp_path / "coco.json"
|
||||
generate_coco_json(ann_filename, img_info=(img_filename, *img_size))
|
||||
|
||||
yield ann_filename
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_backup_file(self, fxt_new_task: Task, fxt_coco_file: str):
|
||||
backup_path = self.tmp_path / "backup.zip"
|
||||
|
||||
fxt_new_task.import_annotations("COCO 1.0", filename=fxt_coco_file)
|
||||
fxt_new_task.download_backup(backup_path)
|
||||
|
||||
yield backup_path
|
||||
|
||||
@pytest.fixture
|
||||
def fxt_new_task(self):
|
||||
files = generate_images(self.tmp_path, 5)
|
||||
|
||||
task = self.client.tasks.create_from_data(
|
||||
spec={
|
||||
"name": "test_task",
|
||||
"labels": [{"name": "car"}, {"name": "person"}],
|
||||
},
|
||||
resource_type=ResourceType.LOCAL,
|
||||
resources=files,
|
||||
)
|
||||
|
||||
return task
|
||||
|
||||
def test_can_create_task_from_local_images(self):
|
||||
files = generate_images(self.tmp_path, 5)
|
||||
|
||||
stdout = self.run_cli(
|
||||
"task",
|
||||
"create",
|
||||
"test_task",
|
||||
ResourceType.LOCAL.name,
|
||||
*map(os.fspath, files),
|
||||
"--labels",
|
||||
json.dumps([{"name": "car"}, {"name": "person"}]),
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
task_id = int(stdout.rstrip("\n"))
|
||||
assert self.client.tasks.retrieve(task_id).size == 5
|
||||
|
||||
def test_can_create_task_from_local_images_with_parameters(self):
|
||||
# Checks for regressions of <https://github.com/cvat-ai/cvat/issues/4962>
|
||||
|
||||
files = generate_images(self.tmp_path, 7)
|
||||
files.sort(reverse=True)
|
||||
frame_step = 3
|
||||
|
||||
stdout = self.run_cli(
|
||||
"task",
|
||||
"create",
|
||||
"test_task",
|
||||
ResourceType.LOCAL.name,
|
||||
*map(os.fspath, files),
|
||||
"--labels",
|
||||
json.dumps([{"name": "car"}, {"name": "person"}]),
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
"--sorting-method",
|
||||
"predefined",
|
||||
"--frame_step",
|
||||
str(frame_step),
|
||||
"--bug_tracker",
|
||||
"http://localhost/bug",
|
||||
)
|
||||
|
||||
task_id = int(stdout.rstrip("\n"))
|
||||
task = self.client.tasks.retrieve(task_id)
|
||||
frames = task.get_frames_info()
|
||||
assert [f.name for f in frames] == [
|
||||
f.name for i, f in enumerate(files) if i % frame_step == 0
|
||||
]
|
||||
assert task.get_meta().frame_filter == f"step={frame_step}"
|
||||
assert task.bug_tracker == "http://localhost/bug"
|
||||
|
||||
def test_can_list_tasks_in_simple_format(self, fxt_new_task: Task):
|
||||
output = self.run_cli("task", "ls")
|
||||
|
||||
results = output.split("\n")
|
||||
assert any(str(fxt_new_task.id) in r for r in results)
|
||||
|
||||
def test_can_list_tasks_in_json_format(self, fxt_new_task: Task):
|
||||
output = self.run_cli("task", "ls", "--json")
|
||||
|
||||
results = json.loads(output)
|
||||
assert any(r["id"] == fxt_new_task.id for r in results)
|
||||
|
||||
def test_can_delete_task(self, fxt_new_task: Task):
|
||||
self.run_cli("task", "delete", str(fxt_new_task.id))
|
||||
|
||||
with pytest.raises(exceptions.NotFoundException):
|
||||
fxt_new_task.fetch()
|
||||
|
||||
def test_can_download_task_annotations(self, fxt_new_task: Task):
|
||||
filename = self.tmp_path / "task_{fxt_new_task.id}-cvat.zip"
|
||||
self.run_cli(
|
||||
"task",
|
||||
"export-dataset",
|
||||
str(fxt_new_task.id),
|
||||
str(filename),
|
||||
"--format",
|
||||
"CVAT for images 1.1",
|
||||
"--with-images",
|
||||
"no",
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
assert 0 < filename.stat().st_size
|
||||
|
||||
def test_can_download_task_annotations_with_server_filename(self, fxt_new_task: Task):
|
||||
output_dir = str(self.tmp_path / "save_dir") + os.path.sep
|
||||
self.run_cli(
|
||||
"task",
|
||||
"export-dataset",
|
||||
str(fxt_new_task.id),
|
||||
output_dir,
|
||||
"--format",
|
||||
"CVAT for images 1.1",
|
||||
"--with-images",
|
||||
"no",
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
output_dir_files = os.listdir(output_dir)
|
||||
assert len(output_dir_files) == 1
|
||||
assert os.stat(output_dir + output_dir_files[0]).st_size > 0
|
||||
|
||||
def test_can_download_task_annotations_to_current_directory(
|
||||
self, fxt_new_task: Task, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
output_dir = self.tmp_path / "current_dir"
|
||||
output_dir.mkdir()
|
||||
monkeypatch.chdir(output_dir)
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"export-dataset",
|
||||
str(fxt_new_task.id),
|
||||
"--format",
|
||||
"CVAT for images 1.1",
|
||||
"--with-images",
|
||||
"no",
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
output_dir_files = list(output_dir.iterdir())
|
||||
assert len(output_dir_files) == 1
|
||||
assert output_dir_files[0].stat().st_size > 0
|
||||
|
||||
def test_can_download_task_backup(self, fxt_new_task: Task):
|
||||
filename = self.tmp_path / "task_{fxt_new_task.id}-cvat.zip"
|
||||
self.run_cli(
|
||||
"task",
|
||||
"backup",
|
||||
str(fxt_new_task.id),
|
||||
str(filename),
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
assert 0 < filename.stat().st_size
|
||||
with zipfile.ZipFile(filename) as backup_zip:
|
||||
assert "task.json" in backup_zip.namelist()
|
||||
|
||||
def test_can_download_task_backup_with_server_filename(self, fxt_new_task: Task):
|
||||
output_dir = str(self.tmp_path / "save_dir") + os.path.sep
|
||||
self.run_cli(
|
||||
"task",
|
||||
"backup",
|
||||
str(fxt_new_task.id),
|
||||
output_dir,
|
||||
"--completion_verification_period",
|
||||
"0.01",
|
||||
)
|
||||
|
||||
output_dir_files = os.listdir(output_dir)
|
||||
assert len(output_dir_files) == 1
|
||||
assert os.stat(output_dir + output_dir_files[0]).st_size > 0
|
||||
|
||||
@pytest.mark.parametrize("quality", ("compressed", "original"))
|
||||
def test_can_download_task_frames(self, fxt_new_task: Task, quality: str):
|
||||
out_dir = str(self.tmp_path / "downloads")
|
||||
self.run_cli(
|
||||
"task",
|
||||
"frames",
|
||||
str(fxt_new_task.id),
|
||||
"0",
|
||||
"1",
|
||||
"--outdir",
|
||||
out_dir,
|
||||
"--quality",
|
||||
quality,
|
||||
)
|
||||
|
||||
assert set(os.listdir(out_dir)) == {
|
||||
"task_{}_frame_{:06d}.jpg".format(fxt_new_task.id, i) for i in range(2)
|
||||
}
|
||||
|
||||
def test_can_upload_annotations(self, fxt_new_task: Task, fxt_coco_file: Path):
|
||||
self.run_cli(
|
||||
"task",
|
||||
"import-dataset",
|
||||
str(fxt_new_task.id),
|
||||
str(fxt_coco_file),
|
||||
"--format",
|
||||
"COCO 1.0",
|
||||
)
|
||||
|
||||
def test_can_create_from_backup(self, fxt_new_task: Task, fxt_backup_file: Path):
|
||||
stdout = self.run_cli("task", "create-from-backup", str(fxt_backup_file))
|
||||
|
||||
task_id = int(stdout.rstrip("\n"))
|
||||
assert task_id
|
||||
assert task_id != fxt_new_task.id
|
||||
assert self.client.tasks.retrieve(task_id).size == fxt_new_task.size
|
||||
|
||||
def test_auto_annotate_with_module(self, fxt_new_task: Task):
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert not annotations.shapes
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-module={__package__}.example_function",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes
|
||||
|
||||
def test_auto_annotate_with_file(self, fxt_new_task: Task):
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert not annotations.shapes
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-file={Path(__file__).with_name('example_function.py')}",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes
|
||||
|
||||
def test_auto_annotate_with_parameters(self, fxt_new_task: Task):
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert not annotations.shapes
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-module={__package__}.example_parameterized_function",
|
||||
"-ps=str:string",
|
||||
"-pi=int:123",
|
||||
"-pf=float:5.5",
|
||||
"-pb=bool:false",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes
|
||||
|
||||
def test_auto_annotate_with_threshold(self, fxt_new_task: Task):
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert not annotations.shapes
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-module={__package__}.conf_threshold_function",
|
||||
"--conf-threshold=0.75",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes[0].points[0] == 0.75 # python:S1244 NOSONAR
|
||||
|
||||
def test_auto_annotate_with_cmtp(self, fxt_new_task: Task):
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-module={__package__}.cmtp_function",
|
||||
"--clear-existing",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes[0].type.value == "mask"
|
||||
|
||||
self.run_cli(
|
||||
"task",
|
||||
"auto-annotate",
|
||||
str(fxt_new_task.id),
|
||||
f"--function-module={__package__}.cmtp_function",
|
||||
"--clear-existing",
|
||||
"--conv-mask-to-poly",
|
||||
)
|
||||
|
||||
annotations = fxt_new_task.get_annotations()
|
||||
assert annotations.shapes[0].type.value == "polygon"
|
||||
|
||||
def test_legacy_alias(self, caplog):
|
||||
# All legacy aliases are implemented the same way;
|
||||
# no need to test every single one.
|
||||
self.run_cli("ls")
|
||||
|
||||
assert "deprecated" in caplog.text
|
||||
assert "task ls" in caplog.text
|
||||
@@ -0,0 +1,94 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import io
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from cvat_sdk import make_client
|
||||
|
||||
from shared.utils.config import BASE_URL, USER_PASS
|
||||
from shared.utils.helpers import generate_image_file
|
||||
|
||||
|
||||
def run_cli(
|
||||
test: unittest.TestCase | Any,
|
||||
*args: str,
|
||||
expected_code: int = 0,
|
||||
) -> None:
|
||||
from cvat_cli.__main__ import main
|
||||
|
||||
if isinstance(test, unittest.TestCase):
|
||||
# Unittest
|
||||
test.assertEqual(expected_code, main(args), str(args))
|
||||
else:
|
||||
# Pytest case
|
||||
assert expected_code == main(args)
|
||||
|
||||
|
||||
def generate_images(dst_dir: Path, count: int) -> list[Path]:
|
||||
filenames = []
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
for i in range(count):
|
||||
filename = dst_dir / f"img_{i}.jpg"
|
||||
filename.write_bytes(generate_image_file(filename.name).getvalue())
|
||||
filenames.append(filename)
|
||||
return filenames
|
||||
|
||||
|
||||
class TestCliBase:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(
|
||||
self,
|
||||
restore_db_per_function, # force fixture call order to allow DB setup
|
||||
restore_redis_inmem_per_function,
|
||||
restore_redis_ondisk_per_function,
|
||||
fxt_stdout: io.StringIO,
|
||||
tmp_path: Path,
|
||||
admin_user: str,
|
||||
):
|
||||
self.tmp_path = tmp_path
|
||||
self.stdout = fxt_stdout
|
||||
self.host, self.port = BASE_URL.rsplit(":", maxsplit=1)
|
||||
self.user = admin_user
|
||||
self.password = USER_PASS
|
||||
self.client = make_client(
|
||||
host=self.host, port=self.port, credentials=(self.user, self.password)
|
||||
)
|
||||
self.client.config.status_check_period = 0.01
|
||||
|
||||
yield
|
||||
|
||||
def run_cli(
|
||||
self,
|
||||
cmd: str,
|
||||
*args: str,
|
||||
expected_code: int = 0,
|
||||
organization: str | None = None,
|
||||
authenticate: bool = True,
|
||||
) -> str:
|
||||
common_args = [
|
||||
f"--server-host={self.host}",
|
||||
f"--server-port={self.port}",
|
||||
]
|
||||
|
||||
if authenticate:
|
||||
common_args += [
|
||||
f"--auth={self.user}:{self.password}",
|
||||
]
|
||||
|
||||
if organization is not None:
|
||||
common_args.append(f"--organization={organization}")
|
||||
|
||||
run_cli(
|
||||
self,
|
||||
*common_args,
|
||||
cmd,
|
||||
*args,
|
||||
expected_code=expected_code,
|
||||
)
|
||||
return self.stdout.getvalue()
|
||||
Reference in New Issue
Block a user