diff --git a/evalai/challenges.py b/evalai/challenges.py index cda1d2ea2..c561f9618 100644 --- a/evalai/challenges.py +++ b/evalai/challenges.py @@ -4,6 +4,7 @@ from evalai.utils.common import Date from evalai.utils.challenges import ( + create_challenge, display_all_challenge_list, display_future_challenge_list, display_ongoing_challenge_list, @@ -76,6 +77,19 @@ def challenge(ctx, challenge): display_challenge_details(challenge) +@challenges.command(context_settings={"ignore_unknown_options": True}) +@click.option("--file", type=click.File("rb"), required=True, help="Challenge Zip file.") +@click.argument("team", type=int) +def create(file, team): + """ + Create challenge with Zip file & HostTeam ID. + """ + """ + Invoked by running 'evalai challenges create --file FILE TEAM' + """ + create_challenge(file, team) + + @challenges.command() def ongoing(): """ diff --git a/evalai/utils/challenges.py b/evalai/utils/challenges.py index c317da095..7a88a70ff 100644 --- a/evalai/utils/challenges.py +++ b/evalai/utils/challenges.py @@ -21,6 +21,57 @@ requests.packages.urllib3.disable_warnings() +def create_challenge(file, team): + """ + Function to create a challenge. + """ + url = "{}{}".format(get_host_url(), URLS.create_challenge.value) + url = url.format(team) + + headers = get_request_header() + file = {"zip_configuration": file} + + try: + response = requests.post(url, headers=headers, files=file) + response.raise_for_status() + except requests.exceptions.HTTPError as err: + if response.status_code in EVALAI_ERROR_CODES: + validate_token(response.json()) + echo( + style( + "\nError: {}\n" + "\nUse `evalai challenges` to fetch the active challenges.\n" + "\nUse `evalai challenge CHALLENGE phases` to fetch the " + "active phases.\n".format(response.json()["error"]), + fg="red", + bold=True, + ) + ) + else: + echo(err) + sys.exit(1) + except requests.exceptions.RequestException as err: + echo( + style( + "\nCould not establish a connection to EvalAI." + " Please check the Host URL.\n", + bold=True, + fg="red", + ) + ) + sys.exit(1) + response = response.json() + echo( + style( + "\n{}\n".format( + response["success"] + ), + fg="green", + bold=True, + ) + ) + + def pretty_print_challenge_data(challenges): """ Function to print the challenge data diff --git a/evalai/utils/urls.py b/evalai/utils/urls.py index b29314e78..48c8d6e76 100644 --- a/evalai/utils/urls.py +++ b/evalai/utils/urls.py @@ -2,6 +2,7 @@ class URLS(Enum): + create_challenge = "/api/challenges/challenge/challenge_host_team/{}/zip_upload/" login = "/api/auth/login" challenge_list = "/api/challenges/challenge/all" past_challenge_list = "/api/challenges/challenge/past" diff --git a/tests/data/challenge_response.py b/tests/data/challenge_response.py index ec567606d..19d29afdc 100644 --- a/tests/data/challenge_response.py +++ b/tests/data/challenge_response.py @@ -392,3 +392,9 @@ ] } """ + +create_challenge_result = """ +{ + "success": "Challenge Challenge Title has been created successfully and sent for review to EvalAI Admin." +} +""" diff --git a/tests/data/test_zip_file.zip b/tests/data/test_zip_file.zip new file mode 100644 index 000000000..5bbb08372 Binary files /dev/null and b/tests/data/test_zip_file.zip differ diff --git a/tests/test_challenges.py b/tests/test_challenges.py index d53122d2e..3d988c594 100644 --- a/tests/test_challenges.py +++ b/tests/test_challenges.py @@ -1,4 +1,5 @@ import json +import os import responses from beautifultable import BeautifulTable @@ -971,3 +972,58 @@ def test_display_challenge_phase_split_list_with_string_argument(self): result = runner.invoke(challenge, ["two", "participate", "3"]) response = result.output assert response == output + + +class TestCreateChallenge(BaseTestClass): + def setup(self): + self.message = json.loads(challenge_response.create_challenge_result) + + url = "{}{}" + responses.add( + responses.POST, + url.format(API_HOST_URL, URLS.create_challenge.value).format("4"), + json=self.message, + status=201, + ) + + @responses.activate + def test_create_challenge_when_file_is_not_valid(self): + expected = ( + "Usage: challenges create [OPTIONS] TEAM\n" + '\nError: Invalid value for "--file": Could not open file: file: No such file or directory\n' + ) + runner = CliRunner() + result = runner.invoke( + challenges, ["create", "--file", "file", "4"] + ) + response = result.output + assert response == expected + + @responses.activate + def test_create_challenge_when_file_and_id_are_valid(self): + expected = 'Challenge Challenge Title has been created successfully and sent for review to EvalAI Admin.' + + runner = CliRunner() + + my_path = os.path.abspath(os.path.dirname(__file__)) + file = os.path.join(my_path, "data") + + result = runner.invoke( + challenges, ["create", "--file", "{}/test_zip_file.zip".format(file), "4"] + ) + assert result.output.strip() == expected + + @responses.activate + def test_create_challenge_when_id_is_not_valid(self): + expected = ("Could not establish a connection to EvalAI." + " Please check the Host URL.") + + runner = CliRunner() + + my_path = os.path.abspath(os.path.dirname(__file__)) + file = os.path.join(my_path, "data") + + result = runner.invoke( + challenges, ["create", "--file", "{}/test_zip_file.zip".format(file), "111"] + ) + assert result.output.strip() == expected diff --git a/tests/test_requests.py b/tests/test_requests.py index 1df0b5016..ae1728d9c 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1,4 +1,5 @@ import json +import os import responses from click.testing import CliRunner @@ -320,6 +321,44 @@ def test_display_leaderboard_for_http_error_404(self): assert response == expected +class TestCreateChallengeWhenZipFileDoesNotExist(BaseTestClass): + def setup(self): + + response_data = """ + { + "error": "The zip file contents cannot be extracted. Please check the format!" + } + """ + error_data = json.loads(response_data) + url = "{}{}" + responses.add( + responses.POST, + url.format(API_HOST_URL, URLS.create_challenge.value).format("4"), + json=error_data, + status=406, + ) + + @responses.activate + def test_create_challenge_when_zip_file_does_not_exist(self): + + expected = ("\nError: The zip file contents cannot be extracted. Please check the format!\n" + "\nUse `evalai challenges` to fetch the active challenges.\n" + "\nUse `evalai challenge CHALLENGE phases` to fetch the " + "active phases.\n\n" + ) + runner = CliRunner() + + with runner.isolated_filesystem(): + with open("test_file.txt", "w") as f: + f.write("1 2 3 4 5 6") + + result = runner.invoke( + challenges, ["create", "--file", "test_file.txt", "4"], + ) + + assert result.output == expected + + class TestSubmissionDetailsWhenObjectDoesNotExist(BaseTestClass): def setup(self): @@ -594,6 +633,12 @@ def setup(self): # Challenge URLS + responses.add( + responses.POST, + url.format(API_HOST_URL, URLS.create_challenge.value), + body=RequestException("..."), + ) + responses.add( responses.GET, url.format(API_HOST_URL, URLS.challenge_list.value), @@ -721,6 +766,14 @@ def setup(self): body=RequestException("..."), ) + @responses.activate + def test_create_challenge_for_request_exception(self): + runner = CliRunner() + my_path = os.path.abspath(os.path.dirname(__file__)) + file = os.path.join(my_path, "data") + result = runner.invoke(challenges, ["create", "--file", "{}/test_zip_file.zip".format(file), "4"]) + assert result.exit_code == 1 + @responses.activate def test_display_challenge_list_for_request_exception(self): runner = CliRunner()