PyKubeGrader 0.2.41__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.2.41
3
+ Version: 0.3.1
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -19,6 +19,7 @@ Requires-Dist: mypy
19
19
  Requires-Dist: nbformat
20
20
  Requires-Dist: nest_asyncio
21
21
  Requires-Dist: numpy
22
+ Requires-Dist: pandas-stubs
22
23
  Requires-Dist: panel
23
24
  Requires-Dist: pynacl
24
25
  Requires-Dist: pytest
@@ -1,13 +1,13 @@
1
1
  pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
2
2
  pykubegrader/initialize.py,sha256=Bwu1q18l18FB9lGppvt-L41D5gzr3S8t6zC0_UbrASw,3994
3
- pykubegrader/telemetry.py,sha256=-XiWKMlwneS4zD8-hPcMVZFY4b6gK4jHKTwhjUBw8gw,6556
4
- pykubegrader/utils.py,sha256=eqQSf2xOAtqB9pWSf6RI-WwwdEowVe8yr2XIiNpc5Rc,870
3
+ pykubegrader/telemetry.py,sha256=50Qp5WXeF7PD5FxDLFXWFAnQ2Yobj-wL3Dxh0Hz_vh0,6552
4
+ pykubegrader/utils.py,sha256=FrxuZ3gtTBTm5FQeH5c0bF9kFjA_AVtE5AYeFhzwKZ0,827
5
5
  pykubegrader/validate.py,sha256=OKnItGyd-L8QPKcsE0KRuwBI_IxKiJzMLJKZiA2j3II,11184
6
6
  pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
7
  pykubegrader/build/api_notebook_builder.py,sha256=dlcVrGgsvxnt6GlAUN3e-FrpsPNJKXSHni1fstRCBik,20311
8
- pykubegrader/build/build_folder.py,sha256=WTdPFsV1PRJ9U_730ckcWezQfMEL2oCH4ZDoflmr30M,83138
9
- pykubegrader/build/clean_folder.py,sha256=dfs9NuZ-EP6q_xYQnZKH74aEafEB6hpTAZcSkY14UWI,1328
10
- pykubegrader/build/markdown_questions.py,sha256=OEEFoM5L4Ptax0dm8Tz-gM01akJEoDvsPNgixE1Vk1s,4675
8
+ pykubegrader/build/build_folder.py,sha256=Ltd2skxEP1OdZA_D3Kb-WyTpDVtv4e35v6_XiMFZ9Vo,85378
9
+ pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6tD-38,1323
10
+ pykubegrader/build/markdown_questions.py,sha256=cSh8mkHK3hh-etJdgrZu9UQi1WPrKQtofkzLCUp1Z-w,4676
11
11
  pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
12
12
  pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
13
13
  pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
@@ -17,21 +17,21 @@ pykubegrader/submit/submit_assignment.py,sha256=UgJXKWw5b8-bRSFnba4iHAyXnujULHcW
17
17
  pykubegrader/tokens/tokens.py,sha256=X9f3SzrGCrAJp_BXhr6VJn5f0LxtgQ7HLPBw7zEF2BY,1198
18
18
  pykubegrader/tokens/validate_token.py,sha256=MQtgz_USvSZ9JahJ48ybjp74F5aYz64lhtvuwVc4kQw,2712
19
19
  pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
- pykubegrader/widgets/multiple_choice.py,sha256=oH1NjL2cuQDhn-9Ds8yBNAWDHr2anGS__F8uel7IsiI,2657
21
- pykubegrader/widgets/question_processor.py,sha256=iWlurO1IRHY94NXI8ToZcBolGkP9lj9C9qZdE8nuH_Q,1233
20
+ pykubegrader/widgets/multiple_choice.py,sha256=CE7y6DPIhm4UuD8I1nwWPF2l9sKtKvYhbyPpeZ1qmQc,2686
21
+ pykubegrader/widgets/question_processor.py,sha256=59R9oBiemuVJP0qzsR1kY8MeqDq4Kh99AYRq9RGujsg,1223
22
22
  pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
23
- pykubegrader/widgets/select_many.py,sha256=l7YQ8QT5k71j36KC1f5LmKIAX2bXpvMDGc6nqIJ1PeQ,4116
23
+ pykubegrader/widgets/select_many.py,sha256=wyRPCEdI6GorPX3HzSbDiT_IHr34mFOUHzUN085-d88,4621
24
24
  pykubegrader/widgets/student_info.py,sha256=xhQgKehk1r5e6N_hnjAIovLdPvQju6ZqQTOiPG0aevg,3568
