canvaslms 5.7__py3-none-any.whl → 5.9__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.
@@ -1338,7 +1338,8 @@ content, accept it (which triggers the update), edit further, or discard changes
1338
1338
  title = attributes.get('name', assignment.name)
1339
1339
  result = canvaslms.cli.content.interactive_confirm_and_edit(
1340
1340
  title, body_content, attributes,
1341
- canvaslms.cli.content.ASSIGNMENT_SCHEMA, "Assignment")
1341
+ canvaslms.cli.content.ASSIGNMENT_SCHEMA, "Assignment",
1342
+ content_attr='description')
1342
1343
 
1343
1344
  if result is None:
1344
1345
  print("Discarded changes for this assignment.", file=sys.stderr)
@@ -768,6 +768,7 @@ def assignments_edit_command(config, canvas, args):
768
768
  attributes,
769
769
  canvaslms.cli.content.ASSIGNMENT_SCHEMA,
770
770
  "Assignment",
771
+ content_attr="description",
771
772
  )
772
773
 
773
774
  if result is None:
canvaslms/cli/content.nw CHANGED
@@ -985,9 +985,14 @@ loop. Users can preview their content, choose to accept it, edit it further,
985
985
  or discard their changes.
986
986
 
987
987
  The function is generalized to work with any content type by accepting the
988
- schema for re-editing.
988
+ schema for re-editing. When the user chooses to edit again, we need to pass
989
+ the current content back to [[get_content_from_editor]]. Since that function
990
+ expects content to be stored in the attributes dictionary under a named key
991
+ (e.g., \enquote{message} for announcements, \enquote{body} for pages), we accept
992
+ a [[content_attr]] parameter specifying which key to use.
989
993
  <<interactive functions>>=
990
- def interactive_confirm_and_edit(title, message, attributes, schema, content_type="Content"):
994
+ def interactive_confirm_and_edit(title, message, attributes, schema,
995
+ content_type="Content", content_attr='message'):
991
996
  """Interactive loop for confirming or editing content.
992
997
 
993
998
  Args:
@@ -996,6 +1001,7 @@ def interactive_confirm_and_edit(title, message, attributes, schema, content_typ
996
1001
  attributes: Current attributes
997
1002
  schema: Schema for re-editing
998
1003
  content_type: Type label for display
1004
+ content_attr: Name of attribute that holds body content (for re-editing)
999
1005
 
1000
1006
  Returns:
1001
1007
  Tuple of (attributes, message), or None if cancelled
@@ -1021,7 +1027,9 @@ def interactive_confirm_and_edit(title, message, attributes, schema, content_typ
1021
1027
  if choice in ['a', 'accept']:
1022
1028
  return current_attributes, current_message
1023
1029
  elif choice in ['e', 'edit']:
1024
- result = get_content_from_editor(schema, current_attributes, current_message)
1030
+ edit_attrs = current_attributes.copy()
1031
+ edit_attrs[content_attr] = current_message
1032
+ result = get_content_from_editor(schema, edit_attrs, content_attr=content_attr)
1025
1033
  if result is None:
1026
1034
  print("Editor cancelled or failed. Keeping previous content.", file=sys.stderr)
1027
1035
  else:
canvaslms/cli/content.py CHANGED
@@ -479,7 +479,7 @@ def render_content_preview(
479
479
 
480
480
 
481
481
  def interactive_confirm_and_edit(
482
- title, message, attributes, schema, content_type="Content"
482
+ title, message, attributes, schema, content_type="Content", content_attr="message"
483
483
  ):
484
484
  """Interactive loop for confirming or editing content.
485
485
 
@@ -489,6 +489,7 @@ def interactive_confirm_and_edit(
489
489
  attributes: Current attributes
490
490
  schema: Schema for re-editing
491
491
  content_type: Type label for display
492
+ content_attr: Name of attribute that holds body content (for re-editing)
492
493
 
493
494
  Returns:
494
495
  Tuple of (attributes, message), or None if cancelled
@@ -516,8 +517,10 @@ def interactive_confirm_and_edit(
516
517
  if choice in ["a", "accept"]:
517
518
  return current_attributes, current_message
518
519
  elif choice in ["e", "edit"]:
520
+ edit_attrs = current_attributes.copy()
521
+ edit_attrs[content_attr] = current_message
519
522
  result = get_content_from_editor(
520
- schema, current_attributes, current_message
523
+ schema, edit_attrs, content_attr=content_attr
521
524
  )
522
525
  if result is None:
523
526
  print(
@@ -394,7 +394,8 @@ for course in course_list:
394
394
 
395
395
  result = canvaslms.cli.content.interactive_confirm_and_edit(
396
396
  title, message, attributes,
397
- canvaslms.cli.content.ANNOUNCEMENT_SCHEMA, "Announcement")
397
+ canvaslms.cli.content.ANNOUNCEMENT_SCHEMA, "Announcement",
398
+ content_attr='message')
398
399
  if result is None:
399
400
  print("Cancelled.", file=sys.stderr)
400
401
  sys.exit(0)
@@ -775,7 +776,8 @@ After editing, we enter the interactive confirm loop.
775
776
  title = edited_attrs.get('title', announcement.title)
776
777
  result = canvaslms.cli.content.interactive_confirm_and_edit(
777
778
  title, body_content, edited_attrs,
778
- canvaslms.cli.content.ANNOUNCEMENT_SCHEMA, "Announcement")
779
+ canvaslms.cli.content.ANNOUNCEMENT_SCHEMA, "Announcement",
780
+ content_attr='message')
779
781
 
780
782
  if result is None:
781
783
  print("Discarded changes.", file=sys.stderr)
@@ -143,6 +143,7 @@ def announce_command(config, canvas, args):
143
143
  attributes,
144
144
  canvaslms.cli.content.ANNOUNCEMENT_SCHEMA,
145
145
  "Announcement",
146
+ content_attr="message",
146
147
  )
147
148
  if result is None:
148
149
  print("Cancelled.", file=sys.stderr)
@@ -436,6 +437,7 @@ def discussions_edit_command(config, canvas, args):
436
437
  edited_attrs,
437
438
  canvaslms.cli.content.ANNOUNCEMENT_SCHEMA,
438
439
  "Announcement",
440
+ content_attr="message",
439
441
  )
440
442
 
441
443
  if result is None:
canvaslms/cli/pages.nw CHANGED
@@ -744,7 +744,8 @@ content, accept it (which triggers the update), edit further, or discard changes
744
744
  title = attributes.get('title', full_page.title)
745
745
  result = canvaslms.cli.content.interactive_confirm_and_edit(
746
746
  title, body_content, attributes,
747
- canvaslms.cli.content.PAGE_SCHEMA, "Page")
747
+ canvaslms.cli.content.PAGE_SCHEMA, "Page",
748
+ content_attr='body')
748
749
 
749
750
  if result is None:
750
751
  print("Discarded changes for this page.", file=sys.stderr)
canvaslms/cli/pages.py CHANGED
@@ -525,6 +525,7 @@ def pages_edit_command(config, canvas, args):
525
525
  attributes,
526
526
  canvaslms.cli.content.PAGE_SCHEMA,
527
527
  "Page",
528
+ content_attr="body",
528
529
  )
529
530
 
530
531
  if result is None:
canvaslms/cli/quizzes.nw CHANGED
@@ -3426,7 +3426,8 @@ def edit_quiz_interactive(quiz, requester, html_mode=False):
3426
3426
  message=body,
3427
3427
  attributes=attributes,
3428
3428
  schema=QUIZ_SCHEMA,
3429
- content_type="Quiz"
3429
+ content_type="Quiz",
3430
+ content_attr='instructions'
3430
3431
  )
3431
3432
 
3432
3433
  if result is None:
canvaslms/cli/quizzes.py CHANGED
@@ -2499,6 +2499,7 @@ def edit_quiz_interactive(quiz, requester, html_mode=False):
2499
2499
  attributes=attributes,
2500
2500
  schema=QUIZ_SCHEMA,
2501
2501
  content_type="Quiz",
2502
+ content_attr="instructions",
2502
2503
  )
2503
2504
 
2504
2505
  if result is None:
canvaslms/cli/results.nw CHANGED
@@ -39,6 +39,7 @@ import importlib.machinery
39
39
  import importlib.util
40
40
  import os
41
41
  import pathlib
42
+ import pkgutil
42
43
  import re
43
44
  import sys
44
45
 
@@ -55,13 +56,27 @@ The command requires two arguments: course and assignment.
55
56
  We also want the option to filter on users.
56
57
  We can add these by using [[add_assignment_option]], however, we don't need the
57
58
  ungraded flag as we want to export results (\ie graded material).
58
- Also, we can just add the [[add_user_or_group_option]] to be able to filter on
59
+ Also, we can just add the [[add_user_or_group_option]] to be able to filter on
59
60
  users or groups.
61
+
62
+ The epilog contains a formatted list of available grading modules.
63
+ We use [[RawDescriptionHelpFormatter]] to preserve the whitespace formatting in
64
+ the epilog while still allowing argument help text to wrap normally.
65
+
66
+ However, we must be careful about how we construct the epilog string.
67
+ When [[black]] formats the generated Python, triple-quoted strings passed as
68
+ arguments get indented on continuation lines.
69
+ Since [[RawDescriptionHelpFormatter]] preserves this whitespace literally, we'd
70
+ end up with oddly indented prose text in the help output.
71
+
72
+ To avoid this, we build the epilog via the [[format_results_epilog]] function,
73
+ which joins individual lines without introducing unwanted indentation.
60
74
  <<add results command to subp>>=
61
75
  results_parser = subp.add_parser("results",
62
76
  help="Lists results of a course",
63
77
  description="""<<results command description>>""",
64
- epilog="""<<results command epilog>>""")
78
+ epilog=format_results_epilog(),
79
+ formatter_class=argparse.RawDescriptionHelpFormatter)
65
80
  results_parser.set_defaults(func=results_command)
66
81
  assignments.add_assignment_option(results_parser, ungraded=False)
67
82
  users.add_user_or_group_option(results_parser)
@@ -83,13 +98,10 @@ prevent the student from getting a grade. Output format, CSV:
83
98
  <course code> <component code> <student ID> <missing assignment> <reason>
84
99
 
85
100
  The reason can be "not submitted" or "not graded".
86
- <<results command epilog>>=
87
- If you specify an assignment group, the results of the assignments in that
88
- group will be summarized. You can supply your own function for summarizing
89
- grades through the -S option. See `pydoc3 canvaslms.grades` for different
90
- options.
91
- @ We will cover the option for and loading of the custom summary module later,
101
+ @ We will cover the option for and loading of the custom summary module later,
92
102
  in \cref{custom-summary-modules}.
103
+ We will also cover the [[format_grading_modules_help]] function, which
104
+ dynamically discovers available modules in the [[canvaslms.grades]] package.
93
105
 
94
106
  Now, that [[results_command]] function must take three arguments: [[config]],
95
107
  [[canvas]] and [[args]].
@@ -395,18 +407,120 @@ if grade is None or grade_date is None \
395
407
  continue
396
408
  @
397
409
 
