Skip to content

Commit

Permalink
Merge pull request #33 from madeddie/27-implement-exercise-solution-s…
Browse files Browse the repository at this point in the history
…ubmission-submit-subcommand

Implement submit action
  • Loading branch information
madeddie committed May 20, 2024
2 parents 1227d9d + bd13201 commit 67f9a42
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 27 deletions.
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: ...
117 changes: 102 additions & 15 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,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)


Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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]:
Expand All @@ -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)

Expand All @@ -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(
[
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -341,10 +366,32 @@ 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 parse_submit_result(html: AnyStr) -> dict[str, str]:
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 ""

return {
"status": submit_status.lower(),
"result": submit_result.lower(),
}


def main() -> None:
Expand Down Expand Up @@ -396,9 +443,49 @@ 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()
results = parse_submit_result(html)

print(f"Submission status: {results['status']}")
print(f"Submission result: {results['result']}")


if __name__ == "__main__":
main()

0 comments on commit 67f9a42

Please sign in to comment.