25
25
  pykubegrader/widgets/style.py,sha256=fVBMYy_a6Yoz21avNpiORWC3f5FD-OrVpaZ3npmunvs,1656
26
26
  pykubegrader/widgets/true_false.py,sha256=QllIhHuJstJft_RuShkxI_fFFTaDAlzNZOFNs00HLIM,2842
27
27
  pykubegrader/widgets/types_question.py,sha256=kZdRRXyFzOtYTmGdC7XWb_2oaxqg1WSuLcQn_sTj6Qc,2300
28
28
  pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
29
- pykubegrader/widgets_base/multi_select.py,sha256=_rvpW-oSeNRE9_RtOh1hA9l_-GHDIE3_7K129_k1N4I,4186
29
+ pykubegrader/widgets_base/multi_select.py,sha256=Btr09qjl2g2-wtKEtI3RYwo1Xm1dfFnHnDzw_1Yfqf4,4148
30
30
  pykubegrader/widgets_base/reading.py,sha256=xmvN1UIXwk32v9S-JhsXwDc7axPlgpvoxSeM3II8sxY,5393
31
- pykubegrader/widgets_base/select.py,sha256=mCwCEYz5wlBL8AZniqnSr_P_jnov349qMBUruaIbQ9o,2666
32
- PyKubeGrader-0.2.41.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
33
- PyKubeGrader-0.2.41.dist-info/METADATA,sha256=lQpVYU6uJvtccUNg_T6MaB1mD7WkgCVFNGO-7J3BeIg,2779
34
- PyKubeGrader-0.2.41.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
35
- PyKubeGrader-0.2.41.dist-info/entry_points.txt,sha256=BbLXpFZObpOXA8e3p3GcFkL-sHdUnDLUcnYmc6zx3NI,201
36
- PyKubeGrader-0.2.41.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
37
- PyKubeGrader-0.2.41.dist-info/RECORD,,
31
+ pykubegrader/widgets_base/select.py,sha256=RgSieRtBapAwXLd_ByJtT1L1EeUSi-9pFTuIm7zwDVE,2649
32
+ PyKubeGrader-0.3.1.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
33
+ PyKubeGrader-0.3.1.dist-info/METADATA,sha256=fqlGOEpvYY7AyGQV7ngPA01-D00cnsja365LanEQoFE,2806
34
+ PyKubeGrader-0.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
35
+ PyKubeGrader-0.3.1.dist-info/entry_points.txt,sha256=BbLXpFZObpOXA8e3p3GcFkL-sHdUnDLUcnYmc6zx3NI,201
36
+ PyKubeGrader-0.3.1.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
37
+ PyKubeGrader-0.3.1.dist-info/RECORD,,
@@ -616,6 +616,21 @@ class NotebookProcessor:
616
616
  shutil.copy("./keys/.client_private_key.bin", client_private_key)
617
617
  shutil.copy("./keys/.server_public_key.bin", server_public_key)
618
618
 
619
+ # Extract the assignment config
620
+ config = extract_config_from_notebook(temp_notebook_path)
621
+
622
+ files = extract_files(config)
623
+
624
+ # print(f"Files: {files}, from {temp_notebook_path}")
625
+
626
+ if files:
627
+ for file in files:
628
+ print(f"Copying {file} to {os.path.join(notebook_subfolder, file)}")
629
+ shutil.copy(
630
+ os.path.join(self.root_folder, file),
631
+ os.path.join(notebook_subfolder, file),
632
+ )
633
+
619
634
  client_private_key = os.path.join(
620
635
  notebook_subfolder,
621
636
  ".client_private_key.bin",
@@ -1352,15 +1367,18 @@ def extract_SELECT_MANY(ipynb_file):
1352
1367
  1 # Increment subquestion number for each question
1353
1368
  )
1354
1369
 
1355
- # Extract question text (### heading)
1356
- question_text_match = re.search(
1357
- r"^###\s*\*\*(.+)\*\*", markdown_content, re.MULTILINE
1358
- )
1359
- question_text = (
1360
- question_text_match.group(1).strip()
1361
- if question_text_match
1362
- else None
1363
- )
1370
+ # # Extract question text (### heading)
1371
+ # question_text_match = re.search(
1372
+ # r"^###\s*\*\*(.+)\*\*", markdown_content, re.MULTILINE
1373
+ # )
1374
+ # question_text = (
1375
+ # question_text_match.group(1).strip()
1376
+ # if question_text_match
1377
+ # else None
1378
+ # )
1379
+
1380
+ # Extract question text enable multiple lines
1381
+ question_text = extract_question(markdown_content)
1364
1382
 
1365
1383
  # Extract OPTIONS (lines after #### options)
1366
1384
  options_match = re.search(
@@ -2126,6 +2144,57 @@ def update_initialize_assignment(
2126
2144
  print(f"No matching lines found in '{notebook_path}'.")
2127
2145
 
2128
2146
 
2147
+ def extract_config_from_notebook(notebook_path):
2148
+ """
2149
+ Extract configuration text from a Jupyter Notebook.
2150
+
2151
+ Parameters:
2152
+ notebook_path (str): Path to the Jupyter Notebook file.
2153
+
2154
+ Returns:
2155
+ str: The configuration text if found, otherwise an empty string.
2156
+ """
2157
+ with open(notebook_path, "r", encoding="utf-8") as f:
2158
+ notebook_data = json.load(f)
2159
+
2160
+ # Iterate through cells to find the configuration text
2161
+ config_text = ""
2162
+ for cell in notebook_data.get("cells", []):
2163
+ if cell.get("cell_type") == "raw": # Check for code cells
2164
+ source = "".join(cell.get("source", []))
2165
+ if "# ASSIGNMENT CONFIG" in source:
2166
+ config_text = source
2167
+ break
2168
+
2169
+ return config_text
2170
+
2171
+
2172
+ def extract_files(config_text):
2173
+ """
2174
+ Extract the list of files from the given configuration text, excluding .bin files.
2175
+
2176
+ Parameters:
2177
+ config_text (str): The configuration text to process.
2178
+
2179
+ Returns:
2180
+ list: A list of file names excluding .bin files.
2181
+ """
2182
+ # Regular expression to extract files list
2183
+ file_pattern = re.search(r"files:\s*\[(.*?)\]", config_text, re.DOTALL)
2184
+
2185
+ if file_pattern:
2186
+ files = file_pattern.group(1)
2187
+ # Split the list into individual file names and exclude .bin files
2188
+ file_list = [
2189
+ file.strip()
2190
+ for file in files.split(",")
2191
+ if not file.strip().endswith(".bin")
2192
+ ]
2193
+ return file_list
2194
+ else:
2195
+ return []
2196
+
2197
+
2129
2198
  def main():
2130
2199
  parser = argparse.ArgumentParser(
2131
2200
  description="Recursively process Jupyter notebooks with '# ASSIGNMENT CONFIG', move them to a solutions folder, and run otter assign."
@@ -5,7 +5,6 @@ import sys
5
5
 
6
6
 
7
7
  class FolderCleaner:
8
-
9
8
  def __init__(self, root_folder: str):
10
9
  """
11
10
  Initializes the FolderCleaner with the root folder to clean.
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import os
3
+
3
4
  import nbformat as nbf
4
5
 
5
6
 
pykubegrader/telemetry.py CHANGED
@@ -5,9 +5,9 @@ import logging
5
5
  import os
6
6
  import socket
7
7
  from typing import Any, Optional
8
- import pandas as pd
9
8
 
10
9
  import nacl.public
10
+ import pandas as pd
11
11
  import requests
12
12
  from IPython.core.interactiveshell import ExecutionInfo
13
13
  from requests import Response
@@ -205,7 +205,7 @@ def verify_server(jhub_user: Optional[str] = None) -> str:
205
205
  return message
206
206
 
207
207
 
208
- def get_my_grades() -> dict[str, float]:
208
+ def get_my_grades() -> pd.DataFrame:
209
209
  if not student_user or not student_pw or not api_base_url:
210
210
  raise ValueError("Necessary environment variables not set")
211
211
 
pykubegrader/utils.py CHANGED
@@ -15,9 +15,7 @@ def list_of_lists(options: list) -> bool:
15
15
 
16
16
  def shuffle_options(options: list[Optional[str]], seed: int) -> list[Optional[str]]:
17
17
  random.seed(seed)
18
-
19
- for inner_list in options:
20
- random.shuffle(inner_list)
18
+ random.shuffle(options)
21
19
 
22
20
  return options
23
21
 
@@ -16,7 +16,6 @@ def MCQ(
16
16
  options: list[str] | list[list[str]],
17
17
  initial_vals: list[str],
18
18
  ) -> Tuple[list[pn.pane.HTML], list[pn.widgets.RadioButtonGroup]]:
19
-
20
19
  # Process descriptions through `process_questions_and_codes`
21
20
  processed_titles, code_blocks = process_questions_and_codes(descriptions)
22
21
 
@@ -29,7 +28,9 @@ def MCQ(
29
28
  )
30
29
  # Add the title and code block in a row
31
30
  if code_block:
32
- desc_widgets.append(pn.Column(title_pane, code_block, sizing_mode="stretch_width"))
31
+ desc_widgets.append(
32
+ pn.Column(title_pane, code_block, sizing_mode="stretch_width")
33
+ )
33
34
  else:
34
35
  desc_widgets.append(pn.Column(title_pane, sizing_mode="stretch_width"))
35
36
 
@@ -1,4 +1,3 @@
1
- import re
2
1
  import panel as pn
3
2
 
4
3
 
@@ -2,6 +2,7 @@ import panel as pn
2
2
 
3
3
  from ..widgets.style import drexel_colors, raw_css
4
4
  from ..widgets_base.multi_select import MultiSelectQuestion
5
+ from .question_processor import process_questions_and_codes
5
6
 
6
7
  # Pass the custom CSS to Panel
7
8
  pn.extension(design="material", global_css=[drexel_colors], raw_css=[raw_css])
@@ -24,15 +25,25 @@ def MultiSelect(
24
25
  separator = pn.pane.HTML("<hr style='border:1px solid lightgray; width:100%;'>")
25
26
 
26
27
  i = 0
28
+ desc_width = "500px"
27
29
 
28
30
  for question, option_set in zip(descriptions, options):
29
- desc_width = "500px"
30
-
31
- # Create description widget with separator
32
- desc_widget = pn.pane.HTML(
31
+ # Process descriptions through `process_questions_and_codes`
32
+ processed_titles, code_blocks = process_questions_and_codes(question)
33
+
34
+ # Create an HTML pane for the title
35
+ title_pane = pn.pane.HTML(
33
36
  f"<hr style='border:1px solid lightgray; width:100%;'>"
34
- f"<div style='text-align: left; width: {desc_width};'><b>{question}</b></div>"
37
+ f"<div style='text-align: left; width: {desc_width};'><b>{processed_titles[0]}</b></div>"
35
38
  )
39
+ # Add the title and code block in a row
40
+ if code_blocks[0]:
41
+ desc_widget = pn.Column(title_pane, code_blocks[0], sizing_mode="stretch_width")
42
+ else:
43
+ desc_widget = title_pane
44
+
45
+ # # Create description widget with separator
46
+
36
47
 
37
48
  # Create checkboxes for current question
38
49
  checkbox_set = [
@@ -4,7 +4,7 @@ from typing import Callable, Tuple
4
4
  import panel as pn
5
5
 
6
6
  from ..telemetry import ensure_responses, score_question, update_responses
7
- from ..utils import shuffle_questions, shuffle_options
7
+ from ..utils import shuffle_questions
8
8
  from ..widgets.style import drexel_colors, raw_css
9
9
 
10
10
  # Pass the custom CSS to Panel
@@ -58,7 +58,7 @@ class MultiSelectQuestion:
58
58
 
59
59
  # # add shuffle options to multi_select.py
60
60
  # options = shuffle_options(options, seed)
61
-
61
+
62
62
  description_widgets, self.widgets = style(
63
63
  descriptions, options, self.initial_vals
64
64
  )
@@ -72,14 +72,13 @@ class MultiSelectQuestion:
72
72
  question_header = pn.pane.HTML(
73
73
  f"<h2>Question {self.question_number}: {title}</h2>"
74
74
  )
75
-
75
+
76
76
  question_body = pn.Column(
77
77
  *[
78
78
  pn.Row(desc_widget, checkbox_set)
79
79
  for desc_widget, checkbox_set in widget_pairs
80
80
  ]
81
81
  )
82
-
83
82
 
84
83
  self.layout = pn.Column(question_header, question_body, self.submit_button)
85
84
 
@@ -4,7 +4,7 @@ from typing import Callable, Tuple
4
4
  import panel as pn
5
5
 
6
6
  from ..telemetry import ensure_responses, score_question, update_responses
7
- from ..utils import shuffle_questions, shuffle_options
7
+ from ..utils import shuffle_questions
8
8
  from ..widgets.style import drexel_colors
9
9
 
10
10
  # Pass custom CSS to Panel