diff --git a/labellerr/core/projects/base.py b/labellerr/core/projects/base.py index c7967bf..3fc5c57 100644 --- a/labellerr/core/projects/base.py +++ b/labellerr/core/projects/base.py @@ -643,3 +643,64 @@ def import_users(self, from_project: "LabellerrProject"): "POST", url, extra_headers={"Content-Type": "application/json"} ) return response.get("response") + + def archive(self, unarchive=False): + """ + Archives or unarchives the project. + + :param unarchive: If True, unarchives the project. Default is False (archive). + :return: Response from the server. + :raises LabellerrError: If the operation fails. + """ + unique_id = str(uuid.uuid4()) + action = "unarchive" if unarchive else "archive" + url = f"{constants.BASE_URL}/projects/{action}?client_id={self.client.client_id}&uuid={unique_id}" + + payload = json.dumps({"project_id": self.project_id}) + + headers = {"content-type": "application/json"} + if self.client.api_key: + headers["email_id"] = self.client.api_key + + return self.client.make_request( + "POST", + url, + extra_headers=headers, + request_id=unique_id, + data=payload, + ) + + + def unarchive(self): + """ + Unarchives the project. + Alias for archive(unarchive=True). + + :return: Response from the server. + :raises LabellerrError: If the operation fails. + """ + return self.archive(unarchive=True) + + + def delete(self): + """ + Deletes the project. + + :return: Response from the server. + :raises LabellerrError: If the operation fails. + """ + unique_id = str(uuid.uuid4()) + + url = f"{constants.BASE_URL}/projects/project/{self.project_id}?client_id={self.client.client_id}&uuid={unique_id}" + + + headers = {} + if self.client.api_key: + headers["email_id"] = self.client.api_key + + return self.client.make_request( + "DELETE", + url, + extra_headers=headers, + request_id=unique_id, + ) diff --git a/labellerr/core/schemas/datasets.py b/labellerr/core/schemas/datasets.py index 2ada187..6127beb 100644 --- a/labellerr/core/schemas/datasets.py +++ b/labellerr/core/schemas/datasets.py @@ -3,7 +3,7 @@ """ import os -from enum import StrEnum +from enum import Enum from typing import List, Literal from uuid import UUID @@ -11,7 +11,7 @@ from pydantic import BaseModel, Field, field_validator -class DataSetScope(StrEnum): +class DataSetScope(str, Enum): project = "project" client = "client" public = "public" diff --git a/tests/integration/test_create_project.py b/tests/integration/test_create_project.py index ff3790b..8bd87c1 100644 --- a/tests/integration/test_create_project.py +++ b/tests/integration/test_create_project.py @@ -48,3 +48,31 @@ def test_create_project(create_project_fixture): assert project.project_id is not None assert isinstance(project.project_id, str) + + +def test_archive_and_delete_project(create_project_fixture): + + project = create_project_fixture + assert project.project_id is not None + + # 1. Archive + print(f"Archiving project {project.project_id}...") + try: + project.archive() + except Exception as e: + pytest.fail(f"Failed to archive project: {e}") + + # 2. Unarchive (test the alias) + print(f"Unarchiving project {project.project_id}...") + try: + project.unarchive() + except Exception as e: + pytest.fail(f"Failed to unarchive project: {e}") + + # 3. Archive again (to test delete on archived project if valid, or just normal delete) + # Let's delete it while active + print(f"Deleting project {project.project_id}...") + try: + project.delete() + except Exception as e: + pytest.fail(f"Failed to delete project: {e}") diff --git a/tests/unit/test_projects.py b/tests/unit/test_projects.py new file mode 100644 index 0000000..80341ce --- /dev/null +++ b/tests/unit/test_projects.py @@ -0,0 +1,86 @@ +import pytest +from unittest.mock import patch, Mock +import json +from labellerr.core.projects.image_project import ImageProject +from labellerr.core.users.base import LabellerrUsers + +@pytest.fixture +def client(): + """Create a mock client""" + from labellerr.client import LabellerrClient + client = Mock(spec=LabellerrClient) + client.client_id = "test-client-id" + client.api_key = "test-api-key" + client.api_secret = "test-api-secret" + return client + +@pytest.fixture +def project(client): + """Create a test project instance""" + project_data = { + "project_id": "test_project_id", + "data_type": "image", + "attached_datasets": [], + } + # Use __new__ to avoid initialization logic if needed, or just mock it + proj = ImageProject.__new__(ImageProject) + proj.client = client + proj._LabellerrProject__project_id_input = "test_project_id" + proj._LabellerrProject__project_data = project_data + return proj + +@pytest.mark.unit +class TestProjectLifecycle: + """Tests for project lifecycle methods: archive, unarchive, delete""" + + def test_archive_project(self, project, client): + """Test archiving a project""" + mock_response = {"status": "success", "msg": "Project archived"} + + with patch.object(client, "make_request", return_value=mock_response) as mock_req: + response = project.archive() + + assert response == mock_response + + # Verify the request + mock_req.assert_called_once() + args, kwargs = mock_req.call_args + assert args[0] == "POST" + assert "/projects/archive" in args[1] + assert kwargs["data"] is not None + assert '"project_id": "test_project_id"' in kwargs["data"] + + def test_unarchive_project(self, project, client): + """Test unarchiving a project""" + mock_response = {"status": "success", "msg": "Project unarchived"} + + with patch.object(client, "make_request", return_value=mock_response) as mock_req: + # Test direct call to archive(unarchive=True) + response = project.archive(unarchive=True) + assert response == mock_response + + args, kwargs = mock_req.call_args + assert "/projects/unarchive" in args[1] + + # Test alias unarchive() + mock_req.reset_mock() + response = project.unarchive() + assert response == mock_response + + args, kwargs = mock_req.call_args + assert "/projects/unarchive" in args[1] + + def test_delete_project(self, project, client): + """Test deleting a project""" + mock_response = {"status": "success", "msg": "Project deleted"} + + with patch.object(client, "make_request", return_value=mock_response) as mock_req: + response = project.delete() + + assert response == mock_response + + # Verify the request + mock_req.assert_called_once() + args, kwargs = mock_req.call_args + assert args[0] == "DELETE" + assert "/projects/project/test_project_id" in args[1]