feat: canvas score utils
This commit is contained in:
parent
7fa19c3cb9
commit
2dbe321d43
|
@ -1,6 +1,7 @@
|
|||
__version__ = "0.0.0"
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from typer import Typer, echo
|
||||
|
@ -66,22 +67,39 @@ def checkout_to_repos_by_release_name(
|
|||
"close-all-issues", help="close all issues and pull requests in gitea organization"
|
||||
)
|
||||
def close_all_issues() -> None:
|
||||
teapot.close_all_issues()
|
||||
teapot.gitea.close_all_issues()
|
||||
|
||||
|
||||
@app.command("archieve-all-repos", help="archieve all repos in gitea organization")
|
||||
def archieve_all_repos() -> None:
|
||||
teapot.archieve_all_repos()
|
||||
teapot.gitea.archieve_all_repos()
|
||||
|
||||
|
||||
@app.command("get-no-collaborator-repos", help="list all repos with no collaborators")
|
||||
def get_no_collaborator_repos() -> None:
|
||||
teapot.get_no_collaborator_repos()
|
||||
teapot.gitea.get_no_collaborator_repos()
|
||||
|
||||
|
||||
@app.command("get-no-commit-repos", help="list all repos with no commit")
|
||||
def get_no_commit_repos() -> None:
|
||||
teapot.get_no_commit_repos()
|
||||
teapot.gitea.get_no_commit_repos()
|
||||
|
||||
|
||||
@app.command(
|
||||
"prepare-assignment-dir",
|
||||
help='prepare assignment dir from extracted canvas "Download Submissions" zip',
|
||||
)
|
||||
def prepare_assignment_dir(dir: Path) -> None:
|
||||
teapot.canvas.prepare_assignment_dir(str(dir))
|
||||
|
||||
|
||||
@app.command(
|
||||
"upload-assignment-scores",
|
||||
help="upload assignment scores to canvas from score file (SCORE.txt by default), "
|
||||
+ "read the first line as score, the rest as comments",
|
||||
)
|
||||
def upload_assignment_scores(dir: Path, assignment_name: str) -> None:
|
||||
teapot.canvas.upload_assignment_scores(str(dir), assignment_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -68,8 +68,13 @@ class Teapot:
|
|||
)
|
||||
|
||||
def create_teams_and_repos_by_canvas_groups(self) -> List[str]:
|
||||
def convertor(name: str) -> str:
|
||||
team_name, number_str = name.split(" ")
|
||||
number = int(number_str)
|
||||
return f"{team_name}-{number:02}"
|
||||
|
||||
return self.gitea.create_teams_and_repos_by_canvas_groups(
|
||||
self.canvas.students, self.canvas.groups
|
||||
self.canvas.students, self.canvas.groups, convertor, convertor
|
||||
)
|
||||
|
||||
def get_public_key_of_all_canvas_students(self) -> List[str]:
|
||||
|
@ -116,18 +121,6 @@ class Teapot:
|
|||
self.git.repo_clean_and_checkout(repo_name, f"tags/{release['tag_name']}")
|
||||
return failed_repos
|
||||
|
||||
def close_all_issues(self) -> None:
|
||||
self.gitea.close_all_issues()
|
||||
|
||||
def archieve_all_repos(self) -> None:
|
||||
self.gitea.archieve_all_repos()
|
||||
|
||||
def get_no_collaborator_repos(self) -> None:
|
||||
self.gitea.get_no_collaborator_repos()
|
||||
|
||||
def get_no_commit_repos(self) -> None:
|
||||
self.gitea.get_no_commit_repos()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
teapot = Teapot()
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import os
|
||||
from glob import glob
|
||||
|
||||
from canvasapi import Canvas as PyCanvas
|
||||
from patoolib import extract_archive
|
||||
from patoolib.util import PatoolError
|
||||
|
||||
from joint_teapot.config import settings
|
||||
from joint_teapot.utils.logger import logger
|
||||
|
||||
# from canvasapi.group import Group, GroupMembership
|
||||
from joint_teapot.utils.main import first
|
||||
|
||||
|
||||
class Canvas:
|
||||
|
@ -11,12 +15,13 @@ class Canvas:
|
|||
self,
|
||||
access_token: str = settings.canvas_access_token,
|
||||
course_id: int = settings.canvas_course_id,
|
||||
score_filename: str = "SCORE.txt",
|
||||
):
|
||||
self.canvas = PyCanvas("https://umjicanvas.com/", access_token)
|
||||
self.course = self.canvas.get_course(course_id)
|
||||
logger.info(f"Canvas course loaded. {self.course}")
|
||||
self.students = self.course.get_users(
|
||||
enrollment_type=["student", "observer"], include=["email"]
|
||||
enrollment_type=["student"], include=["email"]
|
||||
)
|
||||
for attr in ["sis_login_id", "sortable_name", "name"]:
|
||||
if not hasattr(self.students[0], attr):
|
||||
|
@ -28,8 +33,57 @@ class Canvas:
|
|||
logger.debug(f"Canvas assignments loaded")
|
||||
self.groups = self.course.get_groups()
|
||||
logger.debug(f"Canvas groups loaded")
|
||||
self.score_filename = score_filename
|
||||
logger.debug("Canvas initialized")
|
||||
|
||||
def prepare_assignment_dir(self, dir: str, create_score_file: bool = True) -> None:
|
||||
login_ids = {stu.id: stu.login_id for stu in self.students}
|
||||
for v in login_ids.values():
|
||||
new_path = os.path.join(dir, v)
|
||||
if not os.path.exists(new_path):
|
||||
os.mkdir(new_path)
|
||||
for path in glob(os.path.join(dir, "*")):
|
||||
file_name = os.path.basename(path)
|
||||
if "_" not in file_name:
|
||||
continue
|
||||
segments = file_name.split("_")
|
||||
if segments[1] == "late":
|
||||
file_id = int(segments[2])
|
||||
student = first(
|
||||
self.students, lambda x: x.login_id == login_ids[file_id]
|
||||
)
|
||||
logger.info(f"{student} submits late")
|
||||
else:
|
||||
file_id = int(segments[1])
|
||||
target_dir = os.path.join(dir, login_ids[file_id])
|
||||
try:
|
||||
extract_archive(path, outdir=target_dir, verbosity=-1)
|
||||
os.remove(path)
|
||||
except PatoolError:
|
||||
os.rename(path, os.path.join(target_dir, file_name))
|
||||
if create_score_file:
|
||||
open(os.path.join(target_dir, self.score_filename), mode="w")
|
||||
|
||||
def upload_assignment_scores(self, dir: str, assignment_name: str) -> None:
|
||||
assignment = first(self.assignments, lambda x: x.name == assignment_name)
|
||||
if assignment is None:
|
||||
logger.info(f"Canvas assignment {assignment_name} not found")
|
||||
return
|
||||
for submission in assignment.get_submissions():
|
||||
student = first(self.students, lambda x: x.id == submission.user_id)
|
||||
if student is None:
|
||||
continue
|
||||
score_file_path = os.path.join(
|
||||
dir, student.sis_login_id, self.score_filename
|
||||
)
|
||||
score, *comments = list(open(score_file_path))
|
||||
data = {
|
||||
"submission": {"posted_grade": float(score)},
|
||||
"comment": {"text_comment": "".join(comments)},
|
||||
}
|
||||
logger.info(f"{assignment} {student} {data.__repr__()}")
|
||||
submission.edit(**data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
canvas = Canvas()
|
||||
|
|
|
@ -151,43 +151,51 @@ class Gitea:
|
|||
permission: PermissionEnum = PermissionEnum.write,
|
||||
) -> List[str]:
|
||||
repo_names = []
|
||||
teams = list_all(self.organization_api.org_list_teams, self.org_name)
|
||||
repos = list_all(self.organization_api.org_list_repos, self.org_name)
|
||||
group: Group
|
||||
for group in groups:
|
||||
team_name = team_name_convertor(group.name)
|
||||
repo_name = repo_name_convertor(group.name)
|
||||
if team_name is None or repo_name is None:
|
||||
continue
|
||||
repo_names.append(repo_name)
|
||||
team = self.organization_api.org_create_team(
|
||||
self.org_name,
|
||||
body={
|
||||
"can_create_org_repo": False,
|
||||
"includes_all_repositories": False,
|
||||
"name": team_name,
|
||||
"permission": permission.value,
|
||||
"units": [
|
||||
"repo.code",
|
||||
"repo.issues",
|
||||
"repo.ext_issues",
|
||||
"repo.wiki",
|
||||
"repo.pulls",
|
||||
"repo.releases",
|
||||
"repo.projects",
|
||||
"repo.ext_wiki",
|
||||
],
|
||||
},
|
||||
)
|
||||
repo = self.organization_api.create_org_repo(
|
||||
self.org_name,
|
||||
body={
|
||||
"auto_init": False,
|
||||
"default_branch": "master",
|
||||
"name": repo_name,
|
||||
"private": True,
|
||||
"template": False,
|
||||
"trust_model": "default",
|
||||
},
|
||||
)
|
||||
team = first(teams, lambda team: team.name == team_name)
|
||||
if team is None:
|
||||
team = self.organization_api.org_create_team(
|
||||
self.org_name,
|
||||
body={
|
||||
"can_create_org_repo": False,
|
||||
"includes_all_repositories": False,
|
||||
"name": team_name,
|
||||
"permission": permission.value,
|
||||
"units": [
|
||||
"repo.code",
|
||||
"repo.issues",
|
||||
"repo.ext_issues",
|
||||
"repo.wiki",
|
||||
"repo.pulls",
|
||||
"repo.releases",
|
||||
"repo.projects",
|
||||
"repo.ext_wiki",
|
||||
],
|
||||
},
|
||||
)
|
||||
logger.info(f"{self.org_name}/{team_name} created")
|
||||
repo = first(repos, lambda repo: repo.name == repo_name)
|
||||
if repo is None:
|
||||
repo_names.append(repo_name)
|
||||
repo = self.organization_api.create_org_repo(
|
||||
self.org_name,
|
||||
body={
|
||||
"auto_init": False,
|
||||
"default_branch": "master",
|
||||
"name": repo_name,
|
||||
"private": True,
|
||||
"template": False,
|
||||
"trust_model": "default",
|
||||
},
|
||||
)
|
||||
logger.info(f"Team {team_name} created")
|
||||
self.organization_api.org_add_team_repository(
|
||||
team.id, self.org_name, repo_name
|
||||
)
|
||||
|
|
|
@ -2,5 +2,6 @@ canvasapi>=2.2.0
|
|||
focs_gitea>=1.0.0
|
||||
GitPython>=3.1.18
|
||||
loguru>=0.5.3
|
||||
patool>=1.12
|
||||
pydantic[dotenv]>=1.8.1
|
||||
typer[all]>=0.3.2
|
||||
|
|
Loading…
Reference in New Issue
Block a user