mirror of
https://github.com/Code-For-Groningen/temmies.git
synced 2025-07-01 11:34:58 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
aa7b91de0d | |||
5c3e884a8b | |||
c14f87aecc | |||
6a781ad238 | |||
a1104522f1 | |||
fb8b5cd454 | |||
1367fd667f | |||
1516ef74be | |||
c37edb59c6 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,7 +3,6 @@ config.py
|
|||||||
tests/
|
tests/
|
||||||
pathfinding/
|
pathfinding/
|
||||||
test.py
|
test.py
|
||||||
setup.py
|
|
||||||
|
|
||||||
#Doc env
|
#Doc env
|
||||||
.docs_env
|
.docs_env
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/Code-For-Groningen/temmies/blob/v1.1.0/docs/img/rugemmie.gif" />
|
<img src="docs/img/temmie.png" width= 200px/>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://temmies.confest.im"><img alt="Read the Docs" src="https://img.shields.io/readthedocs/temmies"></a>
|
<a href="https://temmies.confest.im"><img alt="Read the Docs" src="https://img.shields.io/readthedocs/temmies"></a>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 190 KiB |
BIN
docs/img/temmie.png
Normal file
BIN
docs/img/temmie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
@ -1,5 +1,5 @@
|
|||||||
# Temmies!
|
# Temmies!
|
||||||
<center></center>
|
<center></center>
|
||||||
|
|
||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
30
setup.py
Normal file
30
setup.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
with open("README.md", "r") as f:
|
||||||
|
l_description = f.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="temmies",
|
||||||
|
version="1.2.12",
|
||||||
|
packages=find_packages(),
|
||||||
|
description="A wrapper for the Themis website",
|
||||||
|
long_description=l_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
url="https://github.com/Code-For-Groningen/temmies",
|
||||||
|
author="Boyan K.",
|
||||||
|
author_email="boyan@confest.im",
|
||||||
|
license="GPLv3",
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
],
|
||||||
|
install_requires=[
|
||||||
|
"requests",
|
||||||
|
"lxml",
|
||||||
|
"beautifulsoup4",
|
||||||
|
"keyring"
|
||||||
|
],
|
||||||
|
python_requires=">=3.9",
|
||||||
|
)
|
@ -11,13 +11,11 @@ class ExerciseGroup(Group):
|
|||||||
super().__init__(session, path, title, parent, submitable=submitable)
|
super().__init__(session, path, title, parent, submitable=submitable)
|
||||||
self.submit_url = f"{self.base_url}/api/submit{self.path}"
|
self.submit_url = f"{self.base_url}/api/submit{self.path}"
|
||||||
self.__find_name()
|
self.__find_name()
|
||||||
|
|
||||||
def __find_name(self):
|
def __find_name(self):
|
||||||
"""
|
"""
|
||||||
Find the name of the exercise group.
|
Find the name of the exercise group.
|
||||||
"""
|
"""
|
||||||
if self.title == "":
|
if self.title == "":
|
||||||
# Find using beautiful soup (it is the last a with class 'fill accent large')
|
|
||||||
response = self.session.get(self.base_url + self.path)
|
response = self.session.get(self.base_url + self.path)
|
||||||
soup = BeautifulSoup(response.text, "lxml")
|
soup = BeautifulSoup(response.text, "lxml")
|
||||||
title_elements = soup.find_all("a", class_="fill accent large")
|
title_elements = soup.find_all("a", class_="fill accent large")
|
||||||
@ -25,27 +23,6 @@ class ExerciseGroup(Group):
|
|||||||
self.title = title_elements[-1].get_text(strip=True)
|
self.title = title_elements[-1].get_text(strip=True)
|
||||||
else:
|
else:
|
||||||
self.title = self.path.split("/")[-1]
|
self.title = self.path.split("/")[-1]
|
||||||
|
|
||||||
def submit(self, files: list[str]) -> Submission:
|
|
||||||
"""
|
|
||||||
Submit files to this exercise.
|
|
||||||
"""
|
|
||||||
if not self.submitable:
|
|
||||||
raise ValueError(f"Cannot submit to non-submittable item '{self.title}'.")
|
|
||||||
|
|
||||||
# Prepare the files and data for submission
|
|
||||||
files_payload = {}
|
|
||||||
for idx, file_path in enumerate(files):
|
|
||||||
file_key = f"file{idx}"
|
|
||||||
with open(file_path, "rb") as f:
|
|
||||||
files_payload[file_key] = (file_path, f.read())
|
|
||||||
|
|
||||||
response = self.session.post(self.submit_url, files=files_payload)
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise ConnectionError(f"Failed to submit to '{self.title}'.")
|
|
||||||
|
|
||||||
submission_data = response.json()
|
|
||||||
return Submission(self.session, submission_data)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"ExerciseGroup({self.title})"
|
return f"ExerciseGroup({self.title})"
|
||||||
|
@ -4,6 +4,8 @@ import os
|
|||||||
from typing import Optional, Union, Dict
|
from typing import Optional, Union, Dict
|
||||||
from .exceptions.illegal_action import IllegalAction
|
from .exceptions.illegal_action import IllegalAction
|
||||||
from .submission import Submission
|
from .submission import Submission
|
||||||
|
from json import loads
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
"""
|
"""
|
||||||
@ -280,14 +282,22 @@ class Group:
|
|||||||
def __parse_table(self, soup: BeautifulSoup, url: str, verbose: bool, __printed: list) -> dict:
|
def __parse_table(self, soup: BeautifulSoup, url: str, verbose: bool, __printed: list) -> dict:
|
||||||
"""
|
"""
|
||||||
Parse the results table from the submission result page.
|
Parse the results table from the submission result page.
|
||||||
|
Wait until all queued status-icons disappear before parsing.
|
||||||
"""
|
"""
|
||||||
cases = soup.find_all("tr", class_="sub-casetop")
|
cases = soup.find_all("tr", class_="sub-casetop")
|
||||||
fail_pass = {}
|
fail_pass = {}
|
||||||
|
any_queued = False
|
||||||
|
|
||||||
for case in cases:
|
for case in cases:
|
||||||
name = case.find("td", class_="sub-casename").text
|
name = case.find("td", class_="sub-casename").text.strip()
|
||||||
status = case.find("td", class_="status-icon")
|
status = case.find("td", class_="status-icon")
|
||||||
|
|
||||||
if "pending" in status.get("class"):
|
status_classes = status.get("class", [])
|
||||||
|
if "queued" in status_classes:
|
||||||
|
any_queued = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if "pending" in status_classes:
|
||||||
sleep(1)
|
sleep(1)
|
||||||
return self.__wait_for_result(url, verbose, __printed)
|
return self.__wait_for_result(url, verbose, __printed)
|
||||||
|
|
||||||
@ -299,20 +309,31 @@ class Group:
|
|||||||
}
|
}
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
for k, v in statuses.items():
|
for key, (symbol, value) in statuses.items():
|
||||||
if k in status.text:
|
if key.lower() in status.text.lower():
|
||||||
found = True
|
found = True
|
||||||
if verbose and int(name) not in __printed:
|
case_number = int(name)
|
||||||
print(f"{name}: {v[0]}")
|
if verbose and case_number not in __printed:
|
||||||
fail_pass[int(name)] = v[1]
|
print(f"Case {case_number}: {symbol}")
|
||||||
|
fail_pass[case_number] = value
|
||||||
break
|
break
|
||||||
if not found:
|
|
||||||
fail_pass[int(name)] = None
|
|
||||||
if verbose and int(name) not in __printed:
|
|
||||||
print(f"{name}: Unrecognized status: {status.text}")
|
|
||||||
|
|
||||||
__printed.append(int(name))
|
if not found:
|
||||||
|
case_number = int(name)
|
||||||
|
fail_pass[case_number] = None
|
||||||
|
if verbose and case_number not in __printed:
|
||||||
|
print(f"{case_number}: Unrecognized status: {status.text.strip()}")
|
||||||
|
|
||||||
|
__printed.append(case_number)
|
||||||
|
|
||||||
|
# Polling
|
||||||
|
# FIXME: Use ws
|
||||||
|
if any_queued:
|
||||||
|
sleep(1)
|
||||||
|
return self.__wait_for_result(url, verbose, __printed)
|
||||||
|
|
||||||
return fail_pass
|
return fail_pass
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Group({self.title}, submitable={self.submitable})"
|
return f"Group({self.title}, submitable={self.submitable})"
|
||||||
|
@ -53,11 +53,9 @@ class Year:
|
|||||||
|
|
||||||
soup = BeautifulSoup(response.text, "lxml")
|
soup = BeautifulSoup(response.text, "lxml")
|
||||||
|
|
||||||
title_element = soup.find("h1")
|
title_elements = soup.find_all("a", class_="fill accent large")
|
||||||
if not title_element:
|
if title_elements:
|
||||||
title_elements = soup.find_all("a", class_="fill accent large")
|
title_element = title_elements[-1]
|
||||||
if title_elements:
|
|
||||||
title_element = title_elements[-1]
|
|
||||||
|
|
||||||
if title_element:
|
if title_element:
|
||||||
course_title = title_element.get_text(strip=True)
|
course_title = title_element.get_text(strip=True)
|
||||||
|
Reference in New Issue
Block a user