PyKubeGrader 0.2.1__py3-none-any.whl → 0.2.3__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.1
3
+ Version: 0.2.3
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -1,17 +1,17 @@
1
1
  pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
2
- pykubegrader/initialize.py,sha256=t3iSdeIcndfY8LoHBVUEZfTW6sUWHyeFLirKo4GwSQE,3328
3
- pykubegrader/telemetry.py,sha256=jRInaDqIpdeT7F0rxLJgO38lA-SMtmLcYE8nEAGah1Q,4922
2
+ pykubegrader/initialize.py,sha256=lQ80bB4LHDLJXFFl820_mNTFXvXpLT-rEAViBBsEPn0,3568
3
+ pykubegrader/telemetry.py,sha256=bgBWt9B3zqbfV2xWKDZKg41ZEBaF6X43O74do1fRylM,5325
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=e27Ni64qpgmzg4Yu94nQieyK3IflZUQ0BvEOzwMGKMU,70729
8
+ pykubegrader/build/build_folder.py,sha256=MyQ7ncPwiuTZ9PD7Q7al1n1bog4cugH1ew5DEC2NqNM,71700
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
- pykubegrader/log_parser/parse.ipynb,sha256=kPTo9gqhDineT7yT56P9xrQP1OKTf6Ro-iR4xlJ-or8,10610
14
- pykubegrader/log_parser/parse.py,sha256=uw8lxWVh0FTaWi-bVmpMBbqwTXIHlJtB9gPc1qKvm4I,7040
13
+ pykubegrader/log_parser/parse.ipynb,sha256=GnEgUmowdN2bZSwwWgxqi43QXaP-syPD2SnHvOGq1vU,11674
14
+ pykubegrader/log_parser/parse.py,sha256=YCs_OCnoxQKsL55MjTZWXBBBsehJL8PIB9ANnC-aE44,7379
15
15
  pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
16
16
  pykubegrader/widgets/multiple_choice.py,sha256=NjD3-uXSnibpUQ0mO3hRp_O-rynFyl0Dz6IXE4tnCRI,2078
17
17
  pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
@@ -24,9 +24,9 @@ pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-
24
24
  pykubegrader/widgets_base/multi_select.py,sha256=Cl0IN21wXLZuFu-zC65aS9tD4jMfzCRJ2DPjHao5_Ak,4044
25
25
  pykubegrader/widgets_base/reading.py,sha256=_vjUPynqmJe_R4vf-7hVhGnQR726S9GL6qT8bflBXBM,5383
26
26
  pykubegrader/widgets_base/select.py,sha256=Fw3uFNOIWo1a3CvlzSx23bvi6bSmA3TqutuRbhD4Dp8,2525
27
- PyKubeGrader-0.2.1.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
28
- PyKubeGrader-0.2.1.dist-info/METADATA,sha256=tY6eIv1slUtGb-qLeuoxkXxPc55sAjxqGG_f9MFf5Oo,2729
29
- PyKubeGrader-0.2.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
- PyKubeGrader-0.2.1.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
31
- PyKubeGrader-0.2.1.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
32
- PyKubeGrader-0.2.1.dist-info/RECORD,,
27
+ PyKubeGrader-0.2.3.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
28
+ PyKubeGrader-0.2.3.dist-info/METADATA,sha256=W63VPeqive20qAS1GAsEGYRiOpMsP028h5u76CfEOdw,2729
29
+ PyKubeGrader-0.2.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
+ PyKubeGrader-0.2.3.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
31
+ PyKubeGrader-0.2.3.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
32
+ PyKubeGrader-0.2.3.dist-info/RECORD,,
@@ -190,6 +190,8 @@ class NotebookProcessor:
190
190
  return {
191
191
  "title": title,
192
192
  "description": str(week),
193
+ "week_number": week,
194
+ "assignment_type": assignment_type,
193
195
  "due_date": due_date,
194
196
  "max_score": int(self.assignment_total_points),
195
197
  }
@@ -503,6 +505,9 @@ class NotebookProcessor:
503
505
  NotebookProcessor.add_initialization_code(
504
506
  temp_notebook_path, self.week, self.assignment_type
505
507
  )
