PyKubeGrader 0.2.12__tar.gz → 0.2.13__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. {pykubegrader-0.2.12/src/PyKubeGrader.egg-info → pykubegrader-0.2.13}/PKG-INFO +1 -1
  2. {pykubegrader-0.2.12 → pykubegrader-0.2.13/src/PyKubeGrader.egg-info}/PKG-INFO +1 -1
  3. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/SOURCES.txt +1 -0
  4. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/build/build_folder.py +69 -43
  5. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/initialize.py +4 -1
  6. pykubegrader-0.2.13/src/pykubegrader/tokens/tokens.py +50 -0
  7. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/.coveragerc +0 -0
  8. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/.github/workflows/main.yml +0 -0
  9. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/.gitignore +0 -0
  10. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/.readthedocs.yml +0 -0
  11. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/AUTHORS.rst +0 -0
  12. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/CHANGELOG.rst +0 -0
  13. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/CONTRIBUTING.rst +0 -0
  14. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/LICENSE.txt +0 -0
  15. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/README.rst +0 -0
  16. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/Makefile +0 -0
  17. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  18. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  19. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/_static/custom.css +0 -0
  20. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/authors.rst +0 -0
  21. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/changelog.rst +0 -0
  22. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/conf.py +0 -0
  23. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/contributing.rst +0 -0
  24. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/index.rst +0 -0
  25. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/license.rst +0 -0
  26. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/readme.rst +0 -0
  27. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/docs/requirements.txt +0 -0
  28. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/examples/.responses.json +0 -0
  29. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/examples/true_false.ipynb +0 -0
  30. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/pyproject.toml +0 -0
  31. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/setup.cfg +0 -0
  32. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/setup.py +0 -0
  33. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  34. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
  35. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  36. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/requires.txt +0 -0
  37. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  38. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/__init__.py +0 -0
  39. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/build/__init__.py +0 -0
  40. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/build/api_notebook_builder.py +0 -0
  41. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/build/clean_folder.py +0 -0
  42. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/graders/__init__.py +0 -0
  43. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/graders/late_assignments.py +0 -0
  44. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/log_parser/__init__.py +0 -0
  45. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/log_parser/parse.ipynb +0 -0
  46. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/log_parser/parse.py +0 -0
  47. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/submit/submit_assignment.py +0 -0
  48. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/telemetry.py +0 -0
  49. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/utils.py +0 -0
  50. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/validate.py +0 -0
  51. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/__init__.py +0 -0
  52. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/multiple_choice.py +0 -0
  53. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/reading_question.py +0 -0
  54. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/select_many.py +0 -0
  55. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/student_info.py +0 -0
  56. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/style.py +0 -0
  57. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/true_false.py +0 -0
  58. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets/types_question.py +0 -0
  59. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets_base/__init__.py +0 -0
  60. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets_base/multi_select.py +0 -0
  61. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets_base/reading.py +0 -0
  62. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/src/pykubegrader/widgets_base/select.py +0 -0
  63. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/tests/conftest.py +0 -0
  64. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/tests/import_test.py +0 -0
  65. {pykubegrader-0.2.12 → pykubegrader-0.2.13}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.2.12
3
+ Version: 0.2.13
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.2.12
3
+ Version: 0.2.13
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -47,6 +47,7 @@ src/pykubegrader/log_parser/__init__.py
47
47
  src/pykubegrader/log_parser/parse.ipynb
48
48
  src/pykubegrader/log_parser/parse.py
49
49
  src/pykubegrader/submit/submit_assignment.py
50
+ src/pykubegrader/tokens/tokens.py
50
51
  src/pykubegrader/widgets/__init__.py
51
52
  src/pykubegrader/widgets/multiple_choice.py
52
53
  src/pykubegrader/widgets/reading_question.py
@@ -1,7 +1,6 @@
1
1
  ### Note
2
2
 
3
3
 
4
-
5
4
  import argparse
6
5
  import importlib.util
7
6
  import json
@@ -28,6 +27,7 @@ import nbformat
28
27
  from .api_notebook_builder import FastAPINotebookBuilder
29
28
  from typing import Optional
30
29
 
30
+
31
31
  @dataclass
32
32
  class NotebookProcessor:
33
33
  """
@@ -160,22 +160,21 @@ class NotebookProcessor:
160
160
 
161
161
  if self.check_if_file_in_folder("assignment_config.yaml"):
162
162
  self.add_assignment()
163
-
163
+
164
164
  self.update_initialize_function()
165
-
165
+
166
166
  def update_initialize_function(self):
167
-
167
+
168
168
  for key, value in self.total_point_log.items():
169
-
169
+
170
170
  assignment_tag = f"week{self.week_num}-{self.assignment_type}"
171
-
171
+
172
172
  update_initialize_assignment(
173
- notebook_path = os.path.join(self.root_folder, key + '.ipynb'),
174
- assignment_points= value,
175
- assignment_tag = assignment_tag,
176
- )
177
-
178
-
173
+ notebook_path=os.path.join(self.root_folder, key + ".ipynb"),
174
+ assignment_points=value,
175
+ assignment_tag=assignment_tag,
176
+ )
177
+
179
178
  def build_payload(self, yaml_content):
180
179
  """
181
180
  Reads YAML content for an assignment and returns Python variables.
@@ -215,15 +214,15 @@ class NotebookProcessor:
215
214
  "due_date": due_date,
216
215
  "max_score": int(self.assignment_total_points),
217
216
  }
218
-
219
- def build_payload_notebook(self, yaml_content, notebook_title, total_points):
217
+
218
+ def build_payload_notebook(self, yaml_content, notebook_title, total_points):
220
219
  # Parse the YAML content
221
220
  with open(yaml_content, "r") as file:
222
221
  data = yaml.safe_load(file)
223
-
222
+
224
223
  # Extract assignment details
225
224
  assignment = data.get("assignment", {})
226
-
225
+
227
226
  week_num = self.week_num
228
227
  assignment_type = self.assignment_type
229
228
  due_date_str = assignment.get("due_date")
@@ -235,7 +234,7 @@ class NotebookProcessor:
235
234
  due_date = parser.parse(due_date_str) # Automatically handles timezones
236
235
  except ValueError as e:
237
236
  print(f"Error parsing due_date: {e}")
238
-
237
+
239
238
  return {
240
239
  "title": notebook_title,
241
240
  "week_number": week_num,
@@ -243,8 +242,7 @@ class NotebookProcessor:
243
242
  "due_date": due_date,
244
243
  "max_score": total_points,
245
244
  }
246
-
247
-
245
+
248
246
  def add_notebook(self, notebook_title, total_points):
249
247
  """
250
248
  Sends a POST request to add a notebook.
@@ -253,9 +251,11 @@ class NotebookProcessor:
253
251
  url = "https://engr-131-api.eastus.cloudapp.azure.com/notebook"
254
252
 
255
253
  # Build the payload
256
- payload = self.build_payload_notebook(yaml_content=f"{self.root_folder}/assignment_config.yaml",
257
- notebook_title=notebook_title,
258
- total_points=total_points)
254
+ payload = self.build_payload_notebook(
255
+ yaml_content=f"{self.root_folder}/assignment_config.yaml",
256
+ notebook_title=notebook_title,
257
+ total_points=total_points,
258
+ )
259
259
 
260
260
  # Define HTTP Basic Authentication
261
261
  auth = (user(), password())
@@ -496,19 +496,50 @@ class NotebookProcessor:
496
496
  self.select_many_total_points
497
497
  + self.mcq_total_points
498
498
  + self.tf_total_points
499
- + self.otter_total_points
499
+ + self.otter_total_points
500
500
  )
501
-
501
+
502
502
  # creates the assignment record in the database
503
503
  self.add_notebook(notebook_name, total_points)
504
504
 
505
505
  self.assignment_total_points += total_points
506
506
 
507
507
  self.total_point_log.update({notebook_name: total_points})
508
-
509
- student_file_path = os.path.join(self.root_folder, notebook_name + '.ipynb')
508
+
509
+ student_file_path = os.path.join(self.root_folder, notebook_name + ".ipynb")
510
510
  self.add_submission_cells(student_file_path, student_file_path)
511
-
511
+ NotebookProcessor.remove_empty_cells(student_file_path)
512
+
513
+ @staticmethod
514
+ def remove_empty_cells(notebook_path, output_path=None):
515
+ """
516
+ Removes empty cells from a Jupyter Notebook and saves the updated notebook.
517
+
518
+ Parameters:
519
+ notebook_path (str): Path to the input Jupyter Notebook.
520
+ output_path (str): Path to save the updated Jupyter Notebook. If None, it overwrites the original file.
521
+ """
522
+ try:
523
+ # Load the notebook
524
+ with open(notebook_path, "r") as nb_file:
525
+ notebook = nbformat.read(nb_file, as_version=4)
526
+
527
+ # Filter out empty cells
528
+ non_empty_cells = [cell for cell in notebook.cells if cell.source.strip()]
529
+
530
+ # Update the notebook cells
531
+ notebook.cells = non_empty_cells
532
+
533
+ # Save the updated notebook
534
+ save_path = output_path if output_path else notebook_path
535
+ with open(save_path, "w") as nb_file:
536
+ nbformat.write(notebook, nb_file)
537
+
538
+ print(f"Empty cells removed. Updated notebook saved at: {save_path}")
539
+
540
+ except Exception as e:
541
+ print(f"An error occurred: {e}")
542
+
512
543
  def add_submission_cells(self, notebook_path: str, output_path: str) -> None:
513
544
  """
514
545
  Adds submission cells to the end of a Jupyter notebook.
@@ -533,12 +564,9 @@ class NotebookProcessor:
533
564
  "from pykubegrader.submit.submit_assignment import submit_assignment\n\n"
534
565
  f'submit_assignment("week{self.week_num}-{self.assignment_type}", "{os.path.basename(notebook_path).replace(".ipynb", "")}")'
535
566
  )
536
-
567
+
537
568
  # Make the code cell non-editable and non-deletable
538
- code_cell.metadata = {
539
- "editable": False,
540
- "deletable": False
541
- }
569
+ code_cell.metadata = {"editable": False, "deletable": False}
542
570
 
543
571
  # Add the cells to the notebook
544
572
  notebook.cells.append(markdown_cell)
@@ -548,7 +576,6 @@ class NotebookProcessor:
548
576
  with open(output_path, "w", encoding="utf-8") as f:
549
577
  nbformat.write(notebook, f)
550
578
 
551
-
552
579
  def free_response_parser(
553
580
  self, temp_notebook_path, notebook_subfolder, notebook_name
554
581
  ):
@@ -783,7 +810,7 @@ class NotebookProcessor:
783
810
  return solution_path, question_path
784
811
  else:
785
812
  return None, None
786
-
813
+
787
814
  @staticmethod
788
815
  def replace_temp_no_otter(input_file, output_file):
789
816
  # Load the notebook
@@ -793,8 +820,8 @@ class NotebookProcessor:
793
820
  # Iterate through the cells and modify `cell.source`
794
821
  for cell in notebook.cells:
795
822
  if cell.cell_type == "code": # Only process code cells
796
- if 'responses = initialize_assignment(' in cell.source:
797
- cell.source = cell.source.replace('_temp', '')
823
+ if "responses = initialize_assignment(" in cell.source:
824
+ cell.source = cell.source.replace("_temp", "")
798
825
 
799
826
  # Save the modified notebook
800
827
  with open(output_file, "w", encoding="utf-8") as f:
@@ -823,7 +850,6 @@ class NotebookProcessor:
823
850
  cell["source"] = [
824
851
  line.replace("_temp.ipynb", ".ipynb") for line in cell["source"]
825
852
  ]
826
-
827
853
 
828
854
  # Write the updated notebook to the output file
829
855
  with open(output_file, "w", encoding="utf-8") as f:
@@ -1508,6 +1534,7 @@ def extract_MCQ(ipynb_file):
1508
1534
  print("Invalid JSON in notebook file.")
1509
1535
  return []
1510
1536
 
1537
+
1511
1538
  def check_for_heading(notebook_path, search_strings):
1512
1539
  """
1513
1540
  Checks if a Jupyter notebook contains a heading cell whose source matches any of the given strings.
@@ -1547,8 +1574,6 @@ def clean_notebook(notebook_path):
1547
1574
  cell.metadata["editable"] = cell.metadata.get("editable", False)
1548
1575
  cell.metadata["deletable"] = cell.metadata.get("deletable", False)
1549
1576
  if cell.cell_type == "code":
1550
- if "grader.check(" in cell.source:
1551
- continue
1552
1577
  cell.metadata["tags"] = cell.metadata.get("tags", [])
1553
1578
  if "skip-execution" not in cell.metadata["tags"]:
1554
1579
  cell.metadata["tags"].append("skip-execution")
@@ -1953,7 +1978,8 @@ def replace_cell_source(notebook_path, cell_index, new_source):
1953
1978
  # Save the notebook
1954
1979
  with open(notebook_path, "w", encoding="utf-8") as f:
1955
1980
  nbformat.write(notebook, f)
1956
-
1981
+
1982
+
1957
1983
  def update_initialize_assignment(
1958
1984
  notebook_path: str,
1959
1985
  assignment_points: Optional[float] = None,
@@ -2001,11 +2027,11 @@ def update_initialize_assignment(
2001
2027
  existing_args = match.group(1).strip()
2002
2028
  # Replace with the updated line
2003
2029
  if additional_variables_str:
2030
+ updated_line = f"responses = initialize_assignment({existing_args}, {additional_variables_str})\n"
2031
+ else:
2004
2032
  updated_line = (
2005
- f"responses = initialize_assignment({existing_args}, {additional_variables_str})\n"
2033
+ f"responses = initialize_assignment({existing_args})\n"
2006
2034
  )
2007
- else:
2008
- updated_line = f"responses = initialize_assignment({existing_args})\n"
2009
2035
  source_code[i] = updated_line
2010
2036
  updated = True
2011
2037
 
@@ -9,7 +9,7 @@ from IPython import get_ipython
9
9
  from typing import Optional
10
10
  from .telemetry import ensure_responses, log_variable, telemetry, update_responses
11
11
 
12
-
12
+ #TODO: could cleanup to remove redundant imports
13
13
  def initialize_assignment(
14
14
  name: str,
15
15
  week: str,
@@ -33,6 +33,9 @@ def initialize_assignment(
33
33
  Raises:
34
34
  Exception: If the environment is unsupported or initialization fails.
35
35
  """
36
+
37
+ if assignment_tag is None:
38
+ assignment_tag = f"{week}-{assignment_type}"
36
39
 
37
40
  ipython = get_ipython()
38
41
  if ipython is None:
@@ -0,0 +1,50 @@
1
+ import requests
2
+
3
+ try:
4
+ from pykubegrader.build.passwords import password, user, juptyerhub_user
5
+ except: # noqa: E722
6
+ print("Passwords not found, cannot access database")
7
+
8
+
9
+ def build_token_payload(token: str, duration: int) -> dict:
10
+
11
+ if juptyerhub_user() is None:
12
+ raise ValueError("JupyterHub user not found")
13
+
14
+ # Return the extracted details as a dictionary
15
+ return {
16
+ "value": token,
17
+ "requester": juptyerhub_user(),
18
+ "duration": duration,
19
+ }
20
+
21
+
22
+ # Need to do for add token as well
23
+ def add_token(self, token, duration=20):
24
+ """
25
+ Sends a POST request to add a notebook.
26
+ """
27
+ # Define the URL
28
+ url = "https://engr-131-api.eastus.cloudapp.azure.com/tokens"
29
+
30
+ # Build the payload
31
+ payload = self.build_token_payload(token= token, duration=duration)
32
+
33
+ # Define HTTP Basic Authentication
34
+ auth = (user(), password())
35
+
36
+ # Define headers
37
+ headers = {"Content-Type": "application/json"}
38
+
39
+ # Serialize the payload with the custom JSON encoder
40
+ serialized_payload = json.dumps(payload, default=self.json_serial)
41
+
42
+ # Send the POST request
43
+ response = requests.post(url, data=serialized_payload, headers=headers, auth=auth)
44
+
45
+ # Print the response
46
+ print(f"Status Code: {response.status_code}")
47
+ try:
48
+ print(f"Response: {response.json()}")
49
+ except ValueError:
50
+ print(f"Response: {response.text}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes