From 8191dd08b9b09f8230bc0df41edcf3c728fe69d6 Mon Sep 17 00:00:00 2001 From: Rafal Slota Date: Tue, 9 Jan 2024 13:01:37 +0100 Subject: [PATCH 1/5] Add assign batch action SDK --- darwin/future/core/client.py | 1 + darwin/future/core/items/assign_items.py | 44 +++++++++ darwin/future/meta/objects/item.py | 26 +++++ darwin/future/meta/queries/item.py | 33 +++++++ .../tests/core/items/test_assign_items.py | 99 +++++++++++++++++++ .../tests/meta/objects/test_itemmeta.py | 27 +++++ 6 files changed, 230 insertions(+) create mode 100644 darwin/future/core/items/assign_items.py create mode 100644 darwin/future/tests/core/items/test_assign_items.py diff --git a/darwin/future/core/client.py b/darwin/future/core/client.py index 6ec5dbb59..be17dcec1 100644 --- a/darwin/future/core/client.py +++ b/darwin/future/core/client.py @@ -68,6 +68,7 @@ def remove_global(cls, values: dict) -> dict: @model_validator(mode="before") @classmethod def validate_defaults(cls, values: Any) -> Any: + print(values) if values["api_key"]: return values assert values["default_team"] in values["teams"] diff --git a/darwin/future/core/items/assign_items.py b/darwin/future/core/items/assign_items.py new file mode 100644 index 000000000..b2e109a12 --- /dev/null +++ b/darwin/future/core/items/assign_items.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from darwin.future.core.client import ClientCore +from darwin.future.core.types.common import JSONType +from darwin.future.data_objects.typing import UnknownType + + +def assign_items( + client: ClientCore, + team_slug: str, + dataset_ids: int | list[int], + assignee_id: int, + workflow_id: str, + filters: dict[str, UnknownType], +) -> JSONType: + """ + Assign a user to all items matched by filters. + + Args: + client (ClientCore): The Darwin Core client. + team_slug (str): The team slug. + dataset_ids (int | list[int]): The dataset ids. + assignee_id (int): The user id to assign. + workflow_id (str): The workflow id that selected items have to belong to. + filters Dict[str, UnknownType]: The parameters of the filter. + + Returns: + JSONType: The response data. + """ + assert ( + filters + ), "No parameters provided, please provide at least one non-dataset id filter" + payload = { + "filters": { + "dataset_ids": dataset_ids + if isinstance(dataset_ids, list) + else [dataset_ids], + **filters, + }, + "assignee_id": assignee_id, + "workflow_id": workflow_id, + } + + return client.post(f"/v2/teams/{team_slug}/items/assign", data=payload) diff --git a/darwin/future/meta/objects/item.py b/darwin/future/meta/objects/item.py index f219d8aee..3c195c8b4 100644 --- a/darwin/future/meta/objects/item.py +++ b/darwin/future/meta/objects/item.py @@ -4,6 +4,7 @@ from uuid import UUID from darwin.future.core.items.archive_items import archive_list_of_items +from darwin.future.core.items.assign_items import assign_items from darwin.future.core.items.delete_items import delete_list_of_items from darwin.future.core.items.move_items_to_folder import move_list_of_items_to_folder from darwin.future.core.items.restore_items import restore_list_of_items @@ -129,6 +130,31 @@ def set_layout(self, layout: ItemLayout) -> None: filters = {"item_ids": [str(self.id)]} set_item_layout(self.client, team_slug, dataset_id, layout, filters) + def assign(self, assignee_id: int, workflow_id: str | None = None) -> None: + if not assignee_id: + raise ValueError("Must specify assignee to assign items to") + if not workflow_id: + # if workflow_id is not specified, get it from the meta_params + # this will be present in the case of a workflow object + if "workflow_id" in self.meta_params: + workflow_id = str(self.meta_params["workflow_id"]) + else: + raise ValueError("Must specify workflow_id to set items to") + assert isinstance(workflow_id, str) + 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)]} + assign_items( + self.client, team_slug, dataset_id, assignee_id, workflow_id, filters + ) + def tag(self, tag_id: int) -> None: team_slug, dataset_id = ( self.meta_params["team_slug"], diff --git a/darwin/future/meta/queries/item.py b/darwin/future/meta/queries/item.py index 58e79c0bc..e60ccf45e 100644 --- a/darwin/future/meta/queries/item.py +++ b/darwin/future/meta/queries/item.py @@ -4,6 +4,7 @@ from typing import Dict, Protocol from darwin.future.core.items.archive_items import archive_list_of_items +from darwin.future.core.items.assign_items import assign_items 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.items.move_items_to_folder import move_list_of_items_to_folder @@ -249,6 +250,38 @@ def untag(self, tag_id: int) -> None: filters = {"item_ids": [str(item) for item in ids]} untag_items(self.client, team_slug, dataset_ids, tag_id, filters) + def assign(self, assignee_id: int, workflow_id: str | None = None) -> None: + if not assignee_id: + raise ValueError("Must specify assignee to assign items to") + + 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") + if not workflow_id: + # if workflow_id is not specified, get it from the meta_params + # this will be present in the case of a workflow object + if "workflow_id" in self.meta_params: + workflow_id = str(self.meta_params["workflow_id"]) + else: + raise ValueError("Must specify workflow_id to set items to") + assert isinstance(workflow_id, str) + + 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]} + + assign(self.client, team_slug, dataset_ids, assignee_id, workflow_id, filters) + def set_stage( self, stage_or_stage_id: hasStage | str, workflow_id: str | None = None ) -> None: diff --git a/darwin/future/tests/core/items/test_assign_items.py b/darwin/future/tests/core/items/test_assign_items.py new file mode 100644 index 000000000..bc9cbe08f --- /dev/null +++ b/darwin/future/tests/core/items/test_assign_items.py @@ -0,0 +1,99 @@ +import pytest +import responses + +from darwin.future.core.client import ClientCore +from darwin.future.core.items.assign_items import assign_items +from darwin.future.exceptions import BadRequest +from darwin.future.tests.core.fixtures import * + + +@responses.activate +def test_assign_items(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + item_ids = [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002", + ] + filters = {"item_ids": item_ids} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"created_commands": 1}, + status=200, + ) + + response = assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) + + assert response == {"created_commands": 1} + + +@responses.activate +def test_assign_items_filters_error(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + filters = {} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"created_commands": 1}, + status=200, + ) + + with pytest.raises(AssertionError) as excinfo: + assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) + (msg,) = excinfo.value.args + assert ( + msg + == "No parameters provided, please provide at least one non-dataset id filter" + ) + + +@responses.activate +def test_assign_items_bad_request_error(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + item_ids = [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002", + ] + filters = {"item_ids": item_ids} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"error": "Bad Request"}, + status=400, + ) + + with pytest.raises(BadRequest): + assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) diff --git a/darwin/future/tests/meta/objects/test_itemmeta.py b/darwin/future/tests/meta/objects/test_itemmeta.py index 74f70ebc6..946b13c16 100644 --- a/darwin/future/tests/meta/objects/test_itemmeta.py +++ b/darwin/future/tests/meta/objects/test_itemmeta.py @@ -362,3 +362,30 @@ def test_set_stage(item: Item) -> None: json={}, ) item.set_stage(stage_id, workflow_id) + + +def test_assign(item: Item) -> None: + with responses.RequestsMock() as rsps: + team_slug = item.meta_params["team_slug"] + dataset_id = item.meta_params["dataset_id"] + assignee_id = 123456 + workflow_id = "123456" + rsps.add( + rsps.POST, + item.client.config.api_endpoint + f"v2/teams/{team_slug}/items/assign", + status=200, + match=[ + json_params_matcher( + { + "filters": { + "item_ids": [str(item.id)], + "dataset_ids": [dataset_id], + }, + "assignee_id": assignee_id, + "workflow_id": workflow_id, + } + ) + ], + json={}, + ) + item.assign(assignee_id, workflow_id) From d07a1cc3932546f497a9e7891d74e634bbe7dfe6 Mon Sep 17 00:00:00 2001 From: Rafal Slota Date: Tue, 9 Jan 2024 13:04:11 +0100 Subject: [PATCH 2/5] fixup! Add assign batch action SDK --- darwin/future/core/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/darwin/future/core/client.py b/darwin/future/core/client.py index be17dcec1..6ec5dbb59 100644 --- a/darwin/future/core/client.py +++ b/darwin/future/core/client.py @@ -68,7 +68,6 @@ def remove_global(cls, values: dict) -> dict: @model_validator(mode="before") @classmethod def validate_defaults(cls, values: Any) -> Any: - print(values) if values["api_key"]: return values assert values["default_team"] in values["teams"] From 8ab433cdc221274b85c21b9dc041f0c618c73dab Mon Sep 17 00:00:00 2001 From: Rafal Slota Date: Tue, 9 Jan 2024 14:01:07 +0100 Subject: [PATCH 3/5] fixup! fixup! Add assign batch action SDK --- darwin/future/core/client.py | 12 ++++-------- darwin/future/core/datasets/list_datasets.py | 1 + darwin/future/meta/queries/item.py | 4 +++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/darwin/future/core/client.py b/darwin/future/core/client.py index 6ec5dbb59..eebf92b98 100644 --- a/darwin/future/core/client.py +++ b/darwin/future/core/client.py @@ -57,23 +57,19 @@ def validate_base_url(cls, v: str) -> str: @model_validator(mode="before") @classmethod - def remove_global(cls, values: dict) -> dict: + def validate_defaults(cls, values: Any) -> Any: if "global" not in values: return values global_conf = values["global"] del values["global"] values.update(global_conf) - return values - @model_validator(mode="before") - @classmethod - def validate_defaults(cls, values: Any) -> Any: - if values["api_key"]: + if "api_key" in values and values["api_key"]: return values assert values["default_team"] in values["teams"] team = values["default_team"] - values["api_key"] = values["teams"][team].api_key - values["datasets_dir"] = values["teams"][team].datasets_dir + values["api_key"] = values["teams"][team]["api_key"] + values["datasets_dir"] = values["teams"][team]["datasets_dir"] return values @staticmethod diff --git a/darwin/future/core/datasets/list_datasets.py b/darwin/future/core/datasets/list_datasets.py index a0d6481ee..755670feb 100644 --- a/darwin/future/core/datasets/list_datasets.py +++ b/darwin/future/core/datasets/list_datasets.py @@ -32,6 +32,7 @@ def list_datasets( response = api_client.get("/datasets") try: for item in response: + print(item) assert isinstance(item, dict) datasets.append(DatasetCore.model_validate(item)) except ValidationError as e: diff --git a/darwin/future/meta/queries/item.py b/darwin/future/meta/queries/item.py index e60ccf45e..a8b2edf78 100644 --- a/darwin/future/meta/queries/item.py +++ b/darwin/future/meta/queries/item.py @@ -280,7 +280,9 @@ def assign(self, assignee_id: int, workflow_id: str | None = None) -> None: ids = [item.id for item in self] filters = {"item_ids": [str(item) for item in ids]} - assign(self.client, team_slug, dataset_ids, assignee_id, workflow_id, filters) + assign_items( + self.client, team_slug, dataset_ids, assignee_id, workflow_id, filters + ) def set_stage( self, stage_or_stage_id: hasStage | str, workflow_id: str | None = None From 16448a92a7ceb08fc284e52a85ebff3a57bc7b42 Mon Sep 17 00:00:00 2001 From: Rafal Slota Date: Wed, 10 Jan 2024 18:02:36 +0100 Subject: [PATCH 4/5] fixup! Merge remote-tracking branch 'origin/master' into add-assign-action --- darwin/future/core/datasets/list_datasets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/darwin/future/core/datasets/list_datasets.py b/darwin/future/core/datasets/list_datasets.py index 755670feb..a0d6481ee 100644 --- a/darwin/future/core/datasets/list_datasets.py +++ b/darwin/future/core/datasets/list_datasets.py @@ -32,7 +32,6 @@ def list_datasets( response = api_client.get("/datasets") try: for item in response: - print(item) assert isinstance(item, dict) datasets.append(DatasetCore.model_validate(item)) except ValidationError as e: From d68c213613e6dd77c22333313d50ae3ee280411c Mon Sep 17 00:00:00 2001 From: Rafal Slota Date: Thu, 11 Jan 2024 15:33:18 +0100 Subject: [PATCH 5/5] fixup! fixup! Merge remote-tracking branch 'origin/master' into add-assign-action --- darwin/future/core/items/assign_items.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/darwin/future/core/items/assign_items.py b/darwin/future/core/items/assign_items.py index b2e109a12..20520a5ab 100644 --- a/darwin/future/core/items/assign_items.py +++ b/darwin/future/core/items/assign_items.py @@ -1,8 +1,7 @@ from __future__ import annotations from darwin.future.core.client import ClientCore -from darwin.future.core.types.common import JSONType -from darwin.future.data_objects.typing import UnknownType +from darwin.future.core.types.common import JSONDict, JSONType def assign_items( @@ -11,7 +10,7 @@ def assign_items( dataset_ids: int | list[int], assignee_id: int, workflow_id: str, - filters: dict[str, UnknownType], + filters: JSONDict, ) -> JSONType: """ Assign a user to all items matched by filters.