feat: joj worker
This commit is contained in:
parent
1bdcab4b6c
commit
9eefd8bc01
|
@ -1,11 +1,17 @@
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import ntpath
|
import ntpath
|
||||||
|
import os
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from canvasapi.assignment import Assignment
|
||||||
|
|
||||||
from joint_teapot import Teapot, logger
|
from joint_teapot import Teapot, logger
|
||||||
|
from joint_teapot.utils.main import default_repo_name_convertor, first, percentile
|
||||||
|
|
||||||
|
|
||||||
class MyTeapot(Teapot):
|
class VE482Teapot(Teapot):
|
||||||
def ve482p1(self) -> None:
|
def p1_check(self) -> None:
|
||||||
fault_repos = []
|
fault_repos = []
|
||||||
for repo_name in self.gitea.get_all_repo_names():
|
for repo_name in self.gitea.get_all_repo_names():
|
||||||
if not repo_name.endswith("p1"):
|
if not repo_name.endswith("p1"):
|
||||||
|
@ -48,7 +54,68 @@ class MyTeapot(Teapot):
|
||||||
fault_repos.append(repo_name)
|
fault_repos.append(repo_name)
|
||||||
logger.info(f"{len(fault_repos)} fault repo(s): {fault_repos}")
|
logger.info(f"{len(fault_repos)} fault repo(s): {fault_repos}")
|
||||||
|
|
||||||
|
def p1_submit(self) -> None:
|
||||||
|
res_dict = {}
|
||||||
|
assignment_name = "p1.3"
|
||||||
|
assignment = first(self.canvas.assignments, lambda x: x.name == assignment_name)
|
||||||
|
if assignment is None:
|
||||||
|
logger.info(f"Canvas assignment {assignment_name} not found")
|
||||||
|
return
|
||||||
|
assignment = cast(Assignment, assignment)
|
||||||
|
students = self.canvas.students
|
||||||
|
for submission in assignment.get_submissions():
|
||||||
|
student = first(students, lambda x: x.id == submission.user_id)
|
||||||
|
if student is None:
|
||||||
|
continue
|
||||||
|
repo_name = default_repo_name_convertor(student) + "-p1"
|
||||||
|
repo_dir = os.path.join(self.git.repos_dir, repo_name)
|
||||||
|
base_score, base_url = self.joj.submit_dir(
|
||||||
|
"https://joj.sjtu.edu.cn/d/ve482_fall_2021/p/61c2d0b27fe7290006b27034",
|
||||||
|
repo_dir,
|
||||||
|
"make",
|
||||||
|
)
|
||||||
|
bonus_score, bonus_url = self.joj.submit_dir(
|
||||||
|
"https://joj.sjtu.edu.cn/d/ve482_fall_2021/p/61c2d49e7fe7290006b2703e",
|
||||||
|
repo_dir,
|
||||||
|
"make",
|
||||||
|
)
|
||||||
|
total_score = base_score / 520 * 100 + bonus_score / 220 * 30
|
||||||
|
res_dict[student.sis_login_id] = total_score
|
||||||
|
data = {
|
||||||
|
"submission": {"posted_grade": round(total_score, 2)},
|
||||||
|
"comment": {
|
||||||
|
"text_comment": (
|
||||||
|
f"base score: {base_score} / 520, url: {base_url}\n"
|
||||||
|
f"bonus score: {bonus_score} / 220, url: {bonus_url}\n"
|
||||||
|
f"total score: {base_score} / 520 * 100 + "
|
||||||
|
f"{bonus_score} / 220 * 30"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
submission.edit(**data)
|
||||||
|
float_grades = list(res_dict.values())
|
||||||
|
summary = [
|
||||||
|
min(float_grades),
|
||||||
|
percentile(float_grades, 0.25),
|
||||||
|
percentile(float_grades, 0.5),
|
||||||
|
percentile(float_grades, 0.75),
|
||||||
|
max(float_grades),
|
||||||
|
]
|
||||||
|
average_grade = sum(float_grades) / len(float_grades)
|
||||||
|
logger.info(
|
||||||
|
f"Grades summary: "
|
||||||
|
f"Min: {summary[0]:.2f}, "
|
||||||
|
f"Q1: {summary[1]:.2f}, "
|
||||||
|
f"Q2: {summary[2]:.2f}, "
|
||||||
|
f"Q3: {summary[3]:.2f}, "
|
||||||
|
f"Max: {summary[4]:.2f}, "
|
||||||
|
f"Average: {average_grade:.2f}"
|
||||||
|
)
|
||||||
|
json.dump(
|
||||||
|
res_dict, open("ve482_p1_grade.json", "w"), ensure_ascii=False, indent=4
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
teapot = MyTeapot()
|
teapot = VE482Teapot()
|
||||||
teapot.ve482p1()
|
teapot.p1_submit()
|
||||||
|
|
|
@ -19,6 +19,9 @@ class Settings(BaseSettings):
|
||||||
# git
|
# git
|
||||||
repos_dir: str = "./repos"
|
repos_dir: str = "./repos"
|
||||||
|
|
||||||
|
# sid
|
||||||
|
joj_sid: str = ""
|
||||||
|
|
||||||
# log file
|
# log file
|
||||||
log_file_path: str = "joint-teapot.log"
|
log_file_path: str = "joint-teapot.log"
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from joint_teapot.config import settings
|
||||||
from joint_teapot.utils.logger import logger
|
from joint_teapot.utils.logger import logger
|
||||||
from joint_teapot.utils.main import first
|
from joint_teapot.utils.main import first
|
||||||
from joint_teapot.workers import Canvas, Git, Gitea
|
from joint_teapot.workers import Canvas, Git, Gitea
|
||||||
|
from joint_teapot.workers.joj import JOJ
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ class Teapot:
|
||||||
_canvas = None
|
_canvas = None
|
||||||
_gitea = None
|
_gitea = None
|
||||||
_git = None
|
_git = None
|
||||||
|
_joj = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def canvas(self) -> Canvas:
|
def canvas(self) -> Canvas:
|
||||||
|
@ -58,6 +60,12 @@ class Teapot:
|
||||||
self._git = Git()
|
self._git = Git()
|
||||||
return self._git
|
return self._git
|
||||||
|
|
||||||
|
@property
|
||||||
|
def joj(self) -> JOJ:
|
||||||
|
if not self._joj:
|
||||||
|
self._joj = JOJ()
|
||||||
|
return self._joj
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Settings loaded. "
|
"Settings loaded. "
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import math
|
import math
|
||||||
|
import re
|
||||||
from typing import Callable, Iterable, Optional, TypeVar
|
from typing import Callable, Iterable, Optional, TypeVar
|
||||||
|
|
||||||
|
from canvasapi.user import User
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,3 +27,11 @@ def percentile(
|
||||||
d0 = key(N[int(f)]) * (c - k)
|
d0 = key(N[int(f)]) * (c - k)
|
||||||
d1 = key(N[int(c)]) * (k - f)
|
d1 = key(N[int(c)]) * (k - f)
|
||||||
return d0 + d1
|
return d0 + d1
|
||||||
|
|
||||||
|
|
||||||
|
def default_repo_name_convertor(user: User) -> str:
|
||||||
|
sis_login_id, name = user.sis_login_id, user.name
|
||||||
|
eng = re.sub("[\u4e00-\u9fa5]", "", name)
|
||||||
|
eng = eng.replace(",", "")
|
||||||
|
eng = "".join([word[0].capitalize() + word[1:] for word in eng.split()])
|
||||||
|
return f"{eng}{sis_login_id}"
|
||||||
|
|
|
@ -3,13 +3,12 @@ import sys
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from git.exc import GitCommandError
|
|
||||||
|
|
||||||
from joint_teapot.utils.logger import logger
|
from joint_teapot.utils.logger import logger
|
||||||
|
|
||||||
current_path = sys.path[0]
|
current_path = sys.path[0]
|
||||||
sys.path.remove(current_path)
|
sys.path.remove(current_path)
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
from git.exc import GitCommandError
|
||||||
|
|
||||||
sys.path.insert(0, current_path)
|
sys.path.insert(0, current_path)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import re
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
@ -11,7 +10,7 @@ from focs_gitea.rest import ApiException
|
||||||
|
|
||||||
from joint_teapot.config import settings
|
from joint_teapot.config import settings
|
||||||
from joint_teapot.utils.logger import logger
|
from joint_teapot.utils.logger import logger
|
||||||
from joint_teapot.utils.main import first
|
from joint_teapot.utils.main import default_repo_name_convertor, first
|
||||||
|
|
||||||
|
|
||||||
class PermissionEnum(Enum):
|
class PermissionEnum(Enum):
|
||||||
|
@ -20,14 +19,6 @@ class PermissionEnum(Enum):
|
||||||
admin = "admin"
|
admin = "admin"
|
||||||
|
|
||||||
|
|
||||||
def default_repo_name_convertor(user: User) -> Optional[str]:
|
|
||||||
sis_login_id, name = user.sis_login_id, user.name
|
|
||||||
eng = re.sub("[\u4e00-\u9fa5]", "", name)
|
|
||||||
eng = eng.replace(",", "")
|
|
||||||
eng = "".join([word[0].capitalize() + word[1:] for word in eng.split()])
|
|
||||||
return f"{eng}{sis_login_id}"
|
|
||||||
|
|
||||||
|
|
||||||
def list_all(method: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
def list_all(method: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||||
all_res = []
|
all_res = []
|
||||||
page = 1
|
page = 1
|
||||||
|
|
|
@ -1,2 +1,52 @@
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from colorama import Fore, Style, init
|
||||||
|
from joj_submitter import JOJSubmitter, Language
|
||||||
|
|
||||||
|
from joint_teapot.config import settings
|
||||||
|
from joint_teapot.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class JOJ:
|
class JOJ:
|
||||||
...
|
def __init__(self, sid: str = settings.joj_sid):
|
||||||
|
init()
|
||||||
|
self.submitter = JOJSubmitter(sid, logger)
|
||||||
|
|
||||||
|
def submit_dir(self, problem_url: str, path: str, lang: str) -> Tuple[int, str]:
|
||||||
|
exclude_paths = [".git"]
|
||||||
|
zip_buffer = io.BytesIO()
|
||||||
|
zip_buffer.name = f"{os.path.basename(path)}.zip"
|
||||||
|
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED) as zip_file:
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
dirs[:] = [d for d in dirs if d not in exclude_paths]
|
||||||
|
for file in files:
|
||||||
|
zip_file.write(
|
||||||
|
os.path.join(root, file),
|
||||||
|
os.path.relpath(os.path.join(root, file), path),
|
||||||
|
)
|
||||||
|
zip_buffer.seek(0)
|
||||||
|
response = self.submitter.upload_file(problem_url, zip_buffer, lang)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(
|
||||||
|
f"{path} submit to JOJ error, status code {response.status_code}"
|
||||||
|
)
|
||||||
|
return -1, ""
|
||||||
|
logger.info(f"{path} submit to JOJ succeed, record url {response.url}")
|
||||||
|
record = self.submitter.get_status(response.url)
|
||||||
|
fore_color = Fore.RED if record.status != "Accepted" else Fore.GREEN
|
||||||
|
logger.info(
|
||||||
|
f"status: {fore_color}{record.status}{Style.RESET_ALL}, "
|
||||||
|
+ f"accept number: {Fore.BLUE}{record.accepted_count}{Style.RESET_ALL}, "
|
||||||
|
+ f"score: {Fore.BLUE}{record.score}{Style.RESET_ALL}, "
|
||||||
|
+ f"total time: {Fore.BLUE}{record.total_time}{Style.RESET_ALL}, "
|
||||||
|
+ f"peak memory: {Fore.BLUE}{record.peak_memory}{Style.RESET_ALL}"
|
||||||
|
)
|
||||||
|
score_int = 0
|
||||||
|
try:
|
||||||
|
score_int = int(record.score)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return score_int, response.url
|
||||||
|
|
Loading…
Reference in New Issue
Block a user