ed-api-client 0.1.7__tar.gz → 0.1.9__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.
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/PKG-INFO +1 -1
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/challenges.py +29 -27
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/workspaces.py +26 -12
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/pyproject.toml +1 -1
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/README.md +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/__init__.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/assignments.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/canvas.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/client.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/quizzes.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/slides.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/threads.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/users.py +0 -0
- {ed_api_client-0.1.7 → ed_api_client-0.1.9}/ed_api_client/websockets.py +0 -0
|
@@ -214,9 +214,6 @@ class ChallengeResult:
|
|
|
214
214
|
The number of attempts that the student submitted for this challenge.
|
|
215
215
|
completed : bool
|
|
216
216
|
True if the student successfully completed this challenge, otherwise false.
|
|
217
|
-
best_submission_by_calendar_days_late : List[Submission]
|
|
218
|
-
The best submission received at each number of days late.
|
|
219
|
-
Index 0 = on time, index 1 = up to 1 day late, etc.
|
|
220
217
|
scores_by_calendar_days_late : List[int]
|
|
221
218
|
The best score received at each number of days late.
|
|
222
219
|
Index 0 = on time, index 1 = up to 1 day late, etc.
|
|
@@ -229,16 +226,16 @@ class ChallengeResult:
|
|
|
229
226
|
max_days_late: int = 7
|
|
230
227
|
attempts: int = 0
|
|
231
228
|
completed: bool = False
|
|
232
|
-
best_submission_by_calendar_days_late: List[Submission] = None
|
|
233
229
|
scores_by_calendar_days_late: List[int] = None
|
|
234
230
|
first_attempt: datetime.datetime = None
|
|
235
231
|
last_attempt: datetime.datetime = None
|
|
232
|
+
_submissions_by_days_late: List[List[Submission]] = None
|
|
233
|
+
_best_submission_by_days_late: List[Submission] = None
|
|
236
234
|
|
|
237
235
|
def add(self, submission: Submission, days_late: int):
|
|
238
236
|
"""
|
|
239
237
|
Record a submission for this challenge.
|
|
240
238
|
|
|
241
|
-
Keeps track of the best submission received at each number of days late.
|
|
242
239
|
Call this for every submission before calling ``finalize``.
|
|
243
240
|
|
|
244
241
|
Parameters
|
|
@@ -249,9 +246,10 @@ class ChallengeResult:
|
|
|
249
246
|
How many calendar days after the deadline this submission was received.
|
|
250
247
|
Pass 0 for on-time submissions.
|
|
251
248
|
"""
|
|
252
|
-
if self.
|
|
253
|
-
self.best_submission_by_calendar_days_late = [None] * (self.max_days_late + 1)
|
|
249
|
+
if self._submissions_by_days_late is None:
|
|
254
250
|
self.scores_by_calendar_days_late = [0] * (self.max_days_late + 1)
|
|
251
|
+
self._submissions_by_days_late = [[] for _ in range(self.max_days_late + 1)]
|
|
252
|
+
self._best_submission_by_days_late = [None] * (self.max_days_late + 1)
|
|
255
253
|
|
|
256
254
|
self.attempts += 1
|
|
257
255
|
if (
|
|
@@ -266,12 +264,7 @@ class ChallengeResult:
|
|
|
266
264
|
self.last_attempt = submission.marked_at
|
|
267
265
|
|
|
268
266
|
if days_late <= self.max_days_late:
|
|
269
|
-
|
|
270
|
-
self.best_submission_by_calendar_days_late[days_late] is None
|
|
271
|
-
or self.best_submission_by_calendar_days_late[days_late].test_cases_passed
|
|
272
|
-
< submission.test_cases_passed
|
|
273
|
-
):
|
|
274
|
-
self.best_submission_by_calendar_days_late[days_late] = submission
|
|
267
|
+
self._submissions_by_days_late[days_late].append(submission)
|
|
275
268
|
|
|
276
269
|
def finalize(self, marker: ChallengeMarker, crawler):
|
|
277
270
|
"""
|
|
@@ -286,25 +279,34 @@ class ChallengeResult:
|
|
|
286
279
|
``BY_TESTCASE_SCORE``. May be None for other mark types.
|
|
287
280
|
"""
|
|
288
281
|
for days_late in range(0, self.max_days_late + 1):
|
|
289
|
-
|
|
290
|
-
if
|
|
282
|
+
submissions = self._submissions_by_days_late[days_late]
|
|
283
|
+
if not submissions:
|
|
291
284
|
continue
|
|
292
285
|
|
|
293
286
|
if marker.mark_type is MarkType.BY_CHALLENGE:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
287
|
+
best = max(
|
|
288
|
+
submissions,
|
|
289
|
+
key=lambda s: (
|
|
290
|
+
s.status == "passed" and s.test_cases_passed == s.test_cases_total,
|
|
291
|
+
s.test_cases_passed,
|
|
292
|
+
),
|
|
293
|
+
)
|
|
294
|
+
self._best_submission_by_days_late[days_late] = best
|
|
295
|
+
if best.status == "passed" and best.test_cases_passed == best.test_cases_total:
|
|
298
296
|
self.scores_by_calendar_days_late[days_late] = marker.points_per_challenge
|
|
299
297
|
elif marker.mark_type is MarkType.BY_TESTCASE:
|
|
300
|
-
|
|
298
|
+
best = max(submissions, key=lambda s: s.test_cases_passed)
|
|
299
|
+
self._best_submission_by_days_late[days_late] = best
|
|
300
|
+
self.scores_by_calendar_days_late[days_late] = best.test_cases_passed
|
|
301
301
|
elif marker.mark_type is MarkType.BY_TESTCASE_SCORE:
|
|
302
|
-
|
|
303
|
-
for
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
best_score = 0
|
|
303
|
+
for candidate in submissions:
|
|
304
|
+
s = crawler.get_submission(candidate.id)
|
|
305
|
+
score = sum(tc.score for tc in s.testcases if tc.passed)
|
|
306
|
+
if score > best_score:
|
|
307
|
+
best_score = score
|
|
308
|
+
self._best_submission_by_days_late[days_late] = s
|
|
309
|
+
self.scores_by_calendar_days_late[days_late] = best_score
|
|
308
310
|
else:
|
|
309
311
|
raise Exception(f"Invalid marker type {marker.mark_type}")
|
|
310
312
|
|
|
@@ -348,7 +350,7 @@ class ChallengeResult:
|
|
|
348
350
|
|
|
349
351
|
for day in range(0, len(self.scores_by_calendar_days_late)):
|
|
350
352
|
score = self.scores_by_calendar_days_late[day]
|
|
351
|
-
submission = self.
|
|
353
|
+
submission = self._best_submission_by_days_late[day]
|
|
352
354
|
if submission is None:
|
|
353
355
|
continue
|
|
354
356
|
|
|
@@ -473,6 +473,7 @@ class FileSummary(File):
|
|
|
473
473
|
_insert_intervals: List[float] = field(default_factory=list)
|
|
474
474
|
_prev_insert_time: datetime.datetime = None
|
|
475
475
|
_pending_deletion: str = ""
|
|
476
|
+
_insert_frontier: int = 0
|
|
476
477
|
|
|
477
478
|
def from_file(file: File):
|
|
478
479
|
fs = FileSummary(file.id, file.path, file.content)
|
|
@@ -522,9 +523,8 @@ class WorkspaceSummary:
|
|
|
522
523
|
log: WorkspaceLog,
|
|
523
524
|
acceptable_selections: List[str] = [],
|
|
524
525
|
stop_at_first_successful_submission: bool = True,
|
|
525
|
-
max_idle_seconds=
|
|
526
|
+
max_idle_seconds=600,
|
|
526
527
|
min_suspicious_paste_chars=50,
|
|
527
|
-
max_continuous_typing_seconds=2,
|
|
528
528
|
):
|
|
529
529
|
"""
|
|
530
530
|
Build a WorkspaceSummary by replaying a workspace event log.
|
|
@@ -569,6 +569,9 @@ class WorkspaceSummary:
|
|
|
569
569
|
for selection in acceptable_selections:
|
|
570
570
|
selected_content.add(selection)
|
|
571
571
|
|
|
572
|
+
session_active_time = datetime.timedelta(0)
|
|
573
|
+
prev_session_event_time = None
|
|
574
|
+
|
|
572
575
|
curr_selection = None
|
|
573
576
|
pending_selection = None
|
|
574
577
|
for event in log.events:
|
|
@@ -595,6 +598,12 @@ class WorkspaceSummary:
|
|
|
595
598
|
if event.type != "cursor" and event.type != "edit" and event.type != "init":
|
|
596
599
|
continue
|
|
597
600
|
|
|
601
|
+
if prev_session_event_time is not None:
|
|
602
|
+
elapsed = event.at - prev_session_event_time
|
|
603
|
+
if elapsed.total_seconds() < max_idle_seconds:
|
|
604
|
+
session_active_time += elapsed
|
|
605
|
+
prev_session_event_time = event.at
|
|
606
|
+
|
|
598
607
|
file = file_summaries.get(event.file_id)
|
|
599
608
|
if file is None:
|
|
600
609
|
continue
|
|
@@ -627,12 +636,8 @@ class WorkspaceSummary:
|
|
|
627
636
|
|
|
628
637
|
pos = 0
|
|
629
638
|
|
|
630
|
-
is_uninterrupted = False
|
|
631
639
|
if file.prev_typing_time is not None:
|
|
632
640
|
elapsed = event.at - file.prev_typing_time
|
|
633
|
-
if elapsed.total_seconds() < max_continuous_typing_seconds:
|
|
634
|
-
is_uninterrupted = True
|
|
635
|
-
|
|
636
641
|
if elapsed.total_seconds() < max_idle_seconds:
|
|
637
642
|
file.typing_time += elapsed
|
|
638
643
|
|
|
@@ -654,6 +659,12 @@ class WorkspaceSummary:
|
|
|
654
659
|
)
|
|
655
660
|
)
|
|
656
661
|
|
|
662
|
+
delete_end = pos + edit.value
|
|
663
|
+
if delete_end <= file._insert_frontier:
|
|
664
|
+
file._insert_frontier -= edit.value
|
|
665
|
+
elif pos < file._insert_frontier:
|
|
666
|
+
file._insert_frontier = pos # deletion overlaps the frontier
|
|
667
|
+
|
|
657
668
|
file.content = file.content[:pos] + file.content[pos + edit.value :]
|
|
658
669
|
file.transcribed_mask = (
|
|
659
670
|
file.transcribed_mask[:pos]
|
|
@@ -673,10 +684,9 @@ class WorkspaceSummary:
|
|
|
673
684
|
)
|
|
674
685
|
)
|
|
675
686
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
):
|
|
687
|
+
frontier_tolerance = max(10, len(file.content) // 10)
|
|
688
|
+
at_frontier = pos >= file._insert_frontier - frontier_tolerance
|
|
689
|
+
if at_frontier:
|
|
680
690
|
file.transcribed_mask = (
|
|
681
691
|
file.transcribed_mask[:pos]
|
|
682
692
|
+ get_masked_section(edit.value)
|
|
@@ -688,6 +698,11 @@ class WorkspaceSummary:
|
|
|
688
698
|
+ edit.value
|
|
689
699
|
+ file.transcribed_mask[pos:]
|
|
690
700
|
)
|
|
701
|
+
n = len(edit.value)
|
|
702
|
+
if pos < file._insert_frontier:
|
|
703
|
+
file._insert_frontier += n # inserted before frontier — shift it right
|
|
704
|
+
else:
|
|
705
|
+
file._insert_frontier = pos + n # advance frontier to end of this insert
|
|
691
706
|
|
|
692
707
|
if len(edit.value) > min_suspicious_paste_chars:
|
|
693
708
|
if not selected_content.contains(edit.value):
|
|
@@ -719,10 +734,9 @@ class WorkspaceSummary:
|
|
|
719
734
|
if '\n' in sel_text:
|
|
720
735
|
sel_file.multiline_selection_count += 1
|
|
721
736
|
|
|
722
|
-
total_active_time =
|
|
737
|
+
total_active_time = session_active_time
|
|
723
738
|
total_typing_time = datetime.timedelta(0)
|
|
724
739
|
for file in file_summaries.values():
|
|
725
|
-
total_active_time += file.active_time
|
|
726
740
|
total_typing_time += file.typing_time
|
|
727
741
|
|
|
728
742
|
pasted_sections = get_pasted_sections(
|
|
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
|