410
+ \subsection{Discovering available grading modules}
411
+ \label{grading-module-discovery}
412
+
413
+ When users install [[canvaslms]] via pipx, the package resides in an isolated
414
+ virtual environment.
415
+ This means the traditional approach of using [[pydoc3 canvaslms.grades]] to
416
+ discover available modules no longer works---pydoc searches the system Python
417
+ path, not pipx's isolated environment.
418
+
419
+ To solve this, we dynamically discover available modules at runtime using
420
+ [[pkgutil.iter_modules]].
421
+ This works regardless of installation method because we introspect from within
422
+ the package itself.
423
+ If a module fails to import (for example, due to missing dependencies), we
424
+ simply skip it rather than failing the entire discovery process.
425
+
426
+ We provide two helper functions: one to list the modules with their
427
+ descriptions, and one to format them for display in help text.
428
+ <<functions>>=
429
+ def list_grading_modules():
430
+ """
431
+ Discover available grading modules in canvaslms.grades package.
432
+ Returns a list of (module_name, description) tuples.
433
+ """
434
+ import canvaslms.grades
435
+
436
+ modules = []
437
+ # These are internal modules, not user-facing grading strategies
438
+ excluded = {"__init__", "grades"}
439
+
440
+ for importer, modname, ispkg in pkgutil.iter_modules(canvaslms.grades.__path__):
441
+ if modname in excluded:
442
+ continue
443
+ try:
444
+ module = importlib.import_module(f"canvaslms.grades.{modname}")
445
+ doc = module.__doc__
446
+ if doc:
447
+ # Take only the first line of the docstring
448
+ first_line = doc.strip().split('\n')[0]
449
+ else:
450
+ first_line = "(no description)"
451
+ modules.append((modname, first_line))
452
+ except ImportError:
453
+ continue
454
+
455
+ return sorted(modules)
456
+
457
+ def format_grading_modules_help():
458
+ """Format the list of grading modules for help text display."""
459
+ modules = list_grading_modules()
460
+ if not modules:
461
+ return " (no modules found)"
462
+
463
+ lines = []
464
+ for name, desc in modules:
465
+ lines.append(f" canvaslms.grades.{name}: {desc}")
466
+ return "\n\n".join(lines)
467
+
468
+ def format_results_epilog():
469
+ """
470
+ Build the epilog for results command help.
471
+
472
+ We construct the epilog by joining lines explicitly rather than using a
473
+ triple-quoted string. This avoids indentation issues when black formats
474
+ the generated Python code---triple-quoted strings in function arguments
475
+ get continuation-line indentation that RawDescriptionHelpFormatter
476
+ would preserve literally.
477
+ """
478
+ parts = [
479
+ "If you specify an assignment group, the results of the assignments in that",
480
+ "group will be summarized. You can supply your own function for summarizing",
481
+ "grades through the -S option.",
482
+ "",
483
+ "Available grading modules:",
484
+ format_grading_modules_help(),
485
+ "",
486
+ "You can also provide a path to your own Python file with a summarize_group",
487
+ "function."
488
+ ]
489
+ return "\n".join(parts)
490
+ @
491
+
492
+ The [[list_grading_modules]] function uses [[pkgutil.iter_modules]] to iterate
493
+ over all submodules in the [[canvaslms.grades]] package.
494
+ We exclude [[__init__]] (the package initializer) and [[grades]] (an internal
495
+ module) since these are not user-facing grading strategies.
496
+
497
+ For each module, we import it and extract the first line of its docstring as a
498
+ brief description.
499
+ This gives users immediate visibility into what each module does, right in the
500
+ help output.
501
+
502
+ The [[format_grading_modules_help]] function joins module entries with blank
503
+ lines between them (double newline) for visual separation in the help output.
504
+ This makes the list easier to scan when module descriptions vary in length.
505
+
506
+ The [[format_results_epilog]] function builds the complete epilog by joining
507
+ individual lines.
508
+ This approach avoids the pitfall where [[black]] indents continuation lines in
509
+ triple-quoted strings, which [[RawDescriptionHelpFormatter]] would preserve
510
+ literally, resulting in oddly indented prose in the help output.
511
+
512
+
398
513
  \subsection{Loading a custom summary module}
399
514
  \label{custom-summary-modules}
400
515
 
401
- Different teachers have different policies for merging several assignments into
516
+ Different teachers have different policies for merging several assignments into
402
517
  one grade.
403
518
  We now want to provide a way to override the default function.
404
519
  <<summary module option doc>>=
405
- Name of Python module or file containing module to load with a custom
406
- summarization function to summarize assignment groups. The default module is
407
- part of the `canvaslms` package: `{default_summary_module}`. But it could be
408
- any Python file in the file system or other built-in modules. See `pydoc3
409
- canvaslms.grades` for alternative modules or how to build your own.
520
+ Name of Python module or file to load with a custom summarization function.
521
+ Default: `{default_summary_module}`. Available modules: """ + \
522
+ ", ".join(m[0] for m in list_grading_modules()) + """. \
523
+ Or provide a path to your own Python file.
410
524
  <<add option for custom summary module>>=
411
525
  default_summary_module = "canvaslms.grades.conjunctavg"
412
526
  results_parser.add_argument("-S", "--summary-module",
canvaslms/cli/results.py CHANGED
@@ -12,6 +12,7 @@ import importlib.machinery
12
12
  import importlib.util
13
13
  import os
14
14
  import pathlib
15
+ import pkgutil
15
16
  import re
16
17
  import sys
17
18
 
@@ -250,6 +251,71 @@ def summarize_modules(canvas, args):
250
251
  ]
251
252
 
252
253
 
