Coverage for model/copycat.py: 100%
118 statements
« prev ^ index » next coverage.py v7.9.2, created at 2026-07-24 16:42 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2026-07-24 16:42 +0000
1import os
2import re
3import logging
4import threading
5from typing import Dict
6import httpx
7import mosspy
8from fastapi import APIRouter, Depends
10from mongo import *
11from mongo.utils import *
12from .utils import *
13from .auth import login_required
14from .schemas import GetReportQuery, DetectBody
16__all__ = ['copycat_router']
18copycat_router = APIRouter()
21def is_valid_url(url):
22 regex = re.compile(
23 r'^https?://'
24 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
25 r'localhost|'
26 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
27 r'(?::\d+)?'
28 r'(?:/?|[/?]\S+)$',
29 re.IGNORECASE,
30 )
31 return url is not None and regex.search(url)
34def get_report_task(user, problem_id, student_dict: Dict):
35 submissions = Submission.filter(
36 user=user,
37 offset=0,
38 count=-1,
39 status=0,
40 problem=problem_id,
41 )
43 last_cc_submission = {}
44 last_python_submission = {}
45 for submission in submissions:
46 s = Submission(submission.id)
47 if s.user.username in student_dict:
48 if s.language in [0, 1
49 ] and s.user.username not in last_cc_submission:
50 last_cc_submission[
51 submission.user.username] = s.main_code_path()
52 elif s.language in [
53 2
54 ] and s.user.username not in last_python_submission:
55 last_python_submission[
56 submission.user.username] = s.main_code_path()
58 moss_userid = 97089070
59 logger = logging.getLogger(__name__)
60 problem = Problem(problem_id)
62 cpp_report_url = ''
63 python_report_url = ''
64 if problem.allowed_language != 4:
65 m1 = mosspy.Moss(moss_userid, "cc")
66 for user, code_path in last_cc_submission.items():
67 logger.info(f'send {user} {code_path}')
68 m1.addFile(code_path)
69 response = m1.send()
70 if is_valid_url(response):
71 cpp_report_url = response
72 else:
73 logger.info(f"[copycat] {response}")
74 cpp_report_url = ''
76 if problem.allowed_language >= 4:
77 m2 = mosspy.Moss(moss_userid, "python")
78 for user, code_path in last_python_submission.items():
79 logger.info(f'send {user} {code_path}')
80 m2.addFile(code_path)
81 response = m2.send()
82 if is_valid_url(response):
83 python_report_url = response
84 else:
85 logger.info(f"[copycat] {response}")
86 python_report_url = ''
88 if cpp_report_url != '':
89 mosspy.download_report(
90 cpp_report_url,
91 f"submissions_report/{problem_id}/cpp_report/",
92 connections=8,
93 log_level=10,
94 )
95 if python_report_url != '':
96 mosspy.download_report(
97 python_report_url,
98 f"submissions_report/{problem_id}/python_report/",
99 connections=8,
100 log_level=10,
101 )
103 problem.obj.update(
104 cpp_report_url=cpp_report_url,
105 python_report_url=python_report_url,
106 moss_status=2,
107 )
110def get_report_by_url(url: str):
111 try:
112 response = httpx.get(url)
113 return response.text
114 except httpx.InvalidURL:
115 return 'No report.'
118@copycat_router.get('')
119def get_report(query: GetReportQuery = Depends(),
120 user=Depends(login_required)):
121 course = query.course
122 problem_id = query.problem_id
123 if not (problem_id and course):
124 return HTTPError(
125 'missing arguments! (In HTTP GET argument format)',
126 400,
127 data={'need': ['course', 'problemId']},
128 )
129 try:
130 problem = Problem(int(problem_id))
131 except ValueError:
132 return HTTPError('problemId must be integer', 400)
134 course = Course(course)
135 if not course.permission(user, Course.Permission.GRADE):
136 return HTTPError('Forbidden.', 403)
137 if not problem:
138 return HTTPError('Problem not exist.', 404)
139 if not course:
140 return HTTPError('Course not found.', 404)
142 cpp_report_url = problem.cpp_report_url
143 python_report_url = problem.python_report_url
145 if problem.moss_status == 0:
146 return HTTPError(
147 "No report found. Please make a post request to copycat api to generate a report",
148 404,
149 data={},
150 )
151 elif problem.moss_status == 1:
152 return HTTPResponse("Report generating...", data={})
153 else:
154 cpp_report = get_report_by_url(cpp_report_url)
155 python_report = get_report_by_url(python_report_url)
156 return HTTPResponse(
157 "Success.",
158 data={
159 "cpp_report": cpp_report,
160 "python_report": python_report
161 },
162 )
165@copycat_router.post('')
166def detect(body: DetectBody, user=Depends(login_required)):
167 course = body.course
168 problem_id = body.problem_id
169 student_nicknames = body.student_nicknames
170 if not (problem_id and course and type(student_nicknames) is dict):
171 return HTTPError(
172 'missing arguments! (In Json format)',
173 400,
174 data={'need': ['course', 'problemId', 'studentNicknames']},
175 )
177 course = Course(course)
178 problem = Problem(problem_id)
180 student_dict = {}
181 for student, nickname in student_nicknames.items():
182 if not User(student):
183 return HTTPResponse(f'User: {student} not found.', 404)
184 student_dict[student] = nickname
185 if not student_dict:
186 return HTTPResponse('Empty student list.', 404)
187 if not course.permission(user, Course.Permission.GRADE):
188 return HTTPError('Forbidden.', 403)
189 if not problem:
190 return HTTPError('Problem not exist.', 404)
191 if not course:
192 return HTTPError('Course not found.', 404)
194 problem = Problem(problem_id)
195 problem.update(cpp_report_url="", python_report_url="", moss_status=1)
196 if not is_testing():
197 threading.Thread(
198 target=get_report_task,
199 args=(user, problem_id, student_dict),
200 ).start()
202 return HTTPResponse('Success.')