508
+ NotebookProcessor.replace_temp_no_otter(
509
+ temp_notebook_path, temp_notebook_path
510
+ )
506
511
  return None, 0
507
512
 
508
513
  @staticmethod
@@ -650,6 +655,22 @@ class NotebookProcessor:
650
655
  return solution_path, question_path
651
656
  else:
652
657
  return None, None
658
+
659
+ @staticmethod
660
+ def replace_temp_no_otter(input_file, output_file):
661
+ # Load the notebook
662
+ with open(input_file, "r", encoding="utf-8") as f:
663
+ notebook = nbformat.read(f, as_version=4)
664
+
665
+ # Iterate through the cells and modify `cell.source`
666
+ for cell in notebook.cells:
667
+ if cell.cell_type == "code": # Only process code cells
668
+ if 'responses = initialize_assignment(' in cell.source:
669
+ cell.source = cell.source.replace('_temp', '')
670
+
671
+ # Save the modified notebook
672
+ with open(output_file, "w", encoding="utf-8") as f:
673
+ nbformat.write(notebook, f)
653
674
 
654
675
  @staticmethod
655
676
  def replace_temp_in_notebook(input_file, output_file):
@@ -674,6 +695,7 @@ class NotebookProcessor:
674
695
  cell["source"] = [
675
696
  line.replace("_temp.ipynb", ".ipynb") for line in cell["source"]
676
697
  ]
698
+
677
699
 
678
700
  # Write the updated notebook to the output file
679
701
  with open(output_file, "w", encoding="utf-8") as f:
@@ -1398,6 +1420,8 @@ def clean_notebook(notebook_path):
1398
1420
  cell.metadata["editable"] = cell.metadata.get("editable", False)
1399
1421
  cell.metadata["deletable"] = cell.metadata.get("deletable", False)
1400
1422
  if cell.cell_type == "code":
1423
+ if "grader.check(" in cell.source:
1424
+ continue
1401
1425
  cell.metadata["tags"] = cell.metadata.get("tags", [])
1402
1426
  if "skip-execution" not in cell.metadata["tags"]:
1403
1427
  cell.metadata["tags"].append("skip-execution")
@@ -1563,7 +1587,7 @@ def generate_mcq_file(data_dict, output_file="mc_questions.py"):
1563
1587
  )
1564
1588
  f.write(" def __init__(self):\n")
1565
1589
  f.write(" super().__init__(\n")
1566
- f.write(f" title=f'{q_value['question_text']}',\n")
1590
+ f.write(f' title=f"{q_value['question_text']}",\n')
1567
1591
  f.write(" style=MCQ,\n")
