From 3b5ad31be2ce70fefac0cc1da22565407b85d7dc Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Sun, 7 Mar 2021 20:43:21 -0500 Subject: [PATCH 1/4] basic working --- labelbox/schema/project.py | 34 +++++++++++++++++++++++++++++++ tests/integration/test_project.py | 7 +++++++ 2 files changed, 41 insertions(+) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index b8afa1ec9..c3594def7 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -173,6 +173,40 @@ def export_labels(self, timeout_seconds=60): self.uid) time.sleep(sleep_time) + def attach_instructions(self, instructions_file: str): + """ + - Upload a set of instructions for labeling + + """ + if self.setup_complete is None: + raise Exception("Cannot attach instructions to a project that has not been setup.") + + assert self.setup_complete is not None #Change this to an actual exception... + #Assert that the editor type is the editor.. + instructions_url = self.client.upload_file(instructions_file) + lfo = list(self.labeling_frontend_options())[-1] + customization_options = json.loads(lfo.customization_options) + customization_options['projectInstructions'] = instructions_url + option_id = lfo.uid + frontendId = self.labeling_frontend().uid + args = {"frontendId": frontendId, + "name": "Editor", #Probably only compatible with the regular editor.. + "description": "Video, image, and text annotation", + "optionsId": option_id, + "customizationOptions": json.dumps(customization_options) + } + return self.client.execute(""" + mutation UpdateFrontendWithExistingOptions($frontendId: ID!, $optionsId: ID!, $name: String!, $description: String!, $customizationOptions: String!) { + updateLabelingFrontend(where: {id: $frontendId}, data: {name: $name, description: $description}) { + id + } + updateLabelingFrontendOptions(where: {id: $optionsId}, data: {customizationOptions: $customizationOptions}) { + id + } + } + """, args) + + def labeler_performance(self): """ Returns the labeler performances for this Project. diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index aadead361..20abb1d45 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -2,6 +2,7 @@ from labelbox import Project from labelbox.exceptions import InvalidQueryError +import json def test_project(client, rand_gen): @@ -64,3 +65,9 @@ def test_extend_reservations(project): assert project.extend_reservations("ReviewQueue") == 0 with pytest.raises(InvalidQueryError): project.extend_reservations("InvalidQueueType") + + + +def test_attach_instructions(setup_project): + setup_project.attach_labeling_instructions("http://www.africau.edu/images/default/sample.pdf") + assert json.loads(list(setup_project.labeling_frontend_options())[-1].customization_options).get('projectInstructions') is not None From b7f545983ddfa2ca52d1cfe62bd2ac9f5c979e80 Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Mon, 8 Mar 2021 11:43:03 -0500 Subject: [PATCH 2/4] instruction upload works and is tested --- labelbox/schema/project.py | 68 ++++++++++++++++++++++--------- tests/integration/test_project.py | 26 +++++++++--- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index c3594def7..d70e325a5 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -175,37 +175,65 @@ def export_labels(self, timeout_seconds=60): def attach_instructions(self, instructions_file: str): """ - - Upload a set of instructions for labeling + * Uploads instructions to the UI. Running more than once will replace the instructions + + Args: + instructions_file (str): Path to a local file. + * Must be either a pdf, text, or html file. + Raises ValueError: + * project must be setup + * instructions file must be one of ".text", ".txt", ".pdf", ".html" """ + if self.setup_complete is None: - raise Exception("Cannot attach instructions to a project that has not been setup.") + raise ValueError( + "Cannot attach instructions to a project that has not been setup." + ) + + frontend = self.labeling_frontend() + frontendId = frontend.uid + + if frontend.name != "Editor": + logger.warn( + f"This function has only been tested to work with the Editor front end. Found {frontend.name}" + ) + + supported_instruction_formats = (".text", ".txt", ".pdf", ".html") + if not instructions_file.endswith(supported_instruction_formats): + raise ValueError( + f"instructions_file must end with one of {supported_instruction_formats}. Found {instructions_file}" + ) - assert self.setup_complete is not None #Change this to an actual exception... - #Assert that the editor type is the editor.. - instructions_url = self.client.upload_file(instructions_file) lfo = list(self.labeling_frontend_options())[-1] + instructions_url = self.client.upload_file(instructions_file) customization_options = json.loads(lfo.customization_options) customization_options['projectInstructions'] = instructions_url option_id = lfo.uid - frontendId = self.labeling_frontend().uid - args = {"frontendId": frontendId, - "name": "Editor", #Probably only compatible with the regular editor.. + + self.client.execute( + """mutation UpdateFrontendWithExistingOptionsPyApi ( + $frontendId: ID!, + $optionsId: ID!, + $name: String!, + $description: String!, + $customizationOptions: String! + ) { + updateLabelingFrontend( + where: {id: $frontendId}, + data: {name: $name, description: $description} + ) {id} + updateLabelingFrontendOptions( + where: {id: $optionsId}, + data: {customizationOptions: $customizationOptions} + ) {id} + }""", { + "frontendId": frontendId, + "name": frontend.name, "description": "Video, image, and text annotation", "optionsId": option_id, "customizationOptions": json.dumps(customization_options) - } - return self.client.execute(""" - mutation UpdateFrontendWithExistingOptions($frontendId: ID!, $optionsId: ID!, $name: String!, $description: String!, $customizationOptions: String!) { - updateLabelingFrontend(where: {id: $frontendId}, data: {name: $name, description: $description}) { - id - } - updateLabelingFrontendOptions(where: {id: $optionsId}, data: {customizationOptions: $customizationOptions}) { - id - } - } - """, args) - + }) def labeler_performance(self): """ Returns the labeler performances for this Project. diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 20abb1d45..1ac8f8841 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,8 +1,8 @@ import pytest +import json -from labelbox import Project +from labelbox import Project, LabelingFrontend from labelbox.exceptions import InvalidQueryError -import json def test_project(client, rand_gen): @@ -67,7 +67,23 @@ def test_extend_reservations(project): project.extend_reservations("InvalidQueueType") +def test_attach_instructions(client, project): + with pytest.raises(ValueError): + project.attach_instructions('/tmp/instructions.txt') + + editor = list( + client.get_labeling_frontends( + where=LabelingFrontend.name == "editor"))[0] + empty_ontology = {"tools": [], "classifications": []} + project.setup(editor, empty_ontology) + + with open('/tmp/instructions.txt', 'w') as file: + file.write("some instructions...") + + project.attach_instructions('/tmp/instructions.txt') + assert json.loads( + list(project.labeling_frontend_options()) + [-1].customization_options).get('projectInstructions') is not None -def test_attach_instructions(setup_project): - setup_project.attach_labeling_instructions("http://www.africau.edu/images/default/sample.pdf") - assert json.loads(list(setup_project.labeling_frontend_options())[-1].customization_options).get('projectInstructions') is not None + with pytest.raises(ValueError): + project.attach_instructions('/tmp/file.invalid_file_extension') From f292247b77124e10e9522f8bd15757ace4ad0418 Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Tue, 9 Mar 2021 13:49:29 -0500 Subject: [PATCH 3/4] format --- labelbox/schema/project.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index d70e325a5..a9b2fbab4 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -181,9 +181,10 @@ def attach_instructions(self, instructions_file: str): instructions_file (str): Path to a local file. * Must be either a pdf, text, or html file. - Raises ValueError: - * project must be setup - * instructions file must be one of ".text", ".txt", ".pdf", ".html" + Raises: + ValueError: + * project must be setup + * instructions file must end with one of ".text", ".txt", ".pdf", ".html" """ if self.setup_complete is None: @@ -229,9 +230,9 @@ def attach_instructions(self, instructions_file: str): ) {id} }""", { "frontendId": frontendId, + "optionsId": option_id, "name": frontend.name, "description": "Video, image, and text annotation", - "optionsId": option_id, "customizationOptions": json.dumps(customization_options) }) From 466c5a50211374e9ce58b96364bf2569dce772d2 Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Wed, 10 Mar 2021 09:31:25 -0500 Subject: [PATCH 4/4] requested changes --- labelbox/schema/project.py | 8 ++++---- tests/integration/test_project.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index a9b2fbab4..5715d0c57 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -173,7 +173,7 @@ def export_labels(self, timeout_seconds=60): self.uid) time.sleep(sleep_time) - def attach_instructions(self, instructions_file: str): + def upsert_instructions(self, instructions_file: str): """ * Uploads instructions to the UI. Running more than once will replace the instructions @@ -189,7 +189,7 @@ def attach_instructions(self, instructions_file: str): if self.setup_complete is None: raise ValueError( - "Cannot attach instructions to a project that has not been setup." + "Cannot attach instructions to a project that has not been set up." ) frontend = self.labeling_frontend() @@ -197,8 +197,8 @@ def attach_instructions(self, instructions_file: str): if frontend.name != "Editor": logger.warn( - f"This function has only been tested to work with the Editor front end. Found {frontend.name}" - ) + f"This function has only been tested to work with the Editor front end. Found %s", + frontend.name) supported_instruction_formats = (".text", ".txt", ".pdf", ".html") if not instructions_file.endswith(supported_instruction_formats): diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 1ac8f8841..6297443f2 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,5 +1,6 @@ import pytest import json +import requests from labelbox import Project, LabelingFrontend from labelbox.exceptions import InvalidQueryError @@ -68,8 +69,11 @@ def test_extend_reservations(project): def test_attach_instructions(client, project): - with pytest.raises(ValueError): - project.attach_instructions('/tmp/instructions.txt') + with pytest.raises(ValueError) as execinfo: + project.upsert_instructions('/tmp/instructions.txt') + assert str( + execinfo.value + ) == "Cannot attach instructions to a project that has not been set up." editor = list( client.get_labeling_frontends( @@ -80,10 +84,11 @@ def test_attach_instructions(client, project): with open('/tmp/instructions.txt', 'w') as file: file.write("some instructions...") - project.attach_instructions('/tmp/instructions.txt') + project.upsert_instructions('/tmp/instructions.txt') assert json.loads( list(project.labeling_frontend_options()) [-1].customization_options).get('projectInstructions') is not None - with pytest.raises(ValueError): - project.attach_instructions('/tmp/file.invalid_file_extension') + with pytest.raises(ValueError) as execinfo: + project.upsert_instructions('/tmp/file.invalid_file_extension') + assert "instructions_file must end with one of" in str(execinfo.value)