254
+ def list_grading_modules():
255
+ """
256
+ Discover available grading modules in canvaslms.grades package.
257
+ Returns a list of (module_name, description) tuples.
258
+ """
259
+ import canvaslms.grades
260
+
261
+ modules = []
262
+ # These are internal modules, not user-facing grading strategies
263
+ excluded = {"__init__", "grades"}
264
+
265
+ for importer, modname, ispkg in pkgutil.iter_modules(canvaslms.grades.__path__):
266
+ if modname in excluded:
267
+ continue
268
+ try:
269
+ module = importlib.import_module(f"canvaslms.grades.{modname}")
270
+ doc = module.__doc__
271
+ if doc:
272
+ # Take only the first line of the docstring
273
+ first_line = doc.strip().split("\n")[0]
274
+ else:
275
+ first_line = "(no description)"
276
+ modules.append((modname, first_line))
277
+ except ImportError:
278
+ continue
279
+
280
+ return sorted(modules)
281
+
282
+
283
+ def format_grading_modules_help():
284
+ """Format the list of grading modules for help text display."""
285
+ modules = list_grading_modules()
286
+ if not modules:
287
+ return " (no modules found)"
288
+
289
+ lines = []
290
+ for name, desc in modules:
291
+ lines.append(f" canvaslms.grades.{name}: {desc}")
292
+ return "\n\n".join(lines)
293
+
294
+
295
+ def format_results_epilog():
296
+ """
297
+ Build the epilog for results command help.
298
+
299
+ We construct the epilog by joining lines explicitly rather than using a
300
+ triple-quoted string. This avoids indentation issues when black formats
301
+ the generated Python code---triple-quoted strings in function arguments
302
+ get continuation-line indentation that RawDescriptionHelpFormatter
303
+ would preserve literally.
304
+ """
305
+ parts = [
306
+ "If you specify an assignment group, the results of the assignments in that",
307
+ "group will be summarized. You can supply your own function for summarizing",
308
+ "grades through the -S option.",
309
+ "",
310
+ "Available grading modules:",
311
+ format_grading_modules_help(),
312
+ "",
313
+ "You can also provide a path to your own Python file with a summarize_group",
314
+ "function.",
315
+ ]
316
+ return "\n".join(parts)
317
+
318
+
253
319
  def load_module(module_name):
254
320
  """
255
321
  Load a module from the file system or a built-in module.
@@ -334,10 +400,8 @@ def add_command(subp):
334
400
  <course code> <component code> <student ID> <missing assignment> <reason>
335
401
 
336
402
  The reason can be "not submitted" or "not graded".""",
337
- epilog="""If you specify an assignment group, the results of the assignments in that
338
- group will be summarized. You can supply your own function for summarizing
339
- grades through the -S option. See `pydoc3 canvaslms.grades` for different
340
- options.""",
403
+ epilog=format_results_epilog(),
404
+ formatter_class=argparse.RawDescriptionHelpFormatter,
341
405
  )
342
406
  results_parser.set_defaults(func=results_command)
343
407
  assignments.add_assignment_option(results_parser, ungraded=False)
@@ -362,11 +426,11 @@ def add_command(subp):
362
426
  "--summary-module",
363
427
  required=False,
364
428
  default=default_summary_module,
365
- help=f"""Name of Python module or file containing module to load with a custom
366
- summarization function to summarize assignment groups. The default module is
367
- part of the `canvaslms` package: `{default_summary_module}`. But it could be
368
- any Python file in the file system or other built-in modules. See `pydoc3
369
- canvaslms.grades` for alternative modules or how to build your own.""",
429
+ help=f"""Name of Python module or file to load with a custom summarization function.
430
+ Default: `{default_summary_module}`. Available modules: """
431
+ + ", ".join(m[0] for m in list_grading_modules())
432
+ + """. \
433
+ Or provide a path to your own Python file.""",
370
434
  )
371
435
  default_missing_module = "canvaslms.cli.results"
