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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.1.3
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=2tG60aAAOsmR0knwl8gaeTH1yEHKrQQAyBKcxDfav-M,60928
7
- pykubegrader/widgets/__init__.py,sha256=muBpzAx8qMxOUA6evRQ0ZLC3pLMEMMire28zpfz55tQ,312
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=rq_SdOysEg0z16kRsdR03XNS6ZPnvZwBd-LnLDx2BDs,4116
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=JDzKWDtVYlpciIuIIyrXqcOkfYc1Hlc70gbZ_DFz3g0,1676
13
- pykubegrader/widgets/true_false.py,sha256=C4BesbQI9VvbcI1UDwMPZXKhCCtMtGFFdxpwspYVsL8,2921
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=9SI5sYonr8ZIUTyCyAARwyeucqwJuio8pxOZo_1V-Wo,3439
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=oBeuqqDaqEKf_522qFW5UzkiLG2fthwvmMAkE_YnN3w,2803
19
- PyKubeGrader-0.1.3.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
20
- PyKubeGrader-0.1.3.dist-info/METADATA,sha256=jK17ffTDJ1k708xrqAyd3yLt7L6qX8iUkswU-DWDPXs,2640
21
- PyKubeGrader-0.1.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
22
- PyKubeGrader-0.1.3.dist-info/entry_points.txt,sha256=Kd4Bh-i3hc4qlnLU1p0nc8yPw9cC5AQGOtkk2eLGnQw,78
23
- PyKubeGrader-0.1.3.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
24
- PyKubeGrader-0.1.3.dist-info/RECORD,,
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
- from dataclasses import dataclass, field
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 argparse
8
- import logging
9
- import json
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, f"dist/autograder/")
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, f"dist/student/")
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(question_file_name.replace("_questions", ""))
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(os.path.join(student_path, question_file_name.replace("_questions", "")), os.path.join(student_path, question_file_name_sanitized))
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, value) in enumerate(_data.items()):
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
- existing_content = ensure_imports(output_file, header_lines)
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
- existing_content = ensure_imports(output_file, header_lines)
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='{grade}',\n")
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
- existing_content = ensure_imports(output_file, header_lines)
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:
@@ -1,10 +1,19 @@
1
1
  # Auto-generated __init__.py
2
2
 
3
- from . import select_many
4
- from . import multiple_choice
5
- from . import true_false
6
- from . import reading_question
7
- from . import student_info
8
- from . import types_question
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__ = ['select_many', 'multiple_choice', 'true_false', 'reading_question', 'student_info', 'types_question']
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])
@@ -1,5 +1,3 @@
1
- import panel as pn
2
-
3
1
  # Extend the Material Design with custom Drexel colors
4
2
  drexel_colors = """
5
3
  :root {
@@ -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,7 +1,7 @@
1
+ import time
1
2
  from typing import Callable, Tuple
2
3
 
3
4
  import panel as pn
4
- import time
5
5
 
6
6
  from ..telemetry import ensure_responses, update_responses
7
7
  from ..utils import shuffle_questions
@@ -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)