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.
@@ -1 +1 @@
1
- __version__ = "1.1.4"
1
+ __version__ = "1.1.6"
@@ -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 queue
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 classback on close
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 Problem", "", "", log_error)
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] = queue.Queue()
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
- await grade(
52
- name='grader',
53
- autograder=p,
54
- paths=(notebook_folder,),
55
- containers=10,
56
- timeout=300,
57
- ext="ipynb",
58
- output_dir=notebook_folder,
59
- result_queue=user_queue
60
- )
61
- user_queue.put("Results available for download")
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 = "&times;"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otter_service_stdalone
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: Grading Service for Instructors using Otter Grader
5
5
  Home-page: https://github.com/sean-morris/otter-service-stdalone
6
6
  Author: Sean Morris
@@ -1,12 +1,12 @@
1
- otter_service_stdalone/__init__.py,sha256=XxXhu8-QnuD9hA8Ah0WX5rgpt_DwOQmAwcK-FtpngyQ,22
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=NCfmpjb1FePUCeAqcGzcYScfdgY4yQH3YJ-qSAA5UYI,13346
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=9pi4F4y5GCXf3kEQf8hCIJyFGOHx1zb8WZd2s8TqCuU,2945
6
- otter_service_stdalone/index.html,sha256=egDgltPDGIgxqDiR4vXNd7xwG58poM1X98DoItKJqAA,6946
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=qDQEIo0V8LIu1EzF9EVb3MxintVXbbR0DXO9YR7p5TY,5034
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.4.dist-info/METADATA,sha256=OBzAJHVSxmBiBE9LC4ryzvKDsNpEL9h2v48pFuDld_I,1467
18
- otter_service_stdalone-1.1.4.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
19
- otter_service_stdalone-1.1.4.dist-info/entry_points.txt,sha256=cx447chuIEl8ly9jEoF5-2xNhaKsWcIMDdhUMH_00qQ,75
20
- otter_service_stdalone-1.1.4.dist-info/top_level.txt,sha256=6UP22fD4OhbLt23E01v8Kvjn44vPRbyTIg_GqMYL-Ng,23
21
- otter_service_stdalone-1.1.4.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5