diff --git a/README.rst b/README.rst index f2c59f7..2d17a2c 100644 --- a/README.rst +++ b/README.rst @@ -310,6 +310,60 @@ __ https://docs.scale.com/reference/set-metadata new_metadata = {'myKey': 'myValue'} task.set_metadata(new_metadata) +Set A Task's Tags +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Set a given task's ``tags``. This will replace all existing tags on a task. Check out `Scale's API documentation`__ for more information. + +__ https://docs.scale.com/reference/setting-tags + +.. code-block :: python + + # set a list of tags on a task by specifying task id + new_tags = ["tag1", "tag2", "tag3"] + task = client.set_task_tags('30553edd0b6a93f8f05f0fee', new_tags) + + # set a list of tags on a task object + task = client.get_task('30553edd0b6a93f8f05f0fee') + new_tags = ["tag1", "tag2", "tag3"] + task.set_tags(new_tags) + +Add Tags to A Task +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add ``tags`` to a given task. Check out `Scale's API documentation`__ for more information. + +__ https://docs.scale.com/reference/adding-tags + +.. code-block :: python + + # add a list of tags on a task by specifying task id + tags_to_add = ["tag4", "tag5"] + task = client.add_task_tags('30553edd0b6a93f8f05f0fee', tags_to_add) + + # add a list of tags on a task object + task = client.get_task('30553edd0b6a93f8f05f0fee') + tags_to_add = ["tag4", "tag5"] + task.add_tags(tags_to_add) + +Delete Tags from A Task +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Delete ``tags`` from a given task. Check out `Scale's API documentation`__ for more information. + +__ https://docs.scale.com/reference/deleting-tags + +.. code-block :: python + + # delete a list of tags on a task by specifying task id + tags_to_delete = ["tag1", "tag2"] + task = client.delete_task_tags('30553edd0b6a93f8f05f0fee', tags_to_delete) + + # delete a list of tags on a task object + task = client.get_task('30553edd0b6a93f8f05f0fee') + tags_to_delete = ["tag1", "tag2"] + task.delete_tags(tags_to_delete) + Batches _______ diff --git a/scaleapi/__init__.py b/scaleapi/__init__.py index 9a4c9b4..7e4368e 100644 --- a/scaleapi/__init__.py +++ b/scaleapi/__init__.py @@ -50,7 +50,9 @@ class ScaleClient: def __init__(self, api_key, source=None, api_instance_url=None): self.api = Api( - api_key, user_agent_extension=source, api_instance_url=api_instance_url + api_key, + user_agent_extension=source, + api_instance_url=api_instance_url, ) def get_task(self, task_id: str) -> Task: @@ -148,6 +150,49 @@ def set_task_metadata(self, task_id: str, metadata: Dict) -> Task: endpoint = f"task/{task_id}/setMetadata" return Task(self.api.post_request(endpoint, body=metadata), self) + def set_task_tags(self, task_id: str, tags: List[str]) -> Task: + """Sets completely new list of tags to a task and returns the + associated task. + Args: + task_id (str): + Task id + tags (List[str]): + List of new tags to set + Returns: + Task + """ + endpoint = f"task/{task_id}/tags" + return Task(self.api.post_request(endpoint, body=tags), self) + + def add_task_tags(self, task_id: str, tags: List[str]) -> Task: + """Adds a list of tags to a task and returns the + associated task. + Args: + task_id (str): + Task id + tags (List[str]): + List of tags to add. + Already present tags will be ignored. + Returns: + Task + """ + endpoint = f"task/{task_id}/tags" + return Task(self.api.put_request(endpoint, body=tags), self) + + def delete_task_tags(self, task_id: str, tags: List[str]) -> Task: + """Deletes a list of tags from a task and returns the + associated task. + Args: + task_id (str): + Task id + tags (List[str]): + List of tags to delete. Nonpresent tags will be ignored. + Returns: + Task + """ + endpoint = f"task/{task_id}/tags" + return Task(self.api.delete_request(endpoint, body=tags), self) + def tasks(self, **kwargs) -> Tasklist: """Returns a list of your tasks. Returns up to 100 at a time, to get more, use the @@ -949,7 +994,11 @@ def list_teammates(self) -> List[Teammate]: teammate_list = self.api.get_request(endpoint) return [Teammate(teammate, self) for teammate in teammate_list] - def invite_teammates(self, emails: List[str], role: TeammateRole) -> List[Teammate]: + def invite_teammates( + self, + emails: List[str], + role: TeammateRole, + ) -> List[Teammate]: """Invites a list of emails to your team. Args: @@ -989,7 +1038,9 @@ def update_teammates_role( teammate_list = self.api.post_request(endpoint, payload) return [Teammate(teammate, self) for teammate in teammate_list] - def list_studio_assignments(self) -> Dict[str, StudioLabelerAssignment]: + def list_studio_assignments( + self, + ) -> Dict[str, StudioLabelerAssignment]: """Returns a dictionary where the keys are user emails and the values are projects the user is assigned to. @@ -999,8 +1050,12 @@ def list_studio_assignments(self) -> Dict[str, StudioLabelerAssignment]: endpoint = "studio/assignments" raw_assignments = self.api.get_request(endpoint) assignments = {} - for (email, assigned_projects) in raw_assignments.items(): - assignments[email] = StudioLabelerAssignment(assigned_projects, email, self) + for email, assigned_projects in raw_assignments.items(): + assignments[email] = StudioLabelerAssignment( + assigned_projects, + email, + self, + ) return assignments def add_studio_assignments( @@ -1023,8 +1078,12 @@ def add_studio_assignments( } raw_assignments = self.api.post_request(endpoint, payload) assignments = {} - for (email, assigned_projects) in raw_assignments.items(): - assignments[email] = StudioLabelerAssignment(assigned_projects, email, self) + for email, assigned_projects in raw_assignments.items(): + assignments[email] = StudioLabelerAssignment( + assigned_projects, + email, + self, + ) return assignments def remove_studio_assignments( @@ -1047,8 +1106,12 @@ def remove_studio_assignments( } raw_assignments = self.api.post_request(endpoint, payload) assignments = {} - for (email, assigned_projects) in raw_assignments.items(): - assignments[email] = StudioLabelerAssignment(assigned_projects, email, self) + for email, assigned_projects in raw_assignments.items(): + assignments[email] = StudioLabelerAssignment( + assigned_projects, + email, + self, + ) return assignments def list_project_groups(self, project: str) -> List[StudioProjectGroup]: @@ -1083,7 +1146,10 @@ def create_project_group( """ endpoint = f"studio/projects/{Api.quote_string(project)}/groups" payload = {"emails": emails, "name": project_group} - return StudioProjectGroup(self.api.post_request(endpoint, payload), self) + return StudioProjectGroup( + self.api.post_request(endpoint, payload), + self, + ) def update_project_group( self, @@ -1110,8 +1176,14 @@ def update_project_group( f"studio/projects/{Api.quote_string(project)}" f"/groups/{Api.quote_string(project_group)}" ) - payload = {"add_emails": add_emails, "remove_emails": remove_emails} - return StudioProjectGroup(self.api.put_request(endpoint, payload), self) + payload = { + "add_emails": add_emails, + "remove_emails": remove_emails, + } + return StudioProjectGroup( + self.api.put_request(endpoint, payload), + self, + ) def list_studio_batches(self) -> List[StudioBatch]: """Returns a list with all pending studio batches, @@ -1152,7 +1224,12 @@ def set_studio_batches_priorities( Returns: List[StudioBatch] """ - batches_names = list(map(lambda batch_name: {"name": batch_name}, batch_names)) + batches_names = list( + map( + lambda batch_name: {"name": batch_name}, + batch_names, + ) + ) endpoint = "studio/batches/set_priorities" payload = {"batches": batches_names} batches = self.api.post_request(endpoint, payload) diff --git a/scaleapi/_version.py b/scaleapi/_version.py index a5d9fc8..1860cc4 100644 --- a/scaleapi/_version.py +++ b/scaleapi/_version.py @@ -1,2 +1,2 @@ -__version__ = "2.12.0" +__version__ = "2.13.0" __package_name__ = "scaleapi" diff --git a/scaleapi/api.py b/scaleapi/api.py index 7c079fc..a3c4e0d 100644 --- a/scaleapi/api.py +++ b/scaleapi/api.py @@ -46,7 +46,6 @@ def _http_request( files=None, data=None, ) -> Response: - https = requests.Session() retry_strategy = Retry( total=HTTP_TOTAL_RETRIES, @@ -80,7 +79,6 @@ def _http_request( @staticmethod def _raise_on_respose(res: Response): - try: message = res.json().get("error", res.text) except ValueError: @@ -157,10 +155,15 @@ def post_request(self, endpoint, body=None, files=None, data=None): data=data, ) - def delete_request(self, endpoint, params=None): + def delete_request(self, endpoint, params=None, body=None): """Generic DELETE Request Wrapper""" return self._api_request( - "DELETE", endpoint, headers=self._headers, auth=self._auth, params=params + "DELETE", + endpoint, + headers=self._headers, + auth=self._auth, + params=params, + body=body, ) def put_request(self, endpoint, body=None, params=None): diff --git a/scaleapi/tasks.py b/scaleapi/tasks.py index 8efa1b6..31b0827 100644 --- a/scaleapi/tasks.py +++ b/scaleapi/tasks.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import List class TaskType(Enum): @@ -103,3 +104,15 @@ def clear_unique_id(self): def set_metadata(self, metadata: dict): """Sets the metadata of a task""" self._client.set_task_metadata(self.id, metadata) + + def set_tags(self, tags: List[str]): + """Sets tags of a task""" + self._client.set_task_tags(self.id, tags) + + def add_tags(self, tags: List[str]): + """Adds tags for a task""" + self._client.add_task_tags(self.id, tags) + + def delete_tags(self, tags: List[str]): + """Sets tags for a task""" + self._client.delete_task_tags(self.id, tags) diff --git a/tests/test_client.py b/tests/test_client.py index 65ce105..63d4c67 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -47,7 +47,6 @@ def test_invalidkey_fail(): def make_a_task(unique_id: str = None, batch: str = None): - args = { "callback_url": "http://www.example.com/callback", "instruction": "Draw a box around each baby cow and big cow.", @@ -112,6 +111,37 @@ def test_task_set_metadata(): assert task.metadata == new_metadata +def test_set_task_tags(): + unique_id = str(uuid.uuid4()) + task = make_a_task(unique_id) + assert not hasattr(task, "tags") + new_tags = ["tag1", "tag2", "tag3"] + task.set_tags(new_tags) + task.refresh() + assert task.tags == new_tags + + +def test_add_task_tags(): + unique_id = str(uuid.uuid4()) + task = make_a_task(unique_id) + assert not hasattr(task, "tags") + new_tags = ["tag1", "tag2", "tag3"] + task.add_tags(new_tags) + task.refresh() + assert task.tags == new_tags + + +def test_delete_task_tags(): + unique_id = str(uuid.uuid4()) + task = make_a_task(unique_id) + assert not hasattr(task, "tags") + new_tags = ["tag1", "tag2", "tag3"] + task.add_tags(new_tags) + task.delete_tags(["tag1", "tag2"]) + task.refresh() + assert task.tags == ["tag3"] + + def test_categorize_ok(): client.create_task( TaskType.Categorization, @@ -137,11 +167,15 @@ def test_transcription_ok(): client.create_task( TaskType.Transcription, callback_url="http://www.example.com/callback", - instruction="Transcribe the given fields. Then for each news item on the page, " + instruction="Transcribe the given fields. " + "Then for each news item on the page, " "transcribe the information for the row.", attachment_type="website", attachment="http://www.google.com/", - fields={"title": "Title of Webpage", "top_result": "Title of the top result"}, + fields={ + "title": "Title of Webpage", + "top_result": "Title of the top result", + }, repeatable_fields={ "username": "Username of submitter", "comment_count": "Number of comments", @@ -367,7 +401,10 @@ def test_get_tasks(): for _ in range(3): tasks.append(make_a_task(batch=batch.name)) task_ids = {task.id for task in tasks} - for task in client.get_tasks(project_name=TEST_PROJECT_NAME, batch_name=batch.name): + for task in client.get_tasks( + project_name=TEST_PROJECT_NAME, + batch_name=batch.name, + ): assert task.id in task_ids