-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement submit action #33
Merged
madeddie
merged 6 commits into
main
from
27-implement-exercise-solution-submission-submit-subcommand
May 20, 2024
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3e38a5a
First version of the submit task code
madeddie a595f44
First version of the submit task code
madeddie e555cc0
Run ruff and black on code
madeddie ee1428b
Merge branch '27-implement-exercise-solution-submission-submit-subcom…
madeddie b2af18f
Fix code and clean up
madeddie bd13201
Split off some functionality into its own function
madeddie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import json | ||
from urllib.parse import urljoin | ||
from pathlib import Path | ||
from time import sleep | ||
from typing import AnyStr, Optional | ||
from xml.etree.ElementTree import Element, tostring | ||
|
||
|
@@ -145,6 +146,15 @@ def parse_args(args: Optional[list[str]] = None) -> argparse.Namespace: | |
parser_show.set_defaults(cmd="show") | ||
parser_show.add_argument("task_id", help="Numerical task identifier") | ||
|
||
# submit exercise solution subparser | ||
parser_submit = subparsers.add_parser("submit", help="Submit an exercise solution") | ||
parser_submit.set_defaults(cmd="submit") | ||
parser_submit.add_argument( | ||
"--filename", | ||
help="Filename of the solution to submit (if not given will be guessed from task description)", | ||
) | ||
parser_submit.add_argument("task_id", help="Numerical task identifier") | ||
|
||
return parser.parse_args(args) | ||
|
||
|
||
|
@@ -221,7 +231,7 @@ def find_link(html: AnyStr, xpath: str) -> dict[str, str | None]: | |
return link_data | ||
|
||
|
||
def parse_form(html: AnyStr, xpath: str = ".//form") -> dict[str, str | None]: | ||
def parse_form(html: AnyStr, xpath: str = ".//form") -> dict: | ||
"""Search for the first form in html and return dict with action and all other found inputs""" | ||
form_element = htmlement.fromstring(html).find(xpath) | ||
form_data = dict() | ||
|
@@ -251,9 +261,10 @@ class Task: | |
id: str | ||
name: str | ||
state: TaskState | ||
description: str = "N/A" | ||
code: str = "N/A" | ||
submit_file: str = "N/A" | ||
description: Optional[str] = None | ||
code: Optional[str] = None | ||
submit_file: Optional[str] = None | ||
submit_link: Optional[str] = None | ||
|
||
|
||
def parse_task_list(html: AnyStr) -> list[Task]: | ||
|
@@ -279,9 +290,11 @@ def parse_task_list(html: AnyStr) -> list[Task]: | |
task = Task( | ||
id=item_id, | ||
name=item_name, | ||
state=TaskState.COMPLETE | ||
if "full" in item_class | ||
else TaskState.INCOMPLETE, | ||
state=( | ||
TaskState.COMPLETE | ||
if "full" in item_class | ||
else TaskState.INCOMPLETE | ||
), | ||
) | ||
task_list.append(task) | ||
|
||
|
@@ -305,14 +318,25 @@ def parse_task(html: AnyStr) -> Task: | |
task_link_element = root.find('.//div[@class="nav sidebar"]/a') | ||
task_link = task_link_element if task_link_element is not None else Element("a") | ||
task_id = task_link.get("href", "").split("/")[-1] | ||
task_name = task_link.text or "N/A" | ||
if not task_id: | ||
raise ValueError("Failed to find task id") | ||
task_name = task_link.text or None | ||
if not task_name: | ||
raise ValueError("Failed to find task name") | ||
task_span_element = task_link.find("span") | ||
task_span = task_span_element if task_span_element is not None else Element("span") | ||
task_span_class = task_span.get("class", "") | ||
desc_div_element = root.find('.//div[@class="md"]') | ||
desc_div = desc_div_element if desc_div_element is not None else Element("div") | ||
description = html2text(tostring(desc_div).decode("utf8")) | ||
code = root.findtext(".//pre", "N/A") | ||
code = root.findtext(".//pre", None) | ||
submit_link_element = root.find('.//a[.="Submit"]') | ||
submit_link = ( | ||
submit_link_element.get("href", None) | ||
if submit_link_element is not None | ||
else None | ||
) | ||
|
||
submit_file = next( | ||
iter( | ||
[ | ||
|
@@ -321,7 +345,7 @@ def parse_task(html: AnyStr) -> Task: | |
if code_element.text is not None and ".py" in code_element.text | ||
] | ||
), | ||
"N/A", | ||
None, | ||
) | ||
task = Task( | ||
id=task_id, | ||
|
@@ -330,6 +354,7 @@ def parse_task(html: AnyStr) -> Task: | |
description=description.strip(), | ||
code=code, | ||
submit_file=submit_file, | ||
submit_link=submit_link, | ||
) | ||
|
||
return task | ||
|
@@ -341,10 +366,13 @@ def print_task(task: Task) -> None: | |
print(f"\nSubmission file name: {task.submit_file}") | ||
|
||
|
||
def submit_task(task_id: str, filename: str) -> None: | ||
"""submit file to the submit form or task_id""" | ||
# NOTE: use parse_form | ||
... | ||
# def submit_task(task_id: str, filename: str) -> None: | ||
# """submit file to the submit form or task_id""" | ||
# html = session.http_request(urljoin(base_url, f"task/{task_id}")) | ||
# task = parse_task(html) | ||
# answer = input("Do you want to submit this task? (y/n): ") | ||
# if answer in ('y', 'Y'): | ||
# with open(filename, 'r') as f: | ||
|
||
|
||
def main() -> None: | ||
|
@@ -396,9 +424,59 @@ def main() -> None: | |
|
||
if args.cmd == "show": | ||
html = session.http_request(urljoin(base_url, f"task/{args.task_id}")) | ||
task = parse_task(html) | ||
try: | ||
task = parse_task(html) | ||
except ValueError as e: | ||
logger.debug(f"Error parsing task: {e}") | ||
raise | ||
print_task(task) | ||
|
||
if args.cmd == "submit": | ||
html = session.http_request(urljoin(base_url, f"task/{args.task_id}")) | ||
task = parse_task(html) | ||
if not task.submit_file and not args.filename: | ||
raise ValueError("No submission filename found") | ||
if not task.submit_link: | ||
raise ValueError("No submission link found") | ||
submit_file = args.filename or task.submit_file or "" | ||
|
||
submit_form_html = session.http_request(urljoin(base_url, task.submit_link)) | ||
submit_form_data = parse_form(submit_form_html) | ||
action = submit_form_data.pop("_action") | ||
|
||
for key, value in submit_form_data.items(): | ||
submit_form_data[key] = (None, value) | ||
submit_form_data["file"] = (submit_file, open(submit_file, "rb")) | ||
submit_form_data["lang"] = (None, "Python3") | ||
submit_form_data["option"] = (None, "CPython3") | ||
|
||
res = session.http_session.post( | ||
urljoin(base_url, action), files=submit_form_data | ||
) | ||
html = res.text | ||
result_url = res.url | ||
print("Waiting for test results.", end="") | ||
while "Test report" not in html: | ||
print(".", end="") | ||
sleep(1) | ||
html = session.http_request(result_url) | ||
|
||
print() | ||
root = htmlement.fromstring(html) | ||
submit_status_element = root.find('.//td[.="Status:"]/..') or Element("td") | ||
submit_status_span_element = submit_status_element.find("td/span") or Element( | ||
"span" | ||
) | ||
submit_status = submit_status_span_element.text or "" | ||
submit_result_element = root.find('.//td[.="Result:"]/..') or Element("td") | ||
submit_result_span_element = submit_result_element.find("td/span") or Element( | ||
"span" | ||
) | ||
submit_result = submit_result_span_element.text or "" | ||
|
||
print(f"Submission status: {submit_status.lower()}") | ||
print(f"Submission result: {submit_result.lower()}") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The submission logic integrated into the def submit_task(session: Session, task: Task, filename: str) -> None:
"""
Handles the submission of a task using the provided session and task details.
Args:
session (Session): The session object with login details and cookies.
task (Task): The task object containing submission details.
filename (str): The filename of the solution to submit.
"""
# Move the submission logic here |
||
|
||
if __name__ == "__main__": | ||
main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The modification in the
parse_form
function to remove type hints for return values aligns with the changes in other parts of the codebase. However, consider adding a more detailed docstring to explain the parameters and the return type more clearly.