PyKubeGrader 0.1.10__tar.gz → 0.1.12__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. {pykubegrader-0.1.10/src/PyKubeGrader.egg-info → pykubegrader-0.1.12}/PKG-INFO +1 -1
  2. {pykubegrader-0.1.10 → pykubegrader-0.1.12/src/PyKubeGrader.egg-info}/PKG-INFO +1 -1
  3. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/build/api_notebook_builder.py +10 -10
  4. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/build/build_folder.py +57 -30
  5. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/initialize.py +11 -14
  6. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/telemetry.py +3 -4
  7. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/validate.py +2 -2
  8. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/.coveragerc +0 -0
  9. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/.github/workflows/main.yml +0 -0
  10. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/.gitignore +0 -0
  11. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/.readthedocs.yml +0 -0
  12. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/AUTHORS.rst +0 -0
  13. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/CHANGELOG.rst +0 -0
  14. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/CONTRIBUTING.rst +0 -0
  15. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/LICENSE.txt +0 -0
  16. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/README.rst +0 -0
  17. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/Makefile +0 -0
  18. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  19. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  20. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/_static/custom.css +0 -0
  21. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/authors.rst +0 -0
  22. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/changelog.rst +0 -0
  23. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/conf.py +0 -0
  24. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/contributing.rst +0 -0
  25. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/index.rst +0 -0
  26. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/license.rst +0 -0
  27. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/readme.rst +0 -0
  28. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/docs/requirements.txt +0 -0
  29. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/examples/.responses.json +0 -0
  30. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/examples/true_false.ipynb +0 -0
  31. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/pyproject.toml +0 -0
  32. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/setup.cfg +0 -0
  33. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/setup.py +0 -0
  34. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
  35. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  36. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
  37. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  38. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/requires.txt +0 -0
  39. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  40. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/__init__.py +0 -0
  41. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/utils.py +0 -0
  42. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/__init__.py +0 -0
  43. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/multiple_choice.py +0 -0
  44. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/reading_question.py +0 -0
  45. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/select_many.py +0 -0
  46. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/student_info.py +0 -0
  47. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/style.py +0 -0
  48. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/true_false.py +0 -0
  49. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets/types_question.py +0 -0
  50. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets_base/__init__.py +0 -0
  51. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets_base/multi_select.py +0 -0
  52. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets_base/reading.py +0 -0
  53. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/src/pykubegrader/widgets_base/select.py +0 -0
  54. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/tests/conftest.py +0 -0
  55. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/tests/import_test.py +0 -0
  56. {pykubegrader-0.1.10 → pykubegrader-0.1.12}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -6,6 +6,7 @@ import nbformat
6
6
  import json
7
7
  import re
8
8
  import shutil
9
+ import ast
9
10
 
10
11
 
11
12
  @dataclass
@@ -20,7 +21,6 @@ class FastAPINotebookBuilder:
20
21
  self.run()
21
22
 
22
23
  def run(self):
23
-
24
24
  # here for easy debugging
25
25
  if self.temp_notebook is not None:
