PyKubeGrader 0.2.1__tar.gz → 0.2.3__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pykubegrader-0.2.1/src/PyKubeGrader.egg-info → pykubegrader-0.2.3}/PKG-INFO +1 -1
- {pykubegrader-0.2.1 → pykubegrader-0.2.3/src/PyKubeGrader.egg-info}/PKG-INFO +1 -1
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/build/build_folder.py +25 -1
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/initialize.py +9 -1
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/log_parser/parse.ipynb +37 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/log_parser/parse.py +9 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/telemetry.py +19 -11
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/.coveragerc +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/.github/workflows/main.yml +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/.gitignore +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/.readthedocs.yml +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/AUTHORS.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/CHANGELOG.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/CONTRIBUTING.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/LICENSE.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/README.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/Makefile +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/_static/custom.css +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/authors.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/changelog.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/conf.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/contributing.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/index.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/license.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/readme.rst +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/docs/requirements.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/examples/.responses.json +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/examples/true_false.ipynb +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/pyproject.toml +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/setup.cfg +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/setup.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/requires.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/build/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/build/api_notebook_builder.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/build/clean_folder.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/graders/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/graders/late_assignments.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/log_parser/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/utils.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/validate.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/multiple_choice.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/reading_question.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/select_many.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/student_info.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/style.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/true_false.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets/types_question.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets_base/__init__.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets_base/multi_select.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets_base/reading.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/src/pykubegrader/widgets_base/select.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/tests/conftest.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/tests/import_test.py +0 -0
- {pykubegrader-0.2.1 → pykubegrader-0.2.3}/tox.ini +0 -0
@@ -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
|
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 =
|
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"],
|
@@ -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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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(
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|