otter-service-stdalone 1.1.29__tar.gz → 1.1.31__tar.gz

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.
Files changed (30) hide show
  1. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/PKG-INFO +1 -1
  2. otter_service_stdalone-1.1.31/src/otter_service_stdalone/__init__.py +1 -0
  3. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/app.py +14 -13
  4. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/grade_notebooks.py +5 -0
  5. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/util.py +35 -0
  6. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone.egg-info/PKG-INFO +1 -1
  7. otter_service_stdalone-1.1.31/tests/test_util.py +32 -0
  8. otter_service_stdalone-1.1.29/src/otter_service_stdalone/__init__.py +0 -1
  9. otter_service_stdalone-1.1.29/tests/test_util.py +0 -10
  10. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/README.md +0 -0
  11. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/pyproject.toml +0 -0
  12. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/setup.cfg +0 -0
  13. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/access_sops_keys.py +0 -0
  14. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/fs_logging.py +0 -0
  15. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/index.html +0 -0
  16. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/scripts/web_socket.js +0 -0
  17. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/secrets/gh_key.dev.yaml +0 -0
  18. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/secrets/gh_key.local.yaml +0 -0
  19. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/secrets/gh_key.prod.yaml +0 -0
  20. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/secrets/gh_key.staging.yaml +0 -0
  21. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/static_files/README_DO_NOT_DISTRIBUTE.txt +0 -0
  22. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/static_templates/403.html +0 -0
  23. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/static_templates/500.html +0 -0
  24. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/upload_handle.py +0 -0
  25. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone/user_auth.py +0 -0
  26. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone.egg-info/SOURCES.txt +0 -0
  27. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone.egg-info/dependency_links.txt +0 -0
  28. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone.egg-info/entry_points.txt +0 -0
  29. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/src/otter_service_stdalone.egg-info/top_level.txt +0 -0
  30. {otter_service_stdalone-1.1.29 → otter_service_stdalone-1.1.31}/tests/test_upload_handle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otter_service_stdalone
3
- Version: 1.1.29
3
+ Version: 1.1.31
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
@@ -0,0 +1 @@
1
+ __version__ = "1.1.31"
@@ -181,7 +181,7 @@ class Download(BaseHandler):
181
181
  m = "Download: Code Not Given!"
182
182
  log.write_logs(download_code, m, f"{download_code}", "debug", log_debug)
183
183
  msg = "Please enter the download code to see your result."
184
- self.render("index.html", download_message=msg)
184
+ self.render("index.html", download_message=msg)
185
185
  elif not os.path.exists(f"{directory}"):
186
186
  m = "Download: Directory for Code Not existing"
187
187
  log.write_logs(download_code, m, f"{download_code}", "debug", log_debug)
@@ -210,7 +210,7 @@ class Download(BaseHandler):
210
210
  zipF.write(f"{file_path}", f_path, compress_type=ZIP_DEFLATED)
211
211
  read_me = os.path.join(os.path.dirname(__file__), "static_files", "README_DO_NOT_DISTRIBUTE.txt")
212
212
  zipF.write(read_me, "README_DO_NOT_DISTRIBUTE.txt", compress_type=ZIP_DEFLATED)
213
-
213
+
214
214
  download_label = f"{'-'.join(download_code.split('-')[:-5])}-results.zip"
215
215
  self.set_header('Content-Type', 'application/octet-stream')
216
216
  self.set_header("Content-Description", "File Transfer")
@@ -272,7 +272,7 @@ class Upload(BaseHandler):
272
272
  if not os.path.exists(__UPLOADS__):
273
273
  os.mkdir(__UPLOADS__)
274
274
  auto_p = f"{__UPLOADS__}/{autograder_name}"
275
-
275
+
276
276
  notebooks_path = f"{__UPLOADS__}/{notebooks_name}"
277
277
  m = "Step 2a: Uploaded File Names Determined"
278
278
  log.write_logs(results_path, m, f"notebooks path: {notebooks_path}", "debug", log_debug)
@@ -302,6 +302,7 @@ class Upload(BaseHandler):
302
302
  try:
303
303
  session_queues[user_id][results_path] = Queue()
304
304
  session_messages[user_id][results_path] = []
305
+
305
306
  await g.grade(auto_p, notebooks_path, autograder_orig_name, results_path, session_queues[user_id].get(results_path))
306
307
  except Exception as e:
307
308
  log.write_logs(results_path, "Grading Problem", str(e), "error", log_error)
@@ -346,16 +347,16 @@ settings = {
346
347
  }
347
348
 
348
349
  application = tornado.web.Application([
349
- (r"/", MainHandler),
350
- (r"/login", LoginHandler),
351
- (r"/upload", Upload),
352
- (r"/download", Download),
353
- (r"/update", WebSocketHandler),
354
- (r"/remove/([a-zA-Z0-9\-]+)", RemoveProgressHandler),
355
- (r"/oauth_callback", GitHubOAuthHandler),
356
- (r"/otterhealth", HealthHandler),
357
- (r"/scripts/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "scripts")}),
358
- ], **settings, debug=False)
350
+ (r"/", MainHandler),
351
+ (r"/login", LoginHandler),
352
+ (r"/upload", Upload),
353
+ (r"/download", Download),
354
+ (r"/update", WebSocketHandler),
355
+ (r"/remove/([a-zA-Z0-9\-]+)", RemoveProgressHandler),
356
+ (r"/oauth_callback", GitHubOAuthHandler),
357
+ (r"/otterhealth", HealthHandler),
358
+ (r"/scripts/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "scripts")}),
359
+ ], **settings, debug=False)
359
360
 
