ygrader 2.6.5__tar.gz → 2.6.6__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.
- {ygrader-2.6.5/ygrader.egg-info → ygrader-2.6.6}/PKG-INFO +1 -1
- {ygrader-2.6.5 → ygrader-2.6.6}/setup.py +1 -1
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/feedback.py +18 -1
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/grader.py +2 -1
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/grading_item.py +57 -42
- {ygrader-2.6.5 → ygrader-2.6.6/ygrader.egg-info}/PKG-INFO +1 -1
- {ygrader-2.6.5 → ygrader-2.6.6}/LICENSE +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/setup.cfg +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/test/test_interactive.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/test/test_unittest.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/__init__.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/deductions.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/grades_csv.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/grading_item_config.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/remote.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/score_input.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/send_ctrl_backtick.ahk +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/student_repos.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/upstream_merger.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader/utils.py +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader.egg-info/SOURCES.txt +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader.egg-info/dependency_links.txt +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader.egg-info/requires.txt +0 -0
- {ygrader-2.6.5 → ygrader-2.6.6}/ygrader.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ setup(
|
|
|
4
4
|
name="ygrader",
|
|
5
5
|
packages=["ygrader"],
|
|
6
6
|
package_data={"ygrader": ["*.ahk"]},
|
|
7
|
-
version="2.6.
|
|
7
|
+
version="2.6.6",
|
|
8
8
|
description="Grading scripts used in BYU's Electrical and Computer Engineering Department",
|
|
9
9
|
author="Jeff Goeders",
|
|
10
10
|
author_email="jeff.goeders@gmail.com",
|
|
@@ -293,11 +293,19 @@ def assemble_grades(
|
|
|
293
293
|
|
|
294
294
|
if items_graded and items_not_graded:
|
|
295
295
|
print_color(
|
|
296
|
-
TermColors.
|
|
296
|
+
TermColors.RED,
|
|
297
297
|
f"Partial grade: {net_id} graded for [{', '.join(items_graded)}] "
|
|
298
298
|
f"but NOT [{', '.join(items_not_graded)}]",
|
|
299
299
|
)
|
|
300
300
|
|
|
301
|
+
# Check if student has no grades at all
|
|
302
|
+
student_graded = len(items_graded) > 0
|
|
303
|
+
if not student_graded:
|
|
304
|
+
print_color(
|
|
305
|
+
TermColors.YELLOW,
|
|
306
|
+
f"No grades: {net_id} has no grades, receiving 0",
|
|
307
|
+
)
|
|
308
|
+
|
|
301
309
|
# Get submit info for this student
|
|
302
310
|
_, effective_due_date, submitted_datetime = (
|
|
303
311
|
_get_student_key_and_submit_info(
|
|
@@ -353,6 +361,7 @@ def assemble_grades(
|
|
|
353
361
|
late_penalty_callback=late_penalty_callback,
|
|
354
362
|
due_date=due_date,
|
|
355
363
|
due_date_exceptions=due_date_exceptions,
|
|
364
|
+
student_graded=student_graded,
|
|
356
365
|
)
|
|
357
366
|
|
|
358
367
|
filename = (
|
|
@@ -391,6 +400,7 @@ def _generate_student_feedback(
|
|
|
391
400
|
late_penalty_callback: Optional[LatePenaltyCallback] = None,
|
|
392
401
|
due_date: Optional[datetime.datetime] = None,
|
|
393
402
|
due_date_exceptions: Optional[Dict[str, datetime.datetime]] = None,
|
|
403
|
+
student_graded: bool = True,
|
|
394
404
|
) -> str:
|
|
395
405
|
"""Generate the feedback text content for a single student.
|
|
396
406
|
|
|
@@ -401,6 +411,7 @@ def _generate_student_feedback(
|
|
|
401
411
|
late_penalty_callback: Optional callback for calculating late penalty.
|
|
402
412
|
due_date: The default due date for the assignment.
|
|
403
413
|
due_date_exceptions: Mapping from net_id to exception due date.
|
|
414
|
+
student_graded: Whether the student was graded at all (False if no submission).
|
|
404
415
|
|
|
405
416
|
Returns:
|
|
406
417
|
The formatted feedback text.
|
|
@@ -517,6 +528,12 @@ def _generate_student_feedback(
|
|
|
517
528
|
lines.append(
|
|
518
529
|
f"{late_label:<{item_col_width}} {-late_penalty_points:>{score_col_width}.1f}"
|
|
519
530
|
)
|
|
531
|
+
elif not student_graded:
|
|
532
|
+
# Student was never graded (no submission)
|
|
533
|
+
final_score = score_before_late
|
|
534
|
+
lines.append(
|
|
535
|
+
f"{'Late Penalty:':<{item_col_width}} {'No Submission':>{score_col_width}}"
|
|
536
|
+
)
|
|
520
537
|
else:
|
|
521
538
|
final_score = score_before_late
|
|
522
539
|
lines.append(
|
|
@@ -896,7 +896,8 @@ class Grader:
|
|
|
896
896
|
student_work_path.mkdir(parents=True, exist_ok=True)
|
|
897
897
|
|
|
898
898
|
# Clone student repo
|
|
899
|
-
|
|
899
|
+
https_url = student_repos.convert_github_url_format(row["github_url"], to_https=True)
|
|
900
|
+
print("Student repo url: " + https_url, file=output)
|
|
900
901
|
if not student_repos.clone_repo(
|
|
901
902
|
row["github_url"], self.github_tag, student_work_path, output=output
|
|
902
903
|
):
|
|
@@ -144,43 +144,20 @@ class GradeItem:
|
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
# Verify workflow hash if configured
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
workflow_errors = []
|
|
148
|
+
student_code_path = callback_args.get("student_code_path")
|
|
149
|
+
if self.grader.workflow_hash is not None and student_code_path:
|
|
150
|
+
workflow_file_path = (
|
|
151
|
+
student_code_path / ".github" / "workflows" / "submission.yml"
|
|
152
|
+
)
|
|
153
|
+
try:
|
|
154
|
+
verify_workflow_hash(
|
|
155
|
+
workflow_file_path, self.grader.workflow_hash
|
|
152
156
|
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
workflow_file_path, self.grader.workflow_hash
|
|
156
|
-
)
|
|
157
|
-
except WorkflowHashError as e:
|
|
158
|
-
print("")
|
|
159
|
-
print_color(TermColors.RED, "=" * 70)
|
|
160
|
-
print_color(
|
|
161
|
-
TermColors.RED,
|
|
162
|
-
"WARNING: WORKFLOW FILE VERIFICATION FAILED!",
|
|
163
|
-
)
|
|
164
|
-
print_color(TermColors.RED, "=" * 70)
|
|
165
|
-
print_color(TermColors.RED, str(e))
|
|
166
|
-
print_color(TermColors.RED, "")
|
|
167
|
-
print_color(
|
|
168
|
-
TermColors.RED,
|
|
169
|
-
"This student may have modified the GitHub workflow system.",
|
|
170
|
-
)
|
|
171
|
-
print_color(
|
|
172
|
-
TermColors.RED, "The submission date CANNOT be guaranteed."
|
|
173
|
-
)
|
|
174
|
-
print_color(TermColors.RED, "")
|
|
175
|
-
print_color(
|
|
176
|
-
TermColors.RED,
|
|
177
|
-
"Please contact the instructor before grading this student.",
|
|
178
|
-
)
|
|
179
|
-
print_color(TermColors.RED, "=" * 70)
|
|
180
|
-
print("")
|
|
157
|
+
except WorkflowHashError as e:
|
|
158
|
+
workflow_errors.append(f"Workflow hash mismatch: {e}")
|
|
181
159
|
|
|
182
160
|
# Display submission date if available and store for later late calculation
|
|
183
|
-
student_code_path = callback_args.get("student_code_path")
|
|
184
161
|
# Store submission time but don't save yet - will be saved only after successful grading
|
|
185
162
|
pending_submit_time = None
|
|
186
163
|
if student_code_path:
|
|
@@ -188,10 +165,12 @@ class GradeItem:
|
|
|
188
165
|
if submission_date_path.is_file():
|
|
189
166
|
try:
|
|
190
167
|
with open(submission_date_path, encoding="utf-8") as f:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
168
|
+
date_str = f.read().strip()
|
|
169
|
+
# Expected format from workflow: "Sun Jan 12 21:51:14 MST 2026"
|
|
170
|
+
submission_time = datetime.datetime.strptime(
|
|
171
|
+
date_str,
|
|
172
|
+
"%a %b %d %H:%M:%S %Z %Y",
|
|
173
|
+
)
|
|
195
174
|
print_color(
|
|
196
175
|
TermColors.BLUE,
|
|
197
176
|
f"Submitted: {submission_time.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
@@ -199,10 +178,46 @@ class GradeItem:
|
|
|
199
178
|
# Store as ISO format for later late calculation
|
|
200
179
|
pending_submit_time = submission_time.isoformat()
|
|
201
180
|
except (ValueError, IOError) as e:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
181
|
+
workflow_errors.append(f"Submission date parsing failed: {e}")
|
|
182
|
+
|
|
183
|
+
# Print unified warning if any workflow-related errors occurred
|
|
184
|
+
if workflow_errors:
|
|
185
|
+
print("")
|
|
186
|
+
print_color(TermColors.RED, "=" * 70)
|
|
187
|
+
print_color(
|
|
188
|
+
TermColors.RED,
|
|
189
|
+
"WARNING: WORKFLOW VERIFICATION FAILED!",
|
|
190
|
+
)
|
|
191
|
+
print_color(TermColors.RED, "=" * 70)
|
|
192
|
+
for err in workflow_errors:
|
|
193
|
+
print_color(TermColors.RED, f" - {err}")
|
|
194
|
+
print_color(TermColors.RED, "")
|
|
195
|
+
print_color(
|
|
196
|
+
TermColors.RED,
|
|
197
|
+
"This student may have modified the GitHub workflow system.",
|
|
198
|
+
)
|
|
199
|
+
print_color(
|
|
200
|
+
TermColors.RED, "The submission date CANNOT be guaranteed."
|
|
201
|
+
)
|
|
202
|
+
print_color(TermColors.RED, "")
|
|
203
|
+
print_color(
|
|
204
|
+
TermColors.RED,
|
|
205
|
+
"Please contact the instructor before grading this student.",
|
|
206
|
+
)
|
|
207
|
+
print_color(TermColors.RED, "=" * 70)
|
|
208
|
+
print("")
|
|
209
|
+
|
|
210
|
+
# Ask for confirmation before grading
|
|
211
|
+
while True:
|
|
212
|
+
response = input(
|
|
213
|
+
"Do you want to grade this student anyway? [y/n]: "
|
|
214
|
+
).strip().lower()
|
|
215
|
+
if response in ("y", "yes"):
|
|
216
|
+
break
|
|
217
|
+
if response in ("n", "no"):
|
|
218
|
+
print_color(TermColors.BLUE, "Skipping student")
|
|
219
|
+
return False
|
|
220
|
+
print("Please enter 'y' or 'n'")
|
|
206
221
|
|
|
207
222
|
# Process callback result:
|
|
208
223
|
# - None: interactive mode (prompt for deductions)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|