1568
1592
  f.write(
1569
1593
  f" question_number={q_value['question number']},\n"
@@ -1,3 +1,4 @@
1
+ import hashlib
1
2
  import os
2
3
  import shutil
3
4
  from pathlib import Path
@@ -46,7 +47,7 @@ def initialize_assignment(
46
47
  raise Exception("Setup unsuccessful. Are you on JupyterHub?")
47
48
 
48
49
  try:
49
- seed = hash(jhub_user) % 1000
50
+ seed = username_to_seed(jhub_user) % 1000
50
51
  update_responses(key="seed", value=seed)
51
52
  update_responses(key="week", value=week)
52
53
  update_responses(key="assignment_type", value=assignment_type)
@@ -111,3 +112,10 @@ def move_dotfiles():
111
112
  shutil.copy2(source_file, target_file)
112
113
  except Exception as e:
113
114
  raise Exception(f"Failed to copy {source_file} to {target_file}: {e}")
115
+
116
+
117
+ def username_to_seed(username: str, mod: int = 1000) -> int:
118
+ hash_object = hashlib.sha256(username.encode())
119
+ hash_hex = hash_object.hexdigest()
120
+ hash_int = int(hash_hex, 16)
121
+ return hash_int % mod
@@ -182,6 +182,43 @@
182
182
  "outputs": [],
183
183
  "source": []
184
184
  },
185
+ {
186
+ "cell_type": "code",
187
+ "execution_count": null,
188
+ "metadata": {},
189
+ "outputs": [],
190
+ "source": [
191
+ "student_email = results[\"student_information\"][\"username\"]\n",
192
+ "time_stamp = results[\"student_information\"][\"timestamp\"]\n",
193
+ "assignments_graded = results[\"assignment_information\"].keys()\n",
194
+ "week_num = results[\"week_num\"]\n",
195
+ "assignment_type = results['assignment_type']\n",
196
+ "\n",
197
+ "\n",
198
+ "total_score = 0\n",
199
+ "for assignment in assignments_graded:\n",
200
+ " max_points = results[\"assignment_information\"][assignment][\"max_points\"]\n",
201
+ " total_score = results[\"assignment_information\"][assignment][\"total_score\"]\n",
202
+ "\n"
203
+ ]
204
+ },
205
+ {
206
+ "cell_type": "code",
207
+ "execution_count": null,
208
+ "metadata": {},
209
+ "outputs": [],
210
+ "source": [
211
+ "def extract_score_from_dict(results):\n",
212
+ " student_email = results[\"student_information\"][\"username\"]"
213
+ ]
214
+ },
215
+ {
216
+ "cell_type": "code",
217
+ "execution_count": null,
218
+ "metadata": {},
219
+ "outputs": [],
220
+ "source": []
221
+ },
185
222
  {
186
223
  "cell_type": "code",
187
224
  "execution_count": null,
@@ -166,6 +166,15 @@ class LogParser:
166
166
  """
167
167
  return {
168
168
  "student_information": self.student_info,
169
+ "week": self.week_tag,
170
+ "week_num": (
171
+ int(self.week_tag.split("-")[0].strip().replace("week", ""))
172
+ if self.week_tag
173
+ else None
174
+ ),
175
+ "assignment_type": (
176
+ self.week_tag.split("-")[1].strip() if self.week_tag else None
177
+ ),
169
178
  "assignment_information": {
170
179
  assignment: {
171
180
  "latest_timestamp": data["latest_timestamp"],
pykubegrader/telemetry.py CHANGED
@@ -10,6 +10,7 @@ import requests
10
10
  from IPython.core.interactiveshell import ExecutionInfo
11
11
  from requests import Response
12
12
  from requests.auth import HTTPBasicAuth
13
+ from requests.exceptions import RequestException
13
14
 
14
15
  #
15
16
  # Logging setup
@@ -122,7 +123,6 @@ def update_responses(key: str, value) -> dict:
122
123
  #
123
124
 
124
125
 
125
- # TODO: Improve error handling
126
126
  def score_question(
127
127
  term: str = "winter_2025",
128
128
  base_url: str = "https://engr-131-api.eastus.cloudapp.azure.com",
@@ -140,16 +140,24 @@ def score_question(
140
140
  "responses": responses,
141
141
  }
142
142
 
143
- res = requests.post(url, json=payload, auth=HTTPBasicAuth("student", "capture"))
144
-
145
- res_data: dict[str, tuple[float, float]] = res.json()
146
-
147
- for question, (points_earned, max_points) in res_data.items():
148
- log_variable(
149
- assignment_name=responses["assignment"],
150
- value=f"{points_earned}, {max_points}",
151
- info_type=question,
152
- )
143
+ try:
144
+ res = requests.post(url, json=payload, auth=HTTPBasicAuth("student", "capture"))
145
+ res.raise_for_status()
146
+
147
+ res_data: dict[str, tuple[float, float]] = res.json()
148
+
149
+ for question, (points_earned, max_points) in res_data.items():
150
+ log_variable(
151
+ assignment_name=responses["assignment"],
152
+ value=f"{points_earned}, {max_points}",
153
+ info_type=question,
154
+ )
155
+ except RequestException as e:
156
+ raise RuntimeError("Failed to access question-scoring endpoint") from e
157
+ except ValueError as e:
158
+ raise ValueError("Failed to parse question-scoring JSON response") from e
159
+ except Exception as e:
160
+ raise RuntimeError("Failed to score question") from e
153
161
 
154
162
 
155
163
  def submit_question(