360
361
 
361
362
  def main():
@@ -6,6 +6,7 @@ from otter.grade import main as grade
6
6
  from otter import logging as loggers
7
7
  from multiprocessing import Process
8
8
  from tornado.ioloop import PeriodicCallback
9
+ from .util import clean_directory
9
10
 
10
11
  log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
11
12
  log_count = f'{os.environ.get("ENVIRONMENT")}-count'
@@ -43,6 +44,10 @@ class GradeNotebooks():
43
44
  """
44
45
  try:
45
46
  notebook_folder = uh.handle_upload(notebooks_path, results_id)
47
+
48
+ # remove special characters from notebooks and remove hidden folders
49
+ clean_directory(notebook_folder)
50
+
46
51
  notebook_count = self.count_ipynb_files(notebook_folder, ".ipynb")
47
52
  log.write_logs(results_id, f"{notebook_count}",
48
53
  "",
@@ -2,6 +2,7 @@ import re
2
2
  import zipfile
3
3
  from packaging import version
4
4
  import os
5
+ import shutil
5
6
 
6
7
 
7
8
  def is_version_6_or_greater(zip_ref, target_file, reg):
@@ -39,3 +40,37 @@ def otter_version_correct(autograder_path):
39
40
  otter_in_req = is_version_6_or_greater(zip_ref, req_target_file, requirements_regex)
40
41
  otter_in_env = is_version_6_or_greater(zip_ref, env_target_file, environment_regex)
41
42
  return otter_in_req or otter_in_env
43
+
44
+
45
+ def sanitize_filename(filename: str) -> str:
46
+ """
47
+ Remove periods and commas from filename except the extension dot.
48
+ """
49
+ # Split name and extension
50
+ name, ext = os.path.splitext(filename)
51
+ # Remove periods and commas from the name part
52
+ clean_name = name.replace('.', '').replace(',', '')
53
+ return f"{clean_name}{ext}"
54
+
55
+
56
+ def clean_directory(path: str):
57
+ """
58
+ Delete hidden folders like __MACOSX and sanitize all file names in the directory tree.
59
+ """
60
+ for root, dirs, files in os.walk(path, topdown=True):
61
+ # Remove hidden/system folders
62
+ for dir_name in list(dirs):
63
+ if dir_name.startswith('.') or dir_name == '__MACOSX':
64
+ dir_path = os.path.join(root, dir_name)
65
+ print(f"Deleting folder: {dir_path}")
66
+ shutil.rmtree(dir_path)
67
+ dirs.remove(dir_name) # remove from list to avoid walking it
68
+
69
+ # Sanitize file names
70
+ for file_name in files:
71
+ old_path = os.path.join(root, file_name)
72
+ new_name = sanitize_filename(file_name)
73
+ new_path = os.path.join(root, new_name)
74
+ if old_path != new_path:
75
+ print(f"Renaming {old_path} -> {new_path}")
76
+ os.rename(old_path, new_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otter_service_stdalone
3
- Version: 1.1.29
3
+ Version: 1.1.31
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
@@ -0,0 +1,32 @@
1
+ import otter_service_stdalone.util as util
2
+ import shutil
3
+ import os
4
+ import re
5
+
6
+
7
+ def test_autograder_zip_version():
8
+ a1 = "tests/files/hw01-autograder_6_0_4.zip"
9
+ a2 = "tests/files/hw02-autograder_6_1_0.zip"
10
+ a3 = "tests/files/hw02-autograder_5_5_0.zip"
11
+ a4 = "tests/files/hw03-autograder_6_1_3.zip"
12
+ assert util.otter_version_correct(a1) is False
13
+ assert util.otter_version_correct(a2) is False
14
+ assert util.otter_version_correct(a3) is False
15
+ assert util.otter_version_correct(a4) is False
16
+
17
+
18
+ def test_clean_directory():
19
+ clean_this_dir = "tests/files/clean_up_dir"
20
+ tmp_dir = clean_this_dir + "_tmp"
21
+ shutil.copytree(clean_this_dir, tmp_dir)
22
+ util.clean_directory(tmp_dir)
23
+ allowed_pattern = re.compile(r'^[\w\-]+(\.[\w\-]+)?$')
24
+ for root, dirs, files in os.walk(tmp_dir):
25
+ for d in dirs:
26
+ assert not d.startswith('.') and d != '__MACOSX', f"Hidden dir found: {d}"
27
+ assert allowed_pattern.match(d), f"Invalid characters in directory name: {d}"
28
+
29
+ for f in files:
30
+ assert not f.startswith('.'), f"Hidden file found: {f}"
31
+ assert allowed_pattern.match(f), f"Invalid characters in filename: {f}"
32
+ shutil.rmtree(tmp_dir)
@@ -1 +0,0 @@
1
- __version__ = "1.1.29"
@@ -1,10 +0,0 @@
1
- import otter_service_stdalone.util as util
2
-
3
-
4
- def test_autograder_zip_version():
5
- a1 = "tests/files/hw01-autograder_6_0_4.zip"
6
- a2 = "tests/files/hw02-autograder_6_1_0.zip"
7
- a3 = "tests/files/hw02-autograder_5_5_0.zip"
8
- assert util.otter_version_correct(a1) is True
9
- assert util.otter_version_correct(a2) is True
10
- assert util.otter_version_correct(a3) is False