372
436
  results_parser.add_argument(
canvaslms/grades/Makefile CHANGED
@@ -6,6 +6,7 @@ MODULES+= disjunctmax.py
6
6
  MODULES+= maxgradesurvey.py
7
7
  MODULES+= conjunctavgsurvey.py
8
8
  MODULES+= tilkryLAB1.py
9
+ MODULES+= participation.py
9
10
 
10
11
  .PHONY: all
11
12
  all: grades.tex
@@ -14,6 +15,7 @@ all: disjunctmax.tex
14
15
  all: maxgradesurvey.tex
15
16
  all: conjunctavgsurvey.tex
16
17
  all: tilkryLAB1.tex
18
+ all: participation.tex
17
19
  all: ${MODULES}
18
20
 
19
21
  grades.tex: conjunctavg.tex
@@ -21,6 +23,7 @@ grades.tex: disjunctmax.tex
21
23
  grades.tex: maxgradesurvey.tex
22
24
  grades.tex: conjunctavgsurvey.tex
23
25
  grades.tex: tilkryLAB1.tex
26
+ grades.tex: participation.tex
24
27
 
25
28
  __init__.py: init.py
26
29
  ${MV} $^ $@
@@ -135,3 +135,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead.
135
135
  \input{../src/canvaslms/grades/disjunctmax.tex}
136
136
  \input{../src/canvaslms/grades/maxgradesurvey.tex}
137
137
  \input{../src/canvaslms/grades/tilkryLAB1.tex}
138
+ \input{../src/canvaslms/grades/participation.tex}
@@ -0,0 +1,218 @@
1
+ \section{Conjunctive P/F grading, the \texttt{participation} module}
2
+
3
+ This module implements conjunctive P/F grading for participation-based
4
+ assignments.
5
+ It's useful when students must complete \emph{all} assignments in a group to
6
+ pass, where each assignment is graded as complete/incomplete or 100/0.
7
+
8
+ As an example, consider a course where students pass by participating in
9
+ seminars and completing reflections:
10
+ \begin{minted}{text}
11
+ $ canvaslms assignments list -c vetcyb25h -A "^Participation INL1" \
12
+ | cut -f 1-3
13
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on literature reviews
14
+ DA2215 HT25 (vetcyb25h) Participation INL1 Overview of Science in Security
15
+ DA2215 HT25 (vetcyb25h) Participation INL1 How to Design Computer Security Experiments
16
+ DA2215 HT25 (vetcyb25h) Participation INL1 How do you know it's secure? Passwords
17
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension, literature review: Of passwords and people
18
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Graphical Passwords: Learning from the First Twelve Years
19
+ DA2215 HT25 (vetcyb25h) Participation INL1 Achieving Rigor in Literature Reviews Insights from Qualitative Data Analysis and Tool-Support
20
+ DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 13/11 at 13:15
21
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Of Passwords and People, Measuring the Effect of Password-Composition Policies
22
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Can long passwords be secure and usable?
23
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Why phishing works
24
+ DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 6/11 at 15:15
25
+ DA2215 HT25 (vetcyb25h) Participation INL1 The RSA and ElGamal cryptosystems
26
+ DA2215 HT25 (vetcyb25h) Participation INL1 On the Security of EIGamal Based Encryption
27
+ DA2215 HT25 (vetcyb25h) Participation INL1 Stealing Keys from PCs using a Radio: Cheap Electromagnetic Attacks on Windowed Exponentiation
28
+ DA2215 HT25 (vetcyb25h) Participation INL1 Timing Analysis of Keystrokes and Timing Attacks on SSH
29
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part I
30
+ DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 1. Introduction
31
+ DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 2. Formal methods and Interactive Theorem Proving
32
+ DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 4. Examples for what can be verified with Interactive Theorem Provers
33
+ DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 5. Limitations of Interactive Theorem Proving and Conclusion
34
+ DA2215 HT25 (vetcyb25h) Participation INL1 Model checking: algorithmic verification and debugging
35
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part II
36
+ DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 28/11 at 10:15
37
+ DA2215 HT25 (vetcyb25h) Participation INL1 Dos and Don'ts of Machine Learning in Computer Security
38
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of statistics
39
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Why Johnny can't encrypt
40
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Comparing the Usability of Cryptographic APIs
41
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on qualitative methods
42
+ DA2215 HT25 (vetcyb25h) Participation INL1 SoK: Science, Security and the Elusive Goal of Security as a Scientific Pursuit
43
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on Science in Security
44
+ DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 12/12 at 12:15
45
+ DA2215 HT25 (vetcyb25h) Participation INL1 Tor: The Second-Generation Onion Router
46
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Users get routed: traffic correlation on tor by realistic adversaries
47
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Shadow: Running Tor in a Box for Accurate and Efficient Experimentation
48
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Website Fingerprinting with Website Oracles
49
+ DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Online Website Fingerprinting: Evaluating Website Fingerprinting Attacks on Tor in the Real World
50
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part III
51
+ DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 3/12 at 12:15
52
+ DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on inferences, experiments and measurements
53
+ \end{minted}
54
+ The exact assignments and names might change though.
55
+ But that's fine.
56
+ The grading system remains the same, we have the following grades:
57
+ \begin{minted}{text}
58
+ $ canvaslms submissions list -c vetcyb25h -A "^Participation INL1" \
59
+ | cut -f 4 | sort -u
60
+
61
+ 100
62
+ complete
63
+ incomplete
64
+ \end{minted}
65
+ We want all assignments to have either complete or 100 to get a passing grade.
66
+ Otherwise the student gets an F.
67
+
68
+ \subsection{Grading logic: conjunctive P/F}
69
+
70
+ This module implements what we call \emph{conjunctive P/F grading}.
71
+ Let's contrast this with other approaches to understand what makes it unique:
72
+ \begin{description}
73
+ \item[conjunctavg] All assignments must pass, and A--E grades are averaged.
74
+ Here we have no A--E grades, only complete/incomplete or 100/empty.
75
+ \item[disjunctmax] At least one assignment must pass (disjunctive), and we take
76
+ the best grade.
77
+ Here \emph{all} assignments must pass (conjunctive), and the only possible
78
+ grades are P or F.
79
+ \end{description}
80
+
81
+ The grading rule is simple: if \emph{all} participation assignments have a
82
+ passing grade, the student passes.
83
+ If \emph{any} assignment is missing or has an incomplete grade, the student
84
+ fails.
85
+
86
+ \subsection{Module structure}
87
+
88
+ We follow the standard module structure: a [[summarize_group]] function that
89
+ iterates over users and delegates to a [[summarize]] helper function.
90
+ <<[[participation.py]]>>=
91
+ """
92
+ <<module doc>>
93
+ """
94
+ import datetime as dt
95
+ from canvaslms.cli import results
96
+ from canvasapi.exceptions import ResourceDoesNotExist
97
+
98
+ <<helper functions>>
99
+
100
+ def summarize_group(assignments_list, users_list):
101
+ """
102
+ Summarizes participation assignments using conjunctive P/F grading.
103
+ All assignments must have 'complete' or '100' for P, otherwise F.
104
+ """
105
+ for user in users_list:
106
+ grade, grade_date, graders = summarize(user, assignments_list)
107
+ yield [user, grade, grade_date, *graders]
108
+ @
109
+
110
+ <<module doc>>=
111
+ Summarizes participation assignments using conjunctive P/F grading.
112
+ All assignments must have 'complete' or '100' for P, otherwise F.
113
+ @
114
+
115
+ \subsection{What counts as passing?}
116
+
117
+ A grade is considered passing if it is either:
118
+ \begin{itemize}
119
+ \item [[complete]] (case-insensitive, so [[Complete]] or [[COMPLETE]] also work)
120
+ \item [[100]] (a numeric grade indicating full completion)
121
+ \end{itemize}
122
+
123
+ Anything else---including [[incomplete]], [[None]], or a missing
124
+ submission---counts as not passing.
125
+ <<helper functions>>=
126
+ def is_passing_grade(grade):
127
+ """
128
+ Returns True if the grade indicates passing (complete or 100).
129
+ """
130
+ if grade is None:
131
+ return False
132
+ if isinstance(grade, str):
133
+ if grade.casefold() == "complete":
134
+ return True
135
+ if grade == "100":
136
+ return True
137
+ return False
138
+ @
139
+
140
+ \subsection{Summarizing a student's participation}
141
+
142
+ The [[summarize]] function iterates through all assignments and checks whether
143
+ each one has a passing grade.
144
+ We collect dates and graders along the way.
145
+ <<helper functions>>=
146
+ def summarize(user, assignments_list):
147
+ """
148
+ Extracts user's submissions for all participation assignments.
149
+ Returns (grade, date, graders) where grade is P if all passed, F otherwise.
150
+ """
151
+ passed = []
152
+ dates = []
153
+ graders = []
154
+
155
+ for assignment in assignments_list:
156
+ <<get submission for assignment>>
157
+ <<check if grade is passing>>
158
+ <<add graders to [[graders]] list>>
159
+ <<add date to [[dates]] list>>
160
+
161
+ <<determine final grade>>
162
+
163
+ return (final_grade, final_date, graders)
164
+ @
165
+
166
+ For each assignment, we attempt to fetch the student's submission.
167
+ If the submission doesn't exist (which can happen in rare cases), we treat it
168
+ as not passing.
169
+ <<get submission for assignment>>=
170
+ try:
171
+ submission = assignment.get_submission(user,
172
+ include=["submission_history"])
173
+ except ResourceDoesNotExist:
174
+ passed.append(False)
175
+ continue
176
+
177
+ submission.assignment = assignment
178
+ @
179
+
180
+ We extract the grade from the submission and check whether it's passing.
181
+ <<check if grade is passing>>=
182
+ grade = submission.grade
183
+ passed.append(is_passing_grade(grade))
184
+ @
185
+
186
+ We collect all graders from the submission history.
187
+ This ensures we credit everyone who participated in the grading process, not
188
+ just the last person to grade.
189
+ <<add graders to [[graders]] list>>=
190
+ graders += results.all_graders(submission)
191
+ @
192
+
193
+ For the date, we prefer the submission date but fall back to the grading date
194
+ if no submission was made (for example, oral presentations where the student
195
+ didn't submit anything).
196
+ <<add date to [[dates]] list>>=
197
+ grade_date = submission.submitted_at or submission.graded_at
198
+ if grade_date:
199
+ grade_date = dt.date.fromisoformat(grade_date.split("T")[0])
200
+ dates.append(grade_date)
201
+ @
202
+
203
+ \subsection{Determining the final grade}
204
+
205
+ The final grade is P if \emph{all} assignments passed, F otherwise.
206
+ If there are no dates (meaning the student has no activity at all), we return
207
+ [[None]] for both grade and date.
208
+ <<determine final grade>>=
209
+ if dates:
210
+ final_date = max(dates)
211
+ if all(passed):
212
+ final_grade = "P"
213
+ else:
214
+ final_grade = "F"
215
+ else:
216
+ final_date = None
217
+ final_grade = None
218
+ @
@@ -0,0 +1,70 @@
1
+ """
2
+ Summarizes participation assignments using conjunctive P/F grading.
3
+ All assignments must have 'complete' or '100' for P, otherwise F.
4
+ """
5
+
6
+ import datetime as dt
7
+ from canvaslms.cli import results
8
+ from canvasapi.exceptions import ResourceDoesNotExist
9
+
10
+
11
+ def is_passing_grade(grade):
12
+ """
13
+ Returns True if the grade indicates passing (complete or 100).
14
+ """
15
+ if grade is None:
16
+ return False
17
+ if isinstance(grade, str):
18
+ if grade.casefold() == "complete":
19
+ return True
20
+ if grade == "100":
21
+ return True
22
+ return False
23
+
24
+
25
+ def summarize(user, assignments_list):
26
+ """
27
+ Extracts user's submissions for all participation assignments.
28
+ Returns (grade, date, graders) where grade is P if all passed, F otherwise.
29
+ """
30
+ passed = []
31
+ dates = []
32
+ graders = []
33
+
34
+ for assignment in assignments_list:
35
+ try:
36
+ submission = assignment.get_submission(user, include=["submission_history"])
37
+ except ResourceDoesNotExist:
38
+ passed.append(False)
39
+ continue
40
+
41
+ submission.assignment = assignment
42
+ grade = submission.grade
43
+ passed.append(is_passing_grade(grade))
44
+ graders += results.all_graders(submission)
45
+ grade_date = submission.submitted_at or submission.graded_at
46
+ if grade_date:
47
+ grade_date = dt.date.fromisoformat(grade_date.split("T")[0])
48
+ dates.append(grade_date)
49
+
50
+ if dates:
51
+ final_date = max(dates)
52
+ if all(passed):
53
+ final_grade = "P"
54
+ else:
55
+ final_grade = "F"
56
+ else:
57
+ final_date = None
58
+ final_grade = None
59
+
60
+ return (final_grade, final_date, graders)
61
+
62
+
63
+ def summarize_group(assignments_list, users_list):
64
+ """
65
+ Summarizes participation assignments using conjunctive P/F grading.
66
+ All assignments must have 'complete' or '100' for P, otherwise F.
67
+ """
68
+ for user in users_list:
69
+ grade, grade_date, graders = summarize(user, assignments_list)
70
+ yield [user, grade, grade_date, *graders]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: canvaslms
3
- Version: 5.7
3
+ Version: 5.9
4
4
  Summary: Command-line interface to Canvas LMS
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -2,32 +2,32 @@ canvaslms/Makefile,sha256=9zE09HzyU-S2B1RI72UV1S9mmqXKiE9gIn-NuhyQP08,119
2
2
  canvaslms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  canvaslms/cli/Makefile,sha256=8uQf6xKYRr_4mrYA-FnYYh7xAq7CcBvF3rbqDTNLUyY,820
4
4
  canvaslms/cli/__init__.py,sha256=KiOWz1mshRZeqQAmj_l27ZP2krgX-ta0X18Rs4KX8Ng,6515
5
- canvaslms/cli/assignments.nw,sha256=3XyaP_xl42tcMyNmmZ2Bi5vdYuRBx0jEC6Wh2Vwo7t4,54749
6
- canvaslms/cli/assignments.py,sha256=4M8cguSPBuUE7V4sjfFJ3oSL3XiGz7bfEquXFzY9TKU,38710
5
+ canvaslms/cli/assignments.nw,sha256=bgKytV48ya0cGJsuur6sIZ_V7ZvuNR1qF7qPvg1xf2U,54781
6
+ canvaslms/cli/assignments.py,sha256=gphQuMgB0H_y-w4flidu9QRPREheBtuLF8zg70-9gZs,38754
7
7
  canvaslms/cli/cache.nw,sha256=TV9nUkwSUrFyltH98uUJ-3BLXT_-yNGsilaFlbByaxU,13350
8
8
  canvaslms/cli/cache.py,sha256=EBD2YLYa7Efq_RWsDgZOf44SaUemsyPWgeYVS-eW1uk,8217
9
9
  canvaslms/cli/calendar.nw,sha256=-dN8Sx9LWGBwhq2wc3w0GjQL3ywwPKfvi9gjsRJ6qVg,22887
10
10
  canvaslms/cli/calendar.py,sha256=f8af6McSxg5G2Etf6TAxwp1cnYodlcyw7SJgcJJLBks,20898
11
11
  canvaslms/cli/canvas_calendar.py,sha256=AbA6nOZ7e-zACfAqwK1utBjuShVGsaERxIJ4xRKEUZc,22196
12
12
  canvaslms/cli/cli.nw,sha256=f3xbi1kzjVSsFzEmOS_7Pfy7nJbkYuAcR11RVqkSSsE,23675
13
- canvaslms/cli/content.nw,sha256=cFl2raYWrXJXXcjZ2cuzWIHdezUMlBypJhq95ZGEENg,36404
14
- canvaslms/cli/content.py,sha256=BXpQxhTfsNBgdRd3bz4E997gsBtPmk8_ILJqZnwEnHA,17149
13
+ canvaslms/cli/content.nw,sha256=hg_KBHAPXTGyZFMV2Y96oE3VUHaVxsY2hvJBQDDHvOA,36995
14
+ canvaslms/cli/content.py,sha256=OQiyhtwNenOZ1V0665f7p9VE9P3Zz3AX6vIzz0VqpKo,17362
15
15
  canvaslms/cli/courses.nw,sha256=W8hNE8wQYPoSRDbqdU_qFvmOFWF6mRap0NLaz-edHH0,14569
16
16
  canvaslms/cli/courses.py,sha256=XKeV43xNT1or_qv40_IATJgey2zbVEg8qHqnz-Z7qUo,3743
17
- canvaslms/cli/discussions.nw,sha256=IPavr0m43OeCsUvZVqnpSEDb9UZ_oh6IDpsAXW0wMmY,46633
18
- canvaslms/cli/discussions.py,sha256=R6GbFbHYXCa0vyDf4eegGp74Pj6WGC38MEb6kY0PkWY,40388
17
+ canvaslms/cli/discussions.nw,sha256=woTvzURp10kimseuYTZs2RTFpCKnpxCsCnDTFK_4jNI,46687
18
+ canvaslms/cli/discussions.py,sha256=fNhJlfzWLzH7Z_fP95w6Scp_ZS7R5Io7zRKQtKa68YU,40464
19
19
  canvaslms/cli/grade.nw,sha256=ms7sBiGRPbK0CJLKxxYx_CDY9LMWgKxUuf2M3FYepMs,2197
20
20
  canvaslms/cli/grade.py,sha256=b7YkFIz64oXzcV2FcptpYJphevuCU3cdx9CilZHcG_A,662
21
21
  canvaslms/cli/login.nw,sha256=93LyHO_LXL1WdEvMg3OLhWulgkdoO8pfjYZVLwUbX4I,4419
22
22
  canvaslms/cli/login.py,sha256=wbA5Q9fTsW1J-vraRcdq2kG4h_LFtvH_MTEay6h8GcE,2737
23
23
  canvaslms/cli/modules.nw,sha256=rvrZe4AU_ok955GZ_ZwwFNFIHIKzft1LR9zTVLW_gic,24122
24
24
  canvaslms/cli/modules.py,sha256=8fZpW_x7eUMUgcn8JrvbGuwtrmHsbVdNvqfcRgfF4x8,12953
25
- canvaslms/cli/pages.nw,sha256=njm6oQT22ryI8Z7O62d-qivQjbMX_je5OwhgHEvtVUg,28934
26
- canvaslms/cli/pages.py,sha256=lW8DpMeyQQoVcu7ztSj_PdW-oUJC0HSh5mDeo8BuaRc,27713
27
- canvaslms/cli/quizzes.nw,sha256=xgivQGColOzayvMn3OcxJf0V7E2K2lzindzPeBqymBw,262940
28
- canvaslms/cli/quizzes.py,sha256=1gX16Mo49rO4j98xy_j7xSuIs0E9DcOJigB90imBNR4,198225
29
- canvaslms/cli/results.nw,sha256=T-ry1k_cHCH_nJfvPl4d9UBbIl_SxvXjBMxyYfXgyaw,22503
30
- canvaslms/cli/results.py,sha256=8ODAhC4r1ndyTHnWSSwFEeV4ab_snxTi1nkrsqqwRLg,15033
25
+ canvaslms/cli/pages.nw,sha256=ZCv1NAwwXRzSy27ePJ3CI1ET1BBZJsM2scnoZxCW4n4,28959
26
+ canvaslms/cli/pages.py,sha256=7E6gjEfs3L6A9_c84gRHvx7auDDV7MQWUXDwQ2-dkz4,27750
27
+ canvaslms/cli/quizzes.nw,sha256=c1zco19uSt9ULIyYWqoCYgNvK4PeSfC6otUpA_b7Flg,262973
28
+ canvaslms/cli/quizzes.py,sha256=jLb1vVU7WClqaDVfXk9akgfii4X_Z5IO1FV6hEhdAAs,198262
29
+ canvaslms/cli/results.nw,sha256=SQIiCwcQUO-irUD6-Ya-4QBVHo19uv5QHH7GXNvdrDg,26862
30
+ canvaslms/cli/results.py,sha256=2KGcqGzmrfqc2Jj5ddQcrm33qF_DYHoljTol4qI9pSg,16845
31
31
  canvaslms/cli/results.py.broken,sha256=njHu8mKfPHqH4daxy-4LMpO6FdUBLPHiVKFFmyH8aJQ,13047
32
32
  canvaslms/cli/submissions.nw,sha256=RCpN7p2Oe9sbMeIPv16CeUGvuesyT9Z5f2PdIdKxHy8,97760
33
33
  canvaslms/cli/submissions.py,sha256=mvXiknSAxmdvoHg6V6k8IqTNdRx2FZqBskniIDRcpw8,49196
@@ -36,7 +36,7 @@ canvaslms/cli/users.nw,sha256=WOutY4VTeyDBqOIbHk6VY_nxgJPk1eKU9CvH6nqjsE4,35236
36
36
  canvaslms/cli/users.py,sha256=YBmL7tUStFd_JPNHTIN1H_n-DmLgYv54JdQrcIRsUDk,16979
37
37
  canvaslms/cli/utils.nw,sha256=E6mIJFZc_mCWIvfCHgOPqFWbOgLKcATEJlJiGZMyuMk,16270
38
38
  canvaslms/cli/utils.py,sha256=RO3-TZzgxSC662vB34WOt4V9S1bHv6N2B9eVo3sFavY,4138
39
- canvaslms/grades/Makefile,sha256=a9IDqfVcT7JImJ4a9o3T16NHdmMQNPqwUan1eoaeWiU,811
39
+ canvaslms/grades/Makefile,sha256=NQoyTmiaW2Cw8sfM9LNYjiNN0rYxM8UmoLpDbdKHwu4,893
40
40
  canvaslms/grades/__init__.py,sha256=wDg-x-fbSsph2R0vradLXO4oDR1yG8Z-6whW2l5_pX8,1027
41
41
  canvaslms/grades/conjunctavg.nw,sha256=uaHcE7l72o66FUjK8x1KijE5YeMsU74WFS-j-ZCVBq0,4671
42
42
  canvaslms/grades/conjunctavg.py,sha256=ARLEWdtiDre8LDsL0DVKjQ9tfJslZAJept1fbhmKc9A,3006
@@ -44,10 +44,12 @@ canvaslms/grades/conjunctavgsurvey.nw,sha256=O158fPIv4y1_Qx8yB9O4wV9wMoVC5b5rVYG
44
44
  canvaslms/grades/conjunctavgsurvey.py,sha256=G-1i2BeapSsXJ9XfxEK0ShK5UxlqQpBcu9yJH9F1Sys,2730
45
45
  canvaslms/grades/disjunctmax.nw,sha256=tiPMd59OBsZyUdW_KqY-kSqQVL9I7u7tMSSyOQDa8YI,3982
46
46
  canvaslms/grades/disjunctmax.py,sha256=6dMah6yxHpJHKdO72SRh47GLC38UinMl0HVdGSIZePM,2703
47
- canvaslms/grades/grades.nw,sha256=EbO-rkqO12keY7nC_eOTGspTS0fZdOA1an9TNRumUUk,5046
47
+ canvaslms/grades/grades.nw,sha256=ldR-_7l3L5ymHJV82TBnQ1YLn2kwyD2Cw3bW--Q-iiY,5096
48
48
  canvaslms/grades/grades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  canvaslms/grades/maxgradesurvey.nw,sha256=agYMqH20K1DLZ0pB9CJPye0E1g_A-ImdGJJzO03HFcE,3263
50
50
  canvaslms/grades/maxgradesurvey.py,sha256=eEysQNgHuFM9-2vrx4h2ovmK5TsXbRI1ukRx7tsct7Y,2089
51
+ canvaslms/grades/participation.nw,sha256=pqSxNdIrAbLvSXAmxLpgLm263yPObGqbEPibZ0BAHj0,9439
52
+ canvaslms/grades/participation.py,sha256=Pex6-vYfqB33Lizn3Pkn6yZUq0l1LZG40555uXSxCj4,2054
51
53
  canvaslms/grades/tilkryLAB1.nw,sha256=xkieCGinC0sLpwsBPlOCxXjUPUiHITQzV7QTnJo3-io,10186
52
54
  canvaslms/grades/tilkryLAB1.py,sha256=_-p2DqeNZCnomCcXkUSSK4_KffJcaPwpjEBzfXlWsqg,3214
53
55
  canvaslms/hacks/Makefile,sha256=Xxk4Fw6U_QSB8KgBlSL8-vUqYIzWzDOXgN1_LNgmNdU,345
@@ -57,8 +59,8 @@ canvaslms/hacks/attachment_cache.py,sha256=LcOZqaa6jPrEJWUD-JYN5GTc3bxCbv2fr_vqu
57
59
  canvaslms/hacks/canvasapi.nw,sha256=ixmIHn4tgy-ZKtQ1rqWSw97hfY2m0qtGX0de2x89lwA,136470
58
60
  canvaslms/hacks/canvasapi.py,sha256=A-r48x7gO6143_QkuZ8n6EW66i-a2AXXr7X7oeehOAU,31868
59
61
  canvaslms/hacks/test_hacks.py,sha256=JSJNvZqHu1E_s51HsPD7yr1gC-R-xVe-tuMMAKU9Gj8,66709
60
- canvaslms-5.7.dist-info/METADATA,sha256=39VhZz1uFN7Bm1W7GU2Yyxt6RI_wh3u5dd1t3kweD_w,6078
61
- canvaslms-5.7.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
62
- canvaslms-5.7.dist-info/entry_points.txt,sha256=lyblfkLbodN5yb7q1c6-rwIoJPV-ygXrB9PYb5boHXM,48
63
- canvaslms-5.7.dist-info/licenses/LICENSE,sha256=N_TKsbzzD5Ax5fWJqEQk9bkwtf394MJkNeFld4HV6-E,1074
64
- canvaslms-5.7.dist-info/RECORD,,
62
+ canvaslms-5.9.dist-info/METADATA,sha256=KADA9jxYVaKQdgBMTccpmz794RWGkw7l296vT6UvxFg,6078
63
+ canvaslms-5.9.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
64
+ canvaslms-5.9.dist-info/entry_points.txt,sha256=lyblfkLbodN5yb7q1c6-rwIoJPV-ygXrB9PYb5boHXM,48
65
+ canvaslms-5.9.dist-info/licenses/LICENSE,sha256=N_TKsbzzD5Ax5fWJqEQk9bkwtf394MJkNeFld4HV6-E,1074
66
+ canvaslms-5.9.dist-info/RECORD,,