otter-service-stdalone 1.1.4__py3-none-any.whl → 1.1.6__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 +49 -5
- 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.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/METADATA +1 -1
- {otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/RECORD +10 -10
- {otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/WHEEL +1 -1
- {otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/entry_points.txt +0 -0
- {otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.1.
|
1
|
+
__version__ = "1.1.6"
|
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,11 +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
|
+
close_code = self.close_code
|
46
|
+
close_reason = self.close_reason
|
45
47
|
if self.get_secure_cookie("user"):
|
46
48
|
user_id = self.get_secure_cookie("user").decode('utf-8')
|
47
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)
|
48
51
|
session_callbacks[user_id].stop()
|
49
52
|
session_callbacks.pop(user_id)
|
50
53
|
|
@@ -64,7 +67,9 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
|
64
67
|
user_messages_dict[result_id].append(q.get())
|
65
68
|
self.write_message({"messages": user_messages_dict})
|
66
69
|
except tornado.websocket.WebSocketClosedError:
|
67
|
-
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)
|
68
73
|
|
69
74
|
|
70
75
|
class HealthHandler(tornado.web.RequestHandler):
|
@@ -227,7 +232,7 @@ class Upload(BaseHandler):
|
|
227
232
|
|
228
233
|
Args:
|
229
234
|
tornado (tornado.web.RequestHandler): The upload request handler
|
230
|
-
"""
|
235
|
+
"""
|
231
236
|
@tornado.web.authenticated
|
232
237
|
def get(self):
|
233
238
|
# this just redirects to login and displays main page
|
@@ -276,7 +281,7 @@ class Upload(BaseHandler):
|
|
276
281
|
m += f"retrieve your files by submitting this code in the \"Results\" section to the right: {results_path}"
|
277
282
|
self.render("index.html", message=m)
|
278
283
|
try:
|
279
|
-
session_queues[user_id][results_path] =
|
284
|
+
session_queues[user_id][results_path] = Queue()
|
280
285
|
session_messages[user_id][results_path] = []
|
281
286
|
await g.grade(auto_p, notebooks_path, results_path, session_queues[user_id].get(results_path))
|
282
287
|
except Exception as e:
|
@@ -288,6 +293,44 @@ class Upload(BaseHandler):
|
|
288
293
|
self.render("index.html", message=m)
|
289
294
|
|
290
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
|
+
|
291
334
|
settings = {
|
292
335
|
"cookie_secret": str(uuid.uuid4()),
|
293
336
|
"xsrf_cookies": True,
|
@@ -300,6 +343,7 @@ application = tornado.web.Application([
|
|
300
343
|
(r"/upload", Upload),
|
301
344
|
(r"/download", Download),
|
302
345
|
(r"/update", WebSocketHandler),
|
346
|
+
(r"/remove/([a-zA-Z0-9\-]+)", RemoveProgressHandler),
|
303
347
|
(r"/oauth_callback", GitHubOAuthHandler),
|
304
348
|
(r"/otterhealth", HealthHandler),
|
305
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=fe1i7ytQ67cmOi4fKSpfB74e36dkRgec1zdg71uuRFM,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.6.dist-info/METADATA,sha256=A_kaQSA9NnkCh5bVufbaOuhdwxVc8ntIULxz19FTMbA,1467
|
18
|
+
otter_service_stdalone-1.1.6.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
19
|
+
otter_service_stdalone-1.1.6.dist-info/entry_points.txt,sha256=cx447chuIEl8ly9jEoF5-2xNhaKsWcIMDdhUMH_00qQ,75
|
20
|
+
otter_service_stdalone-1.1.6.dist-info/top_level.txt,sha256=6UP22fD4OhbLt23E01v8Kvjn44vPRbyTIg_GqMYL-Ng,23
|
21
|
+
otter_service_stdalone-1.1.6.dist-info/RECORD,,
|
{otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/entry_points.txt
RENAMED
File without changes
|
{otter_service_stdalone-1.1.4.dist-info → otter_service_stdalone-1.1.6.dist-info}/top_level.txt
RENAMED
File without changes
|