diff --git a/darwin/future/core/items/get.py b/darwin/future/core/items/get.py index 8701d5828..4103b2bde 100644 --- a/darwin/future/core/items/get.py +++ b/darwin/future/core/items/get.py @@ -1,11 +1,13 @@ -from typing import List, Tuple, Union +from __future__ import annotations + +from typing import List, Literal, Tuple, Union from uuid import UUID from pydantic import ValidationError, parse_obj_as from darwin.future.core.client import ClientCore from darwin.future.core.types.common import QueryString -from darwin.future.data_objects.item import Folder, Item +from darwin.future.data_objects.item import Folder, ItemCore def get_item_ids( @@ -86,7 +88,7 @@ def get_item( team_slug: str, item_id: Union[UUID, str], params: QueryString = QueryString({}), -) -> Item: +) -> ItemCore: """ Returns an item @@ -106,14 +108,15 @@ def get_item( """ response = api_client.get(f"/v2/teams/{team_slug}/items/{item_id}", params) assert isinstance(response, dict) - return parse_obj_as(Item, response) + return parse_obj_as(ItemCore, response) def list_items( api_client: ClientCore, team_slug: str, - params: QueryString, -) -> Tuple[List[Item], List[ValidationError]]: + dataset_ids: int | list[int] | Literal["all"], + params: QueryString = QueryString({}), +) -> Tuple[List[ItemCore], List[ValidationError]]: """ Returns a list of items for the dataset @@ -133,15 +136,20 @@ def list_items( List[ValidationError] A list of ValidationError on failed objects """ - assert "dataset_ids" in params.value, "dataset_ids must be provided" + dataset_ids = ( + dataset_ids + if isinstance(dataset_ids, list) or dataset_ids == "all" + else [dataset_ids] + ) + params = params + QueryString({"dataset_ids": dataset_ids}) response = api_client.get(f"/v2/teams/{team_slug}/items", params) assert isinstance(response, dict) - items: List[Item] = [] + items: List[ItemCore] = [] exceptions: List[ValidationError] = [] for item in response["items"]: assert isinstance(item, dict) try: - items.append(parse_obj_as(Item, item)) + items.append(parse_obj_as(ItemCore, item)) except ValidationError as e: exceptions.append(e) return items, exceptions diff --git a/darwin/future/core/types/common.py b/darwin/future/core/types/common.py index 489916509..9725c2d64 100644 --- a/darwin/future/core/types/common.py +++ b/darwin/future/core/types/common.py @@ -1,13 +1,20 @@ from __future__ import annotations -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Mapping, Protocol, Union from darwin.future.data_objects import validators as darwin_validators -from darwin.future.data_objects.typing import UnknownType JSONType = Union[Dict[str, Any], List[Dict[str, Any]]] # type: ignore +class Implements_str(Protocol): + def __str__(self) -> str: + ... + + +Stringable = Union[str, Implements_str] + + class TeamSlug(str): """ Represents a team slug, which is a string identifier for a team. @@ -73,18 +80,31 @@ class QueryString: Returns a string representation of the QueryString object, in the format "?key1=value1&key2=value2". """ - value: Dict[str, str] + value: dict[str, list[str] | str] - def dict_check(self, value: UnknownType) -> Dict[str, str]: - assert isinstance(value, dict) - assert all(isinstance(k, str) and isinstance(v, str) for k, v in value.items()) - return value + def dict_check( + self, value: Mapping[str, list[Stringable] | Stringable] + ) -> dict[str, list[str] | str]: + mapped: dict[str, list[str] | str] = {} + for k, v in value.items(): + if isinstance(v, list): + mapped[k] = [str(x) for x in v] + else: + mapped[k] = str(v) + return mapped - def __init__(self, value: Dict[str, str]) -> None: + def __init__(self, value: Mapping[str, list[Stringable] | Stringable]) -> None: self.value = self.dict_check(value) def __str__(self) -> str: - return "?" + "&".join(f"{k}={v}" for k, v in self.value.items()) + output: str = "?" if self.value else "" + for k, v in self.value.items(): + if isinstance(v, list): + for x in v: + output += f"{k}={x}&" + else: + output += f"{k}={v}&" + return output[:-1] # remove trailing & def __add__(self, other: QueryString) -> QueryString: return QueryString({**self.value, **other.value}) diff --git a/darwin/future/data_objects/item.py b/darwin/future/data_objects/item.py index b649c63a2..714b24610 100644 --- a/darwin/future/data_objects/item.py +++ b/darwin/future/data_objects/item.py @@ -15,7 +15,6 @@ def validate_no_slashes(v: UnknownType) -> str: assert isinstance(v, str), "Must be a string" assert len(v) > 0, "cannot be empty" assert "/" not in v, "cannot contain slashes" - assert " " not in v, "cannot contain spaces" return v @@ -75,7 +74,7 @@ def validate_fps(cls, values: dict) -> dict: elif isinstance(value, (int, float)): type = values.get("type") if type == "image": - assert value == 0, "fps must be 0 for images" + assert value == 0 or value == 1.0, "fps must be '0' or '1.0' for images" else: assert value >= 0, "fps must be greater than or equal to 0 for videos" @@ -127,7 +126,7 @@ def validate_name(cls, v: UnknownType) -> str: return validate_no_slashes(v) -class Item(DefaultDarwin): +class ItemCore(DefaultDarwin): # GraphotateWeb.Schemas.DatasetsV2.ItemRegistration.NewItem # Required fields diff --git a/darwin/future/meta/objects/dataset.py b/darwin/future/meta/objects/dataset.py index e44eff0ae..3f17e15e4 100644 --- a/darwin/future/meta/objects/dataset.py +++ b/darwin/future/meta/objects/dataset.py @@ -2,6 +2,7 @@ from typing import List, Optional, Sequence, Union + from darwin.cli_functions import upload_data from darwin.dataset.upload_manager import LocalFile from darwin.datatypes import PathLike @@ -10,6 +11,7 @@ from darwin.future.data_objects.dataset import DatasetCore from darwin.future.helpers.assertion import assert_is from darwin.future.meta.objects.base import MetaBase +from darwin.future.meta.queries.item import ItemQuery from darwin.future.meta.queries.item_id import ItemIDQuery @@ -74,6 +76,11 @@ def item_ids(self) -> ItemIDQuery: meta_params = {"dataset_ids": self.id, **self.meta_params} return ItemIDQuery(self.client, meta_params=meta_params) + @property + def items(self) -> ItemQuery: + meta_params = {"dataset_ids": self.id, **self.meta_params} + return ItemQuery(self.client, meta_params=meta_params) + @classmethod def create_dataset(cls, client: ClientCore, slug: str) -> DatasetCore: """ diff --git a/darwin/future/meta/objects/item.py b/darwin/future/meta/objects/item.py new file mode 100644 index 000000000..7b438f721 --- /dev/null +++ b/darwin/future/meta/objects/item.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from typing import Dict, List, Optional, Union, cast +from uuid import UUID + +from darwin.future.core.items.delete_items import delete_list_of_items +from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot +from darwin.future.meta.objects.base import MetaBase + + +class Item(MetaBase[ItemCore]): + """ + Represents an item in a Darwin dataset. + + Args: + MetaBase (Stage): Generic MetaBase object expanded by ItemCore object + return type + + Attributes: + name (str): The name of the item. + id (UUID): The unique identifier of the item. + slots (List[ItemSlot]): A list of slots associated with the item. + path (str): The path of the item. + dataset_id (int): The ID of the dataset the item belongs to. + processing_status (str): The processing status of the item. + archived (Optional[bool]): Whether the item is archived or not. + priority (Optional[int]): The priority of the item. + tags (Optional[Union[List[str], Dict[str, str]]]): The tags associated with the item. + layout (Optional[ItemLayout]): The layout of the item. + + Methods: + delete(self) -> None: + Deletes the item from the Darwin dataset. + + Example usage: + # Get the item object + items = workflow.items.where(name='test').collect() # gets first page of items + + # Delete the items + [item.delete() for item in items] # will collect all pages of items and delete individually + + """ + + def delete(self) -> None: + team_slug, dataset_id = ( + self.meta_params["team_slug"], + self.meta_params["dataset_id"] + if "dataset_id" in self.meta_params + else self.meta_params["dataset_ids"], + ) + assert isinstance(team_slug, str) + dataset_id = cast(Union[int, List[int]], dataset_id) + filters = {"item_ids": [str(self.id)]} + delete_list_of_items(self.client, team_slug, dataset_id, filters) + + @property + def name(self) -> str: + return self._element.name + + @property + def id(self) -> UUID: + return self._element.id + + @property + def slots(self) -> List[ItemSlot]: + return self._element.slots + + @property + def path(self) -> str: + return self._element.path + + @property + def dataset_id(self) -> int: + return self._element.dataset_id + + @property + def processing_status(self) -> str: + return self._element.processing_status + + @property + def archived(self) -> Optional[bool]: + return self._element.archived + + @property + def priority(self) -> Optional[int]: + return self._element.priority + + @property + def tags(self) -> Optional[Union[List[str], Dict[str, str]]]: + return self._element.tags + + @property + def layout(self) -> Optional[ItemLayout]: + return self._element.layout diff --git a/darwin/future/meta/objects/stage.py b/darwin/future/meta/objects/stage.py index 477558287..068d9de2e 100644 --- a/darwin/future/meta/objects/stage.py +++ b/darwin/future/meta/objects/stage.py @@ -7,6 +7,7 @@ from darwin.future.core.types.query import QueryFilter from darwin.future.data_objects.workflow import WFEdgeCore, WFStageCore from darwin.future.meta.objects.base import MetaBase +from darwin.future.meta.queries.item import ItemQuery from darwin.future.meta.queries.item_id import ItemIDQuery @@ -43,6 +44,22 @@ class Stage(MetaBase[WFStageCore]): stage.move_attached_files_to_stage(new_stage_id=new_stage.id) """ + @property + def items(self) -> ItemQuery: + """Item ids attached to the stage + + Returns: + List[Item]: List of item ids + """ + assert self._element.id is not None + return ItemQuery( + self.client, + meta_params=self.meta_params, + filters=[ + QueryFilter(name="workflow_stage_ids", param=str(self._element.id)) + ], + ) + @property def item_ids(self) -> ItemIDQuery: """Item ids attached to the stage diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py index 078693230..6bc82ecfa 100644 --- a/darwin/future/meta/objects/team.py +++ b/darwin/future/meta/objects/team.py @@ -9,6 +9,7 @@ from darwin.future.meta.objects.base import MetaBase from darwin.future.meta.objects.dataset import Dataset from darwin.future.meta.queries.dataset import DatasetQuery +from darwin.future.meta.queries.item import ItemQuery from darwin.future.meta.queries.team_member import TeamMemberQuery from darwin.future.meta.queries.workflow import WorkflowQuery @@ -95,6 +96,12 @@ def datasets(self) -> DatasetQuery: def workflows(self) -> WorkflowQuery: return WorkflowQuery(self.client, meta_params={"team_slug": self.slug}) + @property + def items(self) -> ItemQuery: + return ItemQuery( + self.client, meta_params={"team_slug": self.slug, "dataset_ids": "all"} + ) + @classmethod def delete_dataset(cls, client: ClientCore, dataset_id: Union[int, str]) -> int: """ diff --git a/darwin/future/meta/objects/workflow.py b/darwin/future/meta/objects/workflow.py index 7e931e1da..e7b857483 100644 --- a/darwin/future/meta/objects/workflow.py +++ b/darwin/future/meta/objects/workflow.py @@ -6,8 +6,10 @@ from darwin.cli_functions import upload_data from darwin.dataset.upload_manager import LocalFile from darwin.datatypes import PathLike +from darwin.future.core.types.query import QueryFilter from darwin.future.data_objects.workflow import WFDatasetCore, WFTypeCore, WorkflowCore from darwin.future.meta.objects.base import MetaBase +from darwin.future.meta.queries.item import ItemQuery from darwin.future.meta.queries.stage import StageQuery @@ -46,6 +48,14 @@ class Workflow(MetaBase[WorkflowCore]): datasets = workflow.datasets """ + @property + def items(self) -> ItemQuery: + return ItemQuery( + self.client, + meta_params=self.meta_params, + filters=[QueryFilter(name="workflow_id", param=str(self.id))], + ) + @property def stages(self) -> StageQuery: meta_params = self.meta_params.copy() diff --git a/darwin/future/meta/queries/item.py b/darwin/future/meta/queries/item.py new file mode 100644 index 000000000..5fd13e0e1 --- /dev/null +++ b/darwin/future/meta/queries/item.py @@ -0,0 +1,61 @@ +from functools import reduce +from typing import Dict + +from darwin.future.core.items.delete_items import delete_list_of_items +from darwin.future.core.items.get import list_items +from darwin.future.core.types.common import QueryString +from darwin.future.core.types.query import PaginatedQuery +from darwin.future.meta.objects.item import Item + + +class ItemQuery(PaginatedQuery[Item]): + def _collect(self) -> Dict[int, Item]: + if "team_slug" not in self.meta_params: + raise ValueError("Must specify team_slug to query items") + if ( + "dataset_ids" not in self.meta_params + and "dataset_id" not in self.meta_params + ): + raise ValueError("Must specify dataset_ids to query items") + dataset_ids = ( + self.meta_params["dataset_ids"] + if "dataset_ids" in self.meta_params + else self.meta_params["dataset_id"] + ) + team_slug = self.meta_params["team_slug"] + params: QueryString = reduce( + lambda s1, s2: s1 + s2, + [ + self.page.to_query_string(), + *[QueryString(f.to_dict()) for f in self.filters], + ], + ) + items_core, errors = list_items(self.client, team_slug, dataset_ids, params) + offset = self.page.offset + items = { + i + + offset: Item( + client=self.client, element=item, meta_params=self.meta_params + ) + for i, item in enumerate(items_core) + } + return items + + def delete(self) -> None: + if "team_slug" not in self.meta_params: + raise ValueError("Must specify team_slug to query items") + if ( + "dataset_ids" not in self.meta_params + and "dataset_id" not in self.meta_params + ): + raise ValueError("Must specify dataset_ids to query items") + dataset_ids = ( + self.meta_params["dataset_ids"] + if "dataset_ids" in self.meta_params + else self.meta_params["dataset_id"] + ) + team_slug = self.meta_params["team_slug"] + self.collect_all() + ids = [item.id for item in self] + filters = {"item_ids": [str(item) for item in ids]} + delete_list_of_items(self.client, team_slug, dataset_ids, filters) diff --git a/darwin/future/tests/core/datasets/test_list_datasets.py b/darwin/future/tests/core/datasets/test_list_datasets.py index 58177b185..b31cb5713 100644 --- a/darwin/future/tests/core/datasets/test_list_datasets.py +++ b/darwin/future/tests/core/datasets/test_list_datasets.py @@ -45,4 +45,4 @@ def test_it_returns_an_error_if_the_client_returns_an_http_error( with pytest.raises(BadRequest) as execinfo: list_datasets(base_client) - assert execinfo.value.args[0].status_code == 400 + assert execinfo.value.args[0].status_code == 400 diff --git a/darwin/future/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 0d4782134..210042929 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -1,11 +1,13 @@ from pathlib import Path from typing import List +from uuid import uuid4 import orjson as json import pytest from darwin.future.core.client import ClientCore, DarwinConfig from darwin.future.data_objects.dataset import DatasetCore +from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot from darwin.future.data_objects.team import TeamCore, TeamMemberCore from darwin.future.data_objects.team_member_role import TeamMemberRole @@ -22,6 +24,36 @@ def base_config() -> DarwinConfig: ) +@pytest.fixture +def items_json(item_core_list: List[ItemCore]) -> List[dict]: + items: List[dict] = [] + for item in item_core_list: + temp = dict(item) + temp["id"] = str(temp["id"]) + temp["slots"] = [dict(slot) for slot in temp["slots"]] + temp["layout"] = dict(temp["layout"]) + items.append(temp) + return items + + +@pytest.fixture +def item_core_list() -> List[ItemCore]: + items = [] + for i in range(5): + slot = ItemSlot(slot_name=f"slot_{i}", file_name=f"file_{i}.jpg") + layout = ItemLayout(slots=[f"slot_{i}"], type="grid", version=1) + item = ItemCore( + name=f"item_{i}", + id=uuid4(), + slots=[slot], + dataset_id=i, + processing_status="processed", + layout=layout, + ) + items.append(item) + return items + + @pytest.fixture def base_client(base_config: DarwinConfig) -> ClientCore: return ClientCore(base_config) @@ -37,6 +69,28 @@ def base_team(base_team_json: dict) -> TeamCore: return TeamCore.parse_obj(base_team_json) +@pytest.fixture +def base_item_json() -> dict: + return { + "name": "test-item", + "id": "123e4567-e89b-12d3-a456-426655440000", + "slots": [ + {"slot_name": "slot1", "file_name": "file1.jpg", "fps": 30}, + {"slot_name": "slot2", "file_name": "file2.jpg", "fps": 24}, + ], + "path": "/", + "archived": False, + "priority": None, + "tags": [], + "layout": None, + } + + +@pytest.fixture +def base_item(base_item_json: dict) -> ItemCore: + return ItemCore.parse_obj(base_item_json) + + @pytest.fixture def base_team_member_json() -> dict: return { diff --git a/darwin/future/tests/core/items/fixtures.py b/darwin/future/tests/core/items/fixtures.py index 92d57a026..330ec5f28 100644 --- a/darwin/future/tests/core/items/fixtures.py +++ b/darwin/future/tests/core/items/fixtures.py @@ -3,7 +3,7 @@ import pytest -from darwin.future.data_objects.item import Folder, Item, ItemLayout +from darwin.future.data_objects.item import Folder, ItemCore, ItemLayout @pytest.fixture @@ -14,9 +14,9 @@ def base_layout() -> ItemLayout: @pytest.fixture -def base_items() -> List[Item]: +def base_items() -> List[ItemCore]: return [ - Item( + ItemCore( name=f"test_{i}", path="test_path", dataset_id=1, @@ -43,7 +43,7 @@ def base_folders() -> List[Folder]: @pytest.fixture -def base_items_json(base_items: List[Item]) -> List[dict]: +def base_items_json(base_items: List[ItemCore]) -> List[dict]: items = [item.dict() for item in base_items] # json library doesn't support UUIDs so need to be str'd for item in items: diff --git a/darwin/future/tests/core/items/test_archive_items.py b/darwin/future/tests/core/items/test_archive_items.py index e063d4953..c0c335fa9 100644 --- a/darwin/future/tests/core/items/test_archive_items.py +++ b/darwin/future/tests/core/items/test_archive_items.py @@ -43,14 +43,12 @@ def test_archive_items_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} with pytest.raises(AssertionError): archive_list_of_items( client=base_client, team_slug=team_slug, dataset_ids=dataset_ids, - filters=filters, ) diff --git a/darwin/future/tests/core/items/test_delete_items.py b/darwin/future/tests/core/items/test_delete_items.py index 5630599c1..a72c2a26b 100644 --- a/darwin/future/tests/core/items/test_delete_items.py +++ b/darwin/future/tests/core/items/test_delete_items.py @@ -1,8 +1,11 @@ +from typing import Dict + import pytest import responses from darwin.future.core.client import ClientCore from darwin.future.core.items.delete_items import delete_list_of_items +from darwin.future.data_objects.typing import UnknownType from darwin.future.exceptions import BadRequest from darwin.future.tests.core.fixtures import * @@ -11,7 +14,7 @@ def test_delete_items_including_filters(base_client: ClientCore) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = { + filters: Dict[str, UnknownType] = { "not_statuses": ["uploading", "annotate"], "not_assignees": [123, 456, 789], "item_ids": [ @@ -43,7 +46,7 @@ def test_delete_items_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} + filters: Dict[str, UnknownType] = {} with pytest.raises(AssertionError): delete_list_of_items( @@ -58,7 +61,7 @@ def test_delete_items_raises_on_incorrect_parameters( def test_delete_items_with_error_response(base_client: ClientCore) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = { + filters: Dict[str, UnknownType] = { "not_statuses": ["uploading", "annotate"], "not_assignees": [123, 456, 789], "item_ids": [ diff --git a/darwin/future/tests/core/items/test_get_items.py b/darwin/future/tests/core/items/test_get_items.py index 3e7a40386..cd174ea0b 100644 --- a/darwin/future/tests/core/items/test_get_items.py +++ b/darwin/future/tests/core/items/test_get_items.py @@ -8,7 +8,7 @@ from darwin.future.core.items import get_item_ids, get_item_ids_stage from darwin.future.core.items.get import get_item, list_folders, list_items from darwin.future.core.types.common import QueryString -from darwin.future.data_objects.item import Folder, Item +from darwin.future.data_objects.item import Folder, ItemCore from darwin.future.tests.core.fixtures import * from darwin.future.tests.core.items.fixtures import * @@ -45,7 +45,7 @@ def test_get_item_ids_stage( def test_get_item( - base_items_json: List[dict], base_items: List[Item], base_client: ClientCore + base_items_json: List[dict], base_items: List[ItemCore], base_client: ClientCore ) -> None: uuid = str(base_items[0].id) with responses.RequestsMock() as rsps: @@ -60,7 +60,7 @@ def test_get_item( def test_list_items( - base_items_json: List[dict], base_items: List[Item], base_client: ClientCore + base_items_json: List[dict], base_items: List[ItemCore], base_client: ClientCore ) -> None: with responses.RequestsMock() as rsps: rsps.add( @@ -70,9 +70,7 @@ def test_list_items( json={"items": base_items_json}, status=200, ) - items, _ = list_items( - base_client, "default-team", QueryString({"dataset_ids": "1337"}) - ) + items, _ = list_items(base_client, "default-team", dataset_ids=[1337]) for item, comparator in zip(items, base_items): assert item == comparator @@ -91,9 +89,7 @@ def test_list_items_breaks( json={"items": base_items_json}, status=200, ) - items, exceptions = list_items( - base_client, "default-team", QueryString({"dataset_ids": "1337"}) - ) + items, exceptions = list_items(base_client, "default-team", dataset_ids=[1337]) assert len(exceptions) == 1 assert isinstance(exceptions[0], ValidationError) diff --git a/darwin/future/tests/core/items/test_move_items.py b/darwin/future/tests/core/items/test_move_items.py index 56c18e13f..d48665956 100644 --- a/darwin/future/tests/core/items/test_move_items.py +++ b/darwin/future/tests/core/items/test_move_items.py @@ -49,7 +49,6 @@ def test_move_items_to_stage_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} workflow_id = UUID("00000000-0000-0000-0000-000000000000") stage_id = UUID("00000000-0000-0000-0000-000000000000") @@ -60,7 +59,6 @@ def test_move_items_to_stage_raises_on_incorrect_parameters( workflow_id=workflow_id, dataset_ids=dataset_ids, stage_id=stage_id, - filters=filters, ) diff --git a/darwin/future/tests/core/items/test_move_items_to_folder.py b/darwin/future/tests/core/items/test_move_items_to_folder.py index fe7063563..9c55d5456 100644 --- a/darwin/future/tests/core/items/test_move_items_to_folder.py +++ b/darwin/future/tests/core/items/test_move_items_to_folder.py @@ -47,7 +47,6 @@ def test_move_list_of_items_to_folder_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} path = "/test/path" with pytest.raises(AssertionError): @@ -56,7 +55,6 @@ def test_move_list_of_items_to_folder_raises_on_incorrect_parameters( team_slug=team_slug, dataset_ids=dataset_ids, path=path, - filters=filters, ) diff --git a/darwin/future/tests/core/items/test_restore_items.py b/darwin/future/tests/core/items/test_restore_items.py index 175ba800b..56e4c6df4 100644 --- a/darwin/future/tests/core/items/test_restore_items.py +++ b/darwin/future/tests/core/items/test_restore_items.py @@ -43,14 +43,12 @@ def test_restore_items_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} with pytest.raises(AssertionError): restore_list_of_items( client=base_client, team_slug=team_slug, dataset_ids=dataset_ids, - filters=filters, ) diff --git a/darwin/future/tests/core/items/test_set_priority.py b/darwin/future/tests/core/items/test_set_priority.py index 92f9cec12..9ea1a9daa 100644 --- a/darwin/future/tests/core/items/test_set_priority.py +++ b/darwin/future/tests/core/items/test_set_priority.py @@ -45,7 +45,6 @@ def test_set_item_priority_raises_on_incorrect_parameters( ) -> None: dataset_ids = [1, 2, 3] team_slug = "test-team" - filters = {} priority = 100 with pytest.raises(AssertionError): @@ -53,7 +52,6 @@ def test_set_item_priority_raises_on_incorrect_parameters( client=base_client, team_slug=team_slug, dataset_ids=dataset_ids, - filters=filters, priority=priority, ) diff --git a/darwin/future/tests/core/types/test_querystring.py b/darwin/future/tests/core/types/test_querystring.py index e92b0af94..4e471c15e 100644 --- a/darwin/future/tests/core/types/test_querystring.py +++ b/darwin/future/tests/core/types/test_querystring.py @@ -1,5 +1,3 @@ -from pytest import raises - from darwin.future.core.types.common import QueryString @@ -13,12 +11,23 @@ def test_querystring_happy_path() -> None: assert str(query_string_2) == "?foo=bar&baz=qux" query_string_3 = QueryString({}) - assert str(query_string_3) == "?" + assert str(query_string_3) == "" assert query_string.value == {"foo": "bar"} assert query_string_2.value == {"foo": "bar", "baz": "qux"} -def test_querystring_sad_path() -> None: - with raises(AssertionError): - QueryString({"foo": 1}) # type: ignore +def test_querystring_coerces_list() -> None: + query_string = QueryString({"foo": ["bar", "baz"]}) + assert str(query_string) == "?foo=bar&foo=baz" + assert query_string.value == {"foo": ["bar", "baz"]} + + +def test_querystring_coerces_stringable() -> None: + class Stringable: + def __str__(self) -> str: + return "bar" + + query_string = QueryString({"foo": Stringable()}) + assert str(query_string) == "?foo=bar" + assert query_string.value == {"foo": "bar"} diff --git a/darwin/future/tests/meta/objects/fixtures.py b/darwin/future/tests/meta/objects/fixtures.py index 6aebae03d..d2c5d6890 100644 --- a/darwin/future/tests/meta/objects/fixtures.py +++ b/darwin/future/tests/meta/objects/fixtures.py @@ -5,15 +5,34 @@ from darwin.future.core.client import ClientCore from darwin.future.data_objects.dataset import DatasetCore +from darwin.future.data_objects.item import ItemCore from darwin.future.data_objects.team import TeamCore from darwin.future.data_objects.workflow import WFStageCore, WorkflowCore from darwin.future.meta.objects.dataset import Dataset +from darwin.future.meta.objects.item import Item from darwin.future.meta.objects.stage import Stage from darwin.future.meta.objects.team import Team from darwin.future.meta.objects.workflow import Workflow from darwin.future.tests.core.fixtures import * +@fixture +def items(base_client: ClientCore, item_core_list: List[ItemCore]) -> List[Item]: + return [ + Item( + client=base_client, + element=item, + meta_params={"team_slug": "test", "dataset_id": 1}, + ) + for item in item_core_list + ] + + +@fixture +def item(items: List[Item]) -> Item: + return items[0] + + @fixture def base_UUID() -> UUID: return UUID("00000000-0000-0000-0000-000000000000") @@ -48,3 +67,8 @@ def base_meta_dataset(base_client: ClientCore, base_dataset: DatasetCore) -> Dat return Dataset( client=base_client, element=base_dataset, meta_params={"team_slug": "test_team"} ) + + +@fixture +def base_meta_item(base_client: ClientCore, base_item: ItemCore) -> Item: + return Item(client=base_client, element=base_item) diff --git a/darwin/future/tests/meta/objects/test_itemmeta.py b/darwin/future/tests/meta/objects/test_itemmeta.py new file mode 100644 index 000000000..ada047eb2 --- /dev/null +++ b/darwin/future/tests/meta/objects/test_itemmeta.py @@ -0,0 +1,46 @@ +from uuid import UUID + +import responses +from responses import json_params_matcher + +from darwin.future.data_objects.item import ItemLayout, ItemSlot +from darwin.future.meta.objects.item import Item +from darwin.future.tests.meta.objects.fixtures import * + + +def test_item_properties(item: Item) -> None: + assert isinstance(item.name, str) + assert isinstance(item.id, UUID) + assert isinstance(item.slots, list) + for slot in item.slots: + assert isinstance(slot, ItemSlot) + assert isinstance(item.path, str) + assert isinstance(item.dataset_id, int) + assert isinstance(item.processing_status, str) + assert isinstance(item.archived, (bool, type(None))) + assert isinstance(item.priority, (int, type(None))) + assert isinstance(item.tags, (list, dict, type(None))) + assert isinstance(item.layout, (ItemLayout, type(None))) + + +def test_delete(item: Item) -> None: + with responses.RequestsMock() as rsps: + team_slug = item.meta_params["team_slug"] + dataset_id = item.meta_params["dataset_id"] + rsps.add( + rsps.DELETE, + item.client.config.api_endpoint + f"v2/teams/{team_slug}/items", + status=200, + match=[ + json_params_matcher( + { + "filters": { + "item_ids": [str(item.id)], + "dataset_ids": [dataset_id], + } + } + ) + ], + json={}, + ) + item.delete() diff --git a/darwin/future/tests/meta/objects/test_stagemeta.py b/darwin/future/tests/meta/objects/test_stagemeta.py index f83b81ede..68c025d34 100644 --- a/darwin/future/tests/meta/objects/test_stagemeta.py +++ b/darwin/future/tests/meta/objects/test_stagemeta.py @@ -8,6 +8,8 @@ from darwin.future.data_objects.workflow import WFEdgeCore, WFStageCore, WFTypeCore from darwin.future.meta.client import Client from darwin.future.meta.objects.stage import Stage +from darwin.future.meta.queries.item import ItemQuery +from darwin.future.meta.queries.item_id import ItemIDQuery from darwin.future.tests.core.fixtures import * from darwin.future.tests.core.items.fixtures import * from darwin.future.tests.meta.fixtures import * @@ -164,3 +166,12 @@ def test_stage_str_method(stage_meta: Stage) -> None: def test_stage_repr_method(stage_meta: Stage) -> None: assert repr(stage_meta) == str(stage_meta) + + +def test_has_item_properties(stage_meta: Stage) -> None: + assert isinstance(stage_meta.items, ItemQuery) + assert isinstance(stage_meta.item_ids, ItemIDQuery) + assert isinstance(stage_meta.id, UUID) + assert isinstance(stage_meta.name, str) + assert isinstance(stage_meta.type, str) + assert isinstance(stage_meta.edges, list) diff --git a/darwin/future/tests/meta/queries/test_item.py b/darwin/future/tests/meta/queries/test_item.py new file mode 100644 index 000000000..3824a8788 --- /dev/null +++ b/darwin/future/tests/meta/queries/test_item.py @@ -0,0 +1,72 @@ +from typing import List + +import pytest +import responses +from responses.matchers import json_params_matcher, query_param_matcher + +from darwin.future.core.client import ClientCore +from darwin.future.meta.objects.item import Item +from darwin.future.meta.queries.item import ItemQuery +from darwin.future.tests.core.fixtures import * +from darwin.future.tests.meta.fixtures import * +from darwin.future.tests.meta.objects.fixtures import * + + +@pytest.fixture +def item_query(base_client: ClientCore) -> ItemQuery: + return ItemQuery( + client=base_client, meta_params={"team_slug": "test", "dataset_id": 1} + ) + + +def test_item_query_collect(item_query: ItemQuery, items_json: List[dict]) -> None: + with responses.RequestsMock() as rsps: + rsps.add( + rsps.GET, + item_query.client.config.api_endpoint + "v2/teams/test/items", + match=[ + query_param_matcher( + {"page[offset]": "0", "page[size]": "500", "dataset_ids": "1"} + ) + ], + json={"items": items_json, "errors": []}, + ) + items = item_query.collect_all() + assert len(items) == 5 + for i in range(5): + assert items[i].name == f"item_{i}" + + +def test_delete( + item_query: ItemQuery, items_json: List[dict], items: List[Item] +) -> None: + with responses.RequestsMock() as rsps: + rsps.add( + rsps.GET, + item_query.client.config.api_endpoint + "v2/teams/test/items", + match=[ + query_param_matcher( + {"page[offset]": "0", "page[size]": "500", "dataset_ids": "1"} + ) + ], + json={"items": items_json, "errors": []}, + ) + team_slug = items[0].meta_params["team_slug"] + dataset_id = items[0].meta_params["dataset_id"] + rsps.add( + rsps.DELETE, + items[0].client.config.api_endpoint + f"v2/teams/{team_slug}/items", + status=200, + match=[ + json_params_matcher( + { + "filters": { + "item_ids": [str(item.id) for item in items], + "dataset_ids": [dataset_id], + } + } + ) + ], + json={}, + ) + item_query.delete() diff --git a/darwin/future/tests/meta/queries/test_team_id.py b/darwin/future/tests/meta/queries/test_item_id.py similarity index 100% rename from darwin/future/tests/meta/queries/test_team_id.py rename to darwin/future/tests/meta/queries/test_item_id.py