PyKubeGrader 0.3.2__tar.gz → 0.3.4__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 (76) hide show
  1. {pykubegrader-0.3.2/src/PyKubeGrader.egg-info → pykubegrader-0.3.4}/PKG-INFO +1 -4
  2. pykubegrader-0.3.4/ruff.toml +2 -0
  3. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/setup.cfg +1 -3
  4. {pykubegrader-0.3.2 → pykubegrader-0.3.4/src/PyKubeGrader.egg-info}/PKG-INFO +1 -4
  5. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/SOURCES.txt +2 -0
  6. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/entry_points.txt +1 -0
  7. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/requires.txt +0 -3
  8. pykubegrader-0.3.4/src/pykubegrader/build/collate.py +190 -0
  9. pykubegrader-0.3.4/src/pykubegrader/grade_reports/grade_reports.py +76 -0
  10. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/grading_tester.ipynb +47 -36
  11. pykubegrader-0.3.4/src/pykubegrader/submit/submit_assignment.py +88 -0
  12. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/telemetry.py +196 -1
  13. pykubegrader-0.3.4/test.ipynb +50 -0
  14. pykubegrader-0.3.2/ruff.toml +0 -2
  15. pykubegrader-0.3.2/src/pykubegrader/grade_reports/grade_reports.py +0 -171
  16. pykubegrader-0.3.2/src/pykubegrader/submit/submit_assignment.py +0 -104
  17. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/.coveragerc +0 -0
  18. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/.github/workflows/main.yml +0 -0
  19. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/.gitignore +0 -0
  20. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/.readthedocs.yml +0 -0
  21. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/AUTHORS.rst +0 -0
  22. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/CHANGELOG.rst +0 -0
  23. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/CONTRIBUTING.rst +0 -0
  24. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/LICENSE.txt +0 -0
  25. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/README.rst +0 -0
  26. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/Makefile +0 -0
  27. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  28. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  29. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/_static/custom.css +0 -0
  30. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/authors.rst +0 -0
  31. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/changelog.rst +0 -0
  32. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/conf.py +0 -0
  33. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/contributing.rst +0 -0
  34. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/index.rst +0 -0
  35. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/license.rst +0 -0
  36. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/readme.rst +0 -0
  37. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/docs/requirements.txt +0 -0
  38. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/examples/.responses.json +0 -0
  39. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/examples/true_false.ipynb +0 -0
  40. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/pyproject.toml +0 -0
  41. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/setup.py +0 -0
  42. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  43. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  44. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  45. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/__init__.py +0 -0
  46. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/build/__init__.py +0 -0
  47. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/build/api_notebook_builder.py +0 -0
  48. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/build/build_folder.py +0 -0
  49. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/build/clean_folder.py +0 -0
  50. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/build/markdown_questions.py +0 -0
  51. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/graders/__init__.py +0 -0
  52. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/graders/late_assignments.py +0 -0
  53. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/initialize.py +0 -0
  54. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/log_parser/__init__.py +0 -0
  55. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/log_parser/parse.ipynb +0 -0
  56. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/log_parser/parse.py +0 -0
  57. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/tokens/tokens.py +0 -0
  58. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/tokens/validate_token.py +0 -0
  59. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/utils.py +0 -0
  60. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/validate.py +0 -0
  61. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/__init__.py +0 -0
  62. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/multiple_choice.py +0 -0
  63. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/question_processor.py +0 -0
  64. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/reading_question.py +0 -0
  65. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/select_many.py +0 -0
  66. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/student_info.py +0 -0
  67. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/style.py +0 -0
  68. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/true_false.py +0 -0
  69. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets/types_question.py +0 -0
  70. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets_base/__init__.py +0 -0
  71. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets_base/multi_select.py +0 -0
  72. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets_base/reading.py +0 -0
  73. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/src/pykubegrader/widgets_base/select.py +0 -0
  74. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/tests/conftest.py +0 -0
  75. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/tests/import_test.py +0 -0
  76. {pykubegrader-0.3.2 → pykubegrader-0.3.4}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -12,12 +12,10 @@ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
13
  Description-Content-Type: text/x-rst; charset=UTF-8
14
14
  License-File: LICENSE.txt
15
- Requires-Dist: httpx
16
15
  Requires-Dist: importlib-metadata; python_version < "3.8"
17
16
  Requires-Dist: ipython
18
17
  Requires-Dist: mypy
19
18
  Requires-Dist: nbformat
20
- Requires-Dist: nest_asyncio
21
19
  Requires-Dist: numpy
22
20
  Requires-Dist: pandas-stubs
23
21
  Requires-Dist: panel
@@ -28,7 +26,6 @@ Requires-Dist: ruff
28
26
  Requires-Dist: setuptools
29
27
  Requires-Dist: sphinx
30
28
  Requires-Dist: types-python-dateutil
31
- Requires-Dist: types-pyyaml
32
29
  Requires-Dist: types-requests
33
30
  Requires-Dist: types-setuptools
34
31
  Provides-Extra: testing
@@ -0,0 +1,2 @@
1
+ [lint.per-file-ignores]
2
+ "*.ipynb" = ["F811", "F821", "F841"]
@@ -22,12 +22,10 @@ include_package_data = True
22
22
  package_dir =
23
23
  =src
24
24
  install_requires =
25
- httpx
26
25
  importlib-metadata; python_version<"3.8"
27
26
  ipython
28
27
  mypy
29
28
  nbformat
30
- nest_asyncio
31
29
  numpy
32
30
  pandas-stubs
33
31
  panel
@@ -38,7 +36,6 @@ install_requires =
38
36
  setuptools
39
37
  sphinx
40
38
  types-python-dateutil
41
- types-pyyaml
42
39
  types-requests
43
40
  types-setuptools
44
41
 
@@ -59,6 +56,7 @@ console_scripts =
59
56
  otter-folder-builder = pykubegrader.build.build_folder:main
60
57
  otter-folder-cleaner = pykubegrader.build.clean_folder:main
61
58
  markdown-question = pykubegrader.build.markdown_questions:main
59
+ collate-questions = pykubegrader.build.collate:main
62
60
 
63
61
  [tool:pytest]
64
62
  addopts =
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -12,12 +12,10 @@ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
13
  Description-Content-Type: text/x-rst; charset=UTF-8
14
14
  License-File: LICENSE.txt
15
- Requires-Dist: httpx
16
15
  Requires-Dist: importlib-metadata; python_version < "3.8"
17
16
  Requires-Dist: ipython
18
17
  Requires-Dist: mypy
19
18
  Requires-Dist: nbformat
20
- Requires-Dist: nest_asyncio
21
19
  Requires-Dist: numpy
22
20
  Requires-Dist: pandas-stubs
23
21
  Requires-Dist: panel
@@ -28,7 +26,6 @@ Requires-Dist: ruff
28
26
  Requires-Dist: setuptools
29
27
  Requires-Dist: sphinx
30
28
  Requires-Dist: types-python-dateutil
31
- Requires-Dist: types-pyyaml
32
29
  Requires-Dist: types-requests
33
30
  Requires-Dist: types-setuptools
34
31
  Provides-Extra: testing
@@ -10,6 +10,7 @@ pyproject.toml
10
10
  ruff.toml
11
11
  setup.cfg
12
12
  setup.py
13
+ test.ipynb
13
14
  tox.ini
14
15
  .github/workflows/main.yml
15
16
  docs/Makefile
@@ -43,6 +44,7 @@ src/pykubegrader/build/__init__.py
43
44
  src/pykubegrader/build/api_notebook_builder.py
44
45
  src/pykubegrader/build/build_folder.py
45
46
  src/pykubegrader/build/clean_folder.py
47
+ src/pykubegrader/build/collate.py
46
48
  src/pykubegrader/build/markdown_questions.py
47
49
  src/pykubegrader/grade_reports/grade_reports.py
48
50
  src/pykubegrader/graders/__init__.py
@@ -1,4 +1,5 @@
1
1
  [console_scripts]
2
+ collate-questions = pykubegrader.build.collate:main
2
3
  markdown-question = pykubegrader.build.markdown_questions:main
3
4
  otter-folder-builder = pykubegrader.build.build_folder:main
4
5
  otter-folder-cleaner = pykubegrader.build.clean_folder:main
@@ -1,8 +1,6 @@
1
- httpx
2
1
  ipython
3
2
  mypy
4
3
  nbformat
5
- nest_asyncio
6
4
  numpy
7
5
  pandas-stubs
8
6
  panel
@@ -13,7 +11,6 @@ ruff
13
11
  setuptools
14
12
  sphinx
15
13
  types-python-dateutil
16
- types-pyyaml
17
14
  types-requests
18
15
  types-setuptools
19
16
 
@@ -0,0 +1,190 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+
5
+ from nbformat.v4 import new_markdown_cell, new_notebook
6
+
7
+
8
+ class QuestionCollator:
9
+ def __init__(self, root_folder: str, output_path: str):
10
+ """
11
+ Initializes the QuestionCollator with the root folder and output path.
12
+
13
+ Args:
14
+ root_folder (str): Path to the root folder containing the solution files.
15
+ output_path (str): Path to save the collated notebook.
16
+ """
17
+ self.root_folder = root_folder
18
+ self.output_path = output_path
19
+
20
+ def find_solution_folders(self):
21
+ """
22
+ Finds all immediate subdirectories inside '_solution*' folders that contain notebooks.
23
+
24
+ Returns:
25
+ list: List of folder paths containing notebooks.
26
+ """
27
+ solution_folders = []
28
+
29
+ # Look for _solution* folders inside the root_folder
30
+ for dir_name in os.listdir(self.root_folder):
31
+ solution_folder_path = os.path.join(self.root_folder, dir_name)
32
+
33
+ if os.path.isdir(solution_folder_path) and dir_name.startswith("_solution"):
34
+ print(f"Found solution folder: {solution_folder_path}") # Debug output
35
+
36
+ # Now, look for immediate subdirectories inside this _solution* folder
37
+ for sub_dir in os.listdir(solution_folder_path):
38
+ sub_dir_path = os.path.join(solution_folder_path, sub_dir)
39
+
40
+ if os.path.isdir(sub_dir_path):
41
+ # Check if this subdirectory contains at least one .ipynb file
42
+ if any(f.endswith(".ipynb") for f in os.listdir(sub_dir_path)):
43
+ solution_folders.append(sub_dir_path)
44
+
45
+ print(f"Final list of solution subfolders: {solution_folders}") # Debug output
46
+ return solution_folders
47
+
48
+ def extract_questions(self, folder_path):
49
+ """
50
+ Extracts questions from all notebooks in the solution folder.
51
+
52
+ Args:
53
+ folder_path (str): Path to the solution folder.
54
+
55
+ Returns:
56
+ dict: Dictionary of categorized questions.
57
+ """
58
+ questions = {
59
+ "multiple_choice": [],
60
+ "select_many": [],
61
+ "true_false": [],
62
+ "other": [],
63
+ }
64
+
65
+ for file in os.listdir(folder_path):
66
+ if file.endswith(".ipynb"):
67
+ file_path = os.path.join(folder_path, file)
68
+ print(
69
+ f"Processing notebook: {file_path}"
70
+ ) # Print the full path of the notebook
71
+ with open(file_path, "r") as f:
72
+ content = json.load(f)
73
+
74
+ # Track whether we are inside a question block
75
+ in_question_block = False
76
+ current_question_content = []
77
+
78
+ for cell in content["cells"]:
79
+ if "# BEGIN MULTIPLE CHOICE" in cell["source"]:
80
+ # Start of a question block
81
+ in_question_block = True
82
+ current_question_content = []
83
+ elif "# END MULTIPLE CHOICE" in cell["source"]:
84
+ # End of a question block
85
+ in_question_block = False
86
+ if current_question_content:
87
+ questions["multiple_choice"].append(
88
+ {"source": "\n".join(current_question_content)}
89
+ )
90
+ current_question_content = []
91
+ elif in_question_block and cell["cell_type"] == "markdown":
92
+ # Capture markdown cells within the question block
93
+ current_question_content.append(cell["source"])
94
+
95
+ return questions
96
+
97
+ def create_collated_notebook(self, questions):
98
+ """
99
+ Creates a new notebook with questions organized by type.
100
+
101
+ Args:
102
+ questions (dict): Dictionary of categorized questions.
103
+
104
+ Returns:
105
+ Notebook: The collated notebook.
106
+ """
107
+ nb = new_notebook()
108
+
109
+ # Add Multiple Choice Questions
110
+ nb.cells.append(new_markdown_cell("# Multiple Choice Questions"))
111
+ for q in questions["multiple_choice"]:
112
+ nb.cells.append(new_markdown_cell(q["source"]))
113
+
114
+ # Add Select Many Questions
115
+ nb.cells.append(new_markdown_cell("# Select Many Questions"))
116
+ for q in questions["select_many"]:
117
+ nb.cells.append(new_markdown_cell(q["source"]))
118
+
119
+ # Add True/False Questions
120
+ nb.cells.append(new_markdown_cell("# True/False Questions"))
121
+ for q in questions["true_false"]:
122
+ nb.cells.append(new_markdown_cell(q["source"]))
123
+
124
+ # Add Other Questions
125
+ nb.cells.append(new_markdown_cell("# Other Questions"))
126
+ for q in questions["other"]:
127
+ nb.cells.append(new_markdown_cell(q["source"]))
128
+
129
+ return nb
130
+
131
+ def save_notebook(self, nb):
132
+ """
133
+ Saves the collated notebook to the specified output path.
134
+
135
+ Args:
136
+ nb (Notebook): The notebook to save.
137
+ """
138
+ import nbformat
139
+
140
+ with open(self.output_path, "w") as f:
141
+ nbformat.write(nb, f)
142
+
143
+ def collate_questions(self):
144
+ """
145
+ Collates questions from all solution folders and saves them to a new notebook.
146
+ """
147
+ solution_folders = self.find_solution_folders()
148
+ all_questions = {
149
+ "multiple_choice": [],
150
+ "select_many": [],
151
+ "true_false": [],
152
+ "other": [],
153
+ }
154
+
155
+ for folder in solution_folders:
156
+ questions = self.extract_questions(folder)
157
+ all_questions["multiple_choice"].extend(questions["multiple_choice"])
158
+ all_questions["select_many"].extend(questions["select_many"])
159
+ all_questions["true_false"].extend(questions["true_false"])
160
+ all_questions["other"].extend(questions["other"])
161
+
162
+ collated_nb = self.create_collated_notebook(all_questions)
163
+ self.save_notebook(collated_nb)
164
+ print(f"Collated notebook saved to {self.output_path}")
165
+
166
+
167
+ def main():
168
+ parser = argparse.ArgumentParser(
169
+ description="Collate questions from solution folders into a single notebook."
170
+ )
171
+ parser.add_argument(
172
+ "root_folder",
173
+ type=str,
174
+ help="Path to the root folder containing solution folders",
175
+ )
176
+ parser.add_argument(
177
+ "output_path", type=str, help="Path to save the collated notebook"
178
+ )
179
+
180
+ args = parser.parse_args()
181
+ collator = QuestionCollator(
182
+ root_folder=args.root_folder, output_path=args.output_path
183
+ )
184
+ collator.collate_questions()
185
+
186
+
187
+ if __name__ == "__main__":
188
+ import sys
189
+
190
+ sys.exit(main())
@@ -0,0 +1,76 @@
1
+ import os
2
+
3
+ import pandas as pd
4
+ import requests
5
+ from requests.auth import HTTPBasicAuth
6
+
7
+
8
+ def format_assignment_table(assignments):
9
+ # Create DataFrame
10
+ df = pd.DataFrame(assignments)
11
+
12
+ # Replacements for normalization
13
+ replacements = {
14
+ "practicequiz": "practice quiz",
15
+ "practice-quiz": "practice quiz",
16
+ "attend": "attendance",
17
+ "attendance": "attendance",
18
+ }
19
+
20
+ # Remove assignments of type 'test'
21
+ remove_assignments = ["test"]
22
+
23
+ # Apply replacements
24
+ df["assignment_name"] = df["assignment_type"].replace(replacements)
25
+
26
+ # Filter out specific assignment types
27
+ df = df[~df["assignment_type"].isin(remove_assignments)]
28
+
29
+ # Sort by week number and assignment name
30
+ df = df.sort_values(by=["assignment_name", "week_number"]).reset_index(drop=True)
31
+
32
+ return df
33
+
34
+
35
+ def get_student_grades(student_username):
36
+ # Get env variables here, in the function, rather than globally
37
+ api_base_url = os.getenv("DB_URL")
38
+ student_user = os.getenv("user_name_student")
39
+ student_pw = os.getenv("keys_student")
40
+
41
+ if not api_base_url or not student_user or not student_pw:
42
+ raise ValueError("Environment variables not set")
43
+
44
+ params = {"username": student_username}
45
+ res = requests.get(
46
+ url=api_base_url.rstrip("/") + "/student-grades-testing",
47
+ params=params,
48
+ auth=HTTPBasicAuth(student_user, student_pw),
49
+ )
50
+ [assignments, sub] = res.json()
51
+
52
+ assignments_df = format_assignment_table(assignments)
53
+
54
+ return assignments_df, pd.DataFrame(sub)
55
+
56
+
57
+ def filter_assignments(df, max_week=None, exclude_types=None):
58
+ """
59
+ Remove assignments with week_number greater than max_week
60
+ or with specific assignment types.
61
+
62
+ :param df: DataFrame containing assignments.
63
+ :param max_week: Maximum allowed week_number (int).
64
+ :param exclude_types: A single assignment type or a list of assignment types to exclude.
65
+ :return: Filtered DataFrame.
66
+ """
67
+ if max_week is not None:
68
+ df = df[df["week_number"] <= max_week]
69
+
70
+ if exclude_types is not None:
71
+ # Ensure exclude_types is a list
72
+ if not isinstance(exclude_types, (list, tuple, set)):
73
+ exclude_types = [exclude_types]
74
+ df = df[~df["assignment_type"].isin(exclude_types)]
75
+
76
+ return df
@@ -1,5 +1,16 @@
1
1
  {
2
2
  "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "from grade_reports.grade_reports import filter_assignments, get_student_grades\n",
10
+ "\n",
11
+ "get_student_grades"
12
+ ]
13
+ },
3
14
  {
4
15
  "cell_type": "code",
5
16
  "execution_count": null,
@@ -19,13 +30,13 @@
19
30
  "api_base_url = \"https://engr-131-api.eastus.cloudapp.azure.com/\"\n",
20
31
  "\n",
21
32
  "\n",
22
- "def get_all_students():\n",
23
- " res = requests.get(\n",
24
- " url=api_base_url.rstrip(\"/\") + \"/get-all-submission-emails\",\n",
25
- " auth=HTTPBasicAuth(user(), password()),\n",
26
- " )\n",
33
+ "# def get_all_students():\n",
34
+ "# res = requests.get(\n",
35
+ "# url=api_base_url.rstrip(\"/\") + \"/get-all-submission-emails\",\n",
36
+ "# auth=HTTPBasicAuth(user(), password()),\n",
37
+ "# )\n",
27
38
  "\n",
28
- " return res.json()\n",
39
+ "# return res.json()\n",
29
40
  "\n",
30
41
  "\n",
31
42
  "# def get_student_grades(student_id):\n",
@@ -41,47 +52,47 @@
41
52
  "# return assignments, sub\n",
42
53
  "\n",
43
54
  "\n",
44
- "def get_student(student):\n",
45
- " print(student)\n",
46
- " # Get assignments and submissions for the student (assumed functions)\n",
47
- " assignments, submissions = get_student_grades(student)\n",
55
+ "# def get_student(student):\n",
56
+ "# print(student)\n",
57
+ "# # Get assignments and submissions for the student (assumed functions)\n",
58
+ "# assignments, submissions = get_student_grades(student)\n",
48
59
  "\n",
49
- " # Recalculate grades and get a grades dictionary\n",
50
- " grades_dict = recalculate_best_grades(assignments, submissions)\n",
60
+ "# # Recalculate grades and get a grades dictionary\n",
61
+ "# grades_dict = recalculate_best_grades(assignments, submissions)\n",
51
62
  "\n",
52
- " # Calculate averages and build a row for the student\n",
53
- " row = calculate_averages(grades_dict, student)\n",
63
+ "# # Calculate averages and build a row for the student\n",
64
+ "# row = calculate_averages(grades_dict, student)\n",
54
65
  "\n",
55
- " # Convert the row (a dictionary) into a DataFrame\n",
56
- " # row_df = pd.DataFrame([row])\n",
66
+ "# # Convert the row (a dictionary) into a DataFrame\n",
67
+ "# # row_df = pd.DataFrame([row])\n",
57
68
  "\n",
58
- " return row\n",
69
+ "# return row\n",
59
70
  "\n",
60
71
  "\n",
61
- "def get_all_student_grades():\n",
62
- " # Initialize an empty DataFrame to hold all student grades\n",
63
- " df = pd.DataFrame()\n",
72
+ "# def get_all_student_grades():\n",
73
+ "# # Initialize an empty DataFrame to hold all student grades\n",
74
+ "# df = pd.DataFrame()\n",
64
75
  "\n",
65
- " # Get all students (assuming get_all_students() is a defined function)\n",
66
- " students = get_all_students()\n",
76
+ "# # Get all students (assuming get_all_students() is a defined function)\n",
77
+ "# students = get_all_students()\n",
67
78
  "\n",
68
- " for student in students:\n",
69
- " row_df = get_student(student)\n",
79
+ "# for student in students:\n",
80
+ "# row_df = get_student(student)\n",
70
81
  "\n",
71
- " # Append the row to the DataFrame\n",
72
- " df = pd.concat([df, row_df], ignore_index=True)\n",
82
+ "# # Append the row to the DataFrame\n",
83
+ "# df = pd.concat([df, row_df], ignore_index=True)\n",
73
84
  "\n",
74
- " return df\n",
85
+ "# return df\n",
75
86
  "\n",
76
87
  "\n",
77
- "def get_max_deadline(assignments, assignment_name, week_number):\n",
78
- " matching_rows = assignments[\n",
79
- " (assignments[\"week_number\"] == week_number)\n",
80
- " & (assignments[\"assignment_name\"] == assignment_name)\n",
81
- " ]\n",
88
+ "# def get_max_deadline(assignments, assignment_name, week_number):\n",
89
+ "# matching_rows = assignments[\n",
90
+ "# (assignments[\"week_number\"] == week_number)\n",
91
+ "# & (assignments[\"assignment_name\"] == assignment_name)\n",
92
+ "# ]\n",
82
93
  "\n",
83
- " max_timestamp = matching_rows[\"due_date\"].max()\n",
84
- " return max_timestamp\n",
94
+ "# max_timestamp = matching_rows[\"due_date\"].max()\n",
95
+ "# return max_timestamp\n",
85
96
  "\n",
86
97
  "\n",
87
98
  "def calculate_averages(grades_dict, student_id):\n",
@@ -455,7 +466,7 @@
455
466
  ],
456
467
  "metadata": {
457
468
  "kernelspec": {
458
- "display_name": "engr131_dev",
469
+ "display_name": "engr131w25",
459
470
  "language": "python",
460
471
  "name": "python3"
461
472
  },
@@ -469,7 +480,7 @@
469
480
  "name": "python",
470
481
  "nbconvert_exporter": "python",
471
482
  "pygments_lexer": "ipython3",
472
- "version": "3.12.7"
483
+ "version": "3.13.1"
473
484
  }
474
485
  },
475
486
  "nbformat": 4,
@@ -0,0 +1,88 @@
1
+ import os
2
+
3
+ import requests
4
+ from requests.auth import HTTPBasicAuth
5
+
6
+
7
+ def get_credentials():
8
+ """
9
+ Fetch the username and password from environment variables.
10
+ """
11
+ username = os.getenv("user_name_student")
12
+ password = os.getenv("keys_student")
13
+ if not username or not password:
14
+ raise ValueError(
15
+ "Environment variables 'user_name_student' or 'keys_student' are not set."
16
+ )
17
+ return {"username": username, "password": password}
18
+
19
+
20
+ def call_score_assignment(
21
+ assignment_title: str, notebook_title: str, file_path: str = ".output_reduced.log"
22
+ ) -> dict[str, str]:
23
+ """
24
+ Submit an assignment to the scoring endpoint
25
+
26
+ Args:
27
+ assignment_title (str): Title of the assignment
28
+ notebook_title (str): Title of the notebook
29
+ file_path (str): Path to the log file to upload
30
+
31
+ Returns:
32
+ dict: JSON response from the server
33
+ """
34
+
35
+ base_url = os.getenv("DB_URL")
36
+ if not base_url:
37
+ raise ValueError("Environment variable 'DB_URL' not set")
38
+
39
+ url = base_url.rstrip("/") + "/score-assignment"
40
+
41
+ params = {
42
+ "assignment_title": assignment_title,
43
+ "notebook_title": notebook_title,
44
+ }
45
+
46
+ username, password = get_credentials().values()
47
+
48
+ try:
49
+ with open(file_path, "rb") as file:
50
+ res = requests.post(
51
+ url=url,
52
+ params=params,
53
+ auth=HTTPBasicAuth(username, password),
54
+ files={"log_file": file},
55
+ )
56
+ res.raise_for_status()
57
+
58
+ return res.json()
59
+
60
+ except FileNotFoundError:
61
+ raise FileNotFoundError(f"File {file_path} does not exist")
62
+ except requests.RequestException as err:
63
+ raise RuntimeError(f"An error occurred while requesting {url}: {err}")
64
+ except Exception as err:
65
+ raise RuntimeError(f"An unexpected error occurred: {err}")
66
+
67
+
68
+ def submit_assignment(
69
+ assignment_title: str,
70
+ notebook_title: str,
71
+ file_path: str = ".output_reduced.log",
72
+ ) -> None:
73
+ """
74
+ Synchronous wrapper for the `call_score_assignment` function.
75
+
76
+ Args:
77
+ assignment_title (str): Title of the assignment.
78
+ file_path (str): Path to the log file to upload.
79
+ """
80
+
81
+ response = call_score_assignment(assignment_title, notebook_title, file_path)
82
+
83
+ print("Server Response:", response.get("message", "No message in response"))
84
+
85
+
86
+ # Example usage (remove this section if only the function needs to be importable):
87
+ if __name__ == "__main__":
88
+ submit_assignment("week1-readings", "path/to/your/log_file.txt")