Skip to content
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
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions tests/test_tyora.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ class TestFindLink:
valid_return = {"href": "somelink", "text": "sometext"}

def test_find_link_success(self) -> None:
assert (
tyora.find_link(self.valid_html, self.valid_xpath)
== self.valid_return
)
assert tyora.find_link(self.valid_html, self.valid_xpath) == self.valid_return

def test_find_link_bad_xpath(self) -> None:
assert tyora.find_link(self.valid_html, self.invalid_xpath) == {}
Expand Down Expand Up @@ -84,17 +81,13 @@ def test_login_failed(mock_session: tyora.Session) -> None:


# TODO: functions that use user input or read or write files
def test_create_config() -> None:
...
def test_create_config() -> None: ...


def test_write_config() -> None:
...
def test_write_config() -> None: ...


def test_read_config() -> None:
...
def test_read_config() -> None: ...


def test_get_cookiejar() -> None:
...
def test_get_cookiejar() -> None: ...
88 changes: 77 additions & 11 deletions tyora.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")

return parser.parse_args(args)


Expand Down Expand Up @@ -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:
Copy link
Contributor

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.

def parse_form(html: AnyStr, xpath: str = ".//form") -> dict:
    """
    Search for the first form in html and return a dictionary with action and all other found inputs.

    Args:
        html (AnyStr): HTML content as a string.
        xpath (str): XPath query to locate the form.

    Returns:
        dict: Dictionary containing form action and inputs.
    """
    # function body remains the same

"""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()
Expand Down Expand Up @@ -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]:
Expand All @@ -279,9 +287,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)

Expand Down Expand Up @@ -313,6 +323,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(
[
Expand All @@ -330,6 +347,7 @@ def parse_task(html: AnyStr) -> Task:
description=description.strip(),
code=code,
submit_file=submit_file,
submit_link=submit_link,
)

return task
Expand All @@ -341,10 +359,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:
Expand Down Expand Up @@ -399,6 +420,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()}")


if __name__ == "__main__":
main()