PyKubeGrader 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/METADATA +2 -1
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/RECORD +13 -13
- pykubegrader/build/build_folder.py +32 -71
- pykubegrader/widgets/__init__.py +16 -7
- pykubegrader/widgets/select_many.py +1 -1
- pykubegrader/widgets/style.py +0 -2
- pykubegrader/widgets/true_false.py +3 -10
- pykubegrader/widgets_base/multi_select.py +1 -1
- pykubegrader/widgets_base/select.py +2 -3
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/WHEEL +0 -0
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/entry_points.txt +0 -0
- {PyKubeGrader-0.1.3.dist-info → PyKubeGrader-0.1.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyKubeGrader
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.5
|
4
4
|
Summary: Add a short description here!
|
5
5
|
Home-page: https://github.com/pyscaffold/pyscaffold/
|
6
6
|
Author: jagar2
|
@@ -15,6 +15,7 @@ License-File: LICENSE.txt
|
|
15
15
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
16
16
|
Requires-Dist: ipython
|
17
17
|
Requires-Dist: mypy
|
18
|
+
Requires-Dist: nbformat
|
18
19
|
Requires-Dist: numpy
|
19
20
|
Requires-Dist: panel
|
20
21
|
Requires-Dist: pynacl
|
@@ -3,22 +3,22 @@ pykubegrader/initialize.py,sha256=PACquuxFkvOKUx51Rv6_RVu1YnYXf9L_W3hmMY5IxMw,70
|
|
3
3
|
pykubegrader/telemetry.py,sha256=eAzb54-lHFcgv0IsLFwsWr4nm_D7u-n-zOdR4cKGID4,3291
|
4
4
|
pykubegrader/utils.py,sha256=dKw6SyRYU3DWRgD3xER7wq-C9e1daWPkqr901LpcwiQ,642
|
5
5
|
pykubegrader/validate.py,sha256=QzdsjSS7rBzvBj_s_VHk-shjkPE-NYHQrYI3UHbq7Yo,10632
|
6
|
-
pykubegrader/build/build_folder.py,sha256=
|
7
|
-
pykubegrader/widgets/__init__.py,sha256=
|
6
|
+
pykubegrader/build/build_folder.py,sha256=YQz11Jpb6huUMrVoi6aupGOMDmIy3e2impCuELUKtG8,58886
|
7
|
+
pykubegrader/widgets/__init__.py,sha256=s3ky3eJDa1RedFVdpKxmqv6mHBYpOSL9Z6qThSH9cbs,303
|
8
8
|
pykubegrader/widgets/multiple_choice.py,sha256=NjD3-uXSnibpUQ0mO3hRp_O-rynFyl0Dz6IXE4tnCRI,2078
|
9
9
|
pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
|
10
|
-
pykubegrader/widgets/select_many.py,sha256=
|
10
|
+
pykubegrader/widgets/select_many.py,sha256=l7YQ8QT5k71j36KC1f5LmKIAX2bXpvMDGc6nqIJ1PeQ,4116
|
11
11
|
pykubegrader/widgets/student_info.py,sha256=xhQgKehk1r5e6N_hnjAIovLdPvQju6ZqQTOiPG0aevg,3568
|
12
|
-
pykubegrader/widgets/style.py,sha256=
|
13
|
-
pykubegrader/widgets/true_false.py,sha256=
|
12
|
+
pykubegrader/widgets/style.py,sha256=fVBMYy_a6Yoz21avNpiORWC3f5FD-OrVpaZ3npmunvs,1656
|
13
|
+
pykubegrader/widgets/true_false.py,sha256=pE2FjvX6WQ-Z423N40nTWMtudtGS7LiNXZ5dERk6uWs,2823
|
14
14
|
pykubegrader/widgets/types_question.py,sha256=kZdRRXyFzOtYTmGdC7XWb_2oaxqg1WSuLcQn_sTj6Qc,2300
|
15
15
|
pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
16
|
-
pykubegrader/widgets_base/multi_select.py,sha256=
|
16
|
+
pykubegrader/widgets_base/multi_select.py,sha256=u50IOhYxC_S_gq31VnFPLdbNajk_SUWhaqlMSJxhqVQ,3439
|
17
17
|
pykubegrader/widgets_base/reading.py,sha256=4uTLmlPzCwxVzufFhPjM7W19uMGguRb6y4eAV3x-zAc,5314
|
18
|
-
pykubegrader/widgets_base/select.py,sha256=
|
19
|
-
PyKubeGrader-0.1.
|
20
|
-
PyKubeGrader-0.1.
|
21
|
-
PyKubeGrader-0.1.
|
22
|
-
PyKubeGrader-0.1.
|
23
|
-
PyKubeGrader-0.1.
|
24
|
-
PyKubeGrader-0.1.
|
18
|
+
pykubegrader/widgets_base/select.py,sha256=h1S5StcbX8S-Wiyga4fVDhPbVvRxffwaqyVbiiuInRs,2743
|
19
|
+
PyKubeGrader-0.1.5.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
|
20
|
+
PyKubeGrader-0.1.5.dist-info/METADATA,sha256=5gaWLJwjr14HUeYaob5VrwSbcTx5brtLIlROWPvltvc,2664
|
21
|
+
PyKubeGrader-0.1.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
22
|
+
PyKubeGrader-0.1.5.dist-info/entry_points.txt,sha256=Kd4Bh-i3hc4qlnLU1p0nc8yPw9cC5AQGOtkk2eLGnQw,78
|
23
|
+
PyKubeGrader-0.1.5.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
|
24
|
+
PyKubeGrader-0.1.5.dist-info/RECORD,,
|
@@ -1,14 +1,15 @@
|
|
1
|
-
|
1
|
+
import argparse
|
2
|
+
import importlib.util
|
3
|
+
import json
|
4
|
+
import logging
|
2
5
|
import os
|
6
|
+
import re
|
3
7
|
import shutil
|
4
|
-
import nbformat
|
5
8
|
import subprocess
|
6
9
|
import sys
|
7
|
-
import
|
8
|
-
|
9
|
-
import
|
10
|
-
import re
|
11
|
-
import importlib.util
|
10
|
+
from dataclasses import dataclass, field
|
11
|
+
|
12
|
+
import nbformat
|
12
13
|
|
13
14
|
|
14
15
|
@dataclass
|
@@ -182,11 +183,11 @@ class NotebookProcessor:
|
|
182
183
|
shutil.copy(notebook_path, temp_notebook_path)
|
183
184
|
|
184
185
|
# Determine the path to the autograder folder
|
185
|
-
autograder_path = os.path.join(notebook_subfolder,
|
186
|
+
autograder_path = os.path.join(notebook_subfolder, "dist/autograder/")
|
186
187
|
os.makedirs(autograder_path, exist_ok=True)
|
187
188
|
|
188
189
|
# Determine the path to the student folder
|
189
|
-
student_path = os.path.join(notebook_subfolder,
|
190
|
+
student_path = os.path.join(notebook_subfolder, "dist/student/")
|
190
191
|
os.makedirs(student_path, exist_ok=True)
|
191
192
|
|
192
193
|
if os.path.abspath(notebook_path) != os.path.abspath(new_notebook_path):
|
@@ -230,7 +231,6 @@ class NotebookProcessor:
|
|
230
231
|
|
231
232
|
### Parse the notebook for TF questions
|
232
233
|
if self.has_assignment(temp_notebook_path, "# BEGIN TF"):
|
233
|
-
|
234
234
|
markers = ("# BEGIN TF", "# END TF")
|
235
235
|
|
236
236
|
self._print_and_log(
|
@@ -262,7 +262,6 @@ class NotebookProcessor:
|
|
262
262
|
|
263
263
|
### Parse the notebook for select_many questions
|
264
264
|
if self.has_assignment(temp_notebook_path, "# BEGIN SELECT MANY"):
|
265
|
-
|
266
265
|
markers = ("# BEGIN SELECT MANY", "# END SELECT MANY")
|
267
266
|
|
268
267
|
self._print_and_log(
|
@@ -347,23 +346,31 @@ class NotebookProcessor:
|
|
347
346
|
|
348
347
|
### CODE TO ENSURE THAT STUDENT NOTEBOOK IS IMPORTABLE
|
349
348
|
if "question_path" in locals():
|
350
|
-
|
351
349
|
# question_root_path = os.path.dirname(question_path)
|
352
350
|
question_file_name = os.path.basename(question_path)
|
353
|
-
question_file_name_sanitized = sanitize_string(
|
351
|
+
question_file_name_sanitized = sanitize_string(
|
352
|
+
question_file_name.replace("_questions", "")
|
353
|
+
)
|
354
354
|
if question_file_name_sanitized.endswith("_py"):
|
355
355
|
question_file_name_sanitized = question_file_name_sanitized[:-3] + ".py"
|
356
|
-
|
356
|
+
|
357
357
|
# Rename the file
|
358
|
-
os.rename(
|
359
|
-
|
358
|
+
os.rename(
|
359
|
+
os.path.join(
|
360
|
+
student_path, question_file_name.replace("_questions", "")
|
361
|
+
),
|
362
|
+
os.path.join(student_path, question_file_name_sanitized),
|
363
|
+
)
|
364
|
+
|
360
365
|
# Ensure the "questions" folder exists
|
361
366
|
questions_folder_jbook = os.path.join(self.root_folder, "questions")
|
362
367
|
os.makedirs(questions_folder_jbook, exist_ok=True)
|
363
|
-
|
364
|
-
# Copy the renamed file to the "questions" folder
|
365
|
-
shutil.copy(os.path.join(student_path, question_file_name_sanitized), os.path.join(questions_folder_jbook, question_file_name_sanitized))
|
366
368
|
|
369
|
+
# Copy the renamed file to the "questions" folder
|
370
|
+
shutil.copy(
|
371
|
+
os.path.join(student_path, question_file_name_sanitized),
|
372
|
+
os.path.join(questions_folder_jbook, question_file_name_sanitized),
|
373
|
+
)
|
367
374
|
|
368
375
|
@staticmethod
|
369
376
|
def replace_temp_in_notebook(input_file, output_file):
|
@@ -434,7 +441,7 @@ class NotebookProcessor:
|
|
434
441
|
# {"Q2": {"question_text": "What is 3+3?", "points": 3.0}}
|
435
442
|
# ]
|
436
443
|
"""
|
437
|
-
merged_data = []
|
444
|
+
# merged_data = []
|
438
445
|
|
439
446
|
# Loop through each question set in the data
|
440
447
|
for i, _data in enumerate(data):
|
@@ -454,7 +461,7 @@ class NotebookProcessor:
|
|
454
461
|
grade_ = [raw[i]["grade"]]
|
455
462
|
|
456
463
|
# Merge each question's metadata with corresponding raw metadata
|
457
|
-
for j, (key,
|
464
|
+
for j, (key, _) in enumerate(_data.items()):
|
458
465
|
# Combine raw metadata with question data
|
459
466
|
data[i][key] = data[i][key] | raw[i]
|
460
467
|
# Assign the correct point value to the question
|
@@ -576,52 +583,6 @@ class NotebookProcessor:
|
|
576
583
|
f.write(f' "{key}": {repr(solution)},\n')
|
577
584
|
f.write("}\n")
|
578
585
|
|
579
|
-
@staticmethod
|
580
|
-
def generate_solution_MCQ(data_list, output_file="output.py"):
|
581
|
-
"""
|
582
|
-
Generates a Python file with solutions and total points based on the input data.
|
583
|
-
If the file already exists, it appends new solutions to the existing solution dictionary.
|
584
|
-
|
585
|
-
Args:
|
586
|
-
data_list (list): A list of dictionaries containing question metadata.
|
587
|
-
output_file (str): Path to the output Python file.
|
588
|
-
"""
|
589
|
-
|
590
|
-
solutions = {}
|
591
|
-
total_points = 0.0
|
592
|
-
|
593
|
-
# If the output file exists, load the existing solutions and total_points
|
594
|
-
if os.path.exists(output_file):
|
595
|
-
spec = importlib.util.spec_from_file_location(
|
596
|
-
"existing_module", output_file
|
597
|
-
)
|
598
|
-
existing_module = importlib.util.module_from_spec(spec)
|
599
|
-
spec.loader.exec_module(existing_module) # Load the module dynamically
|
600
|
-
|
601
|
-
# Attempt to read existing solutions and total_points
|
602
|
-
if hasattr(existing_module, "solutions"):
|
603
|
-
solutions.update(existing_module.solutions)
|
604
|
-
if hasattr(existing_module, "total_points"):
|
605
|
-
total_points += existing_module.total_points
|
606
|
-
|
607
|
-
# Process new question data and update solutions and total_points
|
608
|
-
for question_set in data_list:
|
609
|
-
for key, question_data in question_set.items():
|
610
|
-
solution_key = f"q{question_data['question number']}-{question_data['subquestion_number']}-{key}"
|
611
|
-
solutions[solution_key] = question_data["solution"]
|
612
|
-
total_points += question_data["points"]
|
613
|
-
|
614
|
-
# Write updated total_points and solutions back to the file
|
615
|
-
with open(output_file, "w", encoding="utf-8") as f:
|
616
|
-
f.write("from typing import Any\n\n")
|
617
|
-
f.write(f"total_points: float = {total_points}\n\n")
|
618
|
-
|
619
|
-
f.write("solutions: dict[str, Any] = {\n")
|
620
|
-
for key, solution in solutions.items():
|
621
|
-
# For safety, we assume solutions are strings, but if not, repr would be safer
|
622
|
-
f.write(f' "{key}": {repr(solution)},\n')
|
623
|
-
f.write("}\n")
|
624
|
-
|
625
586
|
def extract_MCQ(ipynb_file):
|
626
587
|
"""
|
627
588
|
Extracts questions from markdown cells and organizes them as a nested dictionary,
|
@@ -1314,7 +1275,7 @@ def generate_mcq_file(data_dict, output_file="mc_questions.py"):
|
|
1314
1275
|
]
|
1315
1276
|
|
1316
1277
|
# Ensure header lines are present
|
1317
|
-
|
1278
|
+
_existing_content = ensure_imports(output_file, header_lines)
|
1318
1279
|
|
1319
1280
|
for question_dict in data_dict:
|
1320
1281
|
with open(output_file, "a", encoding="utf-8") as f:
|
@@ -1383,7 +1344,7 @@ def generate_select_many_file(data_dict, output_file="select_many_questions.py")
|
|
1383
1344
|
]
|
1384
1345
|
|
1385
1346
|
# Ensure header lines are present
|
1386
|
-
|
1347
|
+
_existing_content = ensure_imports(output_file, header_lines)
|
1387
1348
|
|
1388
1349
|
for question_dict in data_dict:
|
1389
1350
|
with open(output_file, "a", encoding="utf-8") as f:
|
@@ -1432,7 +1393,7 @@ def generate_select_many_file(data_dict, output_file="select_many_questions.py")
|
|
1432
1393
|
first_key = next(iter(question_dict))
|
1433
1394
|
if "grade" in question_dict[first_key]:
|
1434
1395
|
grade = question_dict[first_key]["grade"]
|
1435
|
-
f.write(f" grade=
|
1396
|
+
f.write(f" grade={grade},\n")
|
1436
1397
|
|
1437
1398
|
f.write(" )\n")
|
1438
1399
|
|
@@ -1458,7 +1419,7 @@ def generate_tf_file(data_dict, output_file="tf_questions.py"):
|
|
1458
1419
|
]
|
1459
1420
|
|
1460
1421
|
# Ensure header lines are present
|
1461
|
-
|
1422
|
+
_existing_content = ensure_imports(output_file, header_lines)
|
1462
1423
|
|
1463
1424
|
for question_dict in data_dict:
|
1464
1425
|
with open(output_file, "a", encoding="utf-8") as f:
|
pykubegrader/widgets/__init__.py
CHANGED
@@ -1,10 +1,19 @@
|
|
1
1
|
# Auto-generated __init__.py
|
2
2
|
|
3
|
-
from . import
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
from . import (
|
4
|
+
multiple_choice,
|
5
|
+
reading_question,
|
6
|
+
select_many,
|
7
|
+
student_info,
|
8
|
+
true_false,
|
9
|
+
types_question,
|
10
|
+
)
|
9
11
|
|
10
|
-
__all__ = [
|
12
|
+
__all__ = [
|
13
|
+
"select_many",
|
14
|
+
"multiple_choice",
|
15
|
+
"true_false",
|
16
|
+
"reading_question",
|
17
|
+
"student_info",
|
18
|
+
"types_question",
|
19
|
+
]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import panel as pn
|
2
2
|
|
3
|
-
from ..widgets_base.multi_select import MultiSelectQuestion
|
4
3
|
from ..widgets.style import drexel_colors, raw_css
|
4
|
+
from ..widgets_base.multi_select import MultiSelectQuestion
|
5
5
|
|
6
6
|
# Pass the custom CSS to Panel
|
7
7
|
pn.extension(design="material", global_css=[drexel_colors], raw_css=[raw_css])
|
pykubegrader/widgets/style.py
CHANGED
@@ -1,13 +1,10 @@
|
|
1
|
-
from typing import Tuple
|
1
|
+
from typing import List, Tuple
|
2
2
|
|
3
3
|
import panel as pn
|
4
4
|
|
5
|
-
from ..utils import list_of_lists
|
6
5
|
from ..widgets_base.select import SelectQuestion
|
7
6
|
from .style import drexel_colors, raw_css
|
8
7
|
|
9
|
-
import panel as pn
|
10
|
-
|
11
8
|
# Pass the custom CSS to Panel
|
12
9
|
pn.extension(design="material", global_css=[drexel_colors], raw_css=[raw_css])
|
13
10
|
|
@@ -16,10 +13,6 @@ pn.extension(design="material", global_css=[drexel_colors], raw_css=[raw_css])
|
|
16
13
|
#
|
17
14
|
|
18
15
|
|
19
|
-
import panel as pn
|
20
|
-
from typing import List, Tuple
|
21
|
-
|
22
|
-
|
23
16
|
def TrueFalse_style(
|
24
17
|
descriptions: List[str],
|
25
18
|
options: List[str] | List[List[str]],
|
@@ -37,7 +30,7 @@ def TrueFalse_style(
|
|
37
30
|
Tuple[List[pn.pane.HTML], List[pn.widgets.RadioBoxGroup]]: Styled description panes and radio button groups.
|
38
31
|
"""
|
39
32
|
desc_width = "100%" # Responsive width for descriptions
|
40
|
-
button_width = "100%" # Responsive width for radio buttons
|
33
|
+
# button_width = "100%" # Responsive width for radio buttons
|
41
34
|
|
42
35
|
# Create description widgets
|
43
36
|
desc_widgets = [
|
@@ -73,8 +66,8 @@ def TrueFalse_style(
|
|
73
66
|
# Question class
|
74
67
|
#
|
75
68
|
|
76
|
-
class TFQuestion(SelectQuestion):
|
77
69
|
|
70
|
+
class TFQuestion(SelectQuestion):
|
78
71
|
def __init__(
|
79
72
|
self,
|
80
73
|
title="Select if the statement is True or False",
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import time
|
1
2
|
from typing import Callable, Tuple
|
2
3
|
|
3
4
|
import panel as pn
|
@@ -5,8 +6,6 @@ import panel as pn
|
|
5
6
|
from ..telemetry import ensure_responses, update_responses
|
6
7
|
from ..utils import shuffle_questions
|
7
8
|
from ..widgets.style import drexel_colors
|
8
|
-
import time
|
9
|
-
from IPython.display import update_display, display
|
10
9
|
|
11
10
|
# Pass the custom CSS to Panel
|
12
11
|
pn.extension(design="material", global_css=[drexel_colors])
|
@@ -75,7 +74,7 @@ class SelectQuestion:
|
|
75
74
|
self.submit_button.name = "Responses Submitted"
|
76
75
|
time.sleep(1)
|
77
76
|
self.submit_button.name = "Submit"
|
78
|
-
|
77
|
+
|
79
78
|
# # Display the message with a unique display_id
|
80
79
|
# display_id = "temp_message"
|
81
80
|
# display("Responses recorded successfully", display_id=display_id)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|