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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.3.8
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=wwT9jyhpR6GGM8r4todaGfrsUxS6JxM0qIqMcDYKM7w,18839
2
+ pykubegrader/grading_tester.ipynb,sha256=D26O0fgIV1aIYDnIJQspu72lOOQfNS_boIx93apjKDQ,20025
3
3
  pykubegrader/initialize.py,sha256=Bwu1q18l18FB9lGppvt-L41D5gzr3S8t6zC0_UbrASw,3994
4
- pykubegrader/telemetry.py,sha256=oFw27YfTLYZvn7iZKh4jzYXjD726MuUoIQ-9twbXxXk,16632
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=EZG4Ow4YATzOWPPNLkdQEdWt7hkpbaI5ZD1Bf2KEWeY,25622
9
- pykubegrader/build/build_folder.py,sha256=HDccW597shBhAd-jxvMUPpsiNj2S4DlKRGMDH_6U9nQ,87590
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/get_my_grades_testing.py,sha256=JNgaHK1eMDPxJASrPpIcXxwMYE0kB0r4p5EgD6GICl4,2359
14
- pykubegrader/grade_reports/grade_reports.py,sha256=n8H_n9jdZRSPn2zlIf-GQt_Y8w91p6M8ZbdVH76Sg5k,2303
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=EJkdADfihojJxAPZEmEiEEg1Vo-FBFHHEjAGzSmK5UA,2660
21
- pykubegrader/tokens/token_panel.py,sha256=NNA5ZV3Q9jB_lz2aSwMyViXV0ESu6V_7T92Qji7UpSQ,1377
22
- pykubegrader/tokens/tokens.py,sha256=qcYMFgNPimbfeS7lXOtbgquGgeJCgOGx5hvXewIs0oQ,1474
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=KtfAP0PyEbcjWlKNpI5_5-PLMtcUbbNX0Es_-w-H34Q,4226
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=uMncmVIqjvJkffMQY1L_PokrFCidK1PeVITX0i70fho,2750
37
- PyKubeGrader-0.3.8.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
38
- PyKubeGrader-0.3.8.dist-info/METADATA,sha256=qrucw04y1mZfpYfKihEEtqTjQ9SLh5YKDgBA_GNQJ2s,2729
39
- PyKubeGrader-0.3.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
- PyKubeGrader-0.3.8.dist-info/entry_points.txt,sha256=RR57KvzDRJrP4omy5heS5cZ3E7g56YxcxJhDnp57ZU0,253
41
- PyKubeGrader-0.3.8.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
42
- PyKubeGrader-0.3.8.dist-info/RECORD,,
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()