26
26
  shutil.copy(
@@ -34,7 +34,6 @@ class FastAPINotebookBuilder:
34
34
  self.add_api_code()
35
35
 
36
36
  def add_api_code(self):
37
-
38
37
  for i, (cell_index, cell_dict) in enumerate(self.assertion_tests_dict.items()):
39
38
  print(
40
39
  f"Processing cell {cell_index + 1}, {i} of {len(self.assertion_tests_dict)}"
@@ -105,7 +104,6 @@ class FastAPINotebookBuilder:
105
104
  @staticmethod
106
105
  def construct_update_responses(cell_dict):
107
106
  update_responses = []
108
- question_id = cell_dict["question"] + "-" + str(cell_dict["test_number"]) + "\n"
109
107
 
110
108
  logging_variables = cell_dict["logging_variables"]
111
109
 
@@ -141,7 +139,6 @@ class FastAPINotebookBuilder:
141
139
 
142
140
  @staticmethod
143
141
  def construct_graders(cell_dict):
144
-
145
142
  # Generate Python code
146
143
  added_code = [
147
144
  "if "
@@ -283,7 +280,7 @@ class FastAPINotebookBuilder:
283
280
  """
284
281
  last_import_index = -1
285
282
  is_multiline_import = False # Flag to track if we're inside a multiline import
286
-
283
+
287
284
  for i, line in enumerate(cell_source):
288
285
  stripped_line = line.strip()
289
286
 
@@ -313,11 +310,15 @@ class FastAPINotebookBuilder:
313
310
  if "source" in cell:
314
311
  for line in cell["source"]:
315
312
  # Look for the log_variables pattern
316
- match = re.search(r"log_variables:\s*\[(.*?)\]", line)
313
+ match = re.search(r"log_variables:\s*(\[.*\])", line)
317
314
  if match:
318
- # Split the variables by comma and strip whitespace
319
- log_variables = [var.strip() for var in match.group(1).split(",")]
320
- return log_variables
315
+ # Parse the list using ast.literal_eval for safety
316
+ try:
317
+ log_variables = ast.literal_eval(match.group(1))
318
+ if isinstance(log_variables, list):
319
+ return [var.strip() for var in log_variables]
320
+ except (SyntaxError, ValueError):
321
+ pass
321
322
  return []
322
323
 
323
324
  def tag_questions(cells_dict):
@@ -363,7 +364,6 @@ class FastAPINotebookBuilder:
363
364
  return cells_dict
364
365
 
365
366
  def question_dict(self):
366
-
367
367
  notebook_path = Path(self.temp_notebook)
368
368
  if not notebook_path.exists():
369
369
  raise FileNotFoundError(f"The file {notebook_path} does not exist.")
@@ -166,7 +166,7 @@ class NotebookProcessor:
166
166
  Returns:
167
167
  None
168
168
  """
169
-
169
+
170
170
  print(f"Processing notebook: {notebook_path}")
171
171
 
172
172
  logging.info(f"Processing notebook: {notebook_path}")
@@ -278,26 +278,38 @@ class NotebookProcessor:
278
278
  self, temp_notebook_path, notebook_subfolder, notebook_name
279
279
  ):
280
280
  if self.has_assignment(temp_notebook_path, "# ASSIGNMENT CONFIG"):
281
-
282
281
  # TODO: This is hardcoded for now, but should be in a configuration file.
283
282
  client_private_key = os.path.join(
284
283
  notebook_subfolder,
285
- "client_private_key.bin",
284
+ ".client_private_key.bin",
286
285
  )
287
286
  server_public_key = os.path.join(
288
287
  notebook_subfolder,
289
- "server_public_key.bin",
288
+ ".server_public_key.bin",
290
289
  )
291
290
 
292
- shutil.copy("./keys/client_private_key.bin", client_private_key)
293
- shutil.copy("./keys/server_public_key.bin", server_public_key)
291
+ shutil.copy("./keys/.client_private_key.bin", client_private_key)
292
+ shutil.copy("./keys/.server_public_key.bin", server_public_key)
294
293
 
295
294
  FastAPINotebookBuilder(notebook_path=temp_notebook_path)
296
295
 
296
+ debug_notebook = os.path.join(
297
+ notebook_subfolder,
298
+ "dist",
299
+ "autograder",
300
+ os.path.basename(temp_notebook_path).replace("_temp", "_debugger"),
301
+ )
302
+
297
303
  self.run_otter_assign(
298
304
  temp_notebook_path, os.path.join(notebook_subfolder, "dist")
299
305
  )
300
306
 
307
+ print(f"Copying {temp_notebook_path} to {debug_notebook}")
308
+
309
+ shutil.copy(temp_notebook_path, debug_notebook)
310
+
311
+ NotebookProcessor.remove_assignment_config_cells(debug_notebook)
312
+
301
313
  student_notebook = os.path.join(
302
314
  notebook_subfolder, "dist", "student", f"{notebook_name}.ipynb"
303
315
  )
@@ -329,18 +341,34 @@ class NotebookProcessor:
329
341
  NotebookProcessor.add_initialization_code(temp_notebook_path)
330
342
  return None
331
343
 
344
+ @staticmethod
345
+ def remove_assignment_config_cells(notebook_path):
346
+ # Read the notebook
347
+ with open(notebook_path, "r", encoding="utf-8") as f:
348
+ notebook = nbformat.read(f, as_version=nbformat.NO_CONVERT)
349
+
350
+ # Filter out cells containing "# ASSIGNMENT CONFIG"
351
+ notebook.cells = [
352
+ cell
353
+ for cell in notebook.cells
354
+ if "# ASSIGNMENT CONFIG" not in cell.get("source", "")
355
+ ]
356
+
357
+ # Save the updated notebook
358
+ with open(notebook_path, "w", encoding="utf-8") as f:
359
+ nbformat.write(notebook, f)
360
+
332
361
  @staticmethod
333
362
  def add_initialization_code(notebook_path):
334
363
  # finds the first code cell
335
364
  index, cell = find_first_code_cell(notebook_path)
336
- cell = cell['source']
365
+ cell = cell["source"]
337
366
  import_text = "from pykubegrader.initialize import initialize_assignment\n"
338
367
  cell = f"{import_text}\n" + cell
339
368
  cell += f'\nresponses = initialize_assignment("{os.path.splitext(os.path.basename(notebook_path))[0]}")\n'
340
369
  replace_cell_source(notebook_path, index, cell)
341
370
 
342
371
  def multiple_choice_parser(self, temp_notebook_path, new_notebook_path):
343
-
344
372
  ### Parse the notebook for multiple choice questions
345
373
  if self.has_assignment(temp_notebook_path, "# BEGIN MULTIPLE CHOICE"):
346
374
  self._print_and_log(
@@ -627,7 +655,7 @@ class NotebookProcessor:
627
655
  """
628
656
 
629
657
  solutions = {}
630
- total_points = 0.0
658
+ total_points = []
631
659
 
632
660
  # If the output file exists, load the existing solutions and total_points
633
661
  if os.path.exists(output_file):
@@ -641,14 +669,14 @@ class NotebookProcessor:
641
669
  if hasattr(existing_module, "solutions"):
642
670
  solutions.update(existing_module.solutions)
643
671
  if hasattr(existing_module, "total_points"):
644
- total_points += existing_module.total_points
672
+ total_points.extend(existing_module.total_points)
645
673
 
646
674
  # Process new question data and update solutions and total_points
647
675
  for question_set in data_list:
648
676
  for key, question_data in question_set.items():
649
677
  solution_key = f"q{question_data['question number']}-{question_data['subquestion_number']}-{key}"
650
678
  solutions[solution_key] = question_data["solution"]
651
- total_points += question_data["points"]
679
+ total_points.extend([question_data["points"]])
652
680
 
653
681
  # Write updated total_points and solutions back to the file
654
682
  with open(output_file, "w", encoding="utf-8") as f:
@@ -1577,29 +1605,28 @@ def find_first_code_cell(notebook_path):
1577
1605
 
1578
1606
 
1579
1607
  def replace_cell_source(notebook_path, cell_index, new_source):
1580
- """
1581
- Replace the source code of a specific Jupyter notebook cell.
1608
+ """
1609
+ Replace the source code of a specific Jupyter notebook cell.
1582
1610
 
1583
- Args:
1584
- cell_index (int): Index of the cell to be modified (0-based).
1585
- new_source (str): New source code to replace the cell's content.
1586
- """
1587
- # Load the notebook
1588
- with open(notebook_path, "r", encoding="utf-8") as f:
1589
- notebook = nbformat.read(f, as_version=4)
1611
+ Args:
1612
+ cell_index (int): Index of the cell to be modified (0-based).
1613
+ new_source (str): New source code to replace the cell's content.
1614
+ """
1615
+ # Load the notebook
1616
+ with open(notebook_path, "r", encoding="utf-8") as f:
1617
+ notebook = nbformat.read(f, as_version=4)
1590
1618
 
1591
- # Check if the cell index is valid
1592
- if cell_index >= len(notebook.cells) or cell_index < 0:
1593
- raise IndexError(
1594
- f"Cell index {cell_index} is out of range for this notebook."
1595
- )
1619
+ # Check if the cell index is valid
1620
+ if cell_index >= len(notebook.cells) or cell_index < 0:
1621
+ raise IndexError(f"Cell index {cell_index} is out of range for this notebook.")
1596
1622
 
1597
- # Replace the source code of the specified cell
1598
- notebook.cells[cell_index]["source"] = new_source
1623
+ # Replace the source code of the specified cell
1624
+ notebook.cells[cell_index]["source"] = new_source
1625
+
1626
+ # Save the notebook
1627
+ with open(notebook_path, "w", encoding="utf-8") as f:
1628
+ nbformat.write(notebook, f)
1599
1629
 
1600
- # Save the notebook
1601
- with open(notebook_path, "w", encoding="utf-8") as f:
1602
- nbformat.write(notebook, f)
1603
1630
 
1604
1631
  def main():
1605
1632
  parser = argparse.ArgumentParser(
@@ -8,11 +8,11 @@ import requests
8
8
  from .telemetry import telemetry, update_responses, ensure_responses
9
9
 
10
10
 
11
-
12
- def initialize_assignment(name: str,
13
- verbose: Optional[bool] = False,
14
- url: Optional[str] = "https://engr-131-api.eastus.cloudapp.azure.com/") -> None:
15
-
11
+ def initialize_assignment(
12
+ name: str,
13
+ verbose: Optional[bool] = False,
14
+ url: Optional[str] = "https://engr-131-api.eastus.cloudapp.azure.com/",
15
+ ) -> None:
16
16
  ipython = get_ipython()
17
17
  if ipython is None:
18
18
  print("Setup unsuccessful. Are you in a Jupyter environment?")
@@ -38,13 +38,12 @@ def initialize_assignment(name: str,
38
38
  except (TypeError, json.JSONDecodeError) as e:
39
39
  print(f"Failed to initialize assignment: {e}")
40
40
  return
41
-
42
-
41
+
43
42
  # extract responses
44
43
  responses = ensure_responses()
45
-
46
- # TODO: Add more checks here??
47
- assert isinstance(responses.get('seed'), int), "valid seed not found in responses"
44
+
45
+ # TODO: Add more checks here??
46
+ assert isinstance(responses.get("seed"), int), "valid seed not found in responses"
48
47
 
49
48
  pn.extension(silent=True)
50
49
 
@@ -52,17 +51,15 @@ def initialize_assignment(name: str,
52
51
  print("Assignment successfully initialized")
53
52
  print(f"Assignment: {name}")
54
53
  print(f"Username: {jhub_user}")
55
-
56
54
 
57
-
58
55
  # Checks connectivity to the API
59
- params = { "jhub_user": responses["jhub_user"] }
56
+ params = {"jhub_user": responses["jhub_user"]}
60
57
  response = requests.get(url, params=params)
61
58
  if verbose:
62
59
  print(f"status code: {response.status_code}")
63
60
  data = response.json()
64
61
  for k, v in data.items():
65
62
  print(f"{k}: {v}")
66
-
63
+
67
64
  print("Assignment successfully initialized")
68
65
  return responses
@@ -20,11 +20,11 @@ logging.basicConfig(filename=".output.log", level=logging.INFO, force=True)
20
20
 
21
21
 
22
22
  def encrypt_to_b64(message: str) -> str:
23
- with open("server_public_key.bin", "rb") as f:
23
+ with open(".server_public_key.bin", "rb") as f:
24
24
  server_pub_key_bytes = f.read()
25
25
  server_pub_key = nacl.public.PublicKey(server_pub_key_bytes)
26
26
 
27
- with open("client_private_key.bin", "rb") as f:
27
+ with open(".client_private_key.bin", "rb") as f:
28
28
  client_private_key_bytes = f.read()
29
29
  client_priv_key = nacl.public.PrivateKey(client_private_key_bytes)
30
30
 
@@ -36,7 +36,6 @@ def encrypt_to_b64(message: str) -> str:
36
36
 
37
37
 
38
38
  def ensure_responses() -> dict:
39
-
40
39
  with open(".responses.json", "a") as _:
41
40
  pass
42
41
 
@@ -48,7 +47,7 @@ def ensure_responses() -> dict:
48
47
  except json.JSONDecodeError:
49
48
  with open(".responses.json", "w") as f:
50
49
  json.dump(responses, f)
51
-
50
+
52
51
  return responses
53
52
 
54
53
 
@@ -252,11 +252,11 @@ def validate_logfile(
252
252
 
253
253
 
254
254
  def generate_keys() -> nacl.public.Box:
255
- with open("server_private_key.bin", "rb") as priv_file:
255
+ with open(".server_private_key.bin", "rb") as priv_file:
256
256
  server_private_key_bytes = priv_file.read()
257
257
  server_priv_key = nacl.public.PrivateKey(server_private_key_bytes)
258
258
 
259
- with open("client_public_key.bin", "rb") as pub_file:
259
+ with open(".client_public_key.bin", "rb") as pub_file:
260
260
  client_public_key_bytes = pub_file.read()
261
261
  client_pub_key = nacl.public.PublicKey(client_public_key_bytes)
262
262
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes