-
-
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
Changes from 1 commit
3e38a5a
a595f44
e555cc0
ee1428b
b2af18f
bd13201
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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,12 @@ 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") | ||
parser_submit.add_argument("task_id", help="Numerical task identifier") | ||
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. Specify parser_submit.add_argument("--filename", required=True, help="Filename of the solution to submit") |
||
|
||
return parser.parse_args(args) | ||
|
||
|
||
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. CODE REVIEWIt's generally clean and modular. However, specifying the Example: parser_submit.add_argument("--filename", required=True, help="Filename of the solution to submit")
parser_submit.add_argument("task_id", type=int, help="Numerical task identifier") |
||
|
@@ -221,7 +228,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: | ||
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. Specify the value types in the return type annotation for clarity and type safety. def parse_form(html: AnyStr, xpath: str = ".//form") -> dict[str, Optional[str]]: |
||
"""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() | ||
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. CODE REVIEWIt's generally good practice to be explicit about types in Python, especially with type hinting in Python 3. It improves code readability and helps with debugging and maintaining. The change here removes the specificity of the return type for Before: def parse_form(html: AnyStr, xpath: str = ".//form") -> dict: After: from typing import Union, Optional, Dict
def parse_form(html: AnyStr, xpath: str = ".//form") -> Dict[str, Optional[str]]: |
||
|
@@ -251,9 +258,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]: | ||
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. CODE REVIEWThe change from default string values to # Original
description: str = "N/A"
# Updated
description: Optional[str] = None Ensure any function handling |
||
|
@@ -313,6 +321,13 @@ def parse_task(html: AnyStr) -> Task: | |
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") | ||
submit_link_element = root.find('.//a[.="Submit"]') | ||
submit_link = ( | ||
submit_link_element.get("href", "N/A") | ||
if submit_link_element is not None | ||
else "N/A" | ||
) | ||
|
||
submit_file = next( | ||
iter( | ||
[ | ||
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. CODE REVIEWThe changes generally look good, but you could improve readability and consistency by adopting the same pattern as your initial condition with submit_link_element = root.find('.//a[.="Submit"]')
submit_link = submit_link_element.get("href", "N/A") if submit_link_element is not None else "N/A" |
||
|
@@ -330,6 +345,7 @@ def parse_task(html: AnyStr) -> Task: | |
description=description.strip(), | ||
code=code, | ||
submit_file=submit_file, | ||
submit_link=submit_link, | ||
) | ||
|
||
return task | ||
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. CODE REVIEWThe code change looks concise, adding a
Example for validation (assuming from urllib.parse import urlparse
def is_valid_url(url):
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
except ValueError:
return False
if not is_valid_url(submit_link):
raise ValueError("Invalid submit link") |
||
|
@@ -341,10 +357,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: | ||
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 |
||
|
||
|
||
def main() -> None: | ||
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. CODE REVIEW
Remember, keeping your codebase clean and only merging complete, fully vetted features helps maintain overall code quality and reduces technical debt. |
||
|
@@ -399,6 +418,51 @@ def main() -> None: | |
task = parse_task(html) | ||
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 or task.submit_file == "N/A": | ||
raise ValueError("No submission filename found") | ||
if not task.submit_link or task.submit_link == "N/A": | ||
raise ValueError("No submission link found") | ||
|
||
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"] = (task.submit_file, open(task.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. Consider refactoring the submission logic into a separate function for better readability and maintainability. def submit_task(session, base_url, task_id, filename):
html = session.http_request(urljoin(base_url, f"task/{task_id}"))
task = parse_task(html)
if not task.submit_file or task.submit_file == "N/A":
raise ValueError("No submission filename found")
if not task.submit_link or task.submit_link == "N/A":
raise ValueError("No submission link found")
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"] = (task.submit_file, open(task.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()}")
# In main function
if args.cmd == "submit":
submit_task(session, base_url, args.task_id, args.filename) |
||
|
||
if __name__ == "__main__": | ||
main() | ||
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. CODE REVIEWConsider abstracting the new functionality into separate functions to improve readability and maintainability. Additionally, verifying file existence before attempting to open it improves robustness. Use Example adjustments: - submit_form_data["file"] = (task.submit_file, open(task.submit_file, "rb"))
+ file_path = task.submit_file
+ if not os.path.exists(file_path):
+ raise FileNotFoundError(f"{file_path} does not exist")
+ with open(file_path, "rb") as file:
+ submit_form_data["file"] = (task.submit_file, file)
+ # Rest of file handling Split large blocks into functions: + if args.cmd == "submit":
+ submit_task(args, session) Refactoring this way enhances code structure, making it easier to manage and understand. 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. @senior-dev-bot can you suggests some unit tests for this method? |
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.
CODE REVIEW
The changes introduce the
sleep
function from thetime
module. Ensure that usingsleep
in your code is necessary, as it can introduce delays that might not be desirable, especially in IO-bound or async applications. If its use is unavoidable for rate-limiting or similar purposes, consider alternatives like async sleep functionalities in asynchronous environments or other throttling mechanisms.No further improvements or issues are noticeable based solely on the import statements. However, always consider the impact of adding new dependencies or modules on the application's performance and maintainability.