helpers
Submission related tasks
S3Uploader
¶
Upload files to s3 with progress bar
__init__(self, host, fields, file_path)
special
¶
Parameters:
Name | Type | Description | Default |
---|---|---|---|
host |
str |
s3 host to upload to |
required |
fields |
dict |
s3 related fields |
required |
file_path |
str |
the file to upload |
required |
Source code in aicrowd/submission/helpers.py
def __init__(self, host: str, fields: dict, file_path: str):
"""
Args:
host: s3 host to upload to
fields: s3 related fields
file_path: the file to upload
"""
self.host = host
self.fields = fields
file_path = Path(file_path)
self.file_name = file_path.name
self.file_size = file_path.stat().st_size
self.fields["key"] = self.fields["key"].replace("${filename}", self.file_name)
self.fields["file"] = (self.file_name, file_path.open("rb"))
self.progress = Progress(
TextColumn("[bold blue]{task.fields[file_name]}", justify="right"),
BarColumn(bar_width=None),
"[progress.percentage]{task.percentage:>3.1f}%",
"•",
DownloadColumn(),
"•",
TransferSpeedColumn(),
"•",
TimeRemainingColumn(),
)
self.progress.console.is_jupyter = is_jupyter()
self.task_id = self.progress.add_task(
"upload", file_name=self.file_name, start=False, total=self.file_size
)
track_progress(self, monitor)
¶
shows progress bar showing how much has been uploaded
Parameters:
Name | Type | Description | Default |
---|---|---|---|
monitor |
MultipartEncoderMonitor |
requests_toolbelt Monitor |
required |
Source code in aicrowd/submission/helpers.py
def track_progress(self, monitor: MultipartEncoderMonitor):
"""
shows progress bar showing how much has been uploaded
Args:
monitor: requests_toolbelt Monitor
"""
self.progress.update(self.task_id, completed=monitor.bytes_read, refresh=True)
upload(self)
¶
upload file to s3
Source code in aicrowd/submission/helpers.py
def upload(self):
"""
upload file to s3
"""
self.progress.start_task(self.task_id)
m = MultipartEncoderMonitor.from_fields(
self.fields, callback=self.track_progress
)
with self.progress:
return (
requests.post(
self.host, data=m, headers={"Content-Type": m.content_type}
),
self.fields["key"],
)
calculate_min_table_width(table)
¶
Calculates minumum width needed to display table text properly
Parameters:
Name | Type | Description | Default |
---|---|---|---|
table |
Table |
rich table |
required |
Source code in aicrowd/submission/helpers.py
def calculate_min_table_width(table: Table):
"""
Calculates minumum width needed to display table text properly
Args:
table: rich table
"""
console = Console(width=200)
width = (
sum(table._calculate_column_widths(console, console.options))
+ table._extra_width
)
term_width = shutil.get_terminal_size().columns
return max(width, term_width)
delete_and_copy_dir(src, dst)
¶
Delete if the src exists and copy files from src to dst
Parameters:
Name | Type | Description | Default |
---|---|---|---|
src |
str |
Source path |
required |
dst |
str |
Destination path |
required |
Source code in aicrowd/submission/helpers.py
def delete_and_copy_dir(src: str, dst: str):
"""
Delete if the src exists and copy files from src to dst
Args:
src: Source path
dst: Destination path
"""
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)
get_upload_details(challenge_slug, api_key)
¶
Contacts AIcrowd website for getting presigned url for uploading
Parameters:
Name | Type | Description | Default |
---|---|---|---|
challenge_slug |
str |
challenge slug |
required |
api_key |
str |
AIcrowd API Key |
required |
Returns:
Type | Description |
---|---|
Tuple[dict, bool] |
the data and whether the request was successful or not |
Source code in aicrowd/submission/helpers.py
def get_upload_details(challenge_slug: str, api_key: str) -> Tuple[dict, bool]:
"""
Contacts AIcrowd website for getting presigned url for uploading
Args:
challenge_slug: challenge slug
api_key: AIcrowd API Key
Returns:
the data and whether the request was successful or not
"""
log = logging.getLogger()
r = get_submission_upload_details(api_key, challenge_slug)
# temporary hack until /api stops redirecting
if r.status_code // 100 == 3:
redirected_to = r.headers["Location"]
expected_redirect = f"https://{RAILS_HOST}"
if redirected_to.startswith(expected_redirect):
redirected_to = redirected_to[len(expected_redirect) :]
else:
log.error("Unexpected redirect location: %s", redirected_to)
# got redirected to a weird place
raise ChallengeNotFoundException(
"Something went wrong with fetching challenge details"
)
challenge_problem = re.match(
r"/challenges/([^/]*)/problems/([^/]*)/.*", redirected_to
).groups()
logging.info("[metachallenge?] Got redirected to %s", redirected_to)
# inform caller about meta_challenge
return {
"meta_challenge": True,
"meta_challenge_id": challenge_problem[0],
"challenge_id": challenge_problem[1],
}, True
try:
resp = r.json()
if not r.ok or not resp.get("success"):
log.error(
"Request to API failed\nReason: %s\nMessage: %s", r.reason, r.text
)
return resp, False
return resp.get("data"), True
except json.JSONDecodeError as e:
log.error("Error while extracting details from API request: %s", e)
raise ChallengeNotFoundException(
"There was some error in contacting AIcrowd servers",
"Please run this command with -v or -vv or .. -vvvvv to get more details",
) from e
notify_rails_upload(challenge_slug, submitted_url, api_key, description, problem_slug=None)
¶
notify rails about the uploaded file on s3
Parameters:
Name | Type | Description | Default |
---|---|---|---|
challenge_slug |
str |
challenge slug |
required |
submitted_url |
str |
the url to which the submitted file was uploaded to |
required |
description |
str |
submission description |
required |
problem_slug |
str |
Used when submitting to a meta challenge |
None |
Returns:
Type | Description |
---|---|
dict |
submission details from AIcrowd API |
Source code in aicrowd/submission/helpers.py
def notify_rails_upload(
challenge_slug: str,
submitted_url: str,
api_key: str,
description: str,
problem_slug: str = None,
) -> dict:
"""
notify rails about the uploaded file on s3
Args:
challenge_slug: challenge slug
submitted_url: the url to which the submitted file was uploaded to
description: submission description
problem_slug: Used when submitting to a meta challenge
Returns:
submission details from AIcrowd API
"""
log = logging.getLogger()
if problem_slug is None:
payload = {"challenge_id": challenge_slug}
else:
payload = {"meta_challenge_id": challenge_slug, "challenge_id": problem_slug}
payload.update(
{
"description": description,
"submission_files": [{"submission_file_s3_key": submitted_url}],
}
)
r = create_rails_submission(api_key, payload)
if not r.ok:
log.error("Request to API failed\nReason: %s\nMessage: %s", r.reason, r.text)
raise SubmissionUploadException(
f"Couldn't decode response from AIcrowd servers. {r.reason}", r.text
)
try:
return r.json()
except json.JSONDecodeError as e:
log.error("Couldn't json-decode rails response")
raise SubmissionUploadException(
"Couldn't decode response from AIcrowd servers"
) from e
print_submission_links(challenge_slug, problem_slug, submission_id)
¶
prints helpful links related to the submission
Parameters:
Name | Type | Description | Default |
---|---|---|---|
challenge_slug |
str |
challenge slug |
required |
problem_slug |
str |
when submitting to a meta challenge |
required |
submission_id |
int |
rails submission id |
required |
Source code in aicrowd/submission/helpers.py
def print_submission_links(challenge_slug: str, problem_slug: str, submission_id: int):
"""
prints helpful links related to the submission
Args:
challenge_slug: challenge slug
problem_slug: when submitting to a meta challenge
submission_id: rails submission id
"""
if submission_id is None:
return
if problem_slug:
challenge_url = (
f"https://{RAILS_HOST}/challenges/{challenge_slug}/problems/{problem_slug}"
)
else:
challenge_url = f"https://{RAILS_HOST}/challenges/{challenge_slug}"
submission_base_url = f"{challenge_url}/submissions"
table = Table(title="Important links", show_header=False, leading=1, box=box.SQUARE)
table.add_column(justify="right")
table.add_column(overflow="fold")
table.add_row("This submission", f"{submission_base_url}/{submission_id}")
table.add_row("All submissions", f"{submission_base_url}?my_submissions=true")
table.add_row("Leaderboard", f"{challenge_url}/leaderboards")
table.add_row(
"Discussion forum", f"https://discourse.aicrowd.com/c/{challenge_slug}"
)
table.add_row("Challenge page", f"{challenge_url}")
width = calculate_min_table_width(table)
console = Console(width=width)
table.min_width = width
console.print(Panel("[bold]Successfully submitted!"), justify="center")
console.print(table)
submit_file(challenge_slug, file_path, description, api_key, print_links)
¶
Submits a file given it's path and challenge_slug with given description
Parameters:
Name | Type | Description | Default |
---|---|---|---|
challenge_slug |
str |
challenge slug |
required |
file_path |
str |
path to the file to be uploaded |
required |
description |
str |
description for the submission |
required |
api_key |
str |
AIcrowd API Key |
required |
print_links |
bool |
should helpful links be printed? |
required |
Returns:
Type | Description |
---|---|
dict |
a message from AIcrowd API |
Source code in aicrowd/submission/helpers.py
def submit_file(
challenge_slug: str,
file_path: str,
description: str,
api_key: str,
print_links: bool,
) -> dict:
"""
Submits a file given it's path and challenge_slug with given description
Args:
challenge_slug: challenge slug
file_path: path to the file to be uploaded
description: description for the submission
api_key: AIcrowd API Key
print_links: should helpful links be printed?
Returns:
a message from AIcrowd API
"""
log = logging.getLogger()
problem_slug = None
if not Path(file_path).is_file():
raise SubmissionFileException(f"Bad file {file_path}")
s3_presigned_details, success = get_upload_details(challenge_slug, api_key)
if s3_presigned_details.get("meta_challenge"):
challenge_slug = s3_presigned_details.get("meta_challenge_id")
problem_slug = s3_presigned_details.get("challenge_id")
s3_presigned_details, success = get_upload_details(challenge_slug, api_key)
if not success:
log.error(
"Error in getting presigned url for s3 upload: %s",
s3_presigned_details.get("message"),
)
raise SubmissionUploadException(
"Something went wrong while uploading your submission",
s3_presigned_details.get("message"),
)
s3_key = upload_submission(
s3_presigned_details["url"], s3_presigned_details["fields"], file_path
)
if s3_key is None:
raise SubmissionUploadException(
"Couldn't submit file. Please recheck the files and details provided"
)
response = notify_rails_upload(
challenge_slug, s3_key, api_key, description, problem_slug
)
if not response.get("success"):
log.error("Couldn't notify AIcrowd servers about uploaded submission")
raise SubmissionUploadException(
response.get(
"message", "Something went wrong while contacting AIcrowd servers"
)
)
if print_links:
print_submission_links(
challenge_slug, problem_slug, response.get("data", {}).get("submission_id")
)
return response.get("data")
upload_submission(host, fields, file_path)
¶
uploads a file to s3 using presigned url details
Parameters:
Name | Type | Description | Default |
---|---|---|---|
host |
str |
s3 host to upload to |
required |
fields |
dict |
s3 related keys |
required |
file_path |
str |
the file to be uploaded |
required |
Returns:
Type | Description |
---|---|
str |
whether it was successful or not |
Source code in aicrowd/submission/helpers.py
def upload_submission(host: str, fields: dict, file_path: str) -> str:
"""
uploads a file to s3 using presigned url details
Args:
host: s3 host to upload to
fields: s3 related keys
file_path: the file to be uploaded
Returns:
whether it was successful or not
"""
log = logging.getLogger()
r, s3_key = S3Uploader(host, fields, file_path).upload()
if not r.ok:
log.error(
"Couldn't upload file to s3\nReason: %s\nMessage: %s", r.reason, r.text
)
return None
return s3_key
zip_assets(assets_dir, target_zip_path)
¶
Zip the files under assets directory
Parameters:
Name | Type | Description | Default |
---|---|---|---|
assets_dir |
str |
Directory containing submission assets |
required |
target_zip_path |
str |
Path to zip file to add the entries to |
required |
Source code in aicrowd/submission/helpers.py
def zip_assets(assets_dir: str, target_zip_path: str):
"""
Zip the files under assets directory
Args:
assets_dir: Directory containing submission assets
target_zip_path: Path to zip file to add the entries to
"""
console = Console()
if not os.path.exists(assets_dir):
os.mkdir(assets_dir)
console.print("WARNING: Assets dir is empty", style="bold red")
zip_fs_path(assets_dir, target_zip_path)