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.
@@ -1 +1 @@
1
- __version__ = "1.1.5"
1
+ __version__ = "1.1.7"
@@ -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,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 classback on close
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 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)
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] = queue.Queue()
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
- 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.5
3
+ Version: 1.1.7
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=KlgaVSJ1AQxCDJZgh4wFk-gpem-k9PfebW8U3hiKJD4,22
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=xawbz74KR6CVHhh-kz0rgZcRCRcYKg6gwN2QQXXzkZQ,13522
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.5.dist-info/METADATA,sha256=YzOu5SoRAHWjhiDbNqQzQclzIS9PcBgUDdncDU8l6u4,1467
18
- otter_service_stdalone-1.1.5.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
19
- otter_service_stdalone-1.1.5.dist-info/entry_points.txt,sha256=cx447chuIEl8ly9jEoF5-2xNhaKsWcIMDdhUMH_00qQ,75
20
- otter_service_stdalone-1.1.5.dist-info/top_level.txt,sha256=6UP22fD4OhbLt23E01v8Kvjn44vPRbyTIg_GqMYL-Ng,23
21
- otter_service_stdalone-1.1.5.dist-info/RECORD,,
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,,
@@ -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