PyKubeGrader 0.2.41__tar.gz → 0.3.1__tar.gz

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.
Files changed (68) hide show
  1. {pykubegrader-0.2.41/src/PyKubeGrader.egg-info → pykubegrader-0.3.1}/PKG-INFO +2 -1
  2. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/setup.cfg +1 -0
  3. {pykubegrader-0.2.41 → pykubegrader-0.3.1/src/PyKubeGrader.egg-info}/PKG-INFO +2 -1
  4. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/requires.txt +1 -0
  5. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/build/build_folder.py +78 -9
  6. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/build/clean_folder.py +0 -1
  7. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/build/markdown_questions.py +1 -0
  8. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/telemetry.py +2 -2
  9. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/utils.py +1 -3
  10. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/multiple_choice.py +3 -2
  11. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/question_processor.py +0 -1
  12. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/select_many.py +16 -5
  13. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets_base/multi_select.py +3 -4
  14. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets_base/select.py +1 -1
  15. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/.coveragerc +0 -0
  16. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/.github/workflows/main.yml +0 -0
  17. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/.gitignore +0 -0
  18. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/.readthedocs.yml +0 -0
  19. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/AUTHORS.rst +0 -0
  20. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/CHANGELOG.rst +0 -0
  21. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/CONTRIBUTING.rst +0 -0
  22. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/LICENSE.txt +0 -0
  23. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/README.rst +0 -0
  24. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/Makefile +0 -0
  25. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  26. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  27. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/_static/custom.css +0 -0
  28. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/authors.rst +0 -0
  29. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/changelog.rst +0 -0
  30. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/conf.py +0 -0
  31. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/contributing.rst +0 -0
  32. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/index.rst +0 -0
  33. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/license.rst +0 -0
  34. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/readme.rst +0 -0
  35. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/docs/requirements.txt +0 -0
  36. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/examples/.responses.json +0 -0
  37. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/examples/true_false.ipynb +0 -0
  38. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/pyproject.toml +0 -0
  39. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/setup.py +0 -0
  40. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
  41. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  42. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
  43. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  44. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  45. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/__init__.py +0 -0
  46. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/build/__init__.py +0 -0
  47. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/build/api_notebook_builder.py +0 -0
  48. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/graders/__init__.py +0 -0
  49. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/graders/late_assignments.py +0 -0
  50. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/initialize.py +0 -0
  51. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/log_parser/__init__.py +0 -0
  52. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/log_parser/parse.ipynb +0 -0
  53. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/log_parser/parse.py +0 -0
  54. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/submit/submit_assignment.py +0 -0
  55. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/tokens/tokens.py +0 -0
  56. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/tokens/validate_token.py +0 -0
  57. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/validate.py +0 -0
  58. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/__init__.py +0 -0
  59. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/reading_question.py +0 -0
  60. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/student_info.py +0 -0
  61. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/style.py +0 -0
  62. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/true_false.py +0 -0
  63. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets/types_question.py +0 -0
  64. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets_base/__init__.py +0 -0
  65. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/src/pykubegrader/widgets_base/reading.py +0 -0
  66. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/tests/conftest.py +0 -0
  67. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/tests/import_test.py +0 -0
  68. {pykubegrader-0.2.41 → pykubegrader-0.3.1}/tox.ini +0 -0
@@ -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
@@ -29,6 +29,7 @@ install_requires =
29
29
  nbformat
30
30
  nest_asyncio
31
31
  numpy
32
+ pandas-stubs
32
33
  panel
33
34
  pynacl
34
35
  pytest
@@ -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
@@ -4,6 +4,7 @@ mypy
4
4
  nbformat
5
5
  nest_asyncio
6
6
  numpy
7
+ pandas-stubs
7
8
  panel
8
9
  pynacl
9
10
  pytest
@@ -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
 
@@ -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
 
@@ -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
 
@@ -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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes