PyKubeGrader 0.3.8__py3-none-any.whl → 0.3.10__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.
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/METADATA +2 -1
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/RECORD +22 -17
- pykubegrader/build/api_notebook_builder.py +4 -4
- pykubegrader/build/build_folder.py +2 -2
- pykubegrader/grade_reports/assignments.py +184 -0
- pykubegrader/grade_reports/class_grade_report.py +137 -0
- pykubegrader/grade_reports/grade_report.py +353 -0
- pykubegrader/grade_reports/grading_config.py +70 -0
- pykubegrader/grade_reports/test.ipynb +43 -0
- pykubegrader/grading_tester.ipynb +53 -2
- pykubegrader/submit/submit_assignment.py +1 -1
- pykubegrader/telemetry.py +88 -85
- pykubegrader/tokens/generator.py +75 -0
- pykubegrader/tokens/token_panel.py +2 -2
- pykubegrader/tokens/tokens.py +0 -1
- pykubegrader/widgets_base/multi_select.py +1 -1
- pykubegrader/widgets_base/select.py +1 -1
- pykubegrader/grade_reports/get_my_grades_testing.py +0 -79
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/WHEEL +0 -0
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/entry_points.txt +0 -0
- {PyKubeGrader-0.3.8.dist-info → PyKubeGrader-0.3.10.dist-info}/top_level.txt +0 -0
- /pykubegrader/grade_reports/{grade_reports.py → __grade_reports.py} +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: PyKubeGrader
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.10
|
4
4
|
Summary: Add a short description here!
|
5
5
|
Home-page: https://github.com/pyscaffold/pyscaffold/
|
6
6
|
Author: jagar2
|
@@ -26,6 +26,7 @@ Requires-Dist: ruff
|
|
26
26
|
Requires-Dist: setuptools
|
27
27
|
Requires-Dist: sphinx
|
28
28
|
Requires-Dist: types-python-dateutil
|
29
|
+
Requires-Dist: types-pyyaml
|
29
30
|
Requires-Dist: types-requests
|
30
31
|
Requires-Dist: types-setuptools
|
31
32
|
Provides-Extra: testing
|
@@ -1,25 +1,30 @@
|
|
1
1
|
pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
|
2
|
-
pykubegrader/grading_tester.ipynb,sha256=
|
2
|
+
pykubegrader/grading_tester.ipynb,sha256=D26O0fgIV1aIYDnIJQspu72lOOQfNS_boIx93apjKDQ,20025
|
3
3
|
pykubegrader/initialize.py,sha256=Bwu1q18l18FB9lGppvt-L41D5gzr3S8t6zC0_UbrASw,3994
|
4
|
-
pykubegrader/telemetry.py,sha256=
|
4
|
+
pykubegrader/telemetry.py,sha256=0Is-LhpVoKcEjmgqEjCfNLCKJVGAmJaQmD2VHF8KI9w,16396
|
5
5
|
pykubegrader/utils.py,sha256=jlJklKvRhY3O7Hz2aaU1m0y3p_n9eMAXNnAF7LUEaPY,1275
|
6
6
|
pykubegrader/validate.py,sha256=OKnItGyd-L8QPKcsE0KRuwBI_IxKiJzMLJKZiA2j3II,11184
|
7
7
|
pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
8
|
-
pykubegrader/build/api_notebook_builder.py,sha256=
|
9
|
-
pykubegrader/build/build_folder.py,sha256=
|
8
|
+
pykubegrader/build/api_notebook_builder.py,sha256=vSnBEgHglKjNqwwQnsY0VrWhkwc6q5soJuszNfyKz9o,25748
|
9
|
+
pykubegrader/build/build_folder.py,sha256=UL5AzApDLRXD0EujHI5nsCbgaYo486SvL4_vYP_HsDE,87645
|
10
10
|
pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6tD-38,1323
|
11
11
|
pykubegrader/build/collate.py,sha256=cVvF7tf2U3iiH4R_dbghTcieedIx5w3Fyw9L_llInM8,6754
|
12
12
|
pykubegrader/build/markdown_questions.py,sha256=cSh8mkHK3hh-etJdgrZu9UQi1WPrKQtofkzLCUp1Z-w,4676
|
13
|
-
pykubegrader/grade_reports/
|
14
|
-
pykubegrader/grade_reports/
|
13
|
+
pykubegrader/grade_reports/__grade_reports.py,sha256=n8H_n9jdZRSPn2zlIf-GQt_Y8w91p6M8ZbdVH76Sg5k,2303
|
14
|
+
pykubegrader/grade_reports/assignments.py,sha256=Wr5jvTeQ0a7Fl89e6EzXKqjhLrsA6KyObOdrJdN1V0Y,7614
|
15
|
+
pykubegrader/grade_reports/class_grade_report.py,sha256=9Uuvy-xoABtjTjv-05VYhmowebgTrgEFSgh1Grws-cQ,5078
|
16
|
+
pykubegrader/grade_reports/grade_report.py,sha256=wxgSGjvX9rcE0s9Up2zxPACGv78sJ3TvP8px3bc7t-c,13993
|
17
|
+
pykubegrader/grade_reports/grading_config.py,sha256=_Wki6ju6OkqIuwEl-YMpYJcG5Yeu5wid-NEUN5Oxshs,2332
|
18
|
+
pykubegrader/grade_reports/test.ipynb,sha256=NkMZHcfBd-uJw3i1Y9ux-epBP5GiVFEhda5wxEbK0cU,808
|
15
19
|
pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
16
20
|
pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
|
17
21
|
pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
18
22
|
pykubegrader/log_parser/parse.ipynb,sha256=5e-9dzUbJk2M8kPP55lVeksm86lSY5ocKfWOP2RSWH0,11921
|
19
23
|
pykubegrader/log_parser/parse.py,sha256=dXzTEOTI6VTRNoHFDAjg6hZUhvB3kHtMb10_KW3NPrw,7641
|
20
|
-
pykubegrader/submit/submit_assignment.py,sha256=
|
21
|
-
pykubegrader/tokens/
|
22
|
-
pykubegrader/tokens/
|
24
|
+
pykubegrader/submit/submit_assignment.py,sha256=73bbCYNfeWHjxZw-TUuva737WEPQNA9DB6czeP5BIOM,2656
|
25
|
+
pykubegrader/tokens/generator.py,sha256=zqfO8GX4Xkhkxi0HGkGwiwuXTmL4ON_hcH8f-9Gg5qc,2648
|
26
|
+
pykubegrader/tokens/token_panel.py,sha256=jWwOUx4mr67iWyoIyjITMtwf9HtbAd_L5b-2x_Fif3g,1377
|
27
|
+
pykubegrader/tokens/tokens.py,sha256=5hEcNMUCpFtps0OjWV1V93zdk2cuIAwCfhBEp3PiuEI,1473
|
23
28
|
pykubegrader/tokens/validate_token.py,sha256=kvHX0NJBm21xzb2p67j7vq1La6J1XbmobEJQ3fTMdZA,3289
|
24
29
|
pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
25
30
|
pykubegrader/widgets/multiple_choice.py,sha256=ag6W-HN7isHkIUmB4BxtK8T1JhuV3FBLUBAhcV6rN80,2729
|
@@ -31,12 +36,12 @@ pykubegrader/widgets/style.py,sha256=fVBMYy_a6Yoz21avNpiORWC3f5FD-OrVpaZ3npmunvs
|
|
31
36
|
pykubegrader/widgets/true_false.py,sha256=QllIhHuJstJft_RuShkxI_fFFTaDAlzNZOFNs00HLIM,2842
|
32
37
|
pykubegrader/widgets/types_question.py,sha256=kZdRRXyFzOtYTmGdC7XWb_2oaxqg1WSuLcQn_sTj6Qc,2300
|
33
38
|
pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
34
|
-
pykubegrader/widgets_base/multi_select.py,sha256=
|
39
|
+
pykubegrader/widgets_base/multi_select.py,sha256=WhpS7a8V3BOuEfEyFPzcDhMbgr7p1a4FFh_mKU1HLbI,4226
|
35
40
|
pykubegrader/widgets_base/reading.py,sha256=ChUS3NOTa_HLtNpxR8hGX80LPKMvYMypnR6dFknfxus,5430
|
36
|
-
pykubegrader/widgets_base/select.py,sha256=
|
37
|
-
PyKubeGrader-0.3.
|
38
|
-
PyKubeGrader-0.3.
|
39
|
-
PyKubeGrader-0.3.
|
40
|
-
PyKubeGrader-0.3.
|
41
|
-
PyKubeGrader-0.3.
|
42
|
-
PyKubeGrader-0.3.
|
41
|
+
pykubegrader/widgets_base/select.py,sha256=qP31bjTWIn8LEgKwtNUJbgJnum6P7bx6A_At-u1ivFk,2750
|
42
|
+
PyKubeGrader-0.3.10.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
|
43
|
+
PyKubeGrader-0.3.10.dist-info/METADATA,sha256=lM-bIik76huAUbqHX0y0fhftiNbaOUoDwDTt6Ut7b8Q,2758
|
44
|
+
PyKubeGrader-0.3.10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
45
|
+
PyKubeGrader-0.3.10.dist-info/entry_points.txt,sha256=RR57KvzDRJrP4omy5heS5cZ3E7g56YxcxJhDnp57ZU0,253
|
46
|
+
PyKubeGrader-0.3.10.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
|
47
|
+
PyKubeGrader-0.3.10.dist-info/RECORD,,
|
@@ -100,7 +100,7 @@ class FastAPINotebookBuilder:
|
|
100
100
|
|
101
101
|
cell = self.get_cell(cell_index)
|
102
102
|
cell_source = FastAPINotebookBuilder.add_import_statements_to_tests(
|
103
|
-
cell["source"], require_key=self.require_key,
|
103
|
+
cell["source"], require_key=self.require_key, assignment_tag = self.assignment_tag,
|
104
104
|
)
|
105
105
|
|
106
106
|
cell_source = FastAPINotebookBuilder.conceal_tests(cell_source)
|
@@ -224,7 +224,7 @@ class FastAPINotebookBuilder:
|
|
224
224
|
|
225
225
|
if self.require_key:
|
226
226
|
first_cell_header.append(
|
227
|
-
"from pykubegrader.tokens.validate_token import validate_token\nvalidate_token()\n"
|
227
|
+
f"from pykubegrader.tokens.validate_token import validate_token\nvalidate_token(assignment='{self.assignment_tag}')\n"
|
228
228
|
)
|
229
229
|
|
230
230
|
short_filename = self.filename.split(".")[0].replace("_temp", "")
|
@@ -330,7 +330,7 @@ class FastAPINotebookBuilder:
|
|
330
330
|
return original_list[:index] + insert_list + original_list[index:]
|
331
331
|
|
332
332
|
@staticmethod
|
333
|
-
def add_import_statements_to_tests(cell_source: list[str], require_key:bool = False) -> list[str]:
|
333
|
+
def add_import_statements_to_tests(cell_source: list[str], require_key:bool = False, assignment_tag = None) -> list[str]:
|
334
334
|
"""
|
335
335
|
Adds the necessary import statements to the first cell of the notebook.
|
336
336
|
"""
|
@@ -353,7 +353,7 @@ class FastAPINotebookBuilder:
|
|
353
353
|
|
354
354
|
if require_key:
|
355
355
|
imports.append(
|
356
|
-
"from pykubegrader.tokens.validate_token import validate_token\nvalidate_token()\n"
|
356
|
+
f"from pykubegrader.tokens.validate_token import validate_token\nvalidate_token(assignment='{assignment_tag}')\n"
|
357
357
|
)
|
358
358
|
|
359
359
|
for i, line in enumerate(cell_source):
|
@@ -785,7 +785,7 @@ class NotebookProcessor:
|
|
785
785
|
nbformat.write(notebook, f)
|
786
786
|
|
787
787
|
@staticmethod
|
788
|
-
def add_validate_block(notebook_path: str, require_key: bool, **kwargs) -> None:
|
788
|
+
def add_validate_block(notebook_path: str, require_key: bool, assignment_tag = None, **kwargs) -> None:
|
789
789
|
"""
|
790
790
|
Modifies the first code cell of a Jupyter notebook to add the validate_token call if require_key is True.
|
791
791
|
|
@@ -804,7 +804,7 @@ class NotebookProcessor:
|
|
804
804
|
notebook = nbformat.read(f, as_version=4)
|
805
805
|
|
806
806
|
# Prepare the validation code
|
807
|
-
validation_code = "validate_token()\n"
|
807
|
+
validation_code = f"validate_token(assignment = '{assignment_tag}')\n"
|
808
808
|
|
809
809
|
# Modify the first cell if it's a code cell, otherwise insert a new one
|
810
810
|
if notebook.cells and notebook.cells[0].cell_type == "code":
|
@@ -0,0 +1,184 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from dateutil import parser
|
3
|
+
from datetime import datetime
|
4
|
+
from pykubegrader.graders.late_assignments import calculate_late_submission
|
5
|
+
|
6
|
+
|
7
|
+
class assignment_type:
|
8
|
+
"""
|
9
|
+
Base class for assignment types.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
weight (float): The weight of the assignment in the overall grade.
|
13
|
+
|
14
|
+
Methods:
|
15
|
+
__init__(name: str, weekly: bool, weight: float):
|
16
|
+
Initializes an instance of the assignment_type class.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self, name: str, weekly: bool, weight: float):
|
20
|
+
"""Initializes an instance of the assignment_type class.
|
21
|
+
Args:
|
22
|
+
name (str): The name of the assignment.
|
23
|
+
weekly (bool): Indicates if the assignment is weekly.
|
24
|
+
weight (float): The weight of the assignment in the overall grade."""
|
25
|
+
self.name = name
|
26
|
+
self.weekly = weekly
|
27
|
+
self.weight = weight
|
28
|
+
|
29
|
+
|
30
|
+
class Assignment(assignment_type):
|
31
|
+
"""
|
32
|
+
Class for storing and updating assignment scores.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
week (int, optional): The week number of the assignment.
|
36
|
+
exempted (bool): Indicates if the assignment is exempted.
|
37
|
+
graded (bool): Indicates if the assignment has been graded.
|
38
|
+
late_adjustment (bool): Indicates if late submissions are allowed.
|
39
|
+
students_exempted (list): List of student IDs exempted from the assignment.
|
40
|
+
due_date (datetime, optional): The due date of the assignment.
|
41
|
+
max_score (float, optional): The maximum score possible for the assignment.
|
42
|
+
grade_adjustment_func (callable, optional): Function to adjust the grade for late or exempted submissions.
|
43
|
+
|
44
|
+
Methods:
|
45
|
+
add_exempted_students(students):
|
46
|
+
Add students to the exempted list.
|
47
|
+
|
48
|
+
update_score(submission=None):
|
49
|
+
Update the score of the assignment based on the submission.
|
50
|
+
|
51
|
+
grade_adjustment(submission):
|
52
|
+
Apply the adjustment function if provided.
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
name: str,
|
58
|
+
weekly: bool,
|
59
|
+
weight: float,
|
60
|
+
score: float,
|
61
|
+
grade_adjustment_func=None,
|
62
|
+
**kwargs,
|
63
|
+
):
|
64
|
+
"""
|
65
|
+
Initializes an instance of the Assignment class.
|
66
|
+
|
67
|
+
weekly (bool): Indicates if the assignment is weekly.
|
68
|
+
grade_adjustment_func (callable, optional): Used to calculate the grade in the case of late or exempted submissions. Defaults to None.
|
69
|
+
**kwargs: Additional keyword arguments.
|
70
|
+
week (int, optional): The week number of the assignment. Defaults to None.
|
71
|
+
exempted (bool, optional): Indicates if the assignment is exempted. Defaults to False.
|
72
|
+
graded (bool, optional): Indicates if the assignment is graded. Defaults to False.
|
73
|
+
late_adjustment (bool, optional): Indicates if late adjustment is applied. Defaults to True.
|
74
|
+
students_exempted (list, optional): List of students exempted from the assignment. Defaults to an empty list.
|
75
|
+
due_date (datetime, optional): The due date of the assignment. Defaults to None.
|
76
|
+
max_score (float, optional): The maximum score possible for the assignment. Defaults to None.
|
77
|
+
"""
|
78
|
+
super().__init__(name, weekly, weight)
|
79
|
+
self.score = score
|
80
|
+
self._score = score
|
81
|
+
self.week = kwargs.get("week", None)
|
82
|
+
self.exempted = kwargs.get("exempted", False)
|
83
|
+
self.graded = kwargs.get("graded", False)
|
84
|
+
self.late_adjustment = kwargs.get("late_adjustment", True)
|
85
|
+
self.students_exempted = kwargs.get("students_exempted", [])
|
86
|
+
self.due_date = kwargs.get("due_date", None)
|
87
|
+
self.max_score = kwargs.get("max_score", None)
|
88
|
+
|
89
|
+
# Store the function for later use
|
90
|
+
self.grade_adjustment_func = grade_adjustment_func
|
91
|
+
|
92
|
+
def add_exempted_students(self, students):
|
93
|
+
"""
|
94
|
+
Add students to the exempted list.
|
95
|
+
Args:
|
96
|
+
students (list): List of student IDs to exempt from the assignment.
|
97
|
+
"""
|
98
|
+
self.students_exempted.extend(students)
|
99
|
+
|
100
|
+
def update_score(self, submission=None):
|
101
|
+
"""Updates the assignment score based on the given submission.
|
102
|
+
|
103
|
+
This method adjusts the score using the `grade_adjustment` function if a submission
|
104
|
+
is provided. If the submission results in a higher score than the current score,
|
105
|
+
the assignment score is updated. If no submission is provided and the student is
|
106
|
+
not exempted, the score is set to zero. If the student is exempted, the score
|
107
|
+
is set to NaN.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
submission (dict, optional): The submission data, expected to contain relevant
|
111
|
+
details for grading. Defaults to None.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
float: The updated assignment score. If exempted, returns NaN. If no submission
|
115
|
+
is provided, returns 0.
|
116
|
+
"""
|
117
|
+
if self.exempted:
|
118
|
+
self.score = np.nan
|
119
|
+
|
120
|
+
# Saves a table with the score of the exempted assignment still recorded.
|
121
|
+
try:
|
122
|
+
# Adjust the score based on submission
|
123
|
+
score_ = self.grade_adjustment(submission)
|
124
|
+
if score_ > self._score:
|
125
|
+
self._score = score_
|
126
|
+
except:
|
127
|
+
pass
|
128
|
+
return self.score
|
129
|
+
elif submission is not None:
|
130
|
+
# Adjust the score based on submission
|
131
|
+
score_ = self.grade_adjustment(submission)
|
132
|
+
|
133
|
+
# Update the score only if the new score is higher
|
134
|
+
if score_ > self.score:
|
135
|
+
self.score = score_
|
136
|
+
self._score = score_
|
137
|
+
|
138
|
+
return self.score
|
139
|
+
else:
|
140
|
+
# Set the score to zero if not exempted and no submission
|
141
|
+
self.score = 0
|
142
|
+
self._score = 0
|
143
|
+
return self.score
|
144
|
+
|
145
|
+
def grade_adjustment(self, submission):
|
146
|
+
"""Applies adjustments to the submission score based on grading policies.
|
147
|
+
|
148
|
+
This method applies any provided grade adjustment function to the raw score.
|
149
|
+
If no custom function is given, it determines the final score by considering
|
150
|
+
lateness penalties based on the submission timestamp and due date.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
submission (dict): A dictionary containing:
|
154
|
+
- `"raw_score"` (float): The initial unadjusted score.
|
155
|
+
- `"timestamp"` (str): The submission timestamp in a parsable format.
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
float: The adjusted score, incorporating lateness penalties if applicable.
|
159
|
+
Returns 0 for late submissions if no late adjustment policy is defined.
|
160
|
+
"""
|
161
|
+
score = submission["raw_score"]
|
162
|
+
entry_date = parser.parse(submission["timestamp"])
|
163
|
+
|
164
|
+
if self.grade_adjustment_func:
|
165
|
+
return self.grade_adjustment_func(score)
|
166
|
+
else:
|
167
|
+
if self.late_adjustment:
|
168
|
+
# Convert due date to datetime object
|
169
|
+
due_date = datetime.fromisoformat(self.due_date.replace("Z", "+00:00"))
|
170
|
+
|
171
|
+
late_modifier = calculate_late_submission(
|
172
|
+
due_date.strftime("%Y-%m-%d %H:%M:%S"),
|
173
|
+
entry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
174
|
+
)
|
175
|
+
|
176
|
+
# Apply late modifier and normalize score
|
177
|
+
return (score / self.max_score) * late_modifier
|
178
|
+
else:
|
179
|
+
# Return normalized score if on time
|
180
|
+
if entry_date < self.due_date:
|
181
|
+
return score / self.max_score
|
182
|
+
# Assign zero score for late submissions without a late adjustment policy
|
183
|
+
else:
|
184
|
+
return 0
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# # import the password and user from the build folder
|
2
|
+
# try:
|
3
|
+
# from pykubegrader.build.passwords import password, user
|
4
|
+
# except: # noqa: E722
|
5
|
+
# print("Passwords not found, cannot access database")
|
6
|
+
|
7
|
+
from pykubegrader.grade_reports.grading_config import assignment_type_list, skipped_users
|
8
|
+
from pykubegrader.grade_reports.grade_report import GradeReport
|
9
|
+
# from ..build.passwords import password, user
|
10
|
+
from pykubegrader.telemetry import get_all_students
|
11
|
+
|
12
|
+
|
13
|
+
import os
|
14
|
+
import requests
|
15
|
+
from requests.auth import HTTPBasicAuth
|
16
|
+
import socket
|
17
|
+
import pandas as pd
|
18
|
+
import numpy as np
|
19
|
+
import tqdm
|
20
|
+
|
21
|
+
# user = user()
|
22
|
+
# password = password()
|
23
|
+
|
24
|
+
# Set the environment variables for the database
|
25
|
+
# os.environ["JUPYTERHUB_USER"] = "jca92"
|
26
|
+
# os.environ["TOKEN"] = "token"
|
27
|
+
# os.environ["DB_URL"] = "https://engr-131-api.eastus.cloudapp.azure.com/"
|
28
|
+
# os.environ["keys_student"] = "capture"
|
29
|
+
# os.environ["user_name_student"] = "student"
|
30
|
+
|
31
|
+
api_base_url = os.getenv("DB_URL")
|
32
|
+
student_user = os.getenv("user_name_student")
|
33
|
+
student_pw = os.getenv("keys_student")
|
34
|
+
|
35
|
+
|
36
|
+
class ClassGradeReport:
|
37
|
+
"""Generates and manages a class-wide grade report.
|
38
|
+
|
39
|
+
This class retrieves a list of students, initializes a structured grade report,
|
40
|
+
and populates it with individual student grade data. The final report includes
|
41
|
+
both assignment-specific grades and a weighted average grade.
|
42
|
+
|
43
|
+
Attributes:
|
44
|
+
student_list (list): A sorted list of all students in the class.
|
45
|
+
all_student_grades_df (pd.DataFrame): A DataFrame storing grades for each student,
|
46
|
+
including assignment scores and a weighted average.
|
47
|
+
|
48
|
+
Methods:
|
49
|
+
setup_class_grades():
|
50
|
+
Initializes an empty DataFrame with assignment names and weighted average columns.
|
51
|
+
update_student_grade(student):
|
52
|
+
Fetches and updates an individual student’s weighted average grades in the DataFrame.
|
53
|
+
fill_class_grades():
|
54
|
+
Iterates through all students to populate the class-wide grade report.
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(self, user, password):
|
58
|
+
"""Initializes the class grade report.
|
59
|
+
|
60
|
+
Retrieves the student list using authentication, sorts it, and sets up
|
61
|
+
the class-wide grade report by initializing and populating a DataFrame.
|
62
|
+
"""
|
63
|
+
self.user = user
|
64
|
+
self.password = password
|
65
|
+
|
66
|
+
self.student_list = get_all_students(self.user, self.password)
|
67
|
+
self.student_list = list( set(self.student_list)-set(skipped_users) )
|
68
|
+
self.student_list.sort()
|
69
|
+
|
70
|
+
self.setup_class_grades()
|
71
|
+
self.fill_class_grades()
|
72
|
+
self.get_class_stats()
|
73
|
+
|
74
|
+
|
75
|
+
def setup_class_grades(self):
|
76
|
+
"""Creates an empty DataFrame to store grades for all students.
|
77
|
+
|
78
|
+
The DataFrame contains assignment columns and a "Weighted Average Grade" column,
|
79
|
+
with students as index labels.
|
80
|
+
"""
|
81
|
+
self.all_student_grades_df = pd.DataFrame(
|
82
|
+
np.nan,
|
83
|
+
dtype=float,
|
84
|
+
index=self.student_list,
|
85
|
+
columns=[a.name for a in assignment_type_list] + ["Weighted Average Grade"],
|
86
|
+
)
|
87
|
+
|
88
|
+
def update_student_grade(self, student):
|
89
|
+
"""Fetches and updates the grade report for an individual student.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
student (str): The username or identifier of the student.
|
93
|
+
|
94
|
+
Updates:
|
95
|
+
The student's row in `all_student_grades_df` with their weighted average grades.
|
96
|
+
"""
|
97
|
+
report = GradeReport(params={"username": student}, display_=False)
|
98
|
+
row_series = report.weighted_average_grades.transpose().iloc[
|
99
|
+
0
|
100
|
+
] # Example transformation
|
101
|
+
row_series = row_series.reindex(self.all_student_grades_df.columns)
|
102
|
+
self.all_student_grades_df.loc[student] = row_series
|
103
|
+
|
104
|
+
def fill_class_grades(self):
|
105
|
+
"""Populates the class-wide grade report with data from all students.
|
106
|
+
|
107
|
+
Iterates through the `student_list` and updates the DataFrame by fetching
|
108
|
+
and storing each student's weighted average grades.
|
109
|
+
"""
|
110
|
+
for student in tqdm.tqdm(self.student_list):
|
111
|
+
self.update_student_grade(student)
|
112
|
+
|
113
|
+
def get_class_stats(self):
|
114
|
+
"""Calculates and stores descriptive statistics for the class-wide grade report.
|
115
|
+
Requires filling class grades first
|
116
|
+
"""
|
117
|
+
# Calculate descriptive statistics
|
118
|
+
self.stats_df = self.all_student_grades_df.describe(include='all')
|
119
|
+
|
120
|
+
def write_excel_spreadsheet(self, out_name='output.xlsx'):
|
121
|
+
"""Exports the class-wide grade report to an Excel spreadsheet.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
out_name (str, optional): Name of output file. Defaults to 'output.xlsx'.
|
125
|
+
"""
|
126
|
+
# Export to Excel with different sheets
|
127
|
+
with pd.ExcelWriter('output.xlsx') as writer:
|
128
|
+
self.all_student_grades_df.to_excel(writer, sheet_name='all_student_grades')
|
129
|
+
self.stats_df.to_excel(writer, sheet_name='performance_statistics')
|
130
|
+
|
131
|
+
|
132
|
+
def main():
|
133
|
+
class_grades = ClassGradeReport()
|
134
|
+
print(class_grades.all_student_grades_df)
|
135
|
+
|
136
|
+
if __name__ == "__main__":
|
137
|
+
main()
|