otter-service-stdalone 1.1.5__py3-none-any.whl → 1.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otter_service_stdalone/__init__.py +1 -1
- otter_service_stdalone/app.py +47 -6
- otter_service_stdalone/grade_notebooks.py +33 -21
- otter_service_stdalone/index.html +19 -2
- otter_service_stdalone/scripts/web_socket.js +37 -3
- {otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/METADATA +1 -1
- {otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/RECORD +10 -10
- {otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/WHEEL +1 -1
- {otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/entry_points.txt +0 -0
- {otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.1.
|
1
|
+
__version__ = "1.1.7"
|
otter_service_stdalone/app.py
CHANGED
@@ -9,7 +9,7 @@ from otter_service_stdalone import fs_logging as log
|
|
9
9
|
from otter_service_stdalone import user_auth as u_auth
|
10
10
|
from otter_service_stdalone import grade_notebooks
|
11
11
|
from zipfile import ZipFile, ZIP_DEFLATED
|
12
|
-
import
|
12
|
+
from multiprocessing import Queue
|
13
13
|
|
14
14
|
|
15
15
|
__UPLOADS__ = "/tmp/uploads"
|
@@ -40,14 +40,14 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
|
40
40
|
pass # No action needed on incoming message
|
41
41
|
|
42
42
|
def on_close(self):
|
43
|
-
"""stop the periodic
|
43
|
+
"""stop the periodic callback on close
|
44
44
|
"""
|
45
45
|
close_code = self.close_code
|
46
46
|
close_reason = self.close_reason
|
47
|
-
log.write_logs("socket", close_code, f"{close_code}: {close_reason}", "debug", log_debug)
|
48
47
|
if self.get_secure_cookie("user"):
|
49
48
|
user_id = self.get_secure_cookie("user").decode('utf-8')
|
50
49
|
if user_id in session_callbacks and session_callbacks[user_id].callback:
|
50
|
+
log.write_logs("ws-close-removing-user!", close_code, f"{close_code}: {close_reason}", "debug", log_debug)
|
51
51
|
session_callbacks[user_id].stop()
|
52
52
|
session_callbacks.pop(user_id)
|
53
53
|
|
@@ -67,7 +67,9 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
|
67
67
|
user_messages_dict[result_id].append(q.get())
|
68
68
|
self.write_message({"messages": user_messages_dict})
|
69
69
|
except tornado.websocket.WebSocketClosedError:
|
70
|
-
log.write_logs("ws-error", "Web Socket
|
70
|
+
log.write_logs("ws-error", "Web Socket Close Error", "", "", log_error)
|
71
|
+
except Exception:
|
72
|
+
log.write_logs("ws-error", "Web Socket Error", "", "", log_error)
|
71
73
|
|
72
74
|
|
73
75
|
class HealthHandler(tornado.web.RequestHandler):
|
@@ -230,7 +232,7 @@ class Upload(BaseHandler):
|
|
230
232
|
|
231
233
|
Args:
|
232
234
|
tornado (tornado.web.RequestHandler): The upload request handler
|
233
|
-
"""
|
235
|
+
"""
|
234
236
|
@tornado.web.authenticated
|
235
237
|
def get(self):
|
236
238
|
# this just redirects to login and displays main page
|
@@ -279,7 +281,7 @@ class Upload(BaseHandler):
|
|
279
281
|
m += f"retrieve your files by submitting this code in the \"Results\" section to the right: {results_path}"
|
280
282
|
self.render("index.html", message=m)
|
281
283
|
try:
|
282
|
-
session_queues[user_id][results_path] =
|
284
|
+
session_queues[user_id][results_path] = Queue()
|
283
285
|
session_messages[user_id][results_path] = []
|
284
286
|
await g.grade(auto_p, notebooks_path, results_path, session_queues[user_id].get(results_path))
|
285
287
|
except Exception as e:
|
@@ -291,6 +293,44 @@ class Upload(BaseHandler):
|
|
291
293
|
self.render("index.html", message=m)
|
292
294
|
|
293
295
|
|
296
|
+
class RemoveProgressHandler(BaseHandler):
|
297
|
+
"""This handles requests to remove progress on a specific submission
|
298
|
+
|
299
|
+
Args:
|
300
|
+
tornado (tornado.web.RequestHandler): The request handler
|
301
|
+
"""
|
302
|
+
def set_default_headers(self):
|
303
|
+
"""Set CORS headers to allow cross-origin requests."""
|
304
|
+
self.set_header("Access-Control-Allow-Origin", "*") # Allow requests from any domain
|
305
|
+
self.set_header("Access-Control-Allow-Headers", "x-requested-with")
|
306
|
+
self.set_header("Access-Control-Allow-Methods", "DELETE, GET, POST, OPTIONS")
|
307
|
+
|
308
|
+
def options(self, *args):
|
309
|
+
"""Respond to OPTIONS requests for preflight in CORS."""
|
310
|
+
self.set_status(204)
|
311
|
+
self.finish()
|
312
|
+
|
313
|
+
@tornado.web.authenticated
|
314
|
+
def get(self):
|
315
|
+
# this just redirects to login and displays main page
|
316
|
+
self.render("index.html", message=None)
|
317
|
+
|
318
|
+
@tornado.web.authenticated
|
319
|
+
def delete(self, result_id):
|
320
|
+
"""this handles the post request and asynchronously launches the grader
|
321
|
+
"""
|
322
|
+
user = self.get_current_user()
|
323
|
+
user_id = user.decode('utf-8')
|
324
|
+
log.write_logs(result_id, f"Deleting Result: {result_id}", "", "debug", log_debug)
|
325
|
+
if user_id in session_queues and result_id in session_queues[user_id]:
|
326
|
+
del session_queues[user_id][result_id]
|
327
|
+
if user_id in session_messages and result_id in session_messages[user_id]:
|
328
|
+
del session_messages[user_id][result_id]
|
329
|
+
|
330
|
+
self.write({'message': f'Item {result_id} removed successfully'})
|
331
|
+
self.set_status(200)
|
332
|
+
|
333
|
+
|
294
334
|
settings = {
|
295
335
|
"cookie_secret": str(uuid.uuid4()),
|
296
336
|
"xsrf_cookies": True,
|
@@ -303,6 +343,7 @@ application = tornado.web.Application([
|
|
303
343
|
(r"/upload", Upload),
|
304
344
|
(r"/download", Download),
|
305
345
|
(r"/update", WebSocketHandler),
|
346
|
+
(r"/remove/([a-zA-Z0-9\-]+)", RemoveProgressHandler),
|
306
347
|
(r"/oauth_callback", GitHubOAuthHandler),
|
307
348
|
(r"/otterhealth", HealthHandler),
|
308
349
|
(r"/scripts/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "scripts")}),
|
@@ -2,6 +2,8 @@ import asyncio
|
|
2
2
|
from otter_service_stdalone import fs_logging as log, upload_handle as uh
|
3
3
|
import os
|
4
4
|
from otter.grade import main as grade
|
5
|
+
from multiprocessing import Process
|
6
|
+
from tornado.ioloop import PeriodicCallback
|
5
7
|
|
6
8
|
log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
|
7
9
|
log_count = f'{os.environ.get("ENVIRONMENT")}-count'
|
@@ -12,7 +14,7 @@ class GradeNotebooks():
|
|
12
14
|
"""The class contains the async grade method for executing
|
13
15
|
otter grader as well as a function for logging the number of
|
14
16
|
notebooks to be graded
|
15
|
-
"""
|
17
|
+
"""
|
16
18
|
def count_ipynb_files(self, directory, extension):
|
17
19
|
"""this count the files for logging purposes"""
|
18
20
|
count = 0
|
@@ -47,28 +49,38 @@ class GradeNotebooks():
|
|
47
49
|
f"Notebook Folder: {notebook_folder}",
|
48
50
|
"debug",
|
49
51
|
log_debug)
|
52
|
+
p = Process(target=grade,
|
53
|
+
kwargs = {
|
54
|
+
"name": "grader",
|
55
|
+
"autograder": p,
|
56
|
+
"paths": (notebook_folder,),
|
57
|
+
"containers": 10,
|
58
|
+
"timeout": 300,
|
59
|
+
"ext": "ipynb",
|
60
|
+
"output_dir": notebook_folder,
|
61
|
+
"result_queue": user_queue,
|
62
|
+
"summaries": True,
|
63
|
+
}
|
64
|
+
)
|
65
|
+
p.start()
|
50
66
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
# Periodically check if the process is alive
|
68
|
+
def check_if_finished():
|
69
|
+
if not p.is_alive():
|
70
|
+
log.write_logs(results_id, "Step 6: Grading: Finished",
|
71
|
+
f"{notebook_folder}",
|
72
|
+
"debug",
|
73
|
+
log_debug)
|
74
|
+
log.write_logs(results_id, f"Grading: Finished: {notebook_folder}",
|
75
|
+
"",
|
76
|
+
"info",
|
77
|
+
log_error)
|
78
|
+
periodic_callback.stop()
|
79
|
+
return True
|
80
|
+
|
81
|
+
periodic_callback = PeriodicCallback(check_if_finished, 500)
|
82
|
+
periodic_callback.start()
|
62
83
|
|
63
|
-
log.write_logs(results_id, "Step 6: Grading: Finished",
|
64
|
-
f"{notebook_folder}",
|
65
|
-
"debug",
|
66
|
-
log_debug)
|
67
|
-
log.write_logs(results_id, f"Grading: Finished: {notebook_folder}",
|
68
|
-
"",
|
69
|
-
"info",
|
70
|
-
log_error)
|
71
|
-
return True
|
72
84
|
except asyncio.TimeoutError:
|
73
85
|
raise Exception(f'Grading timed out for {notebook_folder}')
|
74
86
|
except Exception as e:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
|
-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
3
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
4
4
|
<title>Upload Form</title>
|
5
5
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
6
6
|
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
|
@@ -18,12 +18,18 @@
|
|
18
18
|
width: auto;
|
19
19
|
margin-bottom: 10px;
|
20
20
|
}
|
21
|
-
|
21
|
+
.fa-minus-circle, .fa-plus-circle {
|
22
|
+
position: relative;
|
23
|
+
top: 5px;
|
24
|
+
right: 4px
|
25
|
+
}
|
22
26
|
/* Styles for the header */
|
23
27
|
.header {
|
24
28
|
background-color: #f1f1f1;
|
25
29
|
padding: 10px;
|
26
30
|
cursor: pointer;
|
31
|
+
display: flex;
|
32
|
+
justify-content: space-between
|
27
33
|
}
|
28
34
|
|
29
35
|
/* Styles for the content */
|
@@ -33,6 +39,17 @@
|
|
33
39
|
padding: 10px;
|
34
40
|
display: none;
|
35
41
|
}
|
42
|
+
.close-btn {
|
43
|
+
background-color: transparent;
|
44
|
+
border: none;
|
45
|
+
font-size: 24px;
|
46
|
+
cursor: pointer;
|
47
|
+
margin-left: auto;
|
48
|
+
}
|
49
|
+
|
50
|
+
.close-btn:hover {
|
51
|
+
color: red;
|
52
|
+
}
|
36
53
|
|
37
54
|
|
38
55
|
</style>
|
@@ -61,14 +61,18 @@ function _setUpNewSubmission(submission_key, index, messages){
|
|
61
61
|
newMessageHeader.id = "header-" + submission_key
|
62
62
|
var toggleSign = document.createElement("span");
|
63
63
|
var submissionHeader = document.createElement("span");
|
64
|
+
var closeBtn = document.createElement("button")
|
64
65
|
submissionHeader.innerHTML = " Submission Progress: Submission #" + (index+1)
|
65
66
|
submissionHeader.id = "sub-header-" + submission_key
|
67
|
+
closeBtn.className = "close-btn"
|
68
|
+
closeBtn.innerHTML = "×"
|
66
69
|
newMessage.className = "collapsible"
|
67
70
|
newMessageHeader.className = "header"
|
68
71
|
toggleSign.innerHTML = '<i class="fas fa-minus-circle"></i>';
|
69
72
|
|
70
73
|
newMessageHeader.appendChild(toggleSign)
|
71
74
|
newMessageHeader.appendChild(submissionHeader)
|
75
|
+
newMessageHeader.appendChild(closeBtn)
|
72
76
|
newMessageContent.className = "content"
|
73
77
|
newMessageContent.style.display = "block"
|
74
78
|
newMessage.appendChild(newMessageHeader)
|
@@ -83,7 +87,7 @@ function _setUpNewSubmission(submission_key, index, messages){
|
|
83
87
|
newMessageContent.appendChild(li);
|
84
88
|
}
|
85
89
|
// Toggle the visibility of the scrollable content
|
86
|
-
newMessageHeader.addEventListener('click', () => {
|
90
|
+
newMessageHeader.addEventListener('click', (event) => {
|
87
91
|
if (newMessageContent.style.display == 'none') {
|
88
92
|
newMessageContent.style.display = 'block';
|
89
93
|
toggleSign.innerHTML = '<i class="fas fa-minus-circle"></i>';
|
@@ -92,6 +96,35 @@ function _setUpNewSubmission(submission_key, index, messages){
|
|
92
96
|
toggleSign.innerHTML = '<i class="fas fa-plus-circle"></i>';
|
93
97
|
}
|
94
98
|
});
|
99
|
+
|
100
|
+
|
101
|
+
function getXsrfToken() {
|
102
|
+
return document.querySelector('input[name="_xsrf"]').value
|
103
|
+
}
|
104
|
+
|
105
|
+
closeBtn.addEventListener('click', (event) => {
|
106
|
+
const removeUri = `/remove/${submission_key}`;
|
107
|
+
fetch(removeUri, {
|
108
|
+
method: 'DELETE',
|
109
|
+
credentials: 'include',
|
110
|
+
headers: {
|
111
|
+
'Content-Type': 'application/json',
|
112
|
+
'X-XSRFToken': getXsrfToken()
|
113
|
+
},
|
114
|
+
})
|
115
|
+
.then(response => {
|
116
|
+
if (response.ok) {
|
117
|
+
newMessage.remove();
|
118
|
+
} else {
|
119
|
+
console.error(`Failed to remove item ${submission_key}`);
|
120
|
+
}
|
121
|
+
})
|
122
|
+
.catch(error => {
|
123
|
+
console.error('Failed to remove item:', error);
|
124
|
+
});
|
125
|
+
|
126
|
+
});
|
127
|
+
|
95
128
|
return newMessage;
|
96
129
|
}
|
97
130
|
|
@@ -124,6 +157,7 @@ function _updateSubmission(submission_key, index, messages){
|
|
124
157
|
newMessageContent.style.display = 'none';
|
125
158
|
}
|
126
159
|
}
|
127
|
-
|
128
|
-
|
160
|
+
function getXsrfToken() {
|
161
|
+
return xsrfToken; // Use the token variable from the script
|
162
|
+
}
|
129
163
|
connectWebSocket()
|
@@ -1,12 +1,12 @@
|
|
1
|
-
otter_service_stdalone/__init__.py,sha256=
|
1
|
+
otter_service_stdalone/__init__.py,sha256=p2qAfdU7zHOShJi2f1FW-_7Tbm0-KCPE7CI7284QiHg,22
|
2
2
|
otter_service_stdalone/access_sops_keys.py,sha256=nboU5aZ84Elrm5vO0dMgpIF5LLcnecpNAwpxKvj6DvU,2129
|
3
|
-
otter_service_stdalone/app.py,sha256=
|
3
|
+
otter_service_stdalone/app.py,sha256=D0Trfw0wwwZaJo9MKPqQptUXhFosn2bq0V_m7dLOsXg,15283
|
4
4
|
otter_service_stdalone/fs_logging.py,sha256=IKFZkc5TmpI6G3vTYFAU9jDjQ-GT5aRxk8kdZ0h0kJE,2390
|
5
|
-
otter_service_stdalone/grade_notebooks.py,sha256=
|
6
|
-
otter_service_stdalone/index.html,sha256=
|
5
|
+
otter_service_stdalone/grade_notebooks.py,sha256=s-nequcOnpsZD4aQeUx_EFbnfKurVj7J8oCTK6Dix1E,3596
|
6
|
+
otter_service_stdalone/index.html,sha256=eEbFGxbV-plE5bAggGYHTv1q7yqoTN4kJbzL729uMmk,7276
|
7
7
|
otter_service_stdalone/upload_handle.py,sha256=PbpQEyUIPKercJ9hegKwvxHBvSc9uylhIfwjvHybjs0,5061
|
8
8
|
otter_service_stdalone/user_auth.py,sha256=L9Kfj1BsQttAteHhRn71IUY8WX9nvBy3MXVGq1yjTtE,4253
|
9
|
-
otter_service_stdalone/scripts/web_socket.js,sha256=
|
9
|
+
otter_service_stdalone/scripts/web_socket.js,sha256=xoG7kt5xU9jS1vqw7_NuPLShzEqHXtQyaCnmvhCOrUk,6081
|
10
10
|
otter_service_stdalone/secrets/gh_key.dev.yaml,sha256=ORUVDu8SDcv0OE2ThwROppeg7y8oLkJJbPTCMn0s5l0,1138
|
11
11
|
otter_service_stdalone/secrets/gh_key.local.yaml,sha256=NtPXXyGf1iSgJ9Oa2ahvIEf_fcmflB3dwd3LWyCgxis,1138
|
12
12
|
otter_service_stdalone/secrets/gh_key.prod.yaml,sha256=6vgLqHzDp8cVAOJlpSXGDTUjSI6EyCb6f1-SSVG2rqw,1138
|
@@ -14,8 +14,8 @@ otter_service_stdalone/secrets/gh_key.staging.yaml,sha256=cKVqj4dcwuz2LhXwMNQy_1
|
|
14
14
|
otter_service_stdalone/static_files/README_DO_NOT_DISTRIBUTE.txt,sha256=eMqBa1du1u0c07fuG3Eu9DDHuixRTFEbiQwrlvAnL1Y,353
|
15
15
|
otter_service_stdalone/static_templates/403.html,sha256=7eO3XQsEkl4nF8PEeFkLwCzGBfdZ3kkkeu_Kgpgbh0k,1440
|
16
16
|
otter_service_stdalone/static_templates/500.html,sha256=t6DeEMp8piSWyBToHb_JpTrw3GStAHFrozlmeuXyamg,1421
|
17
|
-
otter_service_stdalone-1.1.
|
18
|
-
otter_service_stdalone-1.1.
|
19
|
-
otter_service_stdalone-1.1.
|
20
|
-
otter_service_stdalone-1.1.
|
21
|
-
otter_service_stdalone-1.1.
|
17
|
+
otter_service_stdalone-1.1.7.dist-info/METADATA,sha256=Lc6rrx3zJDV1Ops-iRN04Q6NLfATZfYHlnHLeuM880g,1467
|
18
|
+
otter_service_stdalone-1.1.7.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
19
|
+
otter_service_stdalone-1.1.7.dist-info/entry_points.txt,sha256=cx447chuIEl8ly9jEoF5-2xNhaKsWcIMDdhUMH_00qQ,75
|
20
|
+
otter_service_stdalone-1.1.7.dist-info/top_level.txt,sha256=6UP22fD4OhbLt23E01v8Kvjn44vPRbyTIg_GqMYL-Ng,23
|
21
|
+
otter_service_stdalone-1.1.7.dist-info/RECORD,,
|
{otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/entry_points.txt
RENAMED
File without changes
|
{otter_service_stdalone-1.1.5.dist-info → otter_service_stdalone-1.1.7.dist-info}/top_level.txt
RENAMED
File without changes
|