PyKubeGrader 0.2.11__py3-none-any.whl → 0.2.13__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.2.11
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,18 +1,19 @@
1
1
  pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
2
- pykubegrader/initialize.py,sha256=DUen_B1cHFOmwiMpB_wkCqHIjvGChsu62-WiFkKa5Wo,3766
2
+ pykubegrader/initialize.py,sha256=lJu0h-fMUMRxZ70rFAEXix3H6j-TcRaxveC2SIKTzF4,3903
3
3
  pykubegrader/telemetry.py,sha256=h8PPrXWGFgPWCcrChJo2woqd_XIPMFfYcxgLJ0CWpH8,5360
4
4
  pykubegrader/utils.py,sha256=T3GYnLnTL9VXjTZNPr00sUgMgobQYsNTGwynMyXdvHk,696
5
5
  pykubegrader/validate.py,sha256=2KLSB3wfFZbBh1NGgmrOV073paKAgrQz4AgA6LmCIj4,11076
6
6
  pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
7
  pykubegrader/build/api_notebook_builder.py,sha256=FEaTj1fEsPAes7KNrPuhClEvKzLuOL3wG7Hgr7FQc-o,20083
8
- pykubegrader/build/build_folder.py,sha256=Ahec6o48wHYpsLViUWi5qIHTirZQg9PJk3N4ABiZD-w,78871
8
+ pykubegrader/build/build_folder.py,sha256=LfghCO0-CMFMua_OYwIK0-c7AlCXgGSTIQ8X45zNw-g,79679
9
9
  pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6tD-38,1323
10
10
  pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
11
  pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
12
12
  pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
13
13
  pykubegrader/log_parser/parse.ipynb,sha256=H1CUuqiUSE-tiZV1IS7VG6HAEvoopGd6i_5QM5CwoE0,12230
14
14
  pykubegrader/log_parser/parse.py,sha256=YCs_OCnoxQKsL55MjTZWXBBBsehJL8PIB9ANnC-aE44,7379
15
- pykubegrader/submit/submit_assignment.py,sha256=9rBB4SgRJQzu315zYamboV954ZFUgH-DNnNrNzMICFo,3404
15
+ pykubegrader/submit/submit_assignment.py,sha256=p-v31479342q0AO3lJrd8Fwk7iJQUz183OeGeiNcXOY,3405
16
+ pykubegrader/tokens/tokens.py,sha256=cM8CKUrwHkIUXE3ucSCsY9JDT-A8K-opmTpliJtGEWs,1404
16
17
  pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
17
18
  pykubegrader/widgets/multiple_choice.py,sha256=NjD3-uXSnibpUQ0mO3hRp_O-rynFyl0Dz6IXE4tnCRI,2078
18
19
  pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
@@ -25,9 +26,9 @@ pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-
25
26
  pykubegrader/widgets_base/multi_select.py,sha256=Cl0IN21wXLZuFu-zC65aS9tD4jMfzCRJ2DPjHao5_Ak,4044
26
27
  pykubegrader/widgets_base/reading.py,sha256=_vjUPynqmJe_R4vf-7hVhGnQR726S9GL6qT8bflBXBM,5383
27
28
  pykubegrader/widgets_base/select.py,sha256=Fw3uFNOIWo1a3CvlzSx23bvi6bSmA3TqutuRbhD4Dp8,2525
28
- PyKubeGrader-0.2.11.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
29
- PyKubeGrader-0.2.11.dist-info/METADATA,sha256=tEO2oOi3YBk0IIhX5g4wkiHAVGPZsUXiIP0OCquLswA,2779
30
- PyKubeGrader-0.2.11.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
31
- PyKubeGrader-0.2.11.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
32
- PyKubeGrader-0.2.11.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
33
- PyKubeGrader-0.2.11.dist-info/RECORD,,
29
+ PyKubeGrader-0.2.13.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
30
+ PyKubeGrader-0.2.13.dist-info/METADATA,sha256=kKG8_Pma2DrHWtCtw-FrI5rEDvW3R5mVXljRVZUY5sI,2779
31
+ PyKubeGrader-0.2.13.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
32
+ PyKubeGrader-0.2.13.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
33
+ PyKubeGrader-0.2.13.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
34
+ PyKubeGrader-0.2.13.dist-info/RECORD,,
@@ -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:
@@ -43,7 +43,7 @@ async def call_score_assignment(
43
43
  # Get credentials
44
44
  credentials = get_credentials()
45
45
  username = credentials["username"]
46
- password = credentials["password"]
46
+ password = credentials["password"]
47
47
 
48
48
  # Encode credentials for Basic Authentication
49
49
  auth_header = (
@@ -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}")