forked from cms-dev/cms
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathAddSubmission.py
executable file
·201 lines (164 loc) · 7.49 KB
/
AddSubmission.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/usr/bin/env python3
# Contest Management System - http://cms-dev.github.io/
# Copyright © 2015-2018 Stefano Maggiolo <s.maggiolo@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Utility to submit a solution for a user.
"""
import argparse
import logging
import sys
from cms import utf8_decoder, ServiceCoord
from cms.db import File, Participation, SessionGen, Submission, Task, User, \
ask_for_contest
from cms.db.filecacher import FileCacher
from cms.grading.languagemanager import filename_to_language
from cms.io import RemoteServiceClient
from cmscommon.datetime import make_datetime
logger = logging.getLogger(__name__)
def maybe_send_notification(submission_id):
"""Non-blocking attempt to notify a running ES of the submission"""
rs = RemoteServiceClient(ServiceCoord("EvaluationService", 0))
rs.connect()
rs.new_submission(submission_id=submission_id)
rs.disconnect()
def language_from_submitted_files(files):
"""Return the language inferred from the submitted files.
files ({str: str}): dictionary mapping the expected filename to a path in
the file system.
return (Language|None): the language inferred from the files.
raise (ValueError): if different files point to different languages, or if
it is impossible to extract the language from a file when it should be.
"""
# TODO: deduplicate with the code in SubmitHandler.
language = None
for filename in files.keys():
this_language = filename_to_language(files[filename])
if this_language is None and ".%l" in filename:
raise ValueError(
"Cannot recognize language for file `%s'." % filename)
if language is None:
language = this_language
elif this_language is not None and language != this_language:
raise ValueError("Mixed-language submission detected.")
return language
def add_submission(contest_id, username, task_name, timestamp, files):
file_cacher = FileCacher()
with SessionGen() as session:
participation = session.query(Participation)\
.join(Participation.user)\
.filter(Participation.contest_id == contest_id)\
.filter(User.username == username)\
.first()
if participation is None:
logging.critical("User `%s' does not exists or "
"does not participate in the contest.", username)
return False
task = session.query(Task)\
.filter(Task.contest_id == contest_id)\
.filter(Task.name == task_name)\
.first()
if task is None:
logging.critical("Unable to find task `%s'.", task_name)
return False
elements = set(task.submission_format)
for file_ in files:
if file_ not in elements:
logging.critical("File `%s' is not in the submission format "
"for the task.", file_)
return False
if any(element not in files for element in elements):
logger.warning("Not all files from the submission format were "
"provided.")
# files is now a subset of elements.
# We ensure we can infer a language if the task requires it.
language = None
need_lang = any(element.find(".%l") != -1 for element in elements)
if need_lang:
try:
language = language_from_submitted_files(files)
except ValueError as e:
logger.critical(e)
return False
if language is None:
# This might happen in case not all files were provided.
logger.critical("Unable to infer language from submission.")
return False
language_name = None if language is None else language.name
# Store all files from the arguments, and obtain their digests..
file_digests = {}
try:
for file_ in files:
digest = file_cacher.put_file_from_path(
files[file_],
"Submission file %s sent by %s at %d."
% (file_, username, timestamp))
file_digests[file_] = digest
except Exception as e:
logger.critical("Error while storing submission's file: %s.", e)
return False
# Create objects in the DB.
submission = Submission(make_datetime(timestamp), language_name,
participation=participation, task=task)
for filename, digest in file_digests.items():
session.add(File(filename, digest, submission=submission))
session.add(submission)
session.commit()
maybe_send_notification(submission.id)
return True
def main():
"""Parse arguments and launch process.
return (int): exit code of the program.
"""
parser = argparse.ArgumentParser(
description="Adds a submission to a contest in CMS.")
parser.add_argument("-c", "--contest-id", action="store", type=int,
help="id of contest where to add the submission")
parser.add_argument("-f", "--file", action="append", type=utf8_decoder,
help="in the form <name>:<file>, where name is the "
"name as required by CMS, and file is the name of "
"the file in the filesystem - may be specified "
"multiple times", required=True)
parser.add_argument("username", action="store", type=utf8_decoder,
help="user doing the submission")
parser.add_argument("task_name", action="store", type=utf8_decoder,
help="name of task the submission is for")
parser.add_argument("-t", "--timestamp", action="store", type=int,
help="timestamp of the submission in seconds from "
"epoch, e.g. `date +%%s` (now if not set)")
args = parser.parse_args()
if args.contest_id is None:
args.contest_id = ask_for_contest()
if args.timestamp is None:
import time
args.timestamp = time.time()
split_files = [file_.split(":", 1) for file_ in args.file]
if any(len(file_) != 2 for file_ in split_files):
parser.error("Invalid value for the file argument: format is "
"<name>:<file>.")
return 1
files = {}
for name, filename in split_files:
if name in files:
parser.error("Duplicate assignment for file `%s'." % name)
return 1
files[name] = filename
success = add_submission(contest_id=args.contest_id,
username=args.username,
task_name=args.task_name,
timestamp=args.timestamp,
files=files)
return 0 if success is True else 1
if __name__ == "__main__":
sys.exit(main())