From e3ce2e31e6808ae862e9c17487a959bfe6fdb454 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 14 Jan 2022 20:25:22 +0100 Subject: [PATCH 001/248] Adds ungraded filter to submissions --- src/canvaslms/cli/assignments.nw | 1 + src/canvaslms/cli/submissions.nw | 39 +++++++++++++++----------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index ed97f78..38fd196 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -12,6 +12,7 @@ The [[assignment]] command lists information about a given assignment. We outline the module: <>= +import argparse import canvaslms.cli.courses as courses import canvaslms.hacks.canvasapi import csv diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 6e2d448..2f85da1 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -44,8 +44,7 @@ submissions_parser = subp.add_parser("submissions", " ") submissions_parser.set_defaults(func=submissions_command) assignments.add_assignment_option(submissions_parser) -<> -<> +add_submission_options(submissions_parser) @ Now, that [[submissions_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. It must also do the processing for the assignment options using @@ -56,24 +55,6 @@ def submissions_command(config, canvas, args): <> @ -\subsection{Limit by group} - -By default, we show all submission in the course. -However, sometimes we're only interested in a group of students. -<>= -users.add_user_or_group_option(submissions_parser) -@ - -\subsection{Selecting the type of submissions} - -We also want to be able to filter out the type of submissions. -For now, we focus on all or ungraded. -<>= -submissions_parser.add_argument("-U", "--ungraded", action="store_true", - help="Show only ungraded submissions.") -@ We don't need to do any particular processing for this option. - - \subsection{Get and print the list of submissions} We then simply call the appropriate list-submissions function with the @@ -164,6 +145,12 @@ for submission in submission_list: We now provide a function to set up the command-line options to select a particular submission along with a function to process those options. +For this we need +\begin{itemize} +\item an assignment, +\item a user or group, +\item to know if we aim for all or just ungraded submissions. +\end{itemize} <>= def add_submission_options(parser): try: @@ -176,10 +163,20 @@ def add_submission_options(parser): except argparse.ArgumentError: pass + submissions_parser = parser.add_argument_group("filter submissions") + submissions_parser.add_argument("-U", "--ungraded", action="store_true", + help="Only ungraded submissions.") + def process_submission_options(canvas, args): assignment_list = assignments.process_assignment_option(canvas, args) user_list = users.process_user_or_group_option(canvas, args) - return list(filter_submissions(list_submissions(assignment_list), user_list)) + + if args.ungraded: + submissions = list_ungraded_submissions(assignment_list) + else: + submissions = list_submissions(assignment_list) + + return list(filter_submissions(submissions, user_list)) @ From 365acf88ef38267e6b19f225771e02689989d6dd Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 15:50:28 +0100 Subject: [PATCH 002/248] Improves depends in docs Makefile --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index b3b802a..db1c3dc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -22,7 +22,7 @@ canvaslms.pdf: ${SRC_DIR}/cli/assignments.tex canvaslms.pdf: ${SRC_DIR}/cli/submissions.tex canvaslms.pdf: ${SRC_DIR}/cli/grade.tex -${SRC_DIR}/%.tex: +${SRC_DIR}/%.tex: ${SRC_DIR}/%.nw ${MAKE} -C $(dir $@) $(notdir $@) From 96588d1eebc5c0bfd9e05486d29b539bbdd86eb0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 17:31:40 +0100 Subject: [PATCH 003/248] Adds assignment group filtering (fixes #59) --- src/canvaslms/cli/assignments.nw | 208 ++++++++++++++++--------------- 1 file changed, 108 insertions(+), 100 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 38fd196..8560df8 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -38,6 +38,67 @@ def add_assignment_command(subp): @ +\section{Selecting assignments on the command line} + +We now provide two functions that sets up the options for selecting an +assignment on the command line. +These are used by the [[assignment_command]] and [[assignments_command]] +functions, but can also be used by other commands. + +To select an assignment, we must first select a course. +<>= +def add_assignment_option(parser): + try: + courses.add_course_option(parser) + except argparse.ArgumentError: + pass + + <> + +def process_assignment_option(canvas, args): + course_list = courses.process_course_option(canvas, args) + <> + return list(assignments_list) +@ + +We want to select assignments in two alternative ways: +\begin{itemize} +\item [[-u]] will filter assignments that have ungraded submissions. +\item [[-a]] will take a regex for assignments. +\item [[-A]] will take a regex for assignment groups. + Then we can return all assignments in those assignment groups. +\end{itemize} +These regular expressions match either the name or the Canvas identifier. +<>= +parser.add_argument("-u", "--ungraded", action="store_true", + help="Filter only assignments with ungraded submissions.") +parser.add_argument("-a", "--assignment", + required=False, default=".*", + help="Regex matching assignment title or Canvas identifier, " + "default: '.*'") +parser.add_argument("-A", "--assignment-group", + required=False, default=".*", + help="Regex matching assignment group title or Canvas identifier, " + "default: '.*'") +@ + +Now we iterate over the [[course_list]] to get to the assignment groups to then +filter out the assignments. +We must get all assignments for a course and all assignment groups. +Then we can filter out the matching assignments. +<>= +assignments_list = [] + +for course in course_list: + all_assignments = list(list_assignments([course], ungraded=args.ungraded)) + assignment_groups = filter_assignment_groups(course, args.assignment_group) + + for assignment_group in assignment_groups: + assignments_list += list(filter_assignments_by_group( + assignment_group, all_assignments)) +@ + + \section{The [[assignments]] subcommand and its options} We add the subparser for [[assignments]]. @@ -45,63 +106,28 @@ We add the subparser for [[assignments]]. assignments_parser = subp.add_parser("assignments", help="Lists assignments of a course", description="Lists assignments of a course. " - "Output, CSV-format: ") + "Output, CSV-format: ") assignments_parser.set_defaults(func=assignments_command) -courses.add_course_option(assignments_parser) -<> +add_assignment_option(assignments_parser) <> @ Now, that [[assignments_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. -We use [[process_course_option]] to parse the options that we added with the -[[add_course_option]] function above. +We use [[process_assignment_option]] to parse the options that we added with +the [[add_assignment_option]] function above. <>= def assignments_command(config, canvas, args): output = csv.writer(sys.stdout, delimiter=args.delimiter) - course_list = courses.process_course_option(canvas, args) - + assignment_list = process_assignment_option(canvas, args) <> @ -\subsection{Regex for filtering assignment groups} - -We want to be able to list only assignments in particular assignment groups. -<>= -assignments_parser.add_argument("regex", - default=".*", nargs="?", - help="Regex for filtering assignment groups, default: '.*'") -@ - -\subsection{Selecting the type of assignments} - -We also want to be able to filter out the type of assignments. -For now, we focus on all or ungraded. -<>= -assignments_parser.add_argument("-u", "--ungraded", action="store_true", - help="Show only ungraded assignments.") -@ We don't need to do any particular processing for this option. - - -\subsection{Filter and output the list of assignments} - -We then simply call the list-assignments function with the [[courses_list]] -object as a parameter. -Then we will print the most useful attributes (identifiers) of an assignment in -CSV format. +We then simply get the filtered list from the processing of the assignment +options, stored in [[assignment_list]] above. +Then we will print the most useful attributes (mentioned in the help text +above) of an assignment in CSV format. <>= -for course in course_list: - if args.ungraded: - all_assignments = list(list_ungraded_assignments([course])) - else: - all_assignments = list(list_assignments([course])) - - assignment_groups = filter_assignment_groups(course, args.regex) - - for assignment_group in assignment_groups: - assignments = filter_assignments_by_group( - assignment_group, all_assignments) - - for assignment in assignments: - output.writerow([assignment_group.name, assignment.name]) +for assignment in assignment_list: + output.writerow([assignment.assignment_group.name, assignment.name]) @ @@ -122,9 +148,10 @@ assignment group. <>= def filter_assignments_by_group(assignment_group, assignments): """Returns elements in assignments that are part of assignment_group""" - return filter( - lambda assignment: assignment.assignment_group_id == assignment_group.id, - assignments) + for assignment in assignments: + if assignment.assignment_group_id == assignment_group.id: + assignment.assignment_group = assignment_group + yield assignment @ @@ -152,45 +179,6 @@ def assignment_command(config, canvas, args): @ -\section{Selecting assignments on the command line} - -We now provide two functions that sets up the options for selecting an -assignment on the command line. -These are used above by the [[assignment_command]], but can also be used by -other commands. - -To select an assignment, we must first select a course. -<>= -def add_assignment_option(parser): - try: - courses.add_course_option(parser) - except argparse.ArgumentError: - pass - - <> - -def process_assignment_option(canvas, args): - course_list = courses.process_course_option(canvas, args) - <> - return list(assignments_list) -@ - -We add one option [[-a]] to select an assignment. -This is a regular expression which matches the assignment name and identifier. -<>= -parser.add_argument("-a", "--assignment", - required=False, default=".*", - help="Regex matching assignment title or Canvas identifier, " - "default: '.*'") -@ - -Now we can use the [[course_list]] and [[filter_assignments]] to filter out the -desired assignments. -<>= -assignments_list = filter_assignments(course_list, args.assignment) -@ - - \section{Formatting assignments} Sometimes we want to format the contents of an assignment in the terminal. @@ -224,25 +212,45 @@ We provide the following functions: We return the assignments for a list of courses, since we can match several courses with a regular expression (using [[filter_courses]]). <>= -def list_assignments(courses): - for course in courses: - for assignment in course.get_assignments(): - assignment.course = course - yield assignment +def list_assignments(assignments_containers, ungraded=False): + """Lists all assignments in all assignments containers (courses or + assignement groups)""" + for container in assignments_containers: + if isinstance(container, canvasapi.course.Course): + course = container + elif isinstance(container, canvasapi.assignment.AssignmentGroup): + assignment_group = container + course = assignment_group.course + + if ungraded: + assignments = container.get_assignments(bucket="ungraded") + else: + assignments = container.get_assignments() + + for assignment in assignments: + try: + assignment.course = course + except NameError: + pass + + try: + assignment.assignment_group = assignment_group + except NameError: + pass -def list_ungraded_assignments(courses): - for course in courses: - for assignment in course.get_assignments(bucket="ungraded"): - assignment.course = course yield assignment + +def list_ungraded_assignments(assignments_containers): + return list_assignments(assignments_containers, ungraded=True) @ We also want to filter out assignments on the title based on regex. <>= -def filter_assignments(courses, regex): - """Returns all assignments from courses whose title matches regex""" +def filter_assignments(assaignments_containers, regex): + """Returns all assignments from assignments_container whose + title matches regex""" p = re.compile(regex) - for assignment in list_assignments(courses): + for assignment in list_assignments(assignments_containers): if p.search(assignment.name): yield assignment elif p.search(str(assignment.id)): From a69e6aacd62eb893e77f977525ded2a4bd3a9024 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 19:23:19 +0100 Subject: [PATCH 004/248] Adds missing dependencies --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 96cbeb4..7fa8ef9 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,9 @@ install_requires = [ "appdirs>=1.4.4", "argcomplete>=1.12.3", + "cachetools>=4.2.2", "canvasapi>=2.0.0", + "keyring>=23.0.1", "pypandoc>=1.6.4" ] ) From 59811516ae0a0f3f843ae4b222f2dd2ba2f173f3 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 19:48:47 +0100 Subject: [PATCH 005/248] Improves code for assignment group options --- src/canvaslms/cli/assignments.nw | 6 +++--- src/canvaslms/cli/submissions.nw | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 8560df8..5eb66a2 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -13,6 +13,7 @@ The [[assignment]] command lists information about a given assignment. We outline the module: <>= import argparse +import canvasapi import canvaslms.cli.courses as courses import canvaslms.hacks.canvasapi import csv @@ -63,14 +64,14 @@ def process_assignment_option(canvas, args): We want to select assignments in two alternative ways: \begin{itemize} -\item [[-u]] will filter assignments that have ungraded submissions. +\item [[-U]] will filter assignments that have ungraded submissions. \item [[-a]] will take a regex for assignments. \item [[-A]] will take a regex for assignment groups. Then we can return all assignments in those assignment groups. \end{itemize} These regular expressions match either the name or the Canvas identifier. <>= -parser.add_argument("-u", "--ungraded", action="store_true", +parser.add_argument("-U", "--ungraded", action="store_true", help="Filter only assignments with ungraded submissions.") parser.add_argument("-a", "--assignment", required=False, default=".*", @@ -109,7 +110,6 @@ assignments_parser = subp.add_parser("assignments", "Output, CSV-format: ") assignments_parser.set_defaults(func=assignments_command) add_assignment_option(assignments_parser) -<> @ Now, that [[assignments_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. We use [[process_assignment_option]] to parse the options that we added with diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 2f85da1..3756c8d 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -164,8 +164,11 @@ def add_submission_options(parser): pass submissions_parser = parser.add_argument_group("filter submissions") - submissions_parser.add_argument("-U", "--ungraded", action="store_true", - help="Only ungraded submissions.") + try: # to protect from this option already existing in add_assignment_option + submissions_parser.add_argument("-U", "--ungraded", action="store_true", + help="Only ungraded submissions.") + except argparse.ArgumentError: + pass def process_submission_options(canvas, args): assignment_list = assignments.process_assignment_option(canvas, args) From 17ac7420f0775f5e4aaa1d93b83858eee48c372f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 19:54:23 +0100 Subject: [PATCH 006/248] Adds missing hacks to recursive build --- src/canvaslms/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvaslms/Makefile b/src/canvaslms/Makefile index 16d1ccb..8a846b5 100644 --- a/src/canvaslms/Makefile +++ b/src/canvaslms/Makefile @@ -1,4 +1,5 @@ SUBDIR+= cli +SUBDIR+= hacks INCLUDE_MAKEFILES=../../makefiles include ${INCLUDE_MAKEFILES}/subdir.mk From 7965029639d6df7448b111cd0b2817d824c5f264 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 20:37:48 +0100 Subject: [PATCH 007/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7fa8ef9..e7f64b5 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.6", + version = "1.7", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 2132f47a382b058abfcd785d18e8f29af0e433e1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 7 Mar 2022 20:39:49 +0100 Subject: [PATCH 008/248] Adds start of results command code --- doc/Makefile | 1 + doc/canvaslms.tex | 1 + src/canvaslms/cli/.gitignore | 2 ++ src/canvaslms/cli/Makefile | 2 ++ src/canvaslms/cli/cli.nw | 10 ++++++ src/canvaslms/cli/results.nw | 68 ++++++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+) create mode 100644 src/canvaslms/cli/results.nw diff --git a/doc/Makefile b/doc/Makefile index db1c3dc..eb420a1 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -21,6 +21,7 @@ canvaslms.pdf: ${SRC_DIR}/cli/users.tex canvaslms.pdf: ${SRC_DIR}/cli/assignments.tex canvaslms.pdf: ${SRC_DIR}/cli/submissions.tex canvaslms.pdf: ${SRC_DIR}/cli/grade.tex +canvaslms.pdf: ${SRC_DIR}/cli/results.tex ${SRC_DIR}/%.tex: ${SRC_DIR}/%.nw ${MAKE} -C $(dir $@) $(notdir $@) diff --git a/doc/canvaslms.tex b/doc/canvaslms.tex index dedd723..82100af 100644 --- a/doc/canvaslms.tex +++ b/doc/canvaslms.tex @@ -104,6 +104,7 @@ \part{The command-line interface} \input{../src/canvaslms/cli/assignments.tex} \input{../src/canvaslms/cli/submissions.tex} \input{../src/canvaslms/cli/grade.tex} +\input{../src/canvaslms/cli/results.tex} \printbibliography diff --git a/src/canvaslms/cli/.gitignore b/src/canvaslms/cli/.gitignore index 536693f..45f1e61 100644 --- a/src/canvaslms/cli/.gitignore +++ b/src/canvaslms/cli/.gitignore @@ -18,3 +18,5 @@ grade.tex grade.py login.py login.tex +results.py +results.tex diff --git a/src/canvaslms/cli/Makefile b/src/canvaslms/cli/Makefile index 8af4f7a..75e9a59 100644 --- a/src/canvaslms/cli/Makefile +++ b/src/canvaslms/cli/Makefile @@ -10,6 +10,7 @@ all: users.py users.tex all: assignments.py assignments.tex all: submissions.py submissions.tex all: grade.py grade.tex +all: results.py results.tex __init__.py: cli.py mv $< $@ @@ -24,6 +25,7 @@ clean: ${RM} assignments.tex assignments.py ${RM} submissions.tex submissions.py ${RM} grade.tex grade.py + ${RM} results.py results.tex INCLUDE_MAKEFILES=../../../makefiles diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 5beb438..e177aeb 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -258,3 +258,13 @@ import canvaslms.cli.grade <>= canvaslms.cli.grade.add_command(subp) @ + +\paragraph{The \texttt{results} command} + +The \texttt{results} command is located in [[canvaslms.cli.results]]. +<>= +import canvaslms.cli.results +@ +<>= +canvaslms.cli.results.add_command(subp) +@ diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw new file mode 100644 index 0000000..dfd15d1 --- /dev/null +++ b/src/canvaslms/cli/results.nw @@ -0,0 +1,68 @@ +\chapter{The \texttt{results} command} + +This chapter provides the subcommand [[results]], which lists the results of a +course. +The purpose of the listing is to export results from Canvas. +The format of the listing is compatible with the \texttt{ladok3} +package\footnote{% + URL: \url{https://github.com/dbosk/ladok3} +}. + +\section{The [[results]] subcommand and its options} + +We outline the module: +<>= +import canvaslms.cli.assignments as assignments +import canvaslms.cli.courses as courses +import canvaslms.hacks.canvasapi + +import argparse +import csv +import re +import sys + +<> + +def add_command(subp): + """Adds the results command to argparse parser subp""" + <> +@ + +We add the subparser for [[results]]. +The command requires two arguments: course and assignment. +<>= +results_parser = subp.add_parser("results", + help="Lists results of a course", + description="Lists results of a course for export. Output format: " + " ") +results_parser.set_defaults(func=results_command) +assignments.add_assignment_option(results_parser) +@ Now, that [[results_command]] function must take three arguments: [[config]], +[[canvas]] and [[args]]. +However, unlike the other commands, we don't want to do the processing for the +assignment options using [[process_assignment_option]]. +We want to handle that ourselves, because we want slightly different handling. +<>= +def results_command(config, canvas, args): + <> +@ + +\section{Get and print the list of results} + +Now we'd simply like to print the results. +The user provides us with a set of courses and a set of assignments or +assignment groups in those courses. +If the user provides assignment groups, we will automatically summarize the +results of all assignments in the assignment group. +<>= +output = csv.writer(sys.stdout, delimiter=args.delimiter) + +if args.assignment_group != ".*": + <> +else: + <> + +for result in results: + output.writerow(result) +@ + From 6e048cab841297bd43231e0f25db79d71223bde0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 09:09:06 +0100 Subject: [PATCH 009/248] Improves main Makefile for packaging --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a4f3bcd..b1ee26d 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ publish-canvaslms: ${dist} python3 -m twine upload -r pypi ${dist} ${dist}: compile canvaslms.bash - python3 -m build + python3 setup.py sdist bdist_wheel canvaslms.bash: register-python-argcomplete canvaslms > $@ From d56633569c78b737350d02989e2bf77f52de8732 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 09:09:58 +0100 Subject: [PATCH 010/248] Adds option for adding -U option or not --- src/canvaslms/cli/assignments.nw | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 5eb66a2..ca79fe1 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -46,9 +46,12 @@ assignment on the command line. These are used by the [[assignment_command]] and [[assignments_command]] functions, but can also be used by other commands. -To select an assignment, we must first select a course. +When we select assignments, we have the option to only select those assignments +with ungraded submissions. +However, this option is not always relevant, so we provide a way to disable it. +But to select an assignment, we must first select a course. <>= -def add_assignment_option(parser): +def add_assignment_option(parser, ungraded=True): try: courses.add_course_option(parser) except argparse.ArgumentError: @@ -62,7 +65,11 @@ def process_assignment_option(canvas, args): return list(assignments_list) @ -We want to select assignments in two alternative ways: +As mentioned above, we want to be able to select only assignments with ungraded +submissions. +Other than that, we want to select assignments in two alternative ways: by +assignment name or assignment group that an assignment belongs to. +In summary, we want the following options: \begin{itemize} \item [[-U]] will filter assignments that have ungraded submissions. \item [[-a]] will take a regex for assignments. @@ -70,13 +77,18 @@ We want to select assignments in two alternative ways: Then we can return all assignments in those assignment groups. \end{itemize} These regular expressions match either the name or the Canvas identifier. +This lets us add the following arguments. +Remember, we add only the ungraded option if that was requested. <>= -parser.add_argument("-U", "--ungraded", action="store_true", - help="Filter only assignments with ungraded submissions.") +if ungraded: + parser.add_argument("-U", "--ungraded", action="store_true", + help="Filter only assignments with ungraded submissions.") + parser.add_argument("-a", "--assignment", required=False, default=".*", help="Regex matching assignment title or Canvas identifier, " "default: '.*'") + parser.add_argument("-A", "--assignment-group", required=False, default=".*", help="Regex matching assignment group title or Canvas identifier, " From d97ce116ef399ade33bbf9018104462afda86e54 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 10:05:36 +0100 Subject: [PATCH 011/248] Bugfixes for assignments filtering --- src/canvaslms/cli/assignments.nw | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index ca79fe1..2bb163e 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -90,9 +90,8 @@ parser.add_argument("-a", "--assignment", "default: '.*'") parser.add_argument("-A", "--assignment-group", - required=False, default=".*", - help="Regex matching assignment group title or Canvas identifier, " - "default: '.*'") + required=False, default="", + help="Regex matching assignment group title or Canvas identifier.") @ Now we iterate over the [[course_list]] to get to the assignment groups to then @@ -103,8 +102,22 @@ Then we can filter out the matching assignments. assignments_list = [] for course in course_list: - all_assignments = list(list_assignments([course], ungraded=args.ungraded)) - assignment_groups = filter_assignment_groups(course, args.assignment_group) + try: + ungraded = args.ungraded + except AttributeError: + ungraded = False + + all_assignments = list(filter_assignments([course], + args.assignment, + ungraded=ungraded)) + + try: + assignm_grp_regex = args.assignment_group + except AttributeError: + print("default to .* for group") + assignm_grp_regex = ".*" + + assignment_groups = filter_assignment_groups(course, assignm_grp_regex) for assignment_group in assignment_groups: assignments_list += list(filter_assignments_by_group( @@ -257,12 +270,14 @@ def list_ungraded_assignments(assignments_containers): @ We also want to filter out assignments on the title based on regex. +We also take an optional default argument to indicate whether we only want +ungraded assignments. <>= -def filter_assignments(assaignments_containers, regex): +def filter_assignments(assignments_containers, regex, ungraded=False): """Returns all assignments from assignments_container whose title matches regex""" p = re.compile(regex) - for assignment in list_assignments(assignments_containers): + for assignment in list_assignments(assignments_containers, ungraded=ungraded): if p.search(assignment.name): yield assignment elif p.search(str(assignment.id)): From bc0d14158b0775c68d9b79b7093f43aa2d05f521 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 10:06:31 +0100 Subject: [PATCH 012/248] Adds more WIP on results command --- src/canvaslms/cli/results.nw | 43 ++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index dfd15d1..a797b65 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -33,10 +33,17 @@ The command requires two arguments: course and assignment. <>= results_parser = subp.add_parser("results", help="Lists results of a course", - description="Lists results of a course for export. Output format: " - " ") + description="Lists results of a course for export, for instance " + "to the `ladok report -f` command. Output format, CSV: " + " .", + epilog="If you specify an assignment group, the results of the " + "assignments in that group will be summarized. That means that " + "all assignments must have a passing grade. If there are assignments " + "with A--F grading scales (in addition to P/F) the avergage of the " + "A--F grades will be used as final grade for the entire group. If any " + "assignment has an F, the whole group will evaluate to an F.") results_parser.set_defaults(func=results_command) -assignments.add_assignment_option(results_parser) +assignments.add_assignment_option(results_parser, ungraded=False) @ Now, that [[results_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. However, unlike the other commands, we don't want to do the processing for the @@ -47,22 +54,44 @@ def results_command(config, canvas, args): <> @ -\section{Get and print the list of results} - Now we'd simply like to print the results. The user provides us with a set of courses and a set of assignments or assignment groups in those courses. If the user provides assignment groups, we will automatically summarize the results of all assignments in the assignment group. + +We will create a list of results, where each result is a tuple. +These tuples will then be printed in CSV format to standard output. <>= output = csv.writer(sys.stdout, delimiter=args.delimiter) -if args.assignment_group != ".*": +assignments_list = assignments.process_assignment_option(canvas, args) + +if args.assignment_group != "": <> else: - <> + <> for result in results: output.writerow(result) @ + +\section{Extracting assignment results} + +In this case, we want to have one assignment per row in the output. +We want to output course, assignment, student ID, grade and submission date. +<>= +pass +@ + + +\section{Summarizing assignment group results} + +In this case, we want to have one assignment group per row in the output. +We want to output course, assignment group, student ID, summarized grade based +on all assignments in the group and the latest submission date. +<>= +pass +@ + From 880506da8a81bc91cc5263cb66dff874e7fc5d61 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 10:05:36 +0100 Subject: [PATCH 013/248] Bugfixes for assignments filtering --- src/canvaslms/cli/assignments.nw | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 5eb66a2..7f10b70 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -78,9 +78,8 @@ parser.add_argument("-a", "--assignment", help="Regex matching assignment title or Canvas identifier, " "default: '.*'") parser.add_argument("-A", "--assignment-group", - required=False, default=".*", - help="Regex matching assignment group title or Canvas identifier, " - "default: '.*'") + required=False, default="", + help="Regex matching assignment group title or Canvas identifier.") @ Now we iterate over the [[course_list]] to get to the assignment groups to then @@ -91,8 +90,22 @@ Then we can filter out the matching assignments. assignments_list = [] for course in course_list: - all_assignments = list(list_assignments([course], ungraded=args.ungraded)) - assignment_groups = filter_assignment_groups(course, args.assignment_group) + try: + ungraded = args.ungraded + except AttributeError: + ungraded = False + + all_assignments = list(filter_assignments([course], + args.assignment, + ungraded=ungraded)) + + try: + assignm_grp_regex = args.assignment_group + except AttributeError: + print("default to .* for group") + assignm_grp_regex = ".*" + + assignment_groups = filter_assignment_groups(course, assignm_grp_regex) for assignment_group in assignment_groups: assignments_list += list(filter_assignments_by_group( @@ -245,12 +258,14 @@ def list_ungraded_assignments(assignments_containers): @ We also want to filter out assignments on the title based on regex. +We also take an optional default argument to indicate whether we only want +ungraded assignments. <>= -def filter_assignments(assaignments_containers, regex): +def filter_assignments(assignments_containers, regex, ungraded=False): """Returns all assignments from assignments_container whose title matches regex""" p = re.compile(regex) - for assignment in list_assignments(assignments_containers): + for assignment in list_assignments(assignments_containers, ungraded=ungraded): if p.search(assignment.name): yield assignment elif p.search(str(assignment.id)): From 7bbcf797ecf3e312686ab48da897e2af88c566bb Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 10:08:05 +0100 Subject: [PATCH 014/248] Bumps version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e7f64b5..fcf5a7d 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.7", + version = "1.8", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 80a0a8b1708648a2f89ed21c7b3b4f109fe2036f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 09:09:06 +0100 Subject: [PATCH 015/248] Improves main Makefile for packaging --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a4f3bcd..b1ee26d 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ publish-canvaslms: ${dist} python3 -m twine upload -r pypi ${dist} ${dist}: compile canvaslms.bash - python3 -m build + python3 setup.py sdist bdist_wheel canvaslms.bash: register-python-argcomplete canvaslms > $@ From 48963329951a3fe335f048c53607eea873de3ce7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 10:14:01 +0100 Subject: [PATCH 016/248] Improves login help --- src/canvaslms/cli/login.nw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/login.nw b/src/canvaslms/cli/login.nw index dc23f7e..b6f4e3d 100644 --- a/src/canvaslms/cli/login.nw +++ b/src/canvaslms/cli/login.nw @@ -42,8 +42,8 @@ login credentials, in order of priority: } } - to the file """ + dirs.user_config_dir + """/config.json (default, or use - the -f option). + to the file """ + dirs.user_config_dir + """/config.json (default, or use the + -f option, see `canvaslms -h`). """) login_parser.set_defaults(func=update_credentials_in_keyring) @ From 1e1d76772eb5324ba29b5edab3477662d6922718 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 8 Mar 2022 14:09:03 +0100 Subject: [PATCH 017/248] Adds results export for assignments (not groups) --- src/canvaslms/cli/results.nw | 53 ++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index a797b65..4434334 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -14,6 +14,7 @@ We outline the module: <>= import canvaslms.cli.assignments as assignments import canvaslms.cli.courses as courses +import canvaslms.cli.submissions as submissions import canvaslms.hacks.canvasapi import argparse @@ -68,9 +69,9 @@ output = csv.writer(sys.stdout, delimiter=args.delimiter) assignments_list = assignments.process_assignment_option(canvas, args) if args.assignment_group != "": - <> + results = summarize_assignment_groups(canvas, args) else: - <> + results = summarize_assignments(canvas, args) for result in results: output.writerow(result) @@ -81,8 +82,44 @@ for result in results: In this case, we want to have one assignment per row in the output. We want to output course, assignment, student ID, grade and submission date. -<>= -pass + +We first get the list of courses. +We do this to then get the list of all users in all courses. +We need these to get the integration ID. + +Then we get the list of assignments in all courses. +We get the submissions for each assignment. +These submissions are filtered by user. +We do this because this attaches a [[user]] attribute to each submissions with +the details of each user. +This gives a trivial [[yield]] statement at the end. +<>= +def summarize_assignments(canvas, args): + """Turn submissions into results, + canvas is a Canvas object, + args is the command-line arguments""" + + courses_list = courses.process_course_option(canvas, args) + + users_list = [] + for course in courses_list: + for user in course.get_users(enrollment_type=["student"]): + users_list.append(user) + + assignments_list = assignments.process_assignment_option(canvas, args) + submissions_list = submissions.filter_submissions( + submissions.list_submissions(assignments_list, include=[]), + users_list) + + for submission in submissions_list: + if submission.grade is not None: + yield ( + submission.assignment.course.course_code, + submission.assignment.name, + submission.user.integration_id, + submission.grade, + submission.submitted_at or submission.graded_at + ) @ @@ -91,7 +128,11 @@ pass In this case, we want to have one assignment group per row in the output. We want to output course, assignment group, student ID, summarized grade based on all assignments in the group and the latest submission date. -<>= -pass +<>= +def summarize_assignment_groups(args): + """Summarize assignment groups into a single grade, + canvas is a Canvas object, + args is the command-line arguments""" + pass @ From 0e43d951b700493d9ccdbf178d3319f1fbfe764f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 9 Mar 2022 14:11:19 +0100 Subject: [PATCH 018/248] Finalizes results output (fixes #48, fixes #41) --- src/canvaslms/cli/Makefile | 4 + src/canvaslms/cli/results.nw | 181 ++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 5 deletions(-) diff --git a/src/canvaslms/cli/Makefile b/src/canvaslms/cli/Makefile index 75e9a59..1dae1de 100644 --- a/src/canvaslms/cli/Makefile +++ b/src/canvaslms/cli/Makefile @@ -11,6 +11,10 @@ all: assignments.py assignments.tex all: submissions.py submissions.tex all: grade.py grade.tex all: results.py results.tex +all: summary.py + +summary.py: results.nw + ${NOTANGLE.py} __init__.py: cli.py mv $< $@ diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 4434334..fdb9a4f 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -19,6 +19,8 @@ import canvaslms.hacks.canvasapi import argparse import csv +import datetime as dt +import importlib import re import sys @@ -31,6 +33,8 @@ def add_command(subp): We add the subparser for [[results]]. The command requires two arguments: course and assignment. +We can add these by using [[add_assignment_option]], however, we don't need the +ungraded flag as we want to export results (\ie graded material). <>= results_parser = subp.add_parser("results", help="Lists results of a course", @@ -38,14 +42,20 @@ results_parser = subp.add_parser("results", "to the `ladok report -f` command. Output format, CSV: " " .", epilog="If you specify an assignment group, the results of the " - "assignments in that group will be summarized. That means that " - "all assignments must have a passing grade. If there are assignments " + "assignments in that group will be summarized. You can supply your " + "own function for summarizing grades through the -S option. The " + "default works as follows: " + "All assignments must have a passing grade. If there are assignments " "with A--F grading scales (in addition to P/F) the avergage of the " "A--F grades will be used as final grade for the entire group. If any " "assignment has an F, the whole group will evaluate to an F.") results_parser.set_defaults(func=results_command) assignments.add_assignment_option(results_parser, ungraded=False) -@ Now, that [[results_command]] function must take three arguments: [[config]], +<> +<> +@ We will cover the option for and loading of the custom summary module later, +in \cref{custom-summary-module}. +Now, that [[results_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. However, unlike the other commands, we don't want to do the processing for the assignment options using [[process_assignment_option]]. @@ -61,6 +71,14 @@ assignment groups in those courses. If the user provides assignment groups, we will automatically summarize the results of all assignments in the assignment group. +We also let the user choose to not include Fs in the output. +<>= +results_parser.add_argument("-F", "--include-Fs", + required=False, default=False, action="store_true", + help="Include failing grades (Fs) in output. By default we only output " + "A--Es and Ps.") +@ + We will create a list of results, where each result is a tuple. These tuples will then be printed in CSV format to standard output. <>= @@ -74,6 +92,8 @@ else: results = summarize_assignments(canvas, args) for result in results: + if not args.include_Fs and result[3][0] == "F": + continue output.writerow(result) @ @@ -128,11 +148,162 @@ def summarize_assignments(canvas, args): In this case, we want to have one assignment group per row in the output. We want to output course, assignment group, student ID, summarized grade based on all assignments in the group and the latest submission date. + +Unlike the previous case, here we must maintain the structure of which +assignments belong to which assignment group so that we can check easily that a +user has passed all assignments in the group. <>= -def summarize_assignment_groups(args): +def summarize_assignment_groups(canvas, args): """Summarize assignment groups into a single grade, canvas is a Canvas object, args is the command-line arguments""" - pass + + <> + + courses_list = courses.process_course_option(canvas, args) + all_assignments = list(assignments.process_assignment_option(canvas, args)) + + for course in courses_list: + users_list = list(course.get_users(enrollment_type=["student"])) + ag_list = assignments.filter_assignment_groups( + course, args.assignment_group) + + for assignment_group in ag_list: + assignments_list = list(assignments.filter_assignments_by_group( + assignment_group, all_assignments)) + for user, grade, grade_date in summary.summarize_group( + assignments_list, users_list): + yield ( + course.course_code, + assignment_group.name, + user.integration_id, + grade, + grade_date + ) +@ We will now cover the [[summarize_group]] function in the [[summary]] module. + + +\subsection{Loading a custom summary module} + +Different teachers have different policies for merging several assignments into +one grade. +We now want to provide a way to override the default function. +<>= +results_parser.add_argument("-S", "--summary-module", + required=False, default="canvaslms.cli.summary", + help="Name of Python module to load with a custom summarization function " + "to summarize assignment groups. The default module is part of the " + "`canvaslms` package: `canvaslms.cli.summary`. This module must contain " + "a function `summarize_group(assignments, users)`, where `assignments` " + "is a list of assignment `canvasapi.assignment.Assignment` objects and " + "`users` is a list of `canvasapi.user.User` objects. The return value " + "must be a tuple " + "`(user object, grade, grade date)`.") +@ + +Now, let's load the module into the identifier [[summary]] for the above code. +This is a very dangerous construction. +An attacker can potentially load their own module and have it execute when +reporting grades. +For instance, a malicious module could change grades, \eg always set +A's. +<>= +summary = importlib.import_module(args.summary_module) +@ + + +\subsection{The default results summarizing module} + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +"""Module with a summary function for summarizing assignment groups""" + +import datetime as dt + +<> + +def summarize_group(assignments_list, users_list): + """Summarizes a particular set of assignments (assignments_list) for all + users in users_list""" + + for user in users_list: + grade, grade_date = summarize(user, assignments_list) + yield (user, grade, grade_date) +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish two things: the most recent date and a suitable grade. + +For the most recent date, we just check the dates as we iterate through the +submissions. + +For the grade, as we iterate through we look for P/F and A--E grades. +We can then check for Fs among the P/F grades, if we find an F the summarized +grade will be an F. +If we find no Fs, then we can compute the average over all A--E grades and use +that as the final grade. +<>= +def summarize(user, assignments_list): + """Extracts user's submissions for assignments in assingments_list to + summarize results into one grade and a grade date""" + + pf_grades = [] + a2e_grades = [] + recent_date = dt.date(year=1970, month=1, day=1) + + for assignment in assignments_list: + submission = assignment.get_submission(user) + grade = submission.grade + + if grade is None: + grade = "F" + + if grade in "ABCDE": + a2e_grades.append(grade) + else: + pf_grades.append(grade) + + grade_date = submission.submitted_at or submission.graded_at + + if not grade_date: + grade_date = recent_date + else: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + + if grade_date > recent_date: + recent_date = grade_date + + if not all(map(lambda x: x == "P", pf_grades)): + return ("F", recent_date) + + if a2e_grades: + return (a2e_average(a2e_grades), recent_date) + return ("P", recent_date) +@ + +To compute the average for the A--E grades; we will convert the grades into +integers, compute the average, round the value to an integer and convert back. +<>= +def a2e_average(grades): + """Takes a list of A--E grades, returns the average.""" + num_grades = map(grade_to_int, grades) + avg_grade = round(sum(num_grades)/len(grades)) + return int_to_grade(avg_grade) + +def grade_to_int(grade): + grade_map = {"E": 1, "D": 2, "C": 3, "B": 4, "A": 5} + return grade_map[grade] + +def int_to_grade(int_grade): + grade_map_inv = {1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} + return grade_map_inv[int_grade] @ From eea5548928a993de151b5f209b873247900e4368 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 9 Mar 2022 14:13:39 +0100 Subject: [PATCH 019/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fcf5a7d..bdd8c9e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.8", + version = "1.9", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From a24ae01acba17819d0b0717b02bbbccffd5e0470 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Mar 2022 14:00:08 +0100 Subject: [PATCH 020/248] Updates what attributes are printed by submissions command --- src/canvaslms/cli/submissions.nw | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 3756c8d..f8d58aa 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -41,7 +41,8 @@ We add the subparser for [[submissions]]. submissions_parser = subp.add_parser("submissions", help="Lists submissions of an assignment", description="Lists submissions of assignment(s). Output format: " - " ") + " " + " ") submissions_parser.set_defaults(func=submissions_command) assignments.add_assignment_option(submissions_parser) add_submission_options(submissions_parser) @@ -245,12 +246,9 @@ We'll format the submission in short format. The most useful data is the identifier, the grade and the date of grading. <>= def format_submission_short(submission): - if submission.submitted_at: - date = submission.submitted_at - else: - date = submission.graded_at return [submission.assignment.course.course_code, submission.assignment.name, - submission.user_id, submission.user.name, submission.grade, date] + submission.user.name, submission.grade, + submission.submitted_at, submission.graded_at] @ We provide the function [[format_submission]] to nicely format a submission. From 5f18b08f9aa26dd7465b65023f6e3a90d74676b7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Mar 2022 14:00:47 +0100 Subject: [PATCH 021/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bdd8c9e..005444a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.9", + version = "1.10", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 9c41da6e78e8cddd76ab162a4969cbdb1f677d9f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Mar 2022 14:22:16 +0100 Subject: [PATCH 022/248] Adds summary.py to gitignore --- src/canvaslms/cli/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvaslms/cli/.gitignore b/src/canvaslms/cli/.gitignore index 45f1e61..167c569 100644 --- a/src/canvaslms/cli/.gitignore +++ b/src/canvaslms/cli/.gitignore @@ -20,3 +20,4 @@ login.py login.tex results.py results.tex +summary.py From e7c59d787583acd7534461de1f279a5e9b20cb77 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 12 Mar 2022 07:09:09 +0100 Subject: [PATCH 023/248] Moves default summary module, improves -S option --- doc/Makefile | 2 + doc/canvaslms.tex | 1 + src/canvaslms/Makefile | 1 + src/canvaslms/cli/.gitignore | 1 - src/canvaslms/cli/Makefile | 4 - src/canvaslms/cli/results.nw | 145 +++++++--------------------- src/canvaslms/grades/.gitignore | 4 + src/canvaslms/grades/Makefile | 19 ++++ src/canvaslms/grades/__init__.py | 19 ++++ src/canvaslms/grades/conjunctavg.nw | 107 ++++++++++++++++++++ src/canvaslms/grades/grades.nw | 45 +++++++++ 11 files changed, 235 insertions(+), 113 deletions(-) create mode 100644 src/canvaslms/grades/.gitignore create mode 100644 src/canvaslms/grades/Makefile create mode 100644 src/canvaslms/grades/__init__.py create mode 100644 src/canvaslms/grades/conjunctavg.nw create mode 100644 src/canvaslms/grades/grades.nw diff --git a/doc/Makefile b/doc/Makefile index eb420a1..83511c4 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -22,6 +22,8 @@ canvaslms.pdf: ${SRC_DIR}/cli/assignments.tex canvaslms.pdf: ${SRC_DIR}/cli/submissions.tex canvaslms.pdf: ${SRC_DIR}/cli/grade.tex canvaslms.pdf: ${SRC_DIR}/cli/results.tex +canvaslms.pdf: ${SRC_DIR}/grades/grades.tex +canvaslms.pdf: ${SRC_DIR}/grades/conjunctavg.tex ${SRC_DIR}/%.tex: ${SRC_DIR}/%.nw ${MAKE} -C $(dir $@) $(notdir $@) diff --git a/doc/canvaslms.tex b/doc/canvaslms.tex index 82100af..36574b6 100644 --- a/doc/canvaslms.tex +++ b/doc/canvaslms.tex @@ -105,6 +105,7 @@ \part{The command-line interface} \input{../src/canvaslms/cli/submissions.tex} \input{../src/canvaslms/cli/grade.tex} \input{../src/canvaslms/cli/results.tex} +\input{../src/canvaslms/grades/grades.tex} \printbibliography diff --git a/src/canvaslms/Makefile b/src/canvaslms/Makefile index 8a846b5..5104bee 100644 --- a/src/canvaslms/Makefile +++ b/src/canvaslms/Makefile @@ -1,4 +1,5 @@ SUBDIR+= cli +SUBDIR+= grades SUBDIR+= hacks INCLUDE_MAKEFILES=../../makefiles diff --git a/src/canvaslms/cli/.gitignore b/src/canvaslms/cli/.gitignore index 167c569..45f1e61 100644 --- a/src/canvaslms/cli/.gitignore +++ b/src/canvaslms/cli/.gitignore @@ -20,4 +20,3 @@ login.py login.tex results.py results.tex -summary.py diff --git a/src/canvaslms/cli/Makefile b/src/canvaslms/cli/Makefile index 1dae1de..75e9a59 100644 --- a/src/canvaslms/cli/Makefile +++ b/src/canvaslms/cli/Makefile @@ -11,10 +11,6 @@ all: assignments.py assignments.tex all: submissions.py submissions.tex all: grade.py grade.tex all: results.py results.tex -all: summary.py - -summary.py: results.nw - ${NOTANGLE.py} __init__.py: cli.py mv $< $@ diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index fdb9a4f..9ec086d 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -1,4 +1,5 @@ \chapter{The \texttt{results} command} +\label{results-command} This chapter provides the subcommand [[results]], which lists the results of a course. @@ -12,6 +13,7 @@ package\footnote{% We outline the module: <>= +import canvaslms.cli import canvaslms.cli.assignments as assignments import canvaslms.cli.courses as courses import canvaslms.cli.submissions as submissions @@ -21,6 +23,10 @@ import argparse import csv import datetime as dt import importlib +import importlib.machinery +import importlib.util +import os +import pathlib import re import sys @@ -43,12 +49,8 @@ results_parser = subp.add_parser("results", " .", epilog="If you specify an assignment group, the results of the " "assignments in that group will be summarized. You can supply your " - "own function for summarizing grades through the -S option. The " - "default works as follows: " - "All assignments must have a passing grade. If there are assignments " - "with A--F grading scales (in addition to P/F) the avergage of the " - "A--F grades will be used as final grade for the entire group. If any " - "assignment has an F, the whole group will evaluate to an F.") + "own function for summarizing grades through the -S option. " + "See `pydoc3 canvaslms.grades` for different options.") results_parser.set_defaults(func=results_command) assignments.add_assignment_option(results_parser, ungraded=False) <> @@ -189,15 +191,18 @@ Different teachers have different policies for merging several assignments into one grade. We now want to provide a way to override the default function. <>= +default_summary_module = "canvaslms.grades.conjunctavg" results_parser.add_argument("-S", "--summary-module", - required=False, default="canvaslms.cli.summary", - help="Name of Python module to load with a custom summarization function " + required=False, default=default_summary_module, + help="Name of Python module or file containing module to " + "load with a custom summarization function " "to summarize assignment groups. The default module is part of the " - "`canvaslms` package: `canvaslms.cli.summary`. This module must contain " - "a function `summarize_group(assignments, users)`, where `assignments` " - "is a list of assignment `canvasapi.assignment.Assignment` objects and " + f"`canvaslms` package: `{default_summary_module}`. " + "This module must contain a function " + "`summarize_group(assignments, users)`, where `assignments` " + "is a list of `canvasapi.assignment.Assignment` objects and " "`users` is a list of `canvasapi.user.User` objects. The return value " - "must be a tuple " + "must be a list of tuples of the form " "`(user object, grade, grade date)`.") @ @@ -207,103 +212,27 @@ An attacker can potentially load their own module and have it execute when reporting grades. For instance, a malicious module could change grades, \eg always set A's. -<>= -summary = importlib.import_module(args.summary_module) -@ - - -\subsection{The default results summarizing module} - -We have one requirement on the summary module: it must contain a function -[[summarize_group]] that takes two arguments; -the first being a list of assignments, -the second being a list of users. -The [[summarize_group]] function is the function that the above code will call. -This gives the following outline of the module. -<>= -"""Module with a summary function for summarizing assignment groups""" - -import datetime as dt - -<> - -def summarize_group(assignments_list, users_list): - """Summarizes a particular set of assignments (assignments_list) for all - users in users_list""" - - for user in users_list: - grade, grade_date = summarize(user, assignments_list) - yield (user, grade, grade_date) -@ - - -\subsection{Summarizing grades: assignment grades to component grade} -Now we will describe the [[summarize]] helper function. -We want to establish two things: the most recent date and a suitable grade. - -For the most recent date, we just check the dates as we iterate through the -submissions. - -For the grade, as we iterate through we look for P/F and A--E grades. -We can then check for Fs among the P/F grades, if we find an F the summarized -grade will be an F. -If we find no Fs, then we can compute the average over all A--E grades and use -that as the final grade. -<>= -def summarize(user, assignments_list): - """Extracts user's submissions for assignments in assingments_list to - summarize results into one grade and a grade date""" - - pf_grades = [] - a2e_grades = [] - recent_date = dt.date(year=1970, month=1, day=1) - - for assignment in assignments_list: - submission = assignment.get_submission(user) - grade = submission.grade - - if grade is None: - grade = "F" - - if grade in "ABCDE": - a2e_grades.append(grade) - else: - pf_grades.append(grade) - - grade_date = submission.submitted_at or submission.graded_at - - if not grade_date: - grade_date = recent_date - else: - grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) - - if grade_date > recent_date: - recent_date = grade_date - - if not all(map(lambda x: x == "P", pf_grades)): - return ("F", recent_date) - - if a2e_grades: - return (a2e_average(a2e_grades), recent_date) - return ("P", recent_date) +Now to the loader, we first try to load a system module, then we look for a +module in the current working directory. +<>= +try: + summary = importlib.import_module(args.summary_module) +except ModuleNotFoundError: + module_path = pathlib.Path.cwd() / args.summary_module + module = module_path.stem + + try: + loader = importlib.machinery.SourceFileLoader( + module, str(module_path)) + spec = importlib.util.spec_from_loader(module, loader) + summary = importlib.util.module_from_spec(spec) + loader.exec_module(summary) + except Exception as err: + canvaslms.cli.err(1, f"Error loading summary module " + f"'{args.summary_module}': {err}") @ -To compute the average for the A--E grades; we will convert the grades into -integers, compute the average, round the value to an integer and convert back. -<>= -def a2e_average(grades): - """Takes a list of A--E grades, returns the average.""" - num_grades = map(grade_to_int, grades) - avg_grade = round(sum(num_grades)/len(grades)) - return int_to_grade(avg_grade) - -def grade_to_int(grade): - grade_map = {"E": 1, "D": 2, "C": 3, "B": 4, "A": 5} - return grade_map[grade] - -def int_to_grade(int_grade): - grade_map_inv = {1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} - return grade_map_inv[int_grade] -@ +The available summary functions and the default one can be found in +\cref{summary-modules}. diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore new file mode 100644 index 0000000..25fd047 --- /dev/null +++ b/src/canvaslms/grades/.gitignore @@ -0,0 +1,4 @@ +grades.tex +mysum.py +conjunctavg.tex +conjunctavg.py diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile new file mode 100644 index 0000000..9957c24 --- /dev/null +++ b/src/canvaslms/grades/Makefile @@ -0,0 +1,19 @@ +NOWEAVEFLAGS.tex= -n -delay -t2 +NOTANGLEFLAGS.py= + + +.PHONY: all +all: grades.tex +all: conjunctavg.py conjunctavg.tex + + +.PHONY: clean +clean: + ${RM} conjunctavg.py conjunctavg.tex + + +INCLUDE_MAKEFILES=../../../makefiles +include ${INCLUDE_MAKEFILES}/tex.mk +include ${INCLUDE_MAKEFILES}/noweb.mk +include ${INCLUDE_MAKEFILES}/pkg.mk + diff --git a/src/canvaslms/grades/__init__.py b/src/canvaslms/grades/__init__.py new file mode 100644 index 0000000..60c8186 --- /dev/null +++ b/src/canvaslms/grades/__init__.py @@ -0,0 +1,19 @@ +""" +Package containing modules to summarize assignment groups in different ways. + +For a module to be used with the `canvaslms results -S module` option, the +module must fulfil the following: + + 1) It must contain a function named `summarize_group`. + 2) `summarize_group` must take two arguments: + + I) `assignment_list`, a list of `canvasapi.assignment.Assignment` + objects. + + II) `users_list`, a list of `canvasapi.user.User` objects. + + 3) The return value should be a list of tuples. Each tuple should have the + form `(user, grade, grade date)`. + +See the built-in modules below. +""" diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw new file mode 100644 index 0000000..073430c --- /dev/null +++ b/src/canvaslms/grades/conjunctavg.nw @@ -0,0 +1,107 @@ +\section{Conjunctive average} + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +Module that summarizes an assignment group by conjunctive average. + +Conjunctive average means: + + 1) We need all assignments to have a non-F grade. + 2) If there are A--F assignments present, we will compute the average of + those grades. For instance; an A and a C will result in a B; an A and a B + will result in an A, but an A with two Bs will become a B (standard + rounding). +""" + +import datetime as dt + +<> + +def summarize_group(assignments_list, users_list): + """Summarizes a particular set of assignments (assignments_list) for all + users in users_list""" + + for user in users_list: + grade, grade_date = summarize(user, assignments_list) + yield (user, grade, grade_date) +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish two things: the most recent date and a suitable grade. + +For the most recent date, we just check the dates as we iterate through the +submissions. + +For the grade, as we iterate through we look for P/F and A--E grades. +We can then check for Fs among the P/F grades, if we find an F the summarized +grade will be an F. +If we find no Fs, then we can compute the average over all A--E grades and use +that as the final grade. +<>= +def summarize(user, assignments_list): + """Extracts user's submissions for assignments in assingments_list to + summarize results into one grade and a grade date""" + + pf_grades = [] + a2e_grades = [] + recent_date = dt.date(year=1970, month=1, day=1) + + for assignment in assignments_list: + submission = assignment.get_submission(user) + grade = submission.grade + + if grade is None: + grade = "F" + + if grade in "ABCDE": + a2e_grades.append(grade) + else: + pf_grades.append(grade) + + grade_date = submission.submitted_at or submission.graded_at + + if not grade_date: + grade_date = recent_date + else: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + + if grade_date > recent_date: + recent_date = grade_date + + if not all(map(lambda x: x == "P", pf_grades)): + return ("F", recent_date) + + if a2e_grades: + return (a2e_average(a2e_grades), recent_date) + return ("P", recent_date) +@ + +\subsection{Computing averages} + +To compute the average for the A--E grades; we will convert the grades into +integers, compute the average, round the value to an integer and convert back. +<>= +def a2e_average(grades): + """Takes a list of A--E grades, returns the average.""" + num_grades = map(grade_to_int, grades) + avg_grade = round(sum(num_grades)/len(grades)) + return int_to_grade(avg_grade) + +def grade_to_int(grade): + grade_map = {"E": 1, "D": 2, "C": 3, "B": 4, "A": 5} + return grade_map[grade] + +def int_to_grade(int_grade): + grade_map_inv = {1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} + return grade_map_inv[int_grade] +@ + diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw new file mode 100644 index 0000000..b20de25 --- /dev/null +++ b/src/canvaslms/grades/grades.nw @@ -0,0 +1,45 @@ +\chapter{Computing grades from groups of assignments} +\label{summary-modules} + +This is the documentation for the \texttt{canvaslms.grades} package. +Here we provide modules to be used with the \texttt{-S} option for the +\texttt{results} command, see \cref{results-command}. + +For a module to be used, it must contain a function named +\texttt{summarize\textunderscore group}. +The function must take two arguments: +\begin{enumerate} + \item a list of assignments that all belong to the same group, \ie the + assignments whose grades should be used to compute the student's grade. + \item a list of users, \ie students, for whom to compute the grades. +\end{enumerate} +See the modules below for examples. + +Let's look at a simple example. +This small module just returns a counter as grade: it starts at 0, increases +one per student. +The grading date is set to today's date for all students. +We don't even look at the students' submissions for these assignments. +<>= +import datetime as dt + +count = 0 + +def summarize_group(assignments, users): + global count + date = dt.date.today() + for user in users: + yield (user, str(count), date) + count += 1 +@ + +To use this module we would run +\begin{center} + \texttt{canvaslms results -S mysum.py} +\end{center} +in the directory where \texttt{mysum.py} is located. +We can also give the relative or absolute path to \texttt{mysum.py} instead. + +%%% Modules %%% + +\input{../src/canvaslms/grades/conjunctavg.tex} From b00f0fd6cbf3d176c6e4dadceba07a823b25bab4 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 12 Mar 2022 07:34:53 +0100 Subject: [PATCH 024/248] Adds module to summarize grades by disjunctive maximum --- doc/Makefile | 1 - src/canvaslms/grades/.gitignore | 2 + src/canvaslms/grades/Makefile | 6 ++ src/canvaslms/grades/conjunctavg.nw | 3 +- src/canvaslms/grades/disjunctmax.nw | 99 +++++++++++++++++++++++++++++ src/canvaslms/grades/grades.nw | 1 + 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/canvaslms/grades/disjunctmax.nw diff --git a/doc/Makefile b/doc/Makefile index 83511c4..508408f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -23,7 +23,6 @@ canvaslms.pdf: ${SRC_DIR}/cli/submissions.tex canvaslms.pdf: ${SRC_DIR}/cli/grade.tex canvaslms.pdf: ${SRC_DIR}/cli/results.tex canvaslms.pdf: ${SRC_DIR}/grades/grades.tex -canvaslms.pdf: ${SRC_DIR}/grades/conjunctavg.tex ${SRC_DIR}/%.tex: ${SRC_DIR}/%.nw ${MAKE} -C $(dir $@) $(notdir $@) diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index 25fd047..d59dbab 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -2,3 +2,5 @@ grades.tex mysum.py conjunctavg.tex conjunctavg.py +disjunctmax.tex +disjunctmax.py diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 9957c24..89eb8c3 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -5,11 +5,17 @@ NOTANGLEFLAGS.py= .PHONY: all all: grades.tex all: conjunctavg.py conjunctavg.tex +all: disjunctmax.py disjunctmax.tex + +grades.tex: conjunctavg.tex +grades.tex: disjunctmax.tex .PHONY: clean clean: + ${RM} grades.tex ${RM} conjunctavg.py conjunctavg.tex + ${RM} disjunctmax.py disjunctmax.tex INCLUDE_MAKEFILES=../../../makefiles diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index 073430c..baad0c4 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -49,7 +49,8 @@ that as the final grade. <>= def summarize(user, assignments_list): """Extracts user's submissions for assignments in assingments_list to - summarize results into one grade and a grade date""" + summarize results into one grade and a grade date. Summarize by conjunctive + average.""" pf_grades = [] a2e_grades = [] diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw new file mode 100644 index 0000000..8c9caa1 --- /dev/null +++ b/src/canvaslms/grades/disjunctmax.nw @@ -0,0 +1,99 @@ +\section{Disjunctive maximum} + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +Module that summarizes an assignment group by disjunctive maximum. + +Disjunctive maximum means: + + 1) We at least one assignment to have a non-F grade. + 2) If there are more than one assignment with a non-F grade, we take the + maximum as the grade. A--E are valued higher than P. The grade F is valued + the lowest. +""" + +import datetime as dt + +<> + +def summarize_group(assignments_list, users_list): + """Summarizes a particular set of assignments (assignments_list) for all + users in users_list""" + + for user in users_list: + grade, grade_date = summarize(user, assignments_list) + yield (user, grade, grade_date) +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish two things: the most recent date and a suitable grade. + +For the most recent date, we just check the dates as we iterate through the +submissions. + +For the grade, as we iterate through we look for P/F and A--E grades. +We can then check for Fs among the P/F grades, if we find an F the summarized +grade will be an F. +If we find no Fs, then we can compute the average over all A--E grades and use +that as the final grade. +<>= +def summarize(user, assignments_list): + """Extracts user's submissions for assignments in assingments_list to + summarize results into one grade and a grade date. Summarize by disjunctive + maximum.""" + + grades = [] + recent_date = dt.date(year=1970, month=1, day=1) + + for assignment in assignments_list: + submission = assignment.get_submission(user) + grade = submission.grade + + if grade is None: + grade = "F" + + grades.append(grade) + + grade_date = submission.submitted_at or submission.graded_at + + if not grade_date: + grade_date = recent_date + else: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + + if grade_date > recent_date: + recent_date = grade_date + + return (grade_max(grades), recent_date) +@ + +\subsection{Computing the maximum} + +To compute the maximum for the A--E grades; we will convert the grades into +integers, compute the maximum, round the value to an integer and convert back. +We also include P/F here, since we can count them as lower than A--E. +<>= +def grade_max(grades): + """Takes a list of A--E/P--F grades, returns the maximum.""" + num_grades = map(grade_to_int, grades) + max_grade = max(num_grades) + return int_to_grade(max_grade) + +def grade_to_int(grade): + grade_map = {"F": -1, "P": 0, "E": 1, "D": 2, "C": 3, "B": 4, "A": 5} + return grade_map[grade] + +def int_to_grade(int_grade): + grade_map_inv = {-1: "F", 0: "P", 1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} + return grade_map_inv[int_grade] +@ + diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index b20de25..c5da5be 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -43,3 +43,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead. %%% Modules %%% \input{../src/canvaslms/grades/conjunctavg.tex} +\input{../src/canvaslms/grades/disjunctmax.tex} From c1dfe28502d1774c733f986dedefb06ba08a14be Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 12 Mar 2022 07:39:55 +0100 Subject: [PATCH 025/248] Fixes typo in disjunctive max's description --- src/canvaslms/grades/disjunctmax.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 8c9caa1..8d1f9c4 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -12,7 +12,7 @@ Module that summarizes an assignment group by disjunctive maximum. Disjunctive maximum means: - 1) We at least one assignment to have a non-F grade. + 1) At least one assignment must have a non-F grade. 2) If there are more than one assignment with a non-F grade, we take the maximum as the grade. A--E are valued higher than P. The grade F is valued the lowest. From aaac6ea9baf3d4d1793dbe12d158fcdd5ef8c4da Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 12 Mar 2022 07:44:11 +0100 Subject: [PATCH 026/248] Bumps version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 005444a..53aeec4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.10", + version = "1.11", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 38839b2ec20bb7f70a186ad2ff96b3b2d5287a9d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 7 Apr 2022 20:33:45 +0200 Subject: [PATCH 027/248] Adds docker image --- Makefile | 11 ++++++----- docker/Dockerfile | 4 ++++ docker/Makefile | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/Makefile diff --git a/Makefile b/Makefile index b1ee26d..387e001 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ SUBDIR_GOALS= all clean distclean -SUBDIR+= doc SUBDIR+= src/canvaslms +SUBDIR+= doc +SUBDIR+= docker version=$(shell sed -n 's/^ *version *= *\"\([^\"]\+\)\",/\1/p' setup.py) dist=$(addprefix dist/canvaslms-${version}, -py3-none-any.whl .tar.gz) @@ -22,7 +23,7 @@ compile: ${MAKE} -C src/canvaslms all .PHONY: publish publish-canvaslms publish-docker -publish: publish-canvaslms doc/canvaslms.pdf +publish: publish-canvaslms doc/canvaslms.pdf publish-docker git push gh release create -t v${version} v${version} doc/canvaslms.pdf @@ -38,9 +39,9 @@ ${dist}: compile canvaslms.bash canvaslms.bash: register-python-argcomplete canvaslms > $@ -#publish-docker: -# sleep 60 -# ${MAKE} -C docker publish +publish-docker: + sleep 60 + ${MAKE} -C docker publish .PHONY: clean diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..d8a71a3 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3 + +RUN pip3 install --no-cache-dir --upgrade canvaslms && \ + ln -f $(find / -name canvaslms.bash) /etc/bash_completion.d diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 0000000..12bc046 --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,22 @@ +VERSION+= latest + +.PHONY: all +all: docker-image + +.PHONY: publish +publish: docker-image + for v in ${VERSION}; do docker push dbosk/canvaslms:$$v; done + +.PHONY: docker-image +docker-image: + docker pull python:3 + docker build --no-cache -t canvaslms . + for v in ${VERSION}; do docker tag canvaslms dbosk/canvaslms:$$v; done + +.PHONY: clean +clean: + true + +.PHONY: distclean +distclean: + -docker image rm -f canvaslms dbosk/canvaslms From d2c709cf43fcc36fe035d7d1956098a7fe9cc639 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 9 Apr 2022 09:33:35 +0200 Subject: [PATCH 028/248] Fixes typo --- src/canvaslms/cli/cli.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index e177aeb..b29263f 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -145,7 +145,7 @@ config = read_configuration(args.config_file) hostname, token = canvaslms.cli.login.load_credentials(config) if not (hostname and token): - err("No hostname or token, rum 'canvaslms login'") + err("No hostname or token, run 'canvaslms login'") canvas = Canvas(hostname, token) From d7f125b58c62576525fa79e41817caec22d902fd Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 9 Apr 2022 09:34:44 +0200 Subject: [PATCH 029/248] Adds missing parameter to err() call --- src/canvaslms/cli/cli.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index b29263f..d7a26df 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -145,7 +145,7 @@ config = read_configuration(args.config_file) hostname, token = canvaslms.cli.login.load_credentials(config) if not (hostname and token): - err("No hostname or token, run 'canvaslms login'") + err(1, "No hostname or token, run 'canvaslms login'") canvas = Canvas(hostname, token) From 356b5d995124cad38fb5e998deabd2db614cfc46 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 9 Apr 2022 09:35:12 +0200 Subject: [PATCH 030/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 53aeec4..523ac56 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.11", + version = "1.12", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From c5838dab4441f19aaabc18b27049554f11f6c363 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 9 Apr 2022 09:49:37 +0200 Subject: [PATCH 031/248] doc: Fixes compilation error for biblatex --- doc/preamble.tex | 6 +++--- setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/preamble.tex b/doc/preamble.tex index c08980c..62c7867 100644 --- a/doc/preamble.tex +++ b/doc/preamble.tex @@ -3,6 +3,9 @@ \usepackage[british]{babel} \usepackage{booktabs} +\usepackage[natbib,style=alphabetic,maxbibnames=99]{biblatex} +\addbibresource{canvas.bib} + \usepackage[all]{foreign} \renewcommand{\foreignfullfont}{} \renewcommand{\foreignabbrfont}{} @@ -13,9 +16,6 @@ \usepackage[strict]{csquotes} \usepackage[single]{acro} -\usepackage[natbib,style=alphabetic,maxbibnames=99]{biblatex} -\addbibresource{canvas.bib} - \usepackage{subcaption} \usepackage[noend]{algpseudocode} diff --git a/setup.py b/setup.py index 523ac56..20b87c7 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.12", + version = "1.13", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 6d06ff15886c0e575487831c007011dd932aff6f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 11 Apr 2022 08:21:23 +0200 Subject: [PATCH 032/248] Adds option to print login ID for submissions --- src/canvaslms/cli/submissions.nw | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index f8d58aa..ae15f85 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -46,6 +46,7 @@ submissions_parser = subp.add_parser("submissions", submissions_parser.set_defaults(func=submissions_command) assignments.add_assignment_option(submissions_parser) add_submission_options(submissions_parser) +<> @ Now, that [[submissions_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. It must also do the processing for the assignment options using @@ -77,7 +78,17 @@ if args.user or args.category or args.group: output = csv.writer(sys.stdout, delimiter=args.delimiter) for submission in submissions: - output.writerow(format_submission_short(submission)) + if args.login_id: + output.writerow(format_submission_short_unique(submission)) + else: + output.writerow(format_submission_short(submission)) +@ + +Now, we must add that option [[args.login_id]]. +<>= +submissions_parser.add_argument("-l", "--login-id", + help="Print login ID instead of name.", + default=False, action="store_true") @ @@ -249,6 +260,11 @@ def format_submission_short(submission): return [submission.assignment.course.course_code, submission.assignment.name, submission.user.name, submission.grade, submission.submitted_at, submission.graded_at] + +def format_submission_short_unique(submission): + return [submission.assignment.course.course_code, submission.assignment.name, + submission.user.login_id, submission.grade, + submission.submitted_at, submission.graded_at] @ We provide the function [[format_submission]] to nicely format a submission. From 3c808a8c4c741da4e98a36ffc5fb593538ea00d7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 11 Apr 2022 13:15:29 +0200 Subject: [PATCH 033/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 20b87c7..78b1339 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.13", + version = "1.14", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 34e33c09186171eae04c7dc6ba129a208e72d467 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Apr 2022 10:45:48 +0200 Subject: [PATCH 034/248] Adds rubric data (if it exists) to submission output This fixes #47. However, we don't need any option as mentioned in that issue. --- src/canvaslms/cli/submissions.nw | 95 +++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index ae15f85..5416a42 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -126,6 +126,7 @@ def speedgrader(submission): return speedgrader_url @ + \section{The [[submission]] subcommand and its options} Here we provide a subcommand [[submission]] which deals with an individual @@ -187,9 +188,13 @@ def process_submission_options(canvas, args): user_list = users.process_user_or_group_option(canvas, args) if args.ungraded: - submissions = list_ungraded_submissions(assignment_list) + submissions = list_ungraded_submissions(assignment_list, + include=["submission_history", "submission_comments", + "rubric_assessment"]) else: - submissions = list_submissions(assignment_list) + submissions = list_submissions(assignment_list, + include=["submission_history", "submission_comments", + "rubric_assessment"]) return list(filter_submissions(submissions, user_list)) @ @@ -286,6 +291,17 @@ def format_submission(submission, canvas): - SpeedGrader: {speedgrader(submission)} """ + try: + if submission.rubric_assessment: + formatted_submission += f""" + +# Rubric assessment + +{format_rubric(submission)} +""" + except AttributeError: + pass + try: if submission.submission_comments: formatted_submission += f""" @@ -302,12 +318,16 @@ def format_submission(submission, canvas): except AttributeError: pass - formatted_submission += f""" + try: + if submission.body: + formatted_submission += f""" # Body {submission.body} """ + except AttributeError: + pass try: for attachment in submission.attachments: @@ -329,3 +349,72 @@ def format_submission(submission, canvas): return formatted_submission @ + +\section{Formatting rubrics} + +For assignments that use rubrics, we want to format those rubrics so that we +can read the results instead of just the cumulated grade. +<>= +def format_rubric(submission): + """Format the rubric assessment of the `submission` in readable form.""" + + result = "" + + for crit_id, rating_data in submission.rubric_assessment.items(): + criterion = get_criterion(crit_id, submission.assignment.rubric) + rating = get_rating(rating_data["rating_id"], criterion) + + result += f"{criterion['description']}: {rating['description']} " \ + f"({rating['points']})\n" + + if rating_data["comments"]: + result += f"Comments: {rating_data['comments']}\n" + + result += "\n" + + return result.strip() +@ + +We can get the rating of a rubric from the rubric assessment. +We can get this data from [[submission.rubric_assessment]] and it looks like +this: +\begin{minted}{python} +{'_7957': {'rating_id': '_6397', 'comments': '', 'points': 1.0}, + '_1100': {'rating_id': '_8950', 'comments': '', 'points': 1.0}} +\end{minted} + +We get the rubric with the assignment. +So we can get it through [[submission.assignment.rubric]] and it looks like +this: +\begin{minted}{python} +[{'id': '_7957', 'points': 1.0, 'description': 'Uppfyller kraven i lydelsen', +'long_description': '', 'criterion_use_range': False, 'ratings': [{'id': +'_6397', 'points': 1.0, 'description': 'OK', 'long_description': ''}, {'id': +'_7836', 'points': 0.0, 'description': 'Påpekande', 'long_description': ''}]}, +{'id': '_1100', 'points': 1.0, 'description': 'Kan redogöra för alla detaljer', +'long_description': '', 'criterion_use_range': False, 'ratings': [{'id': +'_8950', 'points': 1.0, 'description': 'OK', 'long_description': ''}, {'id': +'_4428', 'points': 0.0, 'description': 'Påpekande', 'long_description': ''}]}] +\end{minted} +It's essentially a list of criterions. +We want to extract a criterion by ID from the rubric. +<>= +def get_criterion(criterion_id, rubric): + """Returns criterion with ID `criterion_id` from rubric `rubric`""" + for criterion in rubric: + if criterion["id"] == criterion_id: + return criterion + + return None +@ And in exactly the same fashion we want to extract a rating from the +criterion. +<>= +def get_rating(rating_id, criterion): + """Returns rating with ID `rating_id` from rubric criterion `criterion`""" + for rating in criterion["ratings"]: + if rating["id"] == rating_id: + return rating + + return None +@ + From be7ebb1d7711462ce3ad436f9295a371c4241496 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Apr 2022 10:48:00 +0200 Subject: [PATCH 035/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78b1339..95df008 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.14", + version = "1.15", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 67ad9452117ff1da60efddf70baa7b34d8bcd0a0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Apr 2022 13:30:07 +0200 Subject: [PATCH 036/248] Outputs submission history with submission However, we only get the rubric assessment for the latest version. --- src/canvaslms/cli/submissions.nw | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 5416a42..f6ff056 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -6,6 +6,8 @@ handles an individual submission. We outline the module: <>= +import canvasapi.submission + import canvaslms.cli.assignments as assignments import canvaslms.cli.users as users import canvaslms.hacks.canvasapi @@ -149,7 +151,7 @@ def submission_command(config, canvas, args): Then we can fetch the submission. <>= for submission in submission_list: - pydoc.pager(format_submission(submission, canvas)) + pydoc.pager(format_submission(submission)) @ @@ -276,7 +278,7 @@ We provide the function [[format_submission]] to nicely format a submission. It prints metadata, downloads any text attachments to include in the output. It also uses the [[canvas]] object to resolve course, assignment and user IDs. <>= -def format_submission(submission, canvas): +def format_submission(submission): """Formats submission for printing to stdout""" student = submission.assignment.course.get_user(submission.user_id) @@ -346,6 +348,17 @@ def format_submission(submission, canvas): except AttributeError: pass + try: + if submission.submission_history: + for prev_submission in submission.submission_history: + prev_submission = canvasapi.submission.Submission( + submission._requester, prev_submission) + prev_submission.assignment = submission.assignment + + formatted_submission += "\n\n" + format_submission(prev_submission) + except AttributeError: + pass + return formatted_submission @ From 197d5ca8f94d7932ec47128ad61925e5074e8416 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Apr 2022 13:33:32 +0200 Subject: [PATCH 037/248] Bumps version number, improves comments --- setup.py | 2 +- src/canvaslms/cli/submissions.nw | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 95df008..aa15c0e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.15", + version = "1.16", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index f6ff056..c0a2654 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -276,7 +276,11 @@ def format_submission_short_unique(submission): We provide the function [[format_submission]] to nicely format a submission. It prints metadata, downloads any text attachments to include in the output. -It also uses the [[canvas]] object to resolve course, assignment and user IDs. +We also output all the submission history at the end. +We no longer need the [[canvas]] object to resolve course, assignment and user +IDs, instead, we add these as attributes when fetching the objects. +So [[submission.assignment]] is the assignment it came from, we don't need to +resolve the assignment from the assignmend ID. <>= def format_submission(submission): """Formats submission for printing to stdout""" From 1804c7ed9919fc9c68dddccabd1e3b24269f7abe Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 2 May 2022 11:00:53 +0200 Subject: [PATCH 038/248] Adds printing of submission_data if exists --- src/canvaslms/cli/submissions.nw | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index c0a2654..a942a08 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -14,6 +14,7 @@ import canvaslms.hacks.canvasapi import argparse import csv +import json import pydoc import pypandoc import re @@ -331,6 +332,17 @@ def format_submission(submission): # Body {submission.body} +""" + except AttributeError: + pass + + try: + if submission.submission_data: + formatted_submission += f""" + +# Submission data (quiz) + +{json.dumps(submission.submission_data, indent=2)} """ except AttributeError: pass From a1f514f043e9f1e6e79865d0d4699c615960987a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 2 May 2022 11:47:46 +0200 Subject: [PATCH 039/248] Rewrites format_submission code --- src/canvaslms/cli/submissions.nw | 165 ++++++++++++++++++------------- 1 file changed, 95 insertions(+), 70 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index a942a08..18bb904 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -287,95 +287,120 @@ def format_submission(submission): """Formats submission for printing to stdout""" student = submission.assignment.course.get_user(submission.user_id) - formatted_submission = f"""# Metadata + formatted_submission = "" -{submission.assignment.course.course_code} > {submission.assignment.name} + <> + <> + <> + <> + <> + <> + <> -- Student: {student.name} ({student.login_id or None}, {submission.user_id}) -- Submission ID: {submission.id} -- Submitted (graded): {submission.submitted_at} ({submission.graded_at}) -- Grade: {submission.grade} ({submission.score}) -- SpeedGrader: {speedgrader(submission)} -""" - - try: - if submission.rubric_assessment: - formatted_submission += f""" - -# Rubric assessment + return formatted_submission +@ -{format_rubric(submission)} -""" - except AttributeError: - pass +\subsection{Some helper functions} - try: - if submission.submission_comments: - formatted_submission += f""" +We want to format some sections. +<>= +def format_section(title, body): + return f"\n# {title}\n\n{body}\n\n" +@ -# Comments -""" +\subsection{Metadata} + +<>= +formatted_submission += format_section( + "Metadata", + f"{submission.assignment.course.course_code} > {submission.assignment.name}" + f"\n\n" + f" - Student: {student.name} " + f"({student.login_id or None}, {submission.user_id})\n" + f" - Submission ID: {submission.id}\n" + f" - Submitted (graded): {submission.submitted_at} " + f"({submission.graded_at})\n" + f" - Grade: {submission.grade} ({submission.score})\n" + f" - SpeedGrader: {speedgrader(submission)}") +@ - for comment in submission.submission_comments: - formatted_submission += f""" -{comment["author_name"]} ({comment["created_at"]}): +\subsection{Rubric data} -{comment["comment"]} -""" - except AttributeError: - pass +<>= +try: + if submission.rubric_assessment: + formatted_submission += format_section( + "Rubric assessment", + format_rubric(submission)) +except AttributeError: + pass +@ - try: - if submission.body: - formatted_submission += f""" +\subsection{General comments} -# Body +<>= +try: + if submission.submission_comments: + body = "" + for comment in submission.submission_comments: + body += f"{comment['author_name']} ({comment['created_at']}):\n\n" + body += comment["comment"] + formatted_submission += format_section("Comments", body) +except AttributeError: + pass +@ -{submission.body} -""" - except AttributeError: - pass +\subsection{Body} - try: - if submission.submission_data: - formatted_submission += f""" +<>= +try: + if submission.body: + formatted_submission += format_section("Body", submission.body) +except AttributeError: + pass +@ -# Submission data (quiz) +\subsection{Quiz answers} -{json.dumps(submission.submission_data, indent=2)} -""" - except AttributeError: - pass +<>= +try: + if submission.submission_data: + formatted_submission += format_section( + "Quiz answers", + json.dumps(submission.submission_data, indent=2)) +except AttributeError: + pass +@ - try: - for attachment in submission.attachments: - if "text/" not in attachment["content-type"]: - continue +\subsection{Attachments} - contents = urllib.request.urlopen(attachment["url"]).read().decode("utf-8") - formatted_submission += f""" +<>= +try: + for attachment in submission.attachments: + if "text/" not in attachment["content-type"]: + continue -## {attachment["filename"]} + contents = urllib.request.urlopen(attachment["url"]).read().decode("utf-8") + formatted_submission += format_section( + attachment["filename"], + f"```\n{contents}\n```") +except AttributeError: + pass +@ -``` -{contents} -``` -""" - except AttributeError: - pass +\subsection{Submission history} - try: - if submission.submission_history: - for prev_submission in submission.submission_history: - prev_submission = canvasapi.submission.Submission( - submission._requester, prev_submission) - prev_submission.assignment = submission.assignment - - formatted_submission += "\n\n" + format_submission(prev_submission) - except AttributeError: - pass +<>= +try: + if submission.submission_history: + for prev_submission in submission.submission_history: + prev_submission = canvasapi.submission.Submission( + submission._requester, prev_submission) + prev_submission.assignment = submission.assignment - return formatted_submission + formatted_submission += "\n\n" + format_submission(prev_submission) +except AttributeError: + pass @ From 4b14d9d3315cb3b93a7bf5f008da4cccf5e97270 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 2 May 2022 12:01:53 +0200 Subject: [PATCH 040/248] Bumps version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aa15c0e..4b5c6f7 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "canvaslms", - version = "1.16", + version = "1.17", author = "Daniel Bosk", author_email = "dbosk@kth.se", description = "Command-line interface for Canvas LMS", From 46b0ed5919d7cc0044f541effe53ddf43be69a41 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:19:49 +0100 Subject: [PATCH 041/248] Fixes conditional for when to suggest running login --- src/canvaslms/cli/cli.nw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index d7a26df..722768a 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -144,8 +144,8 @@ config = read_configuration(args.config_file) hostname, token = canvaslms.cli.login.load_credentials(config) -if not (hostname and token): - err(1, "No hostname or token, run 'canvaslms login'") +if args.func != canvaslms.cli.login.command and not (hostname and token): + err(1, "No hostname or token, run `canvaslms login`") canvas = Canvas(hostname, token) From 31bd884674338f6aebf8b271d12f96d309543a12 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:20:50 +0100 Subject: [PATCH 042/248] Adds spacing to submission listing --- src/canvaslms/cli/submissions.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 18bb904..c879492 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -344,7 +344,7 @@ try: body = "" for comment in submission.submission_comments: body += f"{comment['author_name']} ({comment['created_at']}):\n\n" - body += comment["comment"] + body += comment["comment"] + "\n\n" formatted_submission += format_section("Comments", body) except AttributeError: pass From 405bb3385dc5d003468e86a3e6be82175d1dee36 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:35:38 +0100 Subject: [PATCH 043/248] Switches to use poetry --- Makefile | 10 +- poetry.lock | 339 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 44 ++++++- 3 files changed, 381 insertions(+), 12 deletions(-) create mode 100644 poetry.lock diff --git a/Makefile b/Makefile index 387e001..f594972 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,7 @@ SUBDIR+= src/canvaslms SUBDIR+= doc SUBDIR+= docker -version=$(shell sed -n 's/^ *version *= *\"\([^\"]\+\)\",/\1/p' setup.py) -dist=$(addprefix dist/canvaslms-${version}, -py3-none-any.whl .tar.gz) +version=$(shell sed -n 's/^ *version *= *\"\([^\"]\+\)\",/\1/p' pyproject.toml) .PHONY: all @@ -30,11 +29,8 @@ publish: publish-canvaslms doc/canvaslms.pdf publish-docker doc/canvaslms.pdf: $(wildcard src/canvaslms/cli/*.tex) ${MAKE} -C $(dir $@) $(notdir $@) -publish-canvaslms: ${dist} - python3 -m twine upload -r pypi ${dist} - -${dist}: compile canvaslms.bash - python3 setup.py sdist bdist_wheel +publish-canvaslms: compile + poetry publish canvaslms.bash: register-python-argcomplete canvaslms > $@ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..0249190 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,339 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "argcomplete" +version = "2.0.0" +description = "Bash tab completion for argparse" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + +[[package]] +name = "arrow" +version = "1.2.3" +description = "Better dates & times for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +python-dateutil = ">=2.7.0" + +[[package]] +name = "cachetools" +version = "5.2.0" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = false +python-versions = "~=3.7" + +[[package]] +name = "canvasapi" +version = "3.0.0" +description = "API wrapper for the Canvas LMS" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +arrow = "*" +pytz = "*" +requests = "*" + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "cryptography" +version = "38.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "5.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] + +[[package]] +name = "jaraco.classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] + +[[package]] +name = "keyring" +version = "23.11.0" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "more-itertools" +version = "9.0.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pypandoc" +version = "1.10" +description = "Thin wrapper for pandoc." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2022.6" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "zipp" +version = "3.10.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "01e061b1117bff57257584085fce1bda65f86573e42c95a6fc7e48d055e0e383" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +argcomplete = [ + {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, + {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, +] +arrow = [] +cachetools = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] +canvasapi = [] +certifi = [] +cffi = [] +charset-normalizer = [] +cryptography = [] +idna = [] +importlib-metadata = [] +"jaraco.classes" = [] +jeepney = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] +keyring = [] +more-itertools = [] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pypandoc = [] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [] +pywin32-ctypes = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] +requests = [] +secretstorage = [] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +urllib3 = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml index a77e526..c0889e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,41 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel" +[tool.poetry] +name = "canvaslms" +version = "1.18" +description = "Command-line interface to Canvas LMS" +authors = ["Daniel Bosk "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/dbosk/canvaslms" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Topic :: Utilities" ] -build-backend = "setuptools.build_meta" + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/dbosk/canvaslms/issues", +"Releases" = "https://github.com/dbosk/canvaslms/releases" + +[tool.poetry.scripts] +canvaslms = "canvaslms.cli:main" + +[tool.poetry.dependencies] +python = "^3.8" +appdirs = "^1.4.4" +argcomplete = "^2.0.0" +cachetools = "^5.2.0" +canvasapi = "^3.0.0" +keyring = "^23.11.0" +pypandoc = "^1.10" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From dc4e4b1278b427e84756bf872dfd1397e6e87ba7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:44:22 +0100 Subject: [PATCH 044/248] Fixes typo in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c0889e0..ff21242 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] [tool.poetry.urls] -"Bug Tracker" = "https://github.com/dbosk/canvaslms/issues", +"Bug Tracker" = "https://github.com/dbosk/canvaslms/issues" "Releases" = "https://github.com/dbosk/canvaslms/releases" [tool.poetry.scripts] From f18445e3374362593f96ef12f2f6522723863764 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:44:46 +0100 Subject: [PATCH 045/248] Fixes conditional for run login error --- src/canvaslms/cli/cli.nw | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 722768a..e59c5e8 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -144,7 +144,8 @@ config = read_configuration(args.config_file) hostname, token = canvaslms.cli.login.load_credentials(config) -if args.func != canvaslms.cli.login.command and not (hostname and token): +if not (hostname and token) and \ + args.func != canvaslms.cli.login.update_credentials_in_keyring: err(1, "No hostname or token, run `canvaslms login`") canvas = Canvas(hostname, token) From 1b5221700d0b770f37b2c110de6d869d4a1d26ff Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:47:58 +0100 Subject: [PATCH 046/248] Adds missing `poetry build` --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f594972..ed9bedd 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ install: compile .PHONY: compile compile: ${MAKE} -C src/canvaslms all + poetry build .PHONY: publish publish-canvaslms publish-docker publish: publish-canvaslms doc/canvaslms.pdf publish-docker From 7813b2eca0f8f53332ef4db65e1a5f67d184ecbe Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:50:44 +0100 Subject: [PATCH 047/248] Adds missing include to pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ff21242..29bc632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ "Intended Audience :: Science/Research", "Topic :: Utilities" ] +include = ["*/**/*.py"] [tool.poetry.urls] "Bug Tracker" = "https://github.com/dbosk/canvaslms/issues" From 6dc468a95bd84dc47220f44056d17f463f7b8aa3 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:54:09 +0100 Subject: [PATCH 048/248] Adds quiz experiment --- experiments/quiz.py | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 experiments/quiz.py diff --git a/experiments/quiz.py b/experiments/quiz.py new file mode 100644 index 0000000..12645da --- /dev/null +++ b/experiments/quiz.py @@ -0,0 +1,61 @@ +import canvasapi +import json +import os + +canvas = canvasapi.Canvas(os.environ["CANVAS_SERVER"], + os.environ["CANVAS_TOKEN"]) + +test_course = None + +for course in canvas.get_courses(): + if course.name == "Sandbox dbosk": + test_course = course + break + +test_quiz = None + +for quiz in course.get_quizzes(): + if quiz.title == "Classic datorprov": + test_quiz = quiz + break + +test_submission = None + +# There is only one in my setup +for quiz_submission in test_quiz.get_submissions(): + test_submission = quiz_submission + +print("\n# Quiz submission questions\n") + +for subm_question in test_submission.get_submission_questions(): + #print(subm_question.__dict__.keys()) + #print(subm_question) + print(f"{subm_question.id} " + f"{subm_question.question_name}:\n" + f"{subm_question.question_text}") + try: + print(f"Alternatives: {subm_question.answers}") + print(f"Correct: {subm_question.correct}") + except AttributeError: + pass + + print() + +print("\n# Quiz submission answers\n") + +quiz = None + +for assignment in test_course.get_assignments(): + if assignment.name == "Classic datorprov": + quiz = assignment + break + +for submission in quiz.get_submissions(include=["submission_history"]): + for subm in submission.submission_history: + #print(subm) + try: + for data in subm["submission_data"]: + print(json.dumps(data, indent=2)) + except KeyError: + pass + From 1b59229a92cc091c3b328102efba408ff12fa22c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:54:22 +0100 Subject: [PATCH 049/248] Removes unused setup.py --- setup.py | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 4b5c6f7..0000000 --- a/setup.py +++ /dev/null @@ -1,47 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name = "canvaslms", - version = "1.17", - author = "Daniel Bosk", - author_email = "dbosk@kth.se", - description = "Command-line interface for Canvas LMS", - long_description = open("README.md").read(), - long_description_content_type = "text/markdown", - url = "https://github.com/dbosk/canvaslms", - project_urls = { - "Bug Tracker": "https://github.com/dbosk/canvaslms/issues", - "Releases": "https://github.com/dbosk/canvaslms/releases" - }, - classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "Topic :: Utilities" - ], - package_dir = { - "": "src" - }, - packages = find_packages(where="src"), - entry_points = { - "console_scripts": [ - "canvaslms = canvaslms.cli:main" - ] - }, - data_files = [ - ("/etc/bash_completion.d", ["canvaslms.bash"]) - ], - python_requires = ">=3.8", - install_requires = [ - "appdirs>=1.4.4", - "argcomplete>=1.12.3", - "cachetools>=4.2.2", - "canvasapi>=2.0.0", - "keyring>=23.0.1", - "pypandoc>=1.6.4" - ] -) From ee925fc12053228776d12d875b0f6bcdde13cce5 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Nov 2022 16:54:47 +0100 Subject: [PATCH 050/248] Updates minor version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 29bc632..c8ab467 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "1.18" +version = "1.19" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 6b929b0d1519e7389ee9ffcb019f39031ab021d1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 14 Nov 2022 10:04:59 +0100 Subject: [PATCH 051/248] Removes use of canvaslms.bash --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index ed9bedd..13b6ab0 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,6 @@ doc/canvaslms.pdf: $(wildcard src/canvaslms/cli/*.tex) publish-canvaslms: compile poetry publish -canvaslms.bash: - register-python-argcomplete canvaslms > $@ - publish-docker: sleep 60 ${MAKE} -C docker publish @@ -43,7 +40,6 @@ publish-docker: .PHONY: clean clean: - ${RM} canvaslms.bash .PHONY: distclean distclean: From 96bc2d2fc094ca4332d9d78e511bf33d78125986 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 14 Nov 2022 10:05:16 +0100 Subject: [PATCH 052/248] Improves Makefile by using variable for targets --- src/canvaslms/cli/Makefile | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/canvaslms/cli/Makefile b/src/canvaslms/cli/Makefile index 75e9a59..4ae6d44 100644 --- a/src/canvaslms/cli/Makefile +++ b/src/canvaslms/cli/Makefile @@ -1,31 +1,26 @@ NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= +MODULES+= __init__.py cli.tex +MODULES+= login.py login.tex +MODULES+= courses.py courses.tex +MODULES+= users.py users.tex +MODULES+= assignments.py assignments.tex +MODULES+= submissions.py submissions.tex +MODULES+= grade.py grade.tex +MODULES+= results.py results.tex .PHONY: all -all: __init__.py cli.tex -all: login.py login.tex -all: courses.py courses.tex -all: users.py users.tex -all: assignments.py assignments.tex -all: submissions.py submissions.tex -all: grade.py grade.tex -all: results.py results.tex +all: ${MODULES} +.INTERMEDIATE: cli.py __init__.py: cli.py mv $< $@ .PHONY: clean clean: - ${RM} cli.tex cli.py __init__.py - ${RM} login.py login.tex - ${RM} courses.tex courses.py - ${RM} users.tex users.py - ${RM} assignments.tex assignments.py - ${RM} submissions.tex submissions.py - ${RM} grade.tex grade.py - ${RM} results.py results.tex + ${RM} ${MODULES} INCLUDE_MAKEFILES=../../../makefiles From 4c321bc03d2e2466f5a835aa1c1dcb154b9e11c1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 15 Nov 2022 20:00:38 +0100 Subject: [PATCH 053/248] Makes courses list only current courses, --all option --- poetry.lock | 2 +- pyproject.toml | 1 + src/canvaslms/cli/courses.nw | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 0249190..f7850f0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -285,7 +285,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "01e061b1117bff57257584085fce1bda65f86573e42c95a6fc7e48d055e0e383" +content-hash = "98ac91c529ff6fb4672e2b1d460edc06b93c9d81b09a541e3bb3560e6f563c0c" [metadata.files] appdirs = [ diff --git a/pyproject.toml b/pyproject.toml index c8ab467..e517cdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ cachetools = "^5.2.0" canvasapi = "^3.0.0" keyring = "^23.11.0" pypandoc = "^1.10" +arrow = "^1.2.3" [tool.poetry.dev-dependencies] diff --git a/src/canvaslms/cli/courses.nw b/src/canvaslms/cli/courses.nw index c853310..6193d46 100644 --- a/src/canvaslms/cli/courses.nw +++ b/src/canvaslms/cli/courses.nw @@ -7,8 +7,10 @@ be used by other subcommands. We outline the module: <>= +import arrow import canvaslms.hacks.canvasapi import csv +import datetime import re import sys @@ -32,6 +34,14 @@ courses_parser.add_argument("regex", help="Regex for filtering courses, default: '.*'") @ +We also take an option [[--all]] to show all courses. +By default we just want to show the courses that haven't ended. +<>= +courses_parser.add_argument("-a", "--all", + action="store_true", default=False, + help="List all courses, by default list only current courses.") +@ + \section{Producing a list of courses, [[courses_command]]}% \label{list-courses-function} @@ -44,12 +54,30 @@ def courses_command(config, canvas, args): output = csv.writer(sys.stdout, delimiter=args.delimiter) course_list = filter_courses(canvas, args.regex) + + <> + for course in course_list: <> @ We will cover the set up and processing of the options in the following sections. +\section{Filter the list of courses} + +We want to filter the courses depending on the [[--all]] argument. +<>= +if not args.all: + is_current_course = lambda x: \ + x.start_at is None \ + or (x.end_at is None and \ + arrow.get(x.start_at)-arrow.now().shift(years=-1) \ + > datetime.timedelta(0)) \ + or x.end_at is not None and arrow.get(x.end_at) > arrow.now() + course_list = filter(is_current_course, course_list) +@ + + \section{Output the course data} We have the course data in a [[course]] object. From 468ff3e033ee9a6ab21589ac4e9b5894f078e50f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 15 Nov 2022 20:01:09 +0100 Subject: [PATCH 054/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e517cdd..01b9ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "1.19" +version = "1.20" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From bb4b4b3b9c13edfbd92d35ccb72e944de99c5276 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 15 Nov 2022 20:04:29 +0100 Subject: [PATCH 055/248] Fixes autocomplete in Docker image --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d8a71a3..d6ff3a5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,5 @@ FROM python:3 RUN pip3 install --no-cache-dir --upgrade canvaslms && \ - ln -f $(find / -name canvaslms.bash) /etc/bash_completion.d + activate-global-python-argcomplete && \ + register-python-argcomplete canvaslms > /etc/bash_completion.d/canvaslms.bash From 318e0264a7d88d35dee32e94d495703ad3a54446 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 16 Nov 2022 20:31:38 +0100 Subject: [PATCH 056/248] Fixes version variable in root Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 13b6ab0..268e3c3 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SUBDIR+= src/canvaslms SUBDIR+= doc SUBDIR+= docker -version=$(shell sed -n 's/^ *version *= *\"\([^\"]\+\)\",/\1/p' pyproject.toml) +version=$(shell sed -n 's/^ *version *= *\"\([^\"]\+\)\"/\1/p' pyproject.toml) .PHONY: all From 72a85c23c53f47203aaf3cdd224b59a7b59d1ae8 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 14 Dec 2022 17:33:19 +0100 Subject: [PATCH 057/248] Makes grade take mandatory -u option --- src/canvaslms/cli/grade.nw | 6 +++++- src/canvaslms/cli/users.nw | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/canvaslms/cli/grade.nw b/src/canvaslms/cli/grade.nw index 916d20b..272546c 100644 --- a/src/canvaslms/cli/grade.nw +++ b/src/canvaslms/cli/grade.nw @@ -5,7 +5,7 @@ submission. We outline the module: <>= -import canvaslms.cli.submissions as submissions +from canvaslms.cli import submissions, users import webbrowser <> @@ -20,6 +20,9 @@ def add_command(subp): We add the subparser for [[grade]]. We must identify submissions, for this we use the options provided by [[add_submission_options]] (\cref{submission-options}). +However, we also need the user option. +The [[add_submission_options]] function will add the user option, but we want +to make the user option mandatory, so we will add it ourselves first. <>= grade_parser = subp.add_parser("grade", help="Grades assignments (hic sunt dracones!)", @@ -27,6 +30,7 @@ grade_parser = subp.add_parser("grade", "the regex matching is very powerful, " "be certain that you match what you think!") grade_parser.set_defaults(func=grade_command) +users.add_user_option(grade_parser, required=True) submissions.add_submission_options(grade_parser) <> @ Now, that [[grade_command]] function must take three arguments: [[config]], diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index c2d8fed..5677dcb 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -417,23 +417,32 @@ We provide the two helper functions for other modules to filter out users from a set of courses. This option requires the course option from [[canvaslms.cli.courses]]. <>= -def add_user_option_wo_depends(parser): - """Adds the -u option to argparse parser, - without adding other required options""" +def add_user_option_wo_depends(parser, required=False): + """ + Adds the -u option to argparse parser, + without adding other required options + """ user_parser = parser.add_argument_group("filter by user") + + help="Filter users on name, login ID or Canvas ID by user_regex" + options = {"required": required} + if not required: + options["default"] = ".*" + help += ", default: '.*'" + else: + help += ", required: use '.*' to match all students" + user_parser.add_argument("-u", "--user", metavar="user_regex", - required=False, default=".*", - help="Filter users on name, login ID or Canvas ID by user_regex, " - "default: '.*'") + help=help, **options) -def add_user_option(parser): +def add_user_option(parser, required=False): """Adds the -u option to argparse parser""" try: courses.add_course_option(parser) except argparse.ArgumentError: pass - add_user_option_wo_depends(parser) + add_user_option_wo_depends(parser, required) @ When processing this option, we need to filter by course first. From a4e61fa780e78180a8f4c51cbf63a1d9a725df2a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 14 Dec 2022 17:33:59 +0100 Subject: [PATCH 058/248] Bumps version: breaking change `grade -u` mandatory --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01b9ad1..1193f02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "1.20" +version = "2.0" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 97bc11cb4e9f60372321ea4ead15623c1f171ae9 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Dec 2022 12:10:00 +0100 Subject: [PATCH 059/248] Downgrades canvasapi to <3.0.0 --- poetry.lock | 49 +++++++++++++++++++++++++++++++++---------------- pyproject.toml | 2 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index f7850f0..e9d4173 100644 --- a/poetry.lock +++ b/poetry.lock @@ -38,20 +38,19 @@ python-versions = "~=3.7" [[package]] name = "canvasapi" -version = "3.0.0" +version = "2.2.0" description = "API wrapper for the Canvas LMS" category = "main" optional = false python-versions = "*" [package.dependencies] -arrow = "*" pytz = "*" requests = "*" [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -81,7 +80,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "cryptography" -version = "38.0.3" +version = "38.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -108,7 +107,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "5.0.0" +version = "5.2.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -118,9 +117,24 @@ python-versions = ">=3.7" zipp = ">=0.5" [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"] + +[[package]] +name = "importlib-resources" +version = "5.10.1" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [[package]] name = "jaraco.classes" @@ -151,7 +165,7 @@ trio = ["trio", "async-generator"] [[package]] name = "keyring" -version = "23.11.0" +version = "23.13.1" description = "Store and access your passwords safely." category = "main" optional = false @@ -159,14 +173,16 @@ python-versions = ">=3.7" [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} "jaraco.classes" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] +completion = ["shtab"] docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [[package]] name = "more-itertools" @@ -205,7 +221,7 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.6" +version = "2022.7" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -259,11 +275,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] @@ -272,7 +288,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.10.0" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -280,12 +296,12 @@ python-versions = ">=3.7" [package.extras] docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "98ac91c529ff6fb4672e2b1d460edc06b93c9d81b09a541e3bb3560e6f563c0c" +content-hash = "76e27cd046a588ceee46d6debd01728f03e0181822a5d2043c013f5620d5a677" [metadata.files] appdirs = [ @@ -308,6 +324,7 @@ charset-normalizer = [] cryptography = [] idna = [] importlib-metadata = [] +importlib-resources = [] "jaraco.classes" = [] jeepney = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, diff --git a/pyproject.toml b/pyproject.toml index 1193f02..85b0a3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ python = "^3.8" appdirs = "^1.4.4" argcomplete = "^2.0.0" cachetools = "^5.2.0" -canvasapi = "^3.0.0" +canvasapi = "<3.0.0" keyring = "^23.11.0" pypandoc = "^1.10" arrow = "^1.2.3" From f85a5d96b125502a69bfb40f26bf60041a89da80 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Dec 2022 12:10:29 +0100 Subject: [PATCH 060/248] Bumps minor version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 85b0a3f..d73a08d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.0" +version = "2.1" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 9259272750256d56f4a371f598ddcbd9d8b4965b Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Dec 2022 20:14:32 +0100 Subject: [PATCH 061/248] Bugfix: add try-except in add_user_or_group_option Not having this failed on the user option causing the group options to never be added. --- src/canvaslms/cli/users.nw | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index 5677dcb..a943cba 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -531,8 +531,15 @@ def add_user_or_group_option(parser): except argparse.ArgumentError: pass - add_user_option_wo_depends(parser) - add_group_option_wo_depends(parser) + try: + add_user_option_wo_depends(parser) + except argparse.ArgumentError: + pass + + try: + add_group_option_wo_depends(parser) + except argparse.ArgumentError: + pass def process_user_or_group_option(canvas, args): """Returns a list of users, filtered either by user regex or by groups""" From 9ec64dbb1384934b251e60ad913b6d5004651967 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Dec 2022 09:07:13 +0100 Subject: [PATCH 062/248] Makes arguments properly required for grade command This fixes some bugs in the previous attempt. --- src/canvaslms/cli/assignments.nw | 11 +++++++++-- src/canvaslms/cli/courses.nw | 6 ++++-- src/canvaslms/cli/grade.nw | 7 ++----- src/canvaslms/cli/submissions.nw | 8 +++++--- src/canvaslms/cli/users.nw | 22 +++++++++++++--------- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 2bb163e..f21a691 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -50,10 +50,13 @@ When we select assignments, we have the option to only select those assignments with ungraded submissions. However, this option is not always relevant, so we provide a way to disable it. But to select an assignment, we must first select a course. + +If the [[required]] option is set, we want to make all (relevant) options +required. <>= -def add_assignment_option(parser, ungraded=True): +def add_assignment_option(parser, ungraded=True, required=False): try: - courses.add_course_option(parser) + courses.add_course_option(parser, required=required) except argparse.ArgumentError: pass @@ -79,11 +82,15 @@ In summary, we want the following options: These regular expressions match either the name or the Canvas identifier. This lets us add the following arguments. Remember, we add only the ungraded option if that was requested. +Also, if we want the required version, we want to require either an assignment +or an assignment group to be specified. <>= if ungraded: parser.add_argument("-U", "--ungraded", action="store_true", help="Filter only assignments with ungraded submissions.") +parser = parser.add_mutually_exclusive_group(required=required) + parser.add_argument("-a", "--assignment", required=False, default=".*", help="Regex matching assignment title or Canvas identifier, " diff --git a/src/canvaslms/cli/courses.nw b/src/canvaslms/cli/courses.nw index 6193d46..ec93dff 100644 --- a/src/canvaslms/cli/courses.nw +++ b/src/canvaslms/cli/courses.nw @@ -125,8 +125,10 @@ name or Canvas ID against a regular expression. We provide a function that can be used by other subcommands to set up options for selecting a course in this way. +If the [[required]] option is specified, we want the course option to be +required (\eg for the [[grade]] command). <>= -def add_course_option(parser): +def add_course_option(parser, required=False): """Adds the -c option to argparse parser to filter out courses""" <> @@ -139,7 +141,7 @@ def process_course_option(canvas, args): We need a course, so we require a regular expression that matches the course title, course code or Canvas identifier. <>= -parser.add_argument("-c", "--course", required=False, default=".*", +parser.add_argument("-c", "--course", required=required, default=".*", help="Regex matching courses on title, course code or Canvas ID, " "default: '.*'") @ diff --git a/src/canvaslms/cli/grade.nw b/src/canvaslms/cli/grade.nw index 272546c..6e93010 100644 --- a/src/canvaslms/cli/grade.nw +++ b/src/canvaslms/cli/grade.nw @@ -20,9 +20,7 @@ def add_command(subp): We add the subparser for [[grade]]. We must identify submissions, for this we use the options provided by [[add_submission_options]] (\cref{submission-options}). -However, we also need the user option. -The [[add_submission_options]] function will add the user option, but we want -to make the user option mandatory, so we will add it ourselves first. +We will add [[required=True]] so that we get all options as required. <>= grade_parser = subp.add_parser("grade", help="Grades assignments (hic sunt dracones!)", @@ -30,8 +28,7 @@ grade_parser = subp.add_parser("grade", "the regex matching is very powerful, " "be certain that you match what you think!") grade_parser.set_defaults(func=grade_command) -users.add_user_option(grade_parser, required=True) -submissions.add_submission_options(grade_parser) +submissions.add_submission_options(grade_parser, required=True) <> @ Now, that [[grade_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index c879492..abacc4f 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -167,15 +167,17 @@ For this we need \item a user or group, \item to know if we aim for all or just ungraded submissions. \end{itemize} +We add the [[required]] parameter to specify if we want to have required +arguments, \eg for the [[grade]] command. <>= -def add_submission_options(parser): +def add_submission_options(parser, required=False): try: - assignments.add_assignment_option(parser) + assignments.add_assignment_option(parser, required=required) except argparse.ArgumentError: pass try: - users.add_user_or_group_option(parser) + users.add_user_or_group_option(parser, required=required) except argparse.ArgumentError: pass diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index a943cba..b7b6655 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -422,8 +422,6 @@ def add_user_option_wo_depends(parser, required=False): Adds the -u option to argparse parser, without adding other required options """ - user_parser = parser.add_argument_group("filter by user") - help="Filter users on name, login ID or Canvas ID by user_regex" options = {"required": required} if not required: @@ -432,7 +430,7 @@ def add_user_option_wo_depends(parser, required=False): else: help += ", required: use '.*' to match all students" - user_parser.add_argument("-u", "--user", metavar="user_regex", + parser.add_argument("-u", "--user", metavar="user_regex", help=help, **options) def add_user_option(parser, required=False): @@ -480,12 +478,16 @@ def add_group_category_option(parser): add_group_category_option_wo_depends(parser) def add_group_option_wo_depends(parser): - """Adds group filtering option to argparse parser, - without adding required options""" + """ + Adds group filtering option to argparse parser, + without adding required options + """ + try: + add_group_category_option_wo_depends(parser) + except argparse.ArgumentError: + pass - group_parser = parser.add_argument_group("filter by group") - add_group_category_option_wo_depends(group_parser) - group_parser.add_argument("-G", "--group", metavar="group_regex", + parser.add_argument("-G", "--group", metavar="group_regex", required=False, help="Filters groups whose name match group_regex") @@ -524,13 +526,15 @@ only one option can be used at a time. The processing must return a list of users, so in case of the group option we must extract the users. <>= -def add_user_or_group_option(parser): +def add_user_or_group_option(parser, required=False): """Adds user and group options as mutually exclusive options to parser""" try: courses.add_course_option(parser) except argparse.ArgumentError: pass + parser = parser.add_mutually_exclusive_group(required=required) + try: add_user_option_wo_depends(parser) except argparse.ArgumentError: From 5ea80be3952a15a9ea3ccd75099754d00006b4e3 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Dec 2022 09:08:43 +0100 Subject: [PATCH 063/248] Bumps version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d73a08d..1d70446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.1" +version = "2.2" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 954858f089981aca2953fa07a0256d7b1da7a892 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 3 Jan 2023 21:05:29 +0100 Subject: [PATCH 064/248] Makes submission use rich for output (fixes #71) Before, the output of the submission command was markdown in the terminal. Now it puts that markdown through rich. This adds syntax highlighting for the code. --- poetry.lock | 52 +++++++++++++++++++++++++++++++- pyproject.toml | 1 + src/canvaslms/cli/submissions.nw | 52 +++++++++++++++++++++++++++++--- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index e9d4173..ff630fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -78,6 +78,17 @@ python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + [[package]] name = "cryptography" version = "38.0.4" @@ -200,6 +211,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pypandoc" version = "1.10" @@ -253,6 +275,22 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.0.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + [[package]] name = "secretstorage" version = "3.3.3" @@ -273,6 +311,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "urllib3" version = "1.26.13" @@ -301,7 +347,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-co [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "76e27cd046a588ceee46d6debd01728f03e0181822a5d2043c013f5620d5a677" +content-hash = "84281c69b6aa90be727f49f48526839b8e47e4e76517906fef81f8fda4dd75d6" [metadata.files] appdirs = [ @@ -321,6 +367,7 @@ canvasapi = [] certifi = [] cffi = [] charset-normalizer = [] +commonmark = [] cryptography = [] idna = [] importlib-metadata = [] @@ -336,6 +383,7 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +pygments = [] pypandoc = [] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, @@ -347,10 +395,12 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] requests = [] +rich = [] secretstorage = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +typing-extensions = [] urllib3 = [] zipp = [] diff --git a/pyproject.toml b/pyproject.toml index 1d70446..1c7dd8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ canvasapi = "<3.0.0" keyring = "^23.11.0" pypandoc = "^1.10" arrow = "^1.2.3" +rich = "^13.0.0" [tool.poetry.dev-dependencies] diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index abacc4f..8041802 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -15,9 +15,11 @@ import canvaslms.hacks.canvasapi import argparse import csv import json -import pydoc +import os import pypandoc import re +import rich.console +import rich.markdown import sys import urllib.request @@ -149,10 +151,38 @@ def submission_command(config, canvas, args): <> @ -Then we can fetch the submission. +Then we can fetch the submission, format it to markdown ([[format_submission]]) +and then print it. +We use the [[rich]] package to print it. +This prints the markdown output of [[format_submission]] nicer in the terminal. +It also adds syntax highlighting for the source code attachments. +However, we need to adapt the use of styles to the pager to be used. <>= +console = rich.console.Console() for submission in submission_list: - pydoc.pager(format_submission(submission)) + <> + with console.pager(styles=styles): + console.print(rich.markdown.Markdown(format_submission(submission))) +@ + +\subsection{Check if we should use styles} + +By default, [[rich.console.Console]] uses the [[pydoc.pager]], which uses the +system pager (as determined by environment variables etc.). +The default usually can't handle colours, so [[rich]] doesn't use colours when +paging. +We want to check if [[less -r]] or [[less -R]] is set as the pager, in that +case we can use styles. +<>= +pager = "" +if "MANPAGER" in os.environ: + pager = os.environ["MANPAGER"] +elif "PAGER" in os.environ: + pager = os.environ["PAGER"] + +styles = False +if "less" in pager and ("-R" in pager or "-r" in pager): + styles = True @ @@ -376,16 +406,28 @@ except AttributeError: \subsection{Attachments} +For the attachment, we want to add it as a Markdown code block. +This means that we can set what type of data the code block contains. +We compute this text from the content type of the attachment. +For instance, Python source code is [[text/x-python]]. +We remove the [[text/]] prefix and check if there is any [[x-]] prefix left, in +which case we remove that as well. <>= try: for attachment in submission.attachments: if "text/" not in attachment["content-type"]: continue - contents = urllib.request.urlopen(attachment["url"]).read().decode("utf-8") + content_type = attachment["content-type"][len("text/"):] + if content_type.startswith("x-"): + content_type = content_type[2:] + + contents = urllib.request.urlopen(attachment["url"]).read().decode("utf8") + formatted_submission += format_section( attachment["filename"], - f"```\n{contents}\n```") + f"```{content_type}\n{contents}\n```" + ) except AttributeError: pass @ From ab7dfc0c11b748a82de76f3efecb7b60d8e2557d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 3 Jan 2023 21:16:11 +0100 Subject: [PATCH 065/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1c7dd8c..533b260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.2" +version = "2.3" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 090c2f8b2d456a9a441148959940fdf6d0dc2f58 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 3 Jan 2023 21:31:01 +0100 Subject: [PATCH 066/248] Only use rich if stdout is tty --- src/canvaslms/cli/submissions.nw | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 8041802..037ba6a 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -157,12 +157,19 @@ We use the [[rich]] package to print it. This prints the markdown output of [[format_submission]] nicer in the terminal. It also adds syntax highlighting for the source code attachments. However, we need to adapt the use of styles to the pager to be used. +If stdout is not a terminal, we don't use [[rich]], then we simply print the +raw markdown. <>= console = rich.console.Console() for submission in submission_list: - <> - with console.pager(styles=styles): - console.print(rich.markdown.Markdown(format_submission(submission))) + output = format_submission(submission) + + if sys.stdout.isatty(): + <> + with console.pager(styles=styles): + console.print(rich.markdown.Markdown(output)) + else: + print(output) @ \subsection{Check if we should use styles} From 8ad86c5e29ad5477fc4f83c59b9210ecc027fa48 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 3 Jan 2023 22:55:05 +0100 Subject: [PATCH 067/248] Adds rich to output of assignment command too --- src/canvaslms/cli/assignments.nw | 35 ++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index f21a691..8d30e5c 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -17,9 +17,11 @@ import canvasapi import canvaslms.cli.courses as courses import canvaslms.hacks.canvasapi import csv -import pydoc +import os import pypandoc import re +import rich.console +import rich.markdown import sys <> @@ -205,9 +207,38 @@ the options that we added with the [[add_course_option]] and [[add_assignment_option]] functions above. <>= def assignment_command(config, canvas, args): + console = rich.console.Console() + assignment_list = process_assignment_option(canvas, args) for assignment in assignment_list: - pydoc.pager(format_assignment(assignment)) + output = format_assignment(assignment) + + if sys.stdout.isatty(): + <> + with console.pager(styles=styles, links=True): + console.print(rich.markdown.Markdown(output)) + else: + print(output) +@ + +\subsection{Check if we should use styles} + +By default, [[rich.console.Console]] uses the [[pydoc.pager]], which uses the +system pager (as determined by environment variables etc.). +The default usually can't handle colours, so [[rich]] doesn't use colours when +paging. +We want to check if [[less -r]] or [[less -R]] is set as the pager, in that +case we can use styles. +<>= +pager = "" +if "MANPAGER" in os.environ: + pager = os.environ["MANPAGER"] +elif "PAGER" in os.environ: + pager = os.environ["PAGER"] + +styles = False +if "less" in pager and ("-R" in pager or "-r" in pager): + styles = True @ From e4406a8419d20752bc1bd889ce8a4b3f63299f76 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 3 Jan 2023 22:55:31 +0100 Subject: [PATCH 068/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 533b260..65bea70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.3" +version = "2.4" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 9340ae6e29f10dfa6945eddcedd0a6b1df56f701 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 5 Jan 2023 21:59:53 +0100 Subject: [PATCH 069/248] Changes theme for code syntax highlighting This makes it work in both light and dark terminals. The default (monokai), changed to dark background, but didn't change font color from the terminal default. This yielded unreadable black on black. --- pyproject.toml | 2 +- src/canvaslms/cli/assignments.nw | 6 ++++-- src/canvaslms/cli/submissions.nw | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 65bea70..cce0431 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.4" +version = "2.5" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 8d30e5c..7e2b024 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -216,10 +216,12 @@ def assignment_command(config, canvas, args): if sys.stdout.isatty(): <> with console.pager(styles=styles, links=True): - console.print(rich.markdown.Markdown(output)) + console.print(rich.markdown.Markdown(output, + code_theme="manni")) else: print(output) -@ +@ Note that we use the theme [[manni]] for the code, as this works in both dark +and light terminals. \subsection{Check if we should use styles} diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 037ba6a..973b560 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -167,10 +167,12 @@ for submission in submission_list: if sys.stdout.isatty(): <> with console.pager(styles=styles): - console.print(rich.markdown.Markdown(output)) + console.print(rich.markdown.Markdown(output, + code_theme="manni")) else: print(output) -@ +@ Note that we use the theme [[manni]] for the code, as this works in both dark +and light terminals. \subsection{Check if we should use styles} From 7af62f257c065e187cefcde8ca4b182588ca7015 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 5 Jan 2023 22:16:08 +0100 Subject: [PATCH 070/248] Improves content-type to markdown conversion --- src/canvaslms/cli/submissions.nw | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 973b560..329ac69 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -424,13 +424,10 @@ which case we remove that as well. <>= try: for attachment in submission.attachments: - if "text/" not in attachment["content-type"]: + content_type = ct_to_md(attachment["content-type"]) + if not content_type: continue - content_type = attachment["content-type"][len("text/"):] - if content_type.startswith("x-"): - content_type = content_type[2:] - contents = urllib.request.urlopen(attachment["url"]).read().decode("utf8") formatted_submission += format_section( @@ -441,6 +438,30 @@ except AttributeError: pass @ +Now, we want to change the content type to the format expected by Markdown to +do proper syntax highlighting. +This requires some ugly processing since one [[.py]] file might have content +type [[text/x-python]] and another [[.py]] file might have +[[text/python-script]]. +<>= +def ct_to_md(content_type): + """ + Takes a content type, returns Markdown code block type. + Returns None if not possible. + """ + if content_type.startswith("text/"): + content_type = content_type[len("text/"):] + else: + return None + + if content_type.startswith("x-"): + content_type = content_type[2:] + if content_type == "python-script": + content_type = "python" + + return content_type +@ + \subsection{Submission history} <>= From 0a1c6248833892e8459dd9405d547514e144fdbf Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 26 Jan 2023 18:56:38 +0100 Subject: [PATCH 071/248] Adds disjunctmaxsurvey module --- src/canvaslms/grades/.gitignore | 3 + src/canvaslms/grades/Makefile | 2 + src/canvaslms/grades/disjunctmax.nw | 50 +++++++------- src/canvaslms/grades/disjunctmaxsurvey.nw | 82 +++++++++++++++++++++++ 4 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/canvaslms/grades/disjunctmaxsurvey.nw diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index d59dbab..1c0cab0 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -4,3 +4,6 @@ conjunctavg.tex conjunctavg.py disjunctmax.tex disjunctmax.py +disjunctmaxsurvey.py +disjunctmaxsurvey.tex + diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 89eb8c3..166138e 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -6,9 +6,11 @@ NOTANGLEFLAGS.py= all: grades.tex all: conjunctavg.py conjunctavg.tex all: disjunctmax.py disjunctmax.tex +all: disjunctmaxsurvey.py disjunctmaxsurvey.tex grades.tex: conjunctavg.tex grades.tex: disjunctmax.tex +grades.tex: disjunctmaxsurvey.tex .PHONY: clean diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 8d1f9c4..a9d7f67 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -1,13 +1,8 @@ \section{Disjunctive maximum} -We have one requirement on the summary module: it must contain a function -[[summarize_group]] that takes two arguments; -the first being a list of assignments, -the second being a list of users. -The [[summarize_group]] function is the function that the above code will call. -This gives the following outline of the module. -<>= -""" +In this module we want to provide the disjunctive maximum way of computing +\emph{one grade} from \emph{several assignments}. +<>= Module that summarizes an assignment group by disjunctive maximum. Disjunctive maximum means: @@ -16,6 +11,20 @@ Disjunctive maximum means: 2) If there are more than one assignment with a non-F grade, we take the maximum as the grade. A--E are valued higher than P. The grade F is valued the lowest. + +We fail if there is an assignment which doesn't have A--F or P/F grading +scales. +@ + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +<> """ import datetime as dt @@ -32,19 +41,16 @@ def summarize_group(assignments_list, users_list): @ -\subsection{Summarizing grades: assignment grades to component grade} +\subsection{Summarizing grades: assignment grades to component grade}% +\label{disjunctmax-summarize} Now we will describe the [[summarize]] helper function. We want to establish two things: the most recent date and a suitable grade. -For the most recent date, we just check the dates as we iterate through the +For the most recent date, we just add them to a list as we iterate through the submissions. - -For the grade, as we iterate through we look for P/F and A--E grades. -We can then check for Fs among the P/F grades, if we find an F the summarized -grade will be an F. -If we find no Fs, then we can compute the average over all A--E grades and use -that as the final grade. +We do the same for grades, as we iterate through we add any grade to a list. +In the end we compute the maximums of both lists. <>= def summarize(user, assignments_list): """Extracts user's submissions for assignments in assingments_list to @@ -52,7 +58,7 @@ def summarize(user, assignments_list): maximum.""" grades = [] - recent_date = dt.date(year=1970, month=1, day=1) + dates = [] for assignment in assignments_list: submission = assignment.get_submission(user) @@ -65,15 +71,11 @@ def summarize(user, assignments_list): grade_date = submission.submitted_at or submission.graded_at - if not grade_date: - grade_date = recent_date - else: + if grade_date: grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) - if grade_date > recent_date: - recent_date = grade_date - - return (grade_max(grades), recent_date) + return (grade_max(grades), max(dates)) @ \subsection{Computing the maximum} diff --git a/src/canvaslms/grades/disjunctmaxsurvey.nw b/src/canvaslms/grades/disjunctmaxsurvey.nw new file mode 100644 index 0000000..c5a978b --- /dev/null +++ b/src/canvaslms/grades/disjunctmaxsurvey.nw @@ -0,0 +1,82 @@ +\section{Disjunctive maximum with surveys} + +In this module we want to provide the disjunctive maximum way of computing +\emph{one grade} from \emph{several assignments}. +But we also want to include ungraded surveys. +<>= +Module that summarizes an assignment group by maximizing grade and date. This +module is the same as `canvaslms.grades.disjunctmax`, but also includes +ungraded surveys (for instance quiz with points, where the number of points is +ignored). Consequently all assignments must have a date. + +This function also doen't fail when there is a grade other than A--F or P/F +present. Such grades are all treated as F. +@ + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +<> +""" + +import datetime as dt +from canvaslms.grades.disjunctmax import grade_max + +<> + +def summarize_group(assignments_list, users_list): + """Summarizes a particular set of assignments (assignments_list) for all + users in users_list""" + + for user in users_list: + grade, grade_date = summarize(user, assignments_list) + yield (user, grade, grade_date) +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish two things: the most recent date and a suitable grade. + +For the most recent date, we just add them to a list as we iterate through the +submissions. +We do the same for grades, as we iterate through we add any grade to a list. +In the end we compute the maximums of both lists. + +The key difference to the [[summarize]] function in +\cref{disjunctmax-Summarize} is the translation of other grades to F. +<>= +def summarize(user, assignments_list): + """Extracts user's submissions for assignments in assingments_list to + summarize results into one grade and a grade date. Summarize by disjunctive + maximum.""" + + grades = [] + dates = [] + + for assignment in assignments_list: + submission = assignment.get_submission(user) + grade = submission.grade + + if grade is None or grade not in "ABCDEPF": + grade = "F" + + grades.append(grade) + + grade_date = submission.submitted_at or submission.graded_at + + if grade_date: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) + + if len(dates) < len(grades): + return ("F", None) + return (grade_max(grades), max(dates)) +@ + From 63a6ce013e12abfccfe5ef74a973bf4c7951fa1c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 10:47:14 +0100 Subject: [PATCH 072/248] Adds maxgradesurvey module to grades --- src/canvaslms/grades/.gitignore | 4 ++-- src/canvaslms/grades/Makefile | 10 ++++++---- src/canvaslms/grades/grades.nw | 1 + .../grades/{disjunctmaxsurvey.nw => maxgradesurvey.nw} | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) rename src/canvaslms/grades/{disjunctmaxsurvey.nw => maxgradesurvey.nw} (99%) diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index 1c0cab0..5c5a01a 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -4,6 +4,6 @@ conjunctavg.tex conjunctavg.py disjunctmax.tex disjunctmax.py -disjunctmaxsurvey.py -disjunctmaxsurvey.tex +maxgradesurvey.py +maxgradesurvey.tex diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 166138e..d229989 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -1,23 +1,25 @@ NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= +MODULES+= conjunctavg.py +MODULES+= disjunctmax.py +MODULES+= maxgradesurvey.py .PHONY: all all: grades.tex all: conjunctavg.py conjunctavg.tex all: disjunctmax.py disjunctmax.tex -all: disjunctmaxsurvey.py disjunctmaxsurvey.tex +all: maxgradesurvey.py maxgradesurvey.tex grades.tex: conjunctavg.tex grades.tex: disjunctmax.tex -grades.tex: disjunctmaxsurvey.tex +grades.tex: maxgradesurvey.tex .PHONY: clean clean: ${RM} grades.tex - ${RM} conjunctavg.py conjunctavg.tex - ${RM} disjunctmax.py disjunctmax.tex + ${RM} ${MODULES} INCLUDE_MAKEFILES=../../../makefiles diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index c5da5be..8e8c45c 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -44,3 +44,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead. \input{../src/canvaslms/grades/conjunctavg.tex} \input{../src/canvaslms/grades/disjunctmax.tex} +\input{../src/canvaslms/grades/maxgradesurvey.tex} diff --git a/src/canvaslms/grades/disjunctmaxsurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw similarity index 99% rename from src/canvaslms/grades/disjunctmaxsurvey.nw rename to src/canvaslms/grades/maxgradesurvey.nw index c5a978b..4076328 100644 --- a/src/canvaslms/grades/disjunctmaxsurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -19,7 +19,7 @@ the first being a list of assignments, the second being a list of users. The [[summarize_group]] function is the function that the above code will call. This gives the following outline of the module. -<>= +<>= """ <> """ From c4c8604847db4d1d697e51e0158e821b4fa0372f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 11:05:44 +0100 Subject: [PATCH 073/248] Adds conjunctavgsurvey to grades --- src/canvaslms/grades/Makefile | 3 + src/canvaslms/grades/conjunctavgsurvey.nw | 91 +++++++++++++++++++++++ src/canvaslms/grades/grades.nw | 1 + 3 files changed, 95 insertions(+) create mode 100644 src/canvaslms/grades/conjunctavgsurvey.nw diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index d229989..19b678a 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -4,16 +4,19 @@ NOTANGLEFLAGS.py= MODULES+= conjunctavg.py MODULES+= disjunctmax.py MODULES+= maxgradesurvey.py +MODULES+= conjunctavgsurvey.py .PHONY: all all: grades.tex all: conjunctavg.py conjunctavg.tex all: disjunctmax.py disjunctmax.tex all: maxgradesurvey.py maxgradesurvey.tex +all: conjunctavgsurvey.py conjunctavgsurvey.tex grades.tex: conjunctavg.tex grades.tex: disjunctmax.tex grades.tex: maxgradesurvey.tex +grades.tex: conjunctavgsurvey.tex .PHONY: clean diff --git a/src/canvaslms/grades/conjunctavgsurvey.nw b/src/canvaslms/grades/conjunctavgsurvey.nw new file mode 100644 index 0000000..28b389d --- /dev/null +++ b/src/canvaslms/grades/conjunctavgsurvey.nw @@ -0,0 +1,91 @@ +\section{Conjunctive average with surveys} + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +This module is the same as `canvaslms.grades.conjunctavg` except that any +submissions with grades other than A--F and P/F are treated as P. For instance, +numeric grades (like points). Also, all submissions must have a date. This +makes this module useful for including mandatory, ungraded surveys. +""" + +import datetime as dt +from canvaslms.grades.conjunctavg import a2e_average + +<> + +def summarize_group(assignments_list, users_list): + """Summarizes a particular set of assignments (assignments_list) for all + users in users_list""" + + for user in users_list: + grade, grade_date = summarize(user, assignments_list) + yield (user, grade, grade_date) +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish two things: the most recent date and a suitable grade. + +For the most recent date, we just check the dates as we iterate through the +submissions. + +For the grade, as we iterate through we look for P/F and A--E grades. +We can then check for Fs among the P/F grades, if we find an F the summarized +grade will be an F. +If we find no Fs, then we can compute the average over all A--E grades and use +that as the final grade. + +If we encounter a grade that is not A--F nor P/F, then it is assumed to be a +survey, which is treated as a P. +<>= +def summarize(user, assignments_list): + """ + Extracts user's submissions for assignments in assingments_list to summarize + results into one grade and a grade date. Summarize by conjunctive average. + + If some submission lacks date, return ("F", None). + """ + + pf_grades = [] + a2e_grades = [] + recent_date = dt.date(year=1970, month=1, day=1) + + for assignment in assignments_list: + submission = assignment.get_submission(user) + grade = submission.grade + + if grade is None: + grade = "F" + + if grade in "ABCDE": + a2e_grades.append(grade) + elif grade in "PF": + pf_grades.append(grade) + else: + pf_grades.append("E") + + grade_date = submission.submitted_at or submission.graded_at + + if not grade_date: + return ("F", None) + else: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + + if grade_date > recent_date: + recent_date = grade_date + + if not all(map(lambda x: x == "P", pf_grades)): + return ("F", recent_date) + + if a2e_grades: + return (a2e_average(a2e_grades), recent_date) + return ("P", recent_date) +@ diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index 8e8c45c..06e328b 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -45,3 +45,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead. \input{../src/canvaslms/grades/conjunctavg.tex} \input{../src/canvaslms/grades/disjunctmax.tex} \input{../src/canvaslms/grades/maxgradesurvey.tex} +\input{../src/canvaslms/grades/conjunctavgsurvey.tex} From fa6e1989d7a68f851509e9eb141925e4a964efd7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 11:06:05 +0100 Subject: [PATCH 074/248] Fixes title of maxgradesurvey --- src/canvaslms/grades/maxgradesurvey.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 4076328..9465fc3 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -1,4 +1,4 @@ -\section{Disjunctive maximum with surveys} +\section{Maximum grade, latest date with surveys} In this module we want to provide the disjunctive maximum way of computing \emph{one grade} from \emph{several assignments}. From 97db0f9e43282df1ca11460d7271d1e68e2b4b89 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 11:06:31 +0100 Subject: [PATCH 075/248] Bumps version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cce0431..de4a762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.5" +version = "2.6" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From f22ad3da41e72096871fceed7cd285cd4c3d9ece Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 11:07:16 +0100 Subject: [PATCH 076/248] Fixes gitignore --- src/canvaslms/grades/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index 5c5a01a..04941ab 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -6,4 +6,5 @@ disjunctmax.tex disjunctmax.py maxgradesurvey.py maxgradesurvey.tex - +conjunctavgsurvey.py +conjunctavgsurvey.tex From 65a74dd0837714efe027656053e2cdf2f5ab8e2a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 27 Jan 2023 11:34:48 +0100 Subject: [PATCH 077/248] Corrects ref label --- src/canvaslms/grades/maxgradesurvey.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 9465fc3..e6c95f0 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -50,7 +50,7 @@ We do the same for grades, as we iterate through we add any grade to a list. In the end we compute the maximums of both lists. The key difference to the [[summarize]] function in -\cref{disjunctmax-Summarize} is the translation of other grades to F. +\cref{disjunctmax-summarize} is the translation of other grades to F. <>= def summarize(user, assignments_list): """Extracts user's submissions for assignments in assingments_list to From be5d350f27f264a43c2620e859c3ba088009f716 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 13:06:59 +0100 Subject: [PATCH 078/248] Improves clarity of documentation --- src/canvaslms/cli/cli.nw | 4 -- src/canvaslms/cli/login.nw | 81 +++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index e59c5e8..0ec1ba1 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -233,7 +233,6 @@ There are two commands related to assignments in the Both are added by the same function call. <>= import canvaslms.cli.assignments -@ <>= canvaslms.cli.assignments.add_command(subp) @ @@ -245,7 +244,6 @@ There are two commands related to submissions in the Both are added by one function call. <>= import canvaslms.cli.submissions -@ <>= canvaslms.cli.submissions.add_command(subp) @ @@ -255,7 +253,6 @@ canvaslms.cli.submissions.add_command(subp) The \texttt{grade} command is located in [[canvaslms.cli.grade]]. <>= import canvaslms.cli.grade -@ <>= canvaslms.cli.grade.add_command(subp) @ @@ -265,7 +262,6 @@ canvaslms.cli.grade.add_command(subp) The \texttt{results} command is located in [[canvaslms.cli.results]]. <>= import canvaslms.cli.results -@ <>= canvaslms.cli.results.add_command(subp) @ diff --git a/src/canvaslms/cli/login.nw b/src/canvaslms/cli/login.nw index b6f4e3d..5c11f54 100644 --- a/src/canvaslms/cli/login.nw +++ b/src/canvaslms/cli/login.nw @@ -1,10 +1,34 @@ -\chapter{Managing credentials} +\chapter{Managing credentials: the \texttt{login} command} We want a subcommand to handle the user's credentials for accessing Canvas. In particular, we need the user to be able to change the credentials in the system keyring, \eg in case the user wrote the wrong password. The rest we don't need to do much about, merely point out the possibilities to the user. +We summarize it like this: +<>= +Manages the user's Canvas login credentials. There are three ways to supply the +login credentials, in order of priority: + +1) Through the system keyring: Just run `canvaslms login` and you'll be guided + to enter the credentials (server name and token) and they will be stored in + the keyring. + +2) Through the environment: Just set the environment variables CANVAS_SERVER + and CANVAS_TOKEN. + +3) Through the configuration file: Just write + + {{ + "canvas": {{ + "host": "the actual hostname", + "access_token": "the actual token" + }} + }} + + to the file {dirs.user_config_dir}/config.json (default, or use the -f + option, see `canvaslms -h`). +@ We outline the module: <>= @@ -22,41 +46,20 @@ def add_command(subp): """Adds the login command to argparse parser""" login_parser = subp.add_parser("login", help="Manage login credentials", - description=""" -Manages the user's Canvas login credentials. There are three ways to supply the -login credentials, in order of priority: - -1) Through the system keyring: Just run `canvaslms login` and you'll be guided - to enter the credentials (server name and token) and they will be stored in - the keyring. - -2) Through the environment: Just set the environment variables CANVAS_SERVER - and CANVAS_TOKEN. - -3) Through the configuration file: Just write - - { - "canvas": { - "host": "the actual hostname", - "access_token": "the actual token" - } - } - - to the file """ + dirs.user_config_dir + """/config.json (default, or use the - -f option, see `canvaslms -h`). + description=f""" +<> """) - login_parser.set_defaults(func=update_credentials_in_keyring) + login_parser.set_defaults(func=login_command) @ -\section{Updating the credentials in the keyring} +\section{The \texttt{login} command function} As stated, if the subcommand is run, we should update the credentials in the keyring. -If we run this subcommand, also want to clear the cache; otherwise, the cache -will keep the outdated credentials. +Or the config file, if there is no keyring. <>= -def update_credentials_in_keyring(config, canvas, args): +def login_command(config, canvas, args): """Guides the user to update credentials""" print("Enter the hostname for Canvas, " @@ -64,13 +67,7 @@ def update_credentials_in_keyring(config, canvas, args): hostname = input("Canvas hostname: ") print(f""" -Open - - https://{hostname}/profile/settings - -in your browser. Scroll down to approved integrations and click the -'+ New access token' button. Fill in the required data and click the -'Generate token' button. Enter the token here. +<> """) token = input("Canvas token: ") @@ -80,15 +77,25 @@ in your browser. Scroll down to approved integrations and click the keyring.set_password("canvaslms", "token", token) except: canvaslms.cli.warn(f"You don't have a working keyring. " - "Will write hostname and token written to config file " - "{args.config_file}.") + f"Will write hostname and token to config file " + f"{args.config_file}.") config["canvas"]["host"] = hostname config["canvas"]["access_token"] = token canvaslms.cli.update_config_file(config, args.config_file) + +<>= +Open + + https://{hostname}/profile/settings + +in your browser. Scroll down to approved integrations and click the +'+ New access token' button. Fill in the required data and click the +'Generate token' button. Enter the token here. @ + \section{Loading user credentials} The [[load_credentials]] function will try to get the user's LADOK credentials. From cdbc09b726219d623eedd1cca5f3d7eddeeeed05 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 13:45:14 +0100 Subject: [PATCH 079/248] Don't create canvas object for login command --- src/canvaslms/cli/cli.nw | 54 ++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 0ec1ba1..42995f9 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -142,14 +142,6 @@ args = argp.parse_args() config = read_configuration(args.config_file) -hostname, token = canvaslms.cli.login.load_credentials(config) - -if not (hostname and token) and \ - args.func != canvaslms.cli.login.update_credentials_in_keyring: - err(1, "No hostname or token, run `canvaslms login`") - -canvas = Canvas(hostname, token) - <> @ @@ -183,26 +175,52 @@ argp.add_argument("-d", "--delimiter", \subsection{The subcommands} Each subcommand will have its own module in the package. -Each such module must have a function [[add_command]] that takes teh -[[subp]] parser as an argument and adds its command and options to that. +Each such module must have a function [[add_command]] that takes the [[subp]] +parser as an argument and adds its command and options to that. +For example, the [[login]] command: +The \texttt{login} command is located in [[canvaslms.cli.login]]. +<>= +import canvaslms.cli.login +<>= +canvaslms.cli.login.add_command(subp) +@ In short, each [[add_command]] must add a subparser ([[subp.add_parser]]) that will set the [[func]] attribute. Then we can execute the correct function and let that function check the remaining arguments. + +Each command function must take three arguments: +\begin{enumerate} +\item the configuration, +\item a Canvas object to use for interaction with Canvas, +\item the processed command-line arguments. +\end{enumerate} +This means that we must read the credentials to create the Canvas object. +One exception is the [[login]] command: this command doesn't need the Canvas +object as it will be run before there are credentials. <>= +if args.func != canvaslms.cli.login.login_command: + <> +else: + canvas = None + if args.func: args.func(config, canvas, args) @ -\paragraph{The \texttt{login} command} +To create the Canvas object, we must read the credentials using the +[[canvaslms.cli.login]] module. +If there are no credentials, we give an error about running the [[login]] +command first. +Otherwise, we create the Canvas object named [[canvas]]. +<>= +hostname, token = canvaslms.cli.login.load_credentials(config) -The \texttt{login} command is located in [[canvaslms.cli.login]]. -<>= -import canvaslms.cli.login -@ -<>= -canvaslms.cli.login.add_command(subp) +if not (hostname and token): + err(1, "No hostname or token, run `canvaslms login`") + +canvas = Canvas(hostname, token) @ \paragraph{The \texttt{courses} command} @@ -211,7 +229,6 @@ The \texttt{courses} command resides in [[canvaslms.cli.courses]] and supports the protocol above. <>= import canvaslms.cli.courses -@ <>= canvaslms.cli.courses.add_command(subp) @ @@ -221,7 +238,6 @@ canvaslms.cli.courses.add_command(subp) The \texttt{users} command resides in [[canvaslms.cli.users]]. <>= import canvaslms.cli.users -@ <>= canvaslms.cli.users.add_command(subp) @ From 1542e5b295c1f81133c1f1e15d1eecf4c1b4bb96 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 21:33:39 +0100 Subject: [PATCH 080/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index de4a762..0cc56bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.6" +version = "2.7" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 1a3aab3b1ea9c5eefe284eeb70d01843216142ce Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 21:48:30 +0100 Subject: [PATCH 081/248] Improves handling of malformed config file --- src/canvaslms/cli/cli.nw | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 42995f9..1d68720 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -33,6 +33,7 @@ import appdirs import argcomplete, argparse from canvasapi import Canvas import canvaslms.cli.login +import json import os import sys @@ -86,12 +87,16 @@ The function gets the configuration file as an argument. We don't return any error if the file doesn't exist. We leave that to the caller to determine what to do if the configuration is empty. +Likewise, we issue a warning if the configuration file is malformed, \ie it +can't be processed as valid JSON. <>= try: with open(config_file, "r") as file: config.update(json.load(file)) except FileNotFoundError: pass +except json.decoder.JSONDecodeError as err: + warn(f"config file is malformed: {err}") @ We can read the credentials from the environment, this takes precedence over From a36a1581b8f7b847999e81f46925ae863b75466f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 21:49:18 +0100 Subject: [PATCH 082/248] Create parent dirs if config dir doesn't exist --- src/canvaslms/cli/cli.nw | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 1d68720..0f1404b 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -35,6 +35,7 @@ from canvasapi import Canvas import canvaslms.cli.login import json import os +import pathlib import sys <> @@ -114,13 +115,24 @@ if "CANVAS_TOKEN" in os.environ: At times, we might want to update the configuration file. When we update the config file, we must ensure that any other part of the -configuration file is kept. -So we write the [[config]] dictionary to the config file. +configuration file is kept, so we write the [[config]] dictionary to the config +file. + +If we get a [[FileNotFoundError]], that means the config directory doesn't +exist and we must create it. <>= def update_config_file(config, config_file): """Updates the config file by writing the config dictionary back to it""" - with open(config_file, "w") as fd: - json.dump(config, fd) + try: + <> + except FileNotFoundError: + <> + <> +<>= +os.makedirs(pathlib.PurePath(config_file).parent) +<>= +with open(config_file, "w") as fd: + json.dump(config, fd) @ From d66d800f15445e2ccf323f219970bb4ebd5b294e Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 16 Feb 2023 21:49:48 +0100 Subject: [PATCH 083/248] Adds protocol to Canvas hostname if needed --- src/canvaslms/cli/cli.nw | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/canvaslms/cli/cli.nw b/src/canvaslms/cli/cli.nw index 0f1404b..f788c71 100644 --- a/src/canvaslms/cli/cli.nw +++ b/src/canvaslms/cli/cli.nw @@ -237,9 +237,19 @@ hostname, token = canvaslms.cli.login.load_credentials(config) if not (hostname and token): err(1, "No hostname or token, run `canvaslms login`") +<> + canvas = Canvas(hostname, token) @ +Now, we must specify a URL to the Canvas server, not actually a hostname. +If the hostname already contains \enquote{https}, fine; if not, we should add +it. +<>= +if "://" not in hostname: + hostname = f"https://{hostname}" +@ + \paragraph{The \texttt{courses} command} The \texttt{courses} command resides in [[canvaslms.cli.courses]] and supports From f29565310b71b6e99f41bdeafa7e2b07255fff08 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 2 Mar 2023 15:47:36 +0100 Subject: [PATCH 084/248] Improves handling of unique identifier for user This improves handling for when the login_id attribute is missing for a user. --- pyproject.toml | 2 +- src/canvaslms/cli/submissions.nw | 37 ++++-- src/canvaslms/cli/users.nw | 187 ++++++++++++++++++++++--------- 3 files changed, 168 insertions(+), 58 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de4a762..0cc56bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.6" +version = "2.7" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 329ac69..b648116 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -300,20 +300,43 @@ def filter_submissions(submission_list, user_list): \section{Printing a submission} We provide two functions to print a submission. -One to print a short identifier and one to print the submission data. +One to print a short summary (row in CSV data) and one to print the submission +data for rendering. +The first is to get an overview of all submissions, the latter to look into the +details of only one submission. We'll format the submission in short format. The most useful data is the identifier, the grade and the date of grading. <>= def format_submission_short(submission): - return [submission.assignment.course.course_code, submission.assignment.name, - submission.user.name, submission.grade, - submission.submitted_at, submission.graded_at] + return [ + submission.assignment.course.course_code, + submission.assignment.name, + submission.user.name, + submission.grade, submission.submitted_at, submission.graded_at + ] +@ +Sometimes we want the short format to contain a unique identifier (such as +[[login_id]]) instead of the name. +<>= def format_submission_short_unique(submission): - return [submission.assignment.course.course_code, submission.assignment.name, - submission.user.login_id, submission.grade, - submission.submitted_at, submission.graded_at] + <> + + return [ + submission.assignment.course.course_code, + submission.assignment.name, + uid, + submission.grade, submission.submitted_at, submission.graded_at + ] +@ + +However, we note that sometimes the student doesn't have a [[login_id]] +attribute, so we can use their [[integration_id]] or [[sis_user_id]] instead +for uniqueness. +See \cref{UserUniqueID} for details. +<>= +uid = users.get_uid(submission.user) @ We provide the function [[format_submission]] to nicely format a submission. diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index b7b6655..135f052 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -24,6 +24,7 @@ import canvaslms.cli import canvaslms.cli.courses as courses import canvaslms.hacks.canvasapi import csv +import operator import re import sys @@ -154,34 +155,70 @@ def make_user_rows(canvas, args, roles): try: row = [] row.append(user.course.course_code) - if args.canvas_id: - row.append(user.id) - row.append(user.login_id) - if args.ladok: - row.append(user.integration_id) - if args.split_name: - lastnames, firstnames = user.sortable_name.split(", ") - row.append(firstnames.strip()) - row.append(lastnames.strip()) - else: - row.append(user.name) - if args.email: - row.append(user.email) + <> except AttributeError as err: canvaslms.cli.warn(f"skipped {user}: {err}") continue yield row -@ Note that in most cases, the [[user.login_id]] and [[user.email]] will be the +@ + +Note that in most cases, the [[user.login_id]] and [[user.email]] will be the same. However, we need both, because the students can change their email address to a non-KTH address. Also note, there are users for whom some attributes don't exist, hence we must first extract them in a try-except construction. This happens if they've been removed from the course or never registered. +<>= +if args.canvas_id: + row.append(user.id) +<> +<> +<> +<> +@ + +Now, some of those attributes are optional and we should check [[args]] whether +the user specified that an attribute should be included. +Some of the attributes might not exist, so we need to try-except them. +(This happens for instance to the [[login_id]] attribute at KTH when the +student is no longer a student with us.) +<>= +try: + row.append(user.login_id) +except AttributeError: + row.append(None) +<>= +if args.ladok: + try: + row.append(user.integration_id) + except AttributeError: + row.append(None) +<>= +if args.email: + try: + row.append(user.email) + except AttributeError: + row.append(None) +@ + +The name attribute always exists, but we must check if we want first and last +names separately or as one string. +To split them, we can use the [[sortable_name]] attribute and split on the [[, +]] following the last name and preceding the first name. +<>= +if args.split_name: + lastnames, firstnames = user.sortable_name.split(", ") + row.append(firstnames.strip()) + row.append(lastnames.strip()) +else: + row.append(user.name) +@ If we filter by groups, then we must iterate over the groups to include the group information. +Other than the group data, we include the user data as before. <>= def make_user_rows_w_groups(canvas, args, roles): """Takes a list of courses and returns a list of users in those courses, @@ -203,19 +240,7 @@ def make_user_rows_w_groups(canvas, args, roles): except AttributeError: pass row.append(group.name) - if args.canvas_id: - row.append(user.id) - row.append(user.login_id) - if args.ladok: - row.append(user.integration_id) - if args.split_name: - lastnames, firstnames = user.sortable_name.split(", ") - row.append(firstnames.strip()) - row.append(lastnames.strip()) - else: - row.append(user.name) - if args.email: - row.append(user.email) + <> except AttributeError as err: canvaslms.cli.warn(f"skipped {user}: {err}") continue @@ -357,35 +382,54 @@ def list_users(courses, roles): Second, we provide the most general function, [[filter_users]], which takes a list of courses, a list of Canvas roles and a regex as arguments. +It returns the matching users. <>= def filter_users(course_list, regex, roles=[]): - """Filter users in courses with roles based on regex - - regex is matched on login ID, Canvas ID and name.""" + """ + Filter users in courses with roles based on regex. `regex` is matched on + - Canvas ID (exact match), + - name (regex), + - login ID (regex), + - integration id (exact match), + - SIS user ID (exact match). + """ pattern = re.compile(regex or ".*") for user in list_users(course_list, roles): - if pattern.search(user.name): - yield user - continue - - try: - if pattern.search(user.login_id): - yield user - continue - except AttributeError: - canvaslms.cli.warn(f"{user} has no login_id") - - if str(user.id) == regex: - yield user - continue + <> +@ - try: - if user.integration_id == regex: - yield user - continue - except AttributeError: - canvaslms.cli.warn(f"{user} has no integration_id") +Now to check if the user matches, we want to match some things by regular +expression, but other things exactly. +<>= +if str(user.id) == regex: + yield user + continue + +if pattern.search(user.name): + yield user + continue + +try: + if pattern.search(user.login_id): + yield user + continue +except AttributeError: + canvaslms.cli.warn(f"{user} has no login_id") + +try: + if user.integration_id == regex: + yield user + continue +except AttributeError: + canvaslms.cli.warn(f"{user} has no integration_id") + +try: + if user.sis_user_id == regex: + yield user + continue +except AttributeError: + canvaslms.cli.warn(f"{user} has no sis_user_id") @ Now, we can define the function~[[list_students]] in terms of [[list_users]]. @@ -400,6 +444,49 @@ def list_teachers(courses): @ +\section{Getting a unique identifier}\label{UserUniqueID} + +In many cases, we want to get a unique identifier for a user. +The natural attribute would be [[login_id]]. +However, sometimes the [[login_id]] attribute doesn't exist. +So we want a function that does the following. +<>= +Takes a user object and returns a unique identifier. + +Returns one of login_id, integration_id, sis_user_id, id (Canvas ID); in that +order of preference. +@ + +This yields the following function. +Try returning the attributes in that order, try the next when failed. +<>= +def get_uid(user): + """ + <> + """ + attributes = ["login_id", "integration_id", "sis_user_id", "id"] + for attribute in attributes: + try: + <> + except AttributeError: + pass + + <> +@ + +To return the attribute, we simply fetch it. +We'll use [[attrgetter]]. +<>= +return operator.attrgetter(attribute)(user) +@ + +If no attribute existed (all failed), which should not happen, then we raise an +exception. +<>= +raise AttributeError(f"no unique user attribute existed, tried: {attributes}") +@ + + \section{Options for the command line} We provide two ways to filter out users by command-line option: From f38a87e857bd0e243e3038acee53869d1dcc44fb Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 2 Mar 2023 15:56:39 +0100 Subject: [PATCH 085/248] Improves root Makefile, bumps version --- Makefile | 6 ++++-- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 268e3c3..3b0c17f 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,10 @@ compile: ${MAKE} -C src/canvaslms all poetry build -.PHONY: publish publish-canvaslms publish-docker -publish: publish-canvaslms doc/canvaslms.pdf publish-docker +.PHONY: publish publish-github publish-canvaslms publish-docker +publish: publish-canvaslms doc/canvaslms.pdf publish-docker publish-github + +publish-github: doc/canvaslms.pdf git push gh release create -t v${version} v${version} doc/canvaslms.pdf diff --git a/pyproject.toml b/pyproject.toml index 0cc56bd..94dc3ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.7" +version = "2.8" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From dc747f2a3163b074cd56ddca79327bfca28ae97f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 1 Jun 2023 09:57:44 +0200 Subject: [PATCH 086/248] Bugfix login: read "host" instead of "hostname" from keyring The login command stores the hostname under "hostname", but the function that loads the credentials tried to read the hostname from "host" instad of "hostname". --- src/canvaslms/cli/login.nw | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/login.nw b/src/canvaslms/cli/login.nw index 5c11f54..ec2de44 100644 --- a/src/canvaslms/cli/login.nw +++ b/src/canvaslms/cli/login.nw @@ -73,8 +73,7 @@ def login_command(config, canvas, args): token = input("Canvas token: ") try: - keyring.set_password("canvaslms", "hostname", hostname) - keyring.set_password("canvaslms", "token", token) + <> except: canvaslms.cli.warn(f"You don't have a working keyring. " f"Will write hostname and token to config file " @@ -95,6 +94,21 @@ in your browser. Scroll down to approved integrations and click the 'Generate token' button. Enter the token here. @ +Now, to keep this data in the keyring, we simply use [[canvaslms]] as the +service, then we store the hostname as the password of user +\enquote{hostname}. +And the same with the token. +<>= +keyring.set_password("canvaslms", "hostname", hostname) +keyring.set_password("canvaslms", "token", token) +@ + +When we need these again, we simply load them. +<>= +hostname = keyring.get_password("canvaslms", "hostname") +token = keyring.get_password("canvaslms", "token") +@ + \section{Loading user credentials} @@ -114,8 +128,7 @@ If all fail, the function will return [[None]] for both. def load_credentials(config): """Load credentials from keyring, environment or config dictionary""" try: - hostname = keyring.get_password("canvaslms", "host") - token = keyring.get_password("canvaslms", "token") + <> if hostname and token: return hostname, token except: From 6b56a03cbf3ddd281f50a19ce5c7dd3969223d5c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 1 Jun 2023 09:59:08 +0200 Subject: [PATCH 087/248] Fixes typo in documentation --- src/canvaslms/cli/login.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/login.nw b/src/canvaslms/cli/login.nw index ec2de44..2324388 100644 --- a/src/canvaslms/cli/login.nw +++ b/src/canvaslms/cli/login.nw @@ -116,7 +116,7 @@ The [[load_credentials]] function will try to get the user's LADOK credentials. There are three locations: \begin{enumerate} \item the system keyring, -\item the environment variables [[LADOK_USER]] and [[LADOK_PASS]], +\item the environment variables [[CANVAS_SERVER]] and [[CANVAS_TOKEN]], \item the configuration file. \end{enumerate} They are given the priority they are listed in above. From ff1d9796eb5d4556a1ab592fe135bc5c50441ed2 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 1 Jun 2023 10:00:52 +0200 Subject: [PATCH 088/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94dc3ae..6556569 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.8" +version = "2.9" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From a38975ce9a17f043bc24a6840e41b1f20fe1fbd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:55:06 +0000 Subject: [PATCH 089/248] Bump cryptography from 38.0.4 to 39.0.1 Bumps [cryptography](https://github.com/pyca/cryptography) from 38.0.4 to 39.0.1. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/38.0.4...39.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 306 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 228 insertions(+), 78 deletions(-) diff --git a/poetry.lock b/poetry.lock index ff630fa..d1820e7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "appdirs" version = "1.4.4" @@ -5,6 +7,10 @@ description = "A small Python module for determining appropriate platform-specif category = "main" optional = false python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] [[package]] name = "argcomplete" @@ -13,6 +19,10 @@ description = "Bash tab completion for argparse" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, + {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, +] [package.extras] test = ["coverage", "flake8", "pexpect", "wheel"] @@ -24,6 +34,10 @@ description = "Better dates & times for Python" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, + {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, +] [package.dependencies] python-dateutil = ">=2.7.0" @@ -35,6 +49,10 @@ description = "Extensible memoizing collections and decorators" category = "main" optional = false python-versions = "~=3.7" +files = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] [[package]] name = "canvasapi" @@ -43,6 +61,9 @@ description = "API wrapper for the Canvas LMS" category = "main" optional = false python-versions = "*" +files = [ + {file = "canvasapi-2.2.0.tar.gz", hash = "sha256:5087db773cac9d92f4f4609b3c160dbeeceb636801421808afee2d438bc43f62"}, +] [package.dependencies] pytz = "*" @@ -55,6 +76,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "cffi" @@ -63,6 +88,72 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] [package.dependencies] pycparser = "*" @@ -74,9 +165,13 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "main" optional = false python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "commonmark" @@ -85,28 +180,59 @@ description = "Python parser for the CommonMark Markdown spec" category = "main" optional = false python-versions = "*" +files = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] [package.extras] test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "cryptography" -version = "38.0.4" +version = "39.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"}, + {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"}, + {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"}, + {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"}, + {file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"}, + {file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"}, + {file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"}, + {file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"}, + {file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"}, +] [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] +test-randomorder = ["pytest-randomly"] +tox = ["tox"] [[package]] name = "idna" @@ -115,6 +241,10 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "importlib-metadata" @@ -123,14 +253,18 @@ description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, + {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, +] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" @@ -139,13 +273,17 @@ description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.10.1-py3-none-any.whl", hash = "sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"}, + {file = "importlib_resources-5.10.1.tar.gz", hash = "sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3"}, +] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "jaraco.classes" @@ -154,13 +292,17 @@ description = "Utility functions for Python class constructs" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "jeepney" @@ -169,10 +311,14 @@ description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] -trio = ["trio", "async-generator"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] [[package]] name = "keyring" @@ -181,6 +327,10 @@ description = "Store and access your passwords safely." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, +] [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} @@ -192,8 +342,8 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab"] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "more-itertools" @@ -202,6 +352,10 @@ description = "More routines for operating on iterables, beyond itertools" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, + {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, +] [[package]] name = "pycparser" @@ -210,6 +364,10 @@ description = "C parser in Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] [[package]] name = "pygments" @@ -218,6 +376,10 @@ description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] [package.extras] plugins = ["importlib-metadata"] @@ -229,6 +391,10 @@ description = "Thin wrapper for pandoc." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pypandoc-1.10-py3-none-any.whl", hash = "sha256:ff9658cda18865a822695349a7c707756ecc454b59b71f920b554579ec54aaa7"}, + {file = "pypandoc-1.10.tar.gz", hash = "sha256:101164d154f0b9957372cdbf285396153b74144fda47f531fb556de244efc86e"}, +] [[package]] name = "python-dateutil" @@ -237,6 +403,10 @@ description = "Extensions to the standard Python datetime module" category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" @@ -248,6 +418,10 @@ description = "World timezone definitions, modern and historical" category = "main" optional = false python-versions = "*" +files = [ + {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"}, + {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"}, +] [[package]] name = "pywin32-ctypes" @@ -256,6 +430,10 @@ description = "" category = "main" optional = false python-versions = "*" +files = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] [[package]] name = "requests" @@ -264,6 +442,10 @@ description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -273,7 +455,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" @@ -282,6 +464,10 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar category = "main" optional = false python-versions = ">=3.7.0" +files = [ + {file = "rich-13.0.0-py3-none-any.whl", hash = "sha256:12b1d77ee7edf251b741531323f0d990f5f570a4e7c054d0bfb59fb7981ad977"}, + {file = "rich-13.0.0.tar.gz", hash = "sha256:3aa9eba7219b8c575c6494446a59f702552efe1aa261e7eeb95548fa586e1950"}, +] [package.dependencies] commonmark = ">=0.9.0,<0.10.0" @@ -298,6 +484,10 @@ description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] [package.dependencies] cryptography = ">=2.0" @@ -310,6 +500,10 @@ description = "Python 2 and 3 compatibility utilities" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "typing-extensions" @@ -318,6 +512,10 @@ description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] [[package]] name = "urllib3" @@ -326,10 +524,14 @@ description = "HTTP library with thread-safe connection pooling, file post, and category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -339,68 +541,16 @@ description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] [package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.8" content-hash = "84281c69b6aa90be727f49f48526839b8e47e4e76517906fef81f8fda4dd75d6" - -[metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -argcomplete = [ - {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, - {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, -] -arrow = [] -cachetools = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, -] -canvasapi = [] -certifi = [] -cffi = [] -charset-normalizer = [] -commonmark = [] -cryptography = [] -idna = [] -importlib-metadata = [] -importlib-resources = [] -"jaraco.classes" = [] -jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] -keyring = [] -more-itertools = [] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pygments = [] -pypandoc = [] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -pytz = [] -pywin32-ctypes = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, -] -requests = [] -rich = [] -secretstorage = [] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -typing-extensions = [] -urllib3 = [] -zipp = [] From 9764ea016f3b1e1a2b5afc3f46676655725e84c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:53:11 +0000 Subject: [PATCH 090/248] Bump requests from 2.28.1 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.28.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index d1820e7..f8adb01 100644 --- a/poetry.lock +++ b/poetry.lock @@ -437,21 +437,21 @@ files = [ [[package]] name = "requests" -version = "2.28.1" +version = "2.31.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] From 650848ec18fd609c0a5d54fa8a6efa6b89674220 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 1 Jun 2023 13:56:07 +0200 Subject: [PATCH 091/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6556569..9cf90fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.9" +version = "2.10" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From e3fa6bfd76847e1c39e70ccb40542ad561d6adf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:20:26 +0000 Subject: [PATCH 092/248] Bump cryptography from 39.0.1 to 41.0.0 Bumps [cryptography](https://github.com/pyca/cryptography) from 39.0.1 to 41.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/39.0.1...41.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 54 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index f8adb01..6cd48be 100644 --- a/poetry.lock +++ b/poetry.lock @@ -190,35 +190,31 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "cryptography" -version = "39.0.1" +version = "41.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"}, - {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"}, - {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"}, - {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"}, - {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"}, - {file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"}, - {file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"}, - {file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"}, - {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"}, - {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"}, - {file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"}, - {file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"}, - {file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"}, + {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8"}, + {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0"}, + {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d"}, + {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46"}, + {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237"}, + {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4"}, + {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75"}, + {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d"}, + {file = "cryptography-41.0.0-cp37-abi3-win32.whl", hash = "sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928"}, + {file = "cryptography-41.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be"}, + {file = "cryptography-41.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5"}, + {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb"}, + {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be"}, + {file = "cryptography-41.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9"}, + {file = "cryptography-41.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2"}, + {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d"}, + {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895"}, + {file = "cryptography-41.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55"}, + {file = "cryptography-41.0.0.tar.gz", hash = "sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78"}, ] [package.dependencies] @@ -227,12 +223,12 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -tox = ["tox"] [[package]] name = "idna" From 18c1860ac35acd85412608c66960d37e127fe85d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 7 Jun 2023 20:33:54 +0200 Subject: [PATCH 093/248] Adds grader to submission command output --- src/canvaslms/cli/submissions.nw | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index b648116..de279eb 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -374,6 +374,8 @@ def format_section(title, body): \subsection{Metadata} +To format the metadata section, we simply pass the right strings to the section +formatting function. <>= formatted_submission += format_section( "Metadata", @@ -385,9 +387,31 @@ formatted_submission += format_section( f" - Submitted (graded): {submission.submitted_at} " f"({submission.graded_at})\n" f" - Grade: {submission.grade} ({submission.score})\n" + f" - Graded by: {resolve_grader(submission)}\n" f" - SpeedGrader: {speedgrader(submission)}") @ +Now to resolve the grader, we need to look up a user ID. +Fortunately, we can do that through the course that is included as part of the +assignment, as part of the submission. +(We add this manually in [[list_submissions]].) +The grader ID is negative if it was graded automatically, \eg by a quiz or LTI +integration. +If negative, it's either the quiz ID or LTI tool ID. +(Negate to get the ID.) +<>= +def resolve_grader(submission): + """ + Returns a user object if the submission was graded by a human. + Otherwise returns None if ungraded or a descriptive string. + """ + if submission.grader_id is None: + return None + elif submission.grader_id < 0: + return "autograded" + return submission.assignment.course.get_user(submission.grader_id) +@ + \subsection{Rubric data} <>= From b56c9f2b12b7383759e234d391c43503b37c2a70 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 7 Jun 2023 20:35:50 +0200 Subject: [PATCH 094/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9cf90fb..ee5eb46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.10" +version = "2.11" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 49a1ecbcea51c61c00526b7a37cffe8fd5e67f61 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 13 Jun 2023 08:48:34 +0200 Subject: [PATCH 095/248] Improves help text for -u option --- src/canvaslms/cli/users.nw | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index 135f052..e5426bc 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -509,7 +509,14 @@ def add_user_option_wo_depends(parser, required=False): Adds the -u option to argparse parser, without adding other required options """ - help="Filter users on name, login ID or Canvas ID by user_regex" + help="Filter users on Canvas ID, name, login ID, integration ID, or " \ + "SIS ID by user_regex. " \ + "Integration ID and SIS ID match exactly, not by regex. " \ + "Note that for login ID, you should start with ^ and $ to avoid " \ + "matching on unintended substrings; c.f. son@institution.tld and " \ + "person@institution.tld, where the first will match both without " \ + "leading ^. The regex allows matching using ^son@, thus skipping " \ + "any domain in this case." options = {"required": required} if not required: options["default"] = ".*" From 378c9080a4e516440f3f93b264d442a589e1a0bc Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 13 Jun 2023 08:56:00 +0200 Subject: [PATCH 096/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ee5eb46..76325c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.11" +version = "2.12" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 4475997e9258c5b81b3a7308d402f2ab7a182bf5 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Jun 2023 20:09:25 +0200 Subject: [PATCH 097/248] Bugfix: makes results command output date (not timestamp) --- src/canvaslms/cli/results.nw | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 9ec086d..75cb973 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -100,14 +100,15 @@ for result in results: @ -\section{Extracting assignment results} +\section{Summarizing assignment results} In this case, we want to have one assignment per row in the output. We want to output course, assignment, student ID, grade and submission date. We first get the list of courses. We do this to then get the list of all users in all courses. -We need these to get the integration ID. +We need these to get the integration ID, that can be used for LADOK for +example. Then we get the list of assignments in all courses. We get the submissions for each assignment. @@ -140,10 +141,20 @@ def summarize_assignments(canvas, args): submission.assignment.name, submission.user.integration_id, submission.grade, - submission.submitted_at or submission.graded_at + round_to_day(submission.submitted_at or submission.graded_at) ) @ +We want the grade date to be a date, not the timestamp supplied by Canvas. +For instance, LADOK wants dates, not timestamps. +<>= +def round_to_day(timestamp): + """ + Takes a Canvas timestamp and returns the corresponding datetime.date object. + """ + return dt.date.fromisoformat(timestamp.split("T")[0]) +@ + \section{Summarizing assignment group results} From d1872539c1834b02a2adb4522572594d0f8b2a23 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Jun 2023 20:11:01 +0200 Subject: [PATCH 098/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 76325c4..aeed63a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.12" +version = "2.13" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From c4d7c46c0a23547195bfb87cc80de6ca0667654c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 09:10:11 +0200 Subject: [PATCH 099/248] Makes results command handle who graded (#75) --- src/canvaslms/cli/results.nw | 190 ++++++++++++++++++++++--------- src/canvaslms/cli/submissions.nw | 8 +- 2 files changed, 141 insertions(+), 57 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 75cb973..468c5e7 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -21,6 +21,7 @@ import canvaslms.hacks.canvasapi import argparse import csv +import canvasapi.submission import datetime as dt import importlib import importlib.machinery @@ -44,19 +45,28 @@ ungraded flag as we want to export results (\ie graded material). <>= results_parser = subp.add_parser("results", help="Lists results of a course", - description="Lists results of a course for export, for instance " - "to the `ladok report -f` command. Output format, CSV: " - " .", - epilog="If you specify an assignment group, the results of the " - "assignments in that group will be summarized. You can supply your " - "own function for summarizing grades through the -S option. " - "See `pydoc3 canvaslms.grades` for different options.") + description="""<>""", + epilog="""<>""") results_parser.set_defaults(func=results_command) assignments.add_assignment_option(results_parser, ungraded=False) <> <> +@ + +Let's summarize what we want to do. +<>= +Lists results of a course for export, for instance to the `ladok report` +command. Output format, CSV: + + +<>= +If you specify an assignment group, the results of the assignments in that +group will be summarized. You can supply your own function for summarizing +grades through the -S option. See `pydoc3 canvaslms.grades` for different +options. @ We will cover the option for and loading of the custom summary module later, in \cref{custom-summary-module}. + Now, that [[results_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. However, unlike the other commands, we don't want to do the processing for the @@ -73,15 +83,8 @@ assignment groups in those courses. If the user provides assignment groups, we will automatically summarize the results of all assignments in the assignment group. -We also let the user choose to not include Fs in the output. -<>= -results_parser.add_argument("-F", "--include-Fs", - required=False, default=False, action="store_true", - help="Include failing grades (Fs) in output. By default we only output " - "A--Es and Ps.") -@ - -We will create a list of results, where each result is a tuple. +We will create a list of results, where each result is a tuple (actually a +list, since the length might vary). These tuples will then be printed in CSV format to standard output. <>= output = csv.writer(sys.stdout, delimiter=args.delimiter) @@ -94,16 +97,31 @@ else: results = summarize_assignments(canvas, args) for result in results: - if not args.include_Fs and result[3][0] == "F": - continue + <> output.writerow(result) @ +We also let the user choose to not include Fs in the output. +<>= +results_parser.add_argument("-F", "--include-Fs", + required=False, default=False, action="store_true", + help="Include failing grades (Fs) in output. By default we only output " + "A--Es and Ps.") +@ + +The grade is the third element of the results list. +If we check the first letter of the grade we can detect both F and Fx. +<>= +if not args.include_Fs and result[3][0] == "F": + continue +@ + \section{Summarizing assignment results} In this case, we want to have one assignment per row in the output. -We want to output course, assignment, student ID, grade and submission date. +We want to output course, assignment, student ID, grade, submission date and +those who participated in the grading. We first get the list of courses. We do this to then get the list of all users in all courses. @@ -118,33 +136,52 @@ the details of each user. This gives a trivial [[yield]] statement at the end. <>= def summarize_assignments(canvas, args): - """Turn submissions into results, - canvas is a Canvas object, - args is the command-line arguments""" - - courses_list = courses.process_course_option(canvas, args) - - users_list = [] - for course in courses_list: - for user in course.get_users(enrollment_type=["student"]): - users_list.append(user) + """ + Turn submissions into results: + - canvas is a Canvas object, + - args is the command-line arguments, as parsed by argparse. + """ - assignments_list = assignments.process_assignment_option(canvas, args) - submissions_list = submissions.filter_submissions( - submissions.list_submissions(assignments_list, include=[]), - users_list) + <> for submission in submissions_list: if submission.grade is not None: - yield ( + yield [ submission.assignment.course.course_code, submission.assignment.name, submission.user.integration_id, submission.grade, - round_to_day(submission.submitted_at or submission.graded_at) - ) + round_to_day(submission.submitted_at or submission.graded_at), + *all_graders(submission) + ] +@ + +To create the list of submissions, [[submissions_list]], we have to do the +following. +First need to list the courses. +For each course we need to get all the users (students). +Then, for each course, we also need all the assignments. +When we have the assignments, we can get the submissions. +Fortunately, we can use the filtering functions provided by the [[courses]], +[[assignments]] and [[submissions]] modules. +They will parse the CLI arguments and generate the lists. +<>= +courses_list = courses.process_course_option(canvas, args) + +users_list = [] +for course in courses_list: + for user in course.get_users(enrollment_type=["student"]): + users_list.append(user) + +assignments_list = assignments.process_assignment_option(canvas, args) +submissions_list = submissions.filter_submissions( + submissions.list_submissions(assignments_list, + include=["submission_history"]), + users_list) @ +\section{Fixing the dates} + We want the grade date to be a date, not the timestamp supplied by Canvas. For instance, LADOK wants dates, not timestamps. <>= @@ -155,6 +192,47 @@ def round_to_day(timestamp): return dt.date.fromisoformat(timestamp.split("T")[0]) @ +\section{Getting all graders for a submission} + +We need all graders who participated in the grading, meaning also those who +previously graded (since the last grader might just complement it). +<>= +def all_graders(submission): + """ + Returns a list of everyone who participated in the grading of the submission. + I.e. also those who graded previous submissions, when submission history is + available. + """ + graders = [] + + for prev_submission in submission.submission_history: + <> + <> + + return graders +@ + +To make the code easier, we'll turn the [[submission_history]] data into +[[Submission]] objects. +We also want to keep the added [[.assignment]] attribute, since we'll use it +later. +<>= +prev_submission = canvasapi.submission.Submission( + submission._requester, prev_submission) +prev_submission.assignment = submission.assignment +@ + +Now, we'd like to extract the grader. +We'll get the grader's Canvas user ID, so we'll need to resolve it to an actual +user. +Fortunately, we can use the [[resolve_grader]] function from the +[[submissions]] module to do all the work. +<>= +grader = submissions.resolve_grader(prev_submission) +if grader: + graders.append(grader) +@ + \section{Summarizing assignment group results} @@ -167,11 +245,13 @@ assignments belong to which assignment group so that we can check easily that a user has passed all assignments in the group. <>= def summarize_assignment_groups(canvas, args): - """Summarize assignment groups into a single grade, - canvas is a Canvas object, - args is the command-line arguments""" + """ + Summarize assignment groups into a single grade: + - canvas is a Canvas object, + - args is the command-line arguments, as parsed by argparse. + """ - <> + <> courses_list = courses.process_course_option(canvas, args) all_assignments = list(assignments.process_assignment_option(canvas, args)) @@ -184,15 +264,16 @@ def summarize_assignment_groups(canvas, args): for assignment_group in ag_list: assignments_list = list(assignments.filter_assignments_by_group( assignment_group, all_assignments)) - for user, grade, grade_date in summary.summarize_group( + for user, grade, grade_date, *graders in summary.summarize_group( assignments_list, users_list): - yield ( + yield [ course.course_code, assignment_group.name, user.integration_id, grade, - grade_date - ) + grade_date, + *graders + ] @ We will now cover the [[summarize_group]] function in the [[summary]] module. @@ -201,20 +282,19 @@ def summarize_assignment_groups(canvas, args): Different teachers have different policies for merging several assignments into one grade. We now want to provide a way to override the default function. +<>= +Name of Python module or file containing module to load with a custom +summarization function to summarize assignment groups. The default module is +part of the `canvaslms` package: `{default_summary_module}`. This module must +contain a function `summarize_group(assignments, users)`, where `assignments` +is a list of `canvasapi.assignment.Assignment` objects and `users` is a list of +`canvasapi.user.User` objects. The return value must be a list of lists of the +form `[user object, grade, grade date, grader 1, grader 2, ...]`. <>= default_summary_module = "canvaslms.grades.conjunctavg" results_parser.add_argument("-S", "--summary-module", required=False, default=default_summary_module, - help="Name of Python module or file containing module to " - "load with a custom summarization function " - "to summarize assignment groups. The default module is part of the " - f"`canvaslms` package: `{default_summary_module}`. " - "This module must contain a function " - "`summarize_group(assignments, users)`, where `assignments` " - "is a list of `canvasapi.assignment.Assignment` objects and " - "`users` is a list of `canvasapi.user.User` objects. The return value " - "must be a list of tuples of the form " - "`(user object, grade, grade date)`.") + help=f"""<>""") @ Now, let's load the module into the identifier [[summary]] for the above code. @@ -226,7 +306,7 @@ A's. Now to the loader, we first try to load a system module, then we look for a module in the current working directory. -<>= +<>= try: summary = importlib.import_module(args.summary_module) except ModuleNotFoundError: diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index de279eb..7bdb8ba 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -405,9 +405,13 @@ def resolve_grader(submission): Returns a user object if the submission was graded by a human. Otherwise returns None if ungraded or a descriptive string. """ - if submission.grader_id is None: + try: + if submission.grader_id is None: + return None + except AttributeError: return None - elif submission.grader_id < 0: + + if submission.grader_id < 0: return "autograded" return submission.assignment.course.get_user(submission.grader_id) @ From 3d3fe7f82797dcb31ba6a17da6785e4005269a59 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 11:26:39 +0200 Subject: [PATCH 100/248] Adapts canvaslms.grades to reporting who graded (#75) --- src/canvaslms/grades/conjunctavg.nw | 27 ++++++--- src/canvaslms/grades/disjunctmax.nw | 14 +++-- src/canvaslms/grades/grades.nw | 83 ++++++++++++++++++++++---- src/canvaslms/grades/maxgradesurvey.nw | 19 ++++-- 4 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index baad0c4..8c7ce4b 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -20,6 +20,7 @@ Conjunctive average means: """ import datetime as dt +from canvaslms.cli import results <> @@ -28,15 +29,16 @@ def summarize_group(assignments_list, users_list): users in users_list""" for user in users_list: - grade, grade_date = summarize(user, assignments_list) - yield (user, grade, grade_date) + grade, grade_date, graders = summarize(user, assignments_list) + yield [user, grade, grade_date, *graders] @ \subsection{Summarizing grades: assignment grades to component grade} Now we will describe the [[summarize]] helper function. -We want to establish two things: the most recent date and a suitable grade. +We want to establish three things: the most recent date, a suitable grade and +who graded. For the most recent date, we just check the dates as we iterate through the submissions. @@ -46,6 +48,8 @@ We can then check for Fs among the P/F grades, if we find an F the summarized grade will be an F. If we find no Fs, then we can compute the average over all A--E grades and use that as the final grade. + +For who graded, we simply extract the list of graders from the submissions. <>= def summarize(user, assignments_list): """Extracts user's submissions for assignments in assingments_list to @@ -55,9 +59,14 @@ def summarize(user, assignments_list): pf_grades = [] a2e_grades = [] recent_date = dt.date(year=1970, month=1, day=1) + graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user) + submission = assignment.get_submission(user, + include=["submission_history"]) + submission.assignment = assignment + graders += results.all_graders(submission) + grade = submission.grade if grade is None: @@ -79,11 +88,13 @@ def summarize(user, assignments_list): recent_date = grade_date if not all(map(lambda x: x == "P", pf_grades)): - return ("F", recent_date) + final_grade = "F" + elif a2e_grades: + final_grade = a2e_average(a2e_grades) + else: + final_grade = "P" - if a2e_grades: - return (a2e_average(a2e_grades), recent_date) - return ("P", recent_date) + return (final_grade, recent_date, graders) @ \subsection{Computing averages} diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index a9d7f67..9c2e3e9 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -28,6 +28,7 @@ This gives the following outline of the module. """ import datetime as dt +from canvaslms.cli import results <> @@ -36,8 +37,8 @@ def summarize_group(assignments_list, users_list): users in users_list""" for user in users_list: - grade, grade_date = summarize(user, assignments_list) - yield (user, grade, grade_date) + grade, grade_date, graders = summarize(user, assignments_list) + yield [user, grade, grade_date, *graders] @ @@ -59,9 +60,14 @@ def summarize(user, assignments_list): grades = [] dates = [] + graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user) + submission = assignment.get_submission(user, + include=["submission_history"]) + submission.assignment = assignment + graders += results.all_graders(submission) + grade = submission.grade if grade is None: @@ -75,7 +81,7 @@ def summarize(user, assignments_list): grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) dates.append(grade_date) - return (grade_max(grades), max(dates)) + return (grade_max(grades), max(dates), graders) @ \subsection{Computing the maximum} diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index 06e328b..e61c0a7 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -13,24 +13,83 @@ The function must take two arguments: assignments whose grades should be used to compute the student's grade. \item a list of users, \ie students, for whom to compute the grades. \end{enumerate} -See the modules below for examples. +It must return a list containing: +\begin{enumerate} +\item the user, +\item the grade, +\item the date of submission (or grading if there was no submission), +\item someone who participated in the grading, +\item another who participated in the grading, +\item \dots +\end{enumerate} Let's look at a simple example. -This small module just returns a counter as grade: it starts at 0, increases -one per student. -The grading date is set to today's date for all students. -We don't even look at the students' submissions for these assignments. +(For more elaborate and complete examples, see the summarizing functions +implemented in the remaining sections.) <>= import datetime as dt - -count = 0 +from canvaslms.cli import results def summarize_group(assignments, users): - global count - date = dt.date.today() - for user in users: - yield (user, str(count), date) - count += 1 + """ + Summarizes the grades for all assignments into one grade for each user. + """ + for user in users: + grades = [] + recent_date = dt.date(year=1970, month=1, day=1) + graders = [] + + <> + + yield [user, final_grade(grades), recent_date, *graders] +@ We omit the implementation of [[final_grade]], there are several examples of +such a function in the following sections. + +To extract the data we need, we simply iterate through all the assignments and +fetch the user's (student's) submission. +Note that we must add the option [[include=["submission_history"]]] to be able +to extract everyone who participated in the grading, not just the last one. +This is important since the last grader might just check the parts that the +previous grader said must be fixed by the student. +So both are part of the grading. +<>= +for assignment in assignments: + submission = assignment.get_submission(user, + include=["submission_history"]) + + <> +@ + +For each submission, we extract the grades and append them to the list of +grades. +<>= +grades.append(submission.grade or "F") +@ + +Then we fetch the graders and append them to the list of graders. +The function [[all_graders]] expects the submission to have an attribute +[[.assignment]] pointing to the assignment in question. +(We want this attribute to not have to use a [[canvas]] object to resolve the +[[.assignment_id]] attribute that is there by default.) +<>= +submission.assignment = assignment +graders += results.all_graders(submission) +@ + +Finally, the date: +We should firstly use the submission date. +However, in some cases, like oral presentations, the student hasn't submitted +anything (even if they should, in case of labs). +Then there is no submission date, so we have to resort to the grading date. +<>= +date = submission.submitted_at or submission.graded_at +try: + grade_date = dt.date.fromisoformat(date.split("T")[0]) +except AttributeError: + pass +else: + if grade_date > recent_date: + recent_date = grade_date @ To use this module we would run diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index e6c95f0..1f455c1 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -26,6 +26,7 @@ This gives the following outline of the module. import datetime as dt from canvaslms.grades.disjunctmax import grade_max +from canvaslms.cli import results <> @@ -34,8 +35,8 @@ def summarize_group(assignments_list, users_list): users in users_list""" for user in users_list: - grade, grade_date = summarize(user, assignments_list) - yield (user, grade, grade_date) + grade, grade_date, graders = summarize(user, assignments_list) + yield [user, grade, grade_date, *graders] @ @@ -59,9 +60,14 @@ def summarize(user, assignments_list): grades = [] dates = [] + graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user) + submission = assignment.get_submission(user, + include=["submission_history"]) + submission.assignment = assignment + graders += results.all_graders(submission) + grade = submission.grade if grade is None or grade not in "ABCDEPF": @@ -76,7 +82,10 @@ def summarize(user, assignments_list): dates.append(grade_date) if len(dates) < len(grades): - return ("F", None) - return (grade_max(grades), max(dates)) + final_grade = "F" + else: + final_grade = grade_max(grades) + + return (final_grade, max(dates), graders) @ From 71377b2b237ed23360b307a0cfc54b32801162bd Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 11:48:23 +0200 Subject: [PATCH 101/248] Adds hack to improve canvasapi.user.User.__str__ We want to use `.login_id` instead of `.id`. --- src/canvaslms/hacks/canvasapi.nw | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/canvaslms/hacks/canvasapi.nw b/src/canvaslms/hacks/canvasapi.nw index 1bfcfb9..241068d 100644 --- a/src/canvaslms/hacks/canvasapi.nw +++ b/src/canvaslms/hacks/canvasapi.nw @@ -103,3 +103,30 @@ for module_name, module in canvasapi_modules.items(): canvas_hashable(obj) @ + +\section{Improve User's [[__str__]] method} + +By default, [[canvasapi]]'s [[User]] class defines a [[__str__]] dunder method +that uses the user's name and Canvas ID. +We want to make it more useful, by using the user's name and login ID. +<>= +def make_useful_user_dunder_str(): + """Improves the user class by changing __str__""" + <> + <> +@ + +Now, we simply need to define a function to use as a drop-in replacement for +the [[__str__]] method. +<>= +def name_and_login(self): + return f"{self.name} <{self.login_id}>" +@ + +Then we simply need to replace the current [[__str__]] method with the new one +above. +<>= +import canvasapi.user +canvasapi.user.User.__str__ = name_and_login +@ + From 23f626646e2837e8d09cf5ae1be5103f4811e3b6 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 11:55:52 +0200 Subject: [PATCH 102/248] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..91abb11 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From 42d12d9ba749d78388a6bb023f2c2f31020dabf2 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 12:02:25 +0200 Subject: [PATCH 103/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aeed63a..69529eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.13" +version = "2.14" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 2d2ace2329eb754cf9b02a5084cea160f44742c1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 12:03:12 +0200 Subject: [PATCH 104/248] Bumps year in LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d7757a6..8cdf029 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020--2021 Daniel Bosk +Copyright (c) 2020--2023 Daniel Bosk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From eafb71046f3abffea4262195e19ac6d2b0f4e970 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 21:02:07 +0200 Subject: [PATCH 105/248] Adds verbose option to `grade` command --- src/canvaslms/cli/grade.nw | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/grade.nw b/src/canvaslms/cli/grade.nw index 6e93010..32b6742 100644 --- a/src/canvaslms/cli/grade.nw +++ b/src/canvaslms/cli/grade.nw @@ -49,10 +49,13 @@ We introduce two options: This can be almost anything: Canvas accepts points, percentages or letter grades and will convert accordingly. \item [[-m]] or [[--message]], which sets a comment. +\item [[-v]] or [[--verbose]], which will cause [[canvaslms]] to print what +grade is set for which assignment and which student. \end{itemize} -Both options are optional. +Both [[-g]] and [[-m]] are optional. If neither is given, the SpeedGrader page of each submission is opened in the web browser. +In that case, [[-v]] make not much sense. <>= grade_options = grade_parser.add_argument_group( "arguments to set the grade and/or comment, " @@ -83,6 +86,34 @@ if not args.grade and not args.message: webbrowser.open(submissions.speedgrader(submission)) else: for submission in submission_list: + <> submission.edit(**results) @ +\subsection{Verbose output when setting grades} + +Now, we want a verbosity option to control whether or not to print what's +happening (even for non-errors). +Using the option turns verbose mode on, it's off by default. +<>= +grade_parser.add_argument("-v", "--verbose", + action="store_true", default=False, + help="Increases verbosity, prints what grade is set " + "for which assignment for which student.") +<>= +if args.verbose: + id = f"{submission.assignment.course.course_code} " \ + f"{submission.assignment.name} {submission.user}" + + event = "" + try: + event += f" grade = {args.grade}" + except: + pass + try: + event += f" msg = '{args.message}'" + except: + pass + + print(f"{id}:{event}") + From f5fe7cbfe0efb0ca2f17435aeff4d72d2dc0eef0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 27 Jun 2023 21:06:02 +0200 Subject: [PATCH 106/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69529eb..b0a799b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.14" +version = "2.15" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 8953a569d6d9ffd12f54deefe63ce4d5868e70c0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 28 Jun 2023 19:44:36 +0200 Subject: [PATCH 107/248] Improves documentation for summarization modules --- src/canvaslms/cli/results.nw | 8 ++-- src/canvaslms/grades/Makefile | 10 +++++ src/canvaslms/grades/__init__.py | 20 ++++++--- src/canvaslms/grades/grades.nw | 73 ++++++++++++++++++++------------ 4 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 468c5e7..e85d377 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -285,11 +285,9 @@ We now want to provide a way to override the default function. <>= Name of Python module or file containing module to load with a custom summarization function to summarize assignment groups. The default module is -part of the `canvaslms` package: `{default_summary_module}`. This module must -contain a function `summarize_group(assignments, users)`, where `assignments` -is a list of `canvasapi.assignment.Assignment` objects and `users` is a list of -`canvasapi.user.User` objects. The return value must be a list of lists of the -form `[user object, grade, grade date, grader 1, grader 2, ...]`. +part of the `canvaslms` package: `{default_summary_module}`. But it could be +any Python file in the file system or other built-in modules. See `pydoc3 +canvaslms.grades` for alternative modules or how to build your own. <>= default_summary_module = "canvaslms.grades.conjunctavg" results_parser.add_argument("-S", "--summary-module", diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 19b678a..0ea0de1 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -1,6 +1,7 @@ NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= +MODULES+= __init__.py MODULES+= conjunctavg.py MODULES+= disjunctmax.py MODULES+= maxgradesurvey.py @@ -12,17 +13,26 @@ all: conjunctavg.py conjunctavg.tex all: disjunctmax.py disjunctmax.tex all: maxgradesurvey.py maxgradesurvey.tex all: conjunctavgsurvey.py conjunctavgsurvey.tex +all: ${MODULES} grades.tex: conjunctavg.tex grades.tex: disjunctmax.tex grades.tex: maxgradesurvey.tex grades.tex: conjunctavgsurvey.tex +__init__.py: init.py + ${MV} $^ $@ + +.INTERMEDIATE: init.py +init.py: grades.nw + ${NOTANGLE.py} + .PHONY: clean clean: ${RM} grades.tex ${RM} ${MODULES} + ${RM} init.py INCLUDE_MAKEFILES=../../../makefiles diff --git a/src/canvaslms/grades/__init__.py b/src/canvaslms/grades/__init__.py index 60c8186..219795e 100644 --- a/src/canvaslms/grades/__init__.py +++ b/src/canvaslms/grades/__init__.py @@ -1,5 +1,6 @@ """ -Package containing modules to summarize assignment groups in different ways. +This package contains modules to summarize assignment groups in different ways. +These modules are used with the `-S` option of the `results` command. For a module to be used with the `canvaslms results -S module` option, the module must fulfil the following: @@ -7,13 +8,18 @@ 1) It must contain a function named `summarize_group`. 2) `summarize_group` must take two arguments: - I) `assignment_list`, a list of `canvasapi.assignment.Assignment` - objects. + I) `assignment_list`, a list of `canvasapi.assignment.Assignment` + objects. These assignments all belong to the same group, \ie their + grades should be used to compute the student's final grade. - II) `users_list`, a list of `canvasapi.user.User` objects. + II) `users_list`, a list of `canvasapi.user.User` objects. This is a + list of users, i.e. students, for whom to compute the grades. - 3) The return value should be a list of tuples. Each tuple should have the - form `(user, grade, grade date)`. + 3) The return value should be a list of lists. Each list should have the + form `[user, grade, grade date, grader 1, ..., grader N]`. -See the built-in modules below. +For more details, see Chapter 11 of the `canvaslms.pdf` file found among the +release files at: + + https://github.com/dbosk/canvaslms/releases """ diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index e61c0a7..b5137a3 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -2,30 +2,45 @@ \label{summary-modules} This is the documentation for the \texttt{canvaslms.grades} package. -Here we provide modules to be used with the \texttt{-S} option for the -\texttt{results} command, see \cref{results-command}. - -For a module to be used, it must contain a function named -\texttt{summarize\textunderscore group}. -The function must take two arguments: -\begin{enumerate} - \item a list of assignments that all belong to the same group, \ie the - assignments whose grades should be used to compute the student's grade. - \item a list of users, \ie students, for whom to compute the grades. -\end{enumerate} -It must return a list containing: -\begin{enumerate} -\item the user, -\item the grade, -\item the date of submission (or grading if there was no submission), -\item someone who participated in the grading, -\item another who participated in the grading, -\item \dots -\end{enumerate} - -Let's look at a simple example. -(For more elaborate and complete examples, see the summarizing functions -implemented in the remaining sections.) +<>= +This package contains modules to summarize assignment groups in different ways. +These modules are used with the `-S` option of the `results` command. + +For a module to be used with the `canvaslms results -S module` option, the +module must fulfil the following: + + 1) It must contain a function named `summarize_group`. + 2) `summarize_group` must take two arguments: + + I) `assignment_list`, a list of `canvasapi.assignment.Assignment` + objects. These assignments all belong to the same group, \ie their + grades should be used to compute the student's final grade. + + II) `users_list`, a list of `canvasapi.user.User` objects. This is a + list of users, i.e. students, for whom to compute the grades. + + 3) The return value should be a list of lists. Each list should have the + form `[user, grade, grade date, grader 1, ..., grader N]`. + +For more details, see Chapter 11 of the `canvaslms.pdf` file found among the +release files at: + + https://github.com/dbosk/canvaslms/releases +@ + +Also see \cref{results-command} for details on the [[results]] command. + +Now, this package's init module ([[__init__.py]], refered to by +[[<>]]) only needs this: +<>= +""" +<> +""" +@ + +Let's look at a simple example module, [[<>]]. +In the [[summarize_group]] function, we extract the all the grades, the dates +and the graders and return the tuple (list) expected. <>= import datetime as dt from canvaslms.cli import results @@ -42,8 +57,14 @@ def summarize_group(assignments, users): <> yield [user, final_grade(grades), recent_date, *graders] -@ We omit the implementation of [[final_grade]], there are several examples of -such a function in the following sections. +@ + +We leave for the reader to imagine all the possibilities of the +[[final_grade(grades)]] function that is supposed to take the list of grades +and turn them into one final grade. +(For more elaborate and complete examples, where the [[final_grade]] function +is actually implemented, see the summarizing functions implemented in the +remaining sections of this chapter.) To extract the data we need, we simply iterate through all the assignments and fetch the user's (student's) submission. From a510283efa60636c83fa22f1336e14e996a99686 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 28 Jun 2023 19:45:01 +0200 Subject: [PATCH 108/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0a799b..574ed23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.15" +version = "2.16" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From a26de946ec163719155b0fba9be81e38ac5864c7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 30 Jun 2023 15:48:02 +0200 Subject: [PATCH 109/248] Fixes broken cross reference in doc --- src/canvaslms/cli/results.nw | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index e85d377..d40dcb5 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -65,7 +65,7 @@ group will be summarized. You can supply your own function for summarizing grades through the -S option. See `pydoc3 canvaslms.grades` for different options. @ We will cover the option for and loading of the custom summary module later, -in \cref{custom-summary-module}. +in \cref{custom-summary-modules}. Now, that [[results_command]] function must take three arguments: [[config]], [[canvas]] and [[args]]. @@ -278,6 +278,7 @@ def summarize_assignment_groups(canvas, args): \subsection{Loading a custom summary module} +\label{custom-summary-modules} Different teachers have different policies for merging several assignments into one grade. From 10bef089e3c002952d38eb1c509484cc332b7438 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 11:07:00 +0200 Subject: [PATCH 110/248] Removes unneeded line of code --- src/canvaslms/cli/results.nw | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index d40dcb5..7b1fea9 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -89,8 +89,6 @@ These tuples will then be printed in CSV format to standard output. <>= output = csv.writer(sys.stdout, delimiter=args.delimiter) -assignments_list = assignments.process_assignment_option(canvas, args) - if args.assignment_group != "": results = summarize_assignment_groups(canvas, args) else: From 50034d0e19a5a94376250227330f4961378fc8ec Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:27:18 +0200 Subject: [PATCH 111/248] Clarifies that -G is for user groups --- src/canvaslms/cli/users.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index e5426bc..ed75e30 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -583,7 +583,7 @@ def add_group_option_wo_depends(parser): parser.add_argument("-G", "--group", metavar="group_regex", required=False, - help="Filters groups whose name match group_regex") + help="Filters user groups whose name match group_regex") def add_group_option(parser): """Adds group filtering options to argparse parser, From 549e6941c8d1d819d6bda64ad9c2cd0b2f5fb118 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:31:05 +0200 Subject: [PATCH 112/248] Improves imports in results --- src/canvaslms/cli/results.nw | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 7b1fea9..8efc654 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -14,9 +14,7 @@ package\footnote{% We outline the module: <>= import canvaslms.cli -import canvaslms.cli.assignments as assignments -import canvaslms.cli.courses as courses -import canvaslms.cli.submissions as submissions +from canvaslms.cli import assignments, courses, submissions, users import canvaslms.hacks.canvasapi import argparse From c1afc4d9c92444451fcfbb5859e6b4dd017a9bf6 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:35:20 +0200 Subject: [PATCH 113/248] Adds noweb xrefs to doc --- doc/Makefile | 1 - src/canvaslms/cli/Makefile | 1 - src/canvaslms/grades/Makefile | 1 - src/canvaslms/hacks/Makefile | 1 - 4 files changed, 4 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 508408f..ed38f57 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,4 +1,3 @@ -NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= LATEXFLAGS+= -shell-escape diff --git a/src/canvaslms/cli/Makefile b/src/canvaslms/cli/Makefile index 4ae6d44..f12b7b1 100644 --- a/src/canvaslms/cli/Makefile +++ b/src/canvaslms/cli/Makefile @@ -1,4 +1,3 @@ -NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= MODULES+= __init__.py cli.tex diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 0ea0de1..4465ede 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -1,4 +1,3 @@ -NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= MODULES+= __init__.py diff --git a/src/canvaslms/hacks/Makefile b/src/canvaslms/hacks/Makefile index 07ecbad..5a7a9d9 100644 --- a/src/canvaslms/hacks/Makefile +++ b/src/canvaslms/hacks/Makefile @@ -1,4 +1,3 @@ -NOWEAVEFLAGS.tex= -n -delay -t2 NOTANGLEFLAGS.py= From 63ea5feff95bba9406cac66944bcaee508d9fcb8 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:36:46 +0200 Subject: [PATCH 114/248] Removes generated __init__.py --- src/canvaslms/grades/__init__.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 src/canvaslms/grades/__init__.py diff --git a/src/canvaslms/grades/__init__.py b/src/canvaslms/grades/__init__.py deleted file mode 100644 index 219795e..0000000 --- a/src/canvaslms/grades/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -This package contains modules to summarize assignment groups in different ways. -These modules are used with the `-S` option of the `results` command. - -For a module to be used with the `canvaslms results -S module` option, the -module must fulfil the following: - - 1) It must contain a function named `summarize_group`. - 2) `summarize_group` must take two arguments: - - I) `assignment_list`, a list of `canvasapi.assignment.Assignment` - objects. These assignments all belong to the same group, \ie their - grades should be used to compute the student's final grade. - - II) `users_list`, a list of `canvasapi.user.User` objects. This is a - list of users, i.e. students, for whom to compute the grades. - - 3) The return value should be a list of lists. Each list should have the - form `[user, grade, grade date, grader 1, ..., grader N]`. - -For more details, see Chapter 11 of the `canvaslms.pdf` file found among the -release files at: - - https://github.com/dbosk/canvaslms/releases -""" From 0dd5eacacfb7771a17b12bbd1ebd8f5716118a1c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:37:13 +0200 Subject: [PATCH 115/248] Makes results use user/group filtering arguments --- src/canvaslms/cli/results.nw | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 8efc654..ee1fff4 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -38,8 +38,11 @@ def add_command(subp): We add the subparser for [[results]]. The command requires two arguments: course and assignment. +We also want the option to filter on users. We can add these by using [[add_assignment_option]], however, we don't need the ungraded flag as we want to export results (\ie graded material). +Also, we can just add the [[add_user_or_group_option]] to be able to filter on +users or groups. <>= results_parser = subp.add_parser("results", help="Lists results of a course", @@ -47,6 +50,7 @@ results_parser = subp.add_parser("results", epilog="""<>""") results_parser.set_defaults(func=results_command) assignments.add_assignment_option(results_parser, ungraded=False) +users.add_user_or_group_option(results_parser) <> <> @ @@ -162,14 +166,9 @@ Fortunately, we can use the filtering functions provided by the [[courses]], [[assignments]] and [[submissions]] modules. They will parse the CLI arguments and generate the lists. <>= -courses_list = courses.process_course_option(canvas, args) - -users_list = [] -for course in courses_list: - for user in course.get_users(enrollment_type=["student"]): - users_list.append(user) - assignments_list = assignments.process_assignment_option(canvas, args) +users_list = process_user_or_group_option(canvas, args) + submissions_list = submissions.filter_submissions( submissions.list_submissions(assignments_list, include=["submission_history"]), @@ -251,9 +250,9 @@ def summarize_assignment_groups(canvas, args): courses_list = courses.process_course_option(canvas, args) all_assignments = list(assignments.process_assignment_option(canvas, args)) + users_list = list(users.process_user_or_group_option(canvas, args)) for course in courses_list: - users_list = list(course.get_users(enrollment_type=["student"])) ag_list = assignments.filter_assignment_groups( course, args.assignment_group) From 0ddfd9a7ae654c3efcf492fe0fa7b6a271aa8892 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:38:44 +0200 Subject: [PATCH 116/248] Adds grades/__init__.py to gitignore since its generated now --- src/canvaslms/grades/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index 04941ab..a0f351b 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -1,4 +1,5 @@ grades.tex +__init__.py mysum.py conjunctavg.tex conjunctavg.py From 9a6f3ecdfe824acbb79a287df97edaee65e26d33 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sat, 1 Jul 2023 19:39:15 +0200 Subject: [PATCH 117/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 574ed23..887188b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.16" +version = "2.17" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From fd468b99cb3a01841f9df4b242c326d7ef5a018e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 07:13:33 +0000 Subject: [PATCH 118/248] Bump rich from 13.0.0 to 13.4.2 Bumps [rich](https://github.com/Textualize/rich) from 13.0.0 to 13.4.2. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.0.0...v13.4.2) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 93 ++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6cd48be..739beae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "2.0.0" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -31,7 +29,6 @@ test = ["coverage", "flake8", "pexpect", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -46,7 +43,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.2.0" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.7" files = [ @@ -58,7 +54,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +68,6 @@ requests = "*" name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +79,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +155,6 @@ pycparser = "*" name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -173,26 +165,10 @@ files = [ [package.extras] unicode-backport = ["unicodedata2"] -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - [[package]] name = "cryptography" version = "41.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -234,7 +210,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -246,7 +221,6 @@ files = [ name = "importlib-metadata" version = "5.2.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -266,7 +240,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "importlib-resources" version = "5.10.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -285,7 +258,6 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec name = "jaraco.classes" version = "3.2.3" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -304,7 +276,6 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -320,7 +291,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "23.13.1" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -341,11 +311,45 @@ completion = ["shtab"] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "more-itertools" version = "9.0.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -357,7 +361,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -369,7 +372,6 @@ files = [ name = "pygments" version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -384,7 +386,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.10" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -396,7 +397,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -411,7 +411,6 @@ six = ">=1.5" name = "pytz" version = "2022.7" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -423,7 +422,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.0" description = "" -category = "main" optional = false python-versions = "*" files = [ @@ -435,7 +433,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -455,29 +452,27 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.0.0" +version = "13.4.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.0.0-py3-none-any.whl", hash = "sha256:12b1d77ee7edf251b741531323f0d990f5f570a4e7c054d0bfb59fb7981ad977"}, - {file = "rich-13.0.0.tar.gz", hash = "sha256:3aa9eba7219b8c575c6494446a59f702552efe1aa261e7eeb95548fa586e1950"}, + {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, + {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, ] [package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -493,7 +488,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -505,7 +499,6 @@ files = [ name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -517,7 +510,6 @@ files = [ name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -534,7 +526,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "zipp" version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.7" files = [ From 5d7928e751302852d842ed11261f3e1bae621675 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 20:05:48 +0200 Subject: [PATCH 119/248] Bugfix: missing module prefix on function --- src/canvaslms/cli/results.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index ee1fff4..79a2e48 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -167,7 +167,7 @@ Fortunately, we can use the filtering functions provided by the [[courses]], They will parse the CLI arguments and generate the lists. <>= assignments_list = assignments.process_assignment_option(canvas, args) -users_list = process_user_or_group_option(canvas, args) +users_list = users.process_user_or_group_option(canvas, args) submissions_list = submissions.filter_submissions( submissions.list_submissions(assignments_list, From c401c5a67995816ec20312b26899092770a13b1a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 20:58:58 +0200 Subject: [PATCH 120/248] Adds the user role option We add this option to fix a problem with including the user option for the results command. When having the user option filtering, we also get TAs since that one doesn't filter on students by default. Now we provide the role option, which has student set to default. --- src/canvaslms/cli/users.nw | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index ed75e30..0e00eca 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -503,11 +503,18 @@ which add these as mutually exclusive options. We provide the two helper functions for other modules to filter out users from a set of courses. This option requires the course option from [[canvaslms.cli.courses]]. + +The first function, [[add_user_option_wo_depends]], simply adds the user +option. +The more useful function, [[add_user_option]], will also try to add the other +required options, like the course(s) where to find users. <>= def add_user_option_wo_depends(parser, required=False): """ - Adds the -u option to argparse parser, - without adding other required options + Adds the -u option to argparse parser, without adding + other required options. + + <> """ help="Filter users on Canvas ID, name, login ID, integration ID, or " \ "SIS ID by user_regex. " \ @@ -527,6 +534,8 @@ def add_user_option_wo_depends(parser, required=False): parser.add_argument("-u", "--user", metavar="user_regex", help=help, **options) + <> + def add_user_option(parser, required=False): """Adds the -u option to argparse parser""" try: @@ -537,13 +546,31 @@ def add_user_option(parser, required=False): add_user_option_wo_depends(parser, required) @ -When processing this option, we need to filter by course first. +When processing this option, we need to filter by course first, so we use the +processing from the [[courses]] module to get the list of courses matching the +courses options. +Then we simply filter all users. <>= def process_user_option(canvas, args): """Processes the user option from command line, returns a list of users""" return list(filter_users( courses.process_course_option(canvas, args), - args.user)) + args.user, + roles=args.role)) +@ + +We note that we need the roles. +Sometimes we want to be able to filter based on role. +However, most of the time, we just want the students, so we'll default to that. +<>= +The `role` option allows specifying which roles to include, for instance +students or TAs. +<>= +parser.add_argument("-r", "--role", + choices={"teacher", "student", "student_view", + "ta", "observer", "designer"}, + default="student", + help="Includes only users in this role") @ \subsection{The group option} From 9eea9181ee8eff4d18cd01d53f43c5d7ea1285f1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 21:11:11 +0200 Subject: [PATCH 121/248] Updates versions of depends due to security - Fixes #95 - Fixes #94 - Fixes #92 --- poetry.lock | 311 +++++++++++++++++++++++++++++++++---------------- pyproject.toml | 6 +- 2 files changed, 212 insertions(+), 105 deletions(-) diff --git a/poetry.lock b/poetry.lock index 739beae..2e7ae60 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = "*" files = [ @@ -13,22 +14,25 @@ files = [ [[package]] name = "argcomplete" -version = "2.0.0" +version = "2.1.2" description = "Bash tab completion for argparse" +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, - {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, + {file = "argcomplete-2.1.2-py3-none-any.whl", hash = "sha256:4ba9cdaa28c361d251edce884cd50b4b1215d65cdc881bd204426cdde9f52731"}, + {file = "argcomplete-2.1.2.tar.gz", hash = "sha256:fc82ef070c607b1559b5c720529d63b54d9dcf2dcfc2632b10e6372314a34457"}, ] [package.extras] -test = ["coverage", "flake8", "pexpect", "wheel"] +lint = ["flake8", "mypy"] +test = ["coverage", "flake8", "mypy", "pexpect", "wheel"] [[package]] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -41,19 +45,21 @@ python-dateutil = ">=2.7.0" [[package]] name = "cachetools" -version = "5.2.0" +version = "5.3.1" description = "Extensible memoizing collections and decorators" +category = "main" optional = false -python-versions = "~=3.7" +python-versions = ">=3.7" files = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, ] [[package]] name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" +category = "main" optional = false python-versions = "*" files = [ @@ -66,19 +72,21 @@ requests = "*" [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -153,44 +161,120 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] -[package.extras] -unicode-backport = ["unicodedata2"] - [[package]] name = "cryptography" -version = "41.0.0" +version = "41.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8"}, - {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0"}, - {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d"}, - {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46"}, - {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237"}, - {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4"}, - {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75"}, - {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d"}, - {file = "cryptography-41.0.0-cp37-abi3-win32.whl", hash = "sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928"}, - {file = "cryptography-41.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be"}, - {file = "cryptography-41.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5"}, - {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb"}, - {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be"}, - {file = "cryptography-41.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9"}, - {file = "cryptography-41.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2"}, - {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d"}, - {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895"}, - {file = "cryptography-41.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55"}, - {file = "cryptography-41.0.0.tar.gz", hash = "sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, ] [package.dependencies] @@ -210,6 +294,7 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -219,13 +304,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "5.2.0" +version = "6.8.0" description = "Read metadata from Python packages" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, - {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] @@ -234,48 +320,51 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" -version = "5.10.1" +version = "6.0.0" description = "Read resources from Python packages" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_resources-5.10.1-py3-none-any.whl", hash = "sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"}, - {file = "importlib_resources-5.10.1.tar.gz", hash = "sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3"}, + {file = "importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"}, + {file = "importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] -name = "jaraco.classes" -version = "3.2.3" +name = "jaraco-classes" +version = "3.3.0" description = "Utility functions for Python class constructs" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, - {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, + {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, + {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, ] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -289,13 +378,14 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "23.13.1" +version = "24.2.0" description = "Store and access your passwords safely." +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, - {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, + {file = "keyring-24.2.0-py3-none-any.whl", hash = "sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6"}, + {file = "keyring-24.2.0.tar.gz", hash = "sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509"}, ] [package.dependencies] @@ -308,13 +398,14 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab"] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -339,6 +430,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -348,19 +440,21 @@ files = [ [[package]] name = "more-itertools" -version = "9.0.0" +version = "10.0.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, - {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, + {file = "more-itertools-10.0.0.tar.gz", hash = "sha256:cd65437d7c4b615ab81c0640c0480bc29a550ea032891977681efd28344d51e1"}, + {file = "more_itertools-10.0.0-py3-none-any.whl", hash = "sha256:928d514ffd22b5b0a8fce326d57f423a55d2ff783b093bab217eda71e732330f"}, ] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -370,13 +464,14 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] @@ -384,19 +479,21 @@ plugins = ["importlib-metadata"] [[package]] name = "pypandoc" -version = "1.10" +version = "1.11" description = "Thin wrapper for pandoc." +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pypandoc-1.10-py3-none-any.whl", hash = "sha256:ff9658cda18865a822695349a7c707756ecc454b59b71f920b554579ec54aaa7"}, - {file = "pypandoc-1.10.tar.gz", hash = "sha256:101164d154f0b9957372cdbf285396153b74144fda47f531fb556de244efc86e"}, + {file = "pypandoc-1.11-py3-none-any.whl", hash = "sha256:b260596934e9cfc6513056110a7c8600171d414f90558bf4407e68b209be8007"}, + {file = "pypandoc-1.11.tar.gz", hash = "sha256:7f6d68db0e57e0f6961bec2190897118c4d305fc2d31c22cd16037f22ee084a5"}, ] [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -409,30 +506,33 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.7" +version = "2023.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"}, - {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"}, + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] name = "pywin32-ctypes" -version = "0.2.0" -description = "" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -452,13 +552,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.4.2" +version = "13.5.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-13.5.1-py3-none-any.whl", hash = "sha256:b97381b204a206e1be618f5e1215a57174a1a7732490b3bf6668cf41d30bc72d"}, + {file = "rich-13.5.1.tar.gz", hash = "sha256:881653ee7037803559d8eae98f145e0a4c4b0ec3ff0300d2cc8d479c71fc6819"}, ] [package.dependencies] @@ -473,6 +574,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -488,6 +590,7 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -497,47 +600,51 @@ files = [ [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" -version = "1.26.13" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" -version = "3.11.0" +version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "84281c69b6aa90be727f49f48526839b8e47e4e76517906fef81f8fda4dd75d6" +content-hash = "adce64fcd9d3a421a927eacf63bfccfed6315e4a6c68188220b519ae76c23b9f" diff --git a/pyproject.toml b/pyproject.toml index 887188b..f4c698d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,10 @@ canvaslms = "canvaslms.cli:main" python = "^3.8" appdirs = "^1.4.4" argcomplete = "^2.0.0" -cachetools = "^5.2.0" +cachetools = "^5.3.1" canvasapi = "<3.0.0" -keyring = "^23.11.0" -pypandoc = "^1.10" +keyring = "^24.2.0" +pypandoc = "^1.11" arrow = "^1.2.3" rich = "^13.0.0" From c705c1ebcf2fc0d6bc97b1eb3582839789be9e71 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 21:20:17 +0200 Subject: [PATCH 122/248] Adds default to help text for --role arg --- src/canvaslms/cli/users.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index 0e00eca..cc938b8 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -570,7 +570,7 @@ parser.add_argument("-r", "--role", choices={"teacher", "student", "student_view", "ta", "observer", "designer"}, default="student", - help="Includes only users in this role") + help="Includes only users in this role, default to student.") @ \subsection{The group option} From ca8837db1feb2728f538e608c32c89d3de721758 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 21:20:17 +0200 Subject: [PATCH 123/248] Adds default to help text for --role arg --- src/canvaslms/cli/users.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index 0e00eca..f9bb8cd 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -570,7 +570,7 @@ parser.add_argument("-r", "--role", choices={"teacher", "student", "student_view", "ta", "observer", "designer"}, default="student", - help="Includes only users in this role") + help="Includes only users in this role, defaults to student.") @ \subsection{The group option} From 9af1515c48b48b9f7de2a362591a71e1c6f61dce Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 31 Jul 2023 21:29:00 +0200 Subject: [PATCH 124/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f4c698d..67f8df4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.17" +version = "2.18" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 6f138418aef3fdc517728b59e9f8ea47ac42e250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 02:30:10 +0000 Subject: [PATCH 125/248] Bump cryptography from 41.0.2 to 41.0.3 Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.2 to 41.0.3. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.2...41.0.3) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 81 +++++++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e7ae60..8213648 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "2.1.2" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -32,7 +30,6 @@ test = ["coverage", "flake8", "mypy", "pexpect", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -47,7 +44,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -59,7 +55,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -74,7 +69,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -86,7 +80,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -163,7 +156,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -246,35 +238,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, - {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, - {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, - {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -294,7 +285,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -306,7 +296,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -326,7 +315,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -345,7 +333,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -364,7 +351,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -380,7 +366,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -405,7 +390,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -430,7 +414,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -442,7 +425,6 @@ files = [ name = "more-itertools" version = "10.0.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -454,7 +436,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -466,7 +447,6 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -481,7 +461,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -493,7 +472,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -508,7 +486,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -520,7 +497,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -532,7 +508,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -554,7 +529,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -574,7 +548,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -590,7 +563,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -602,7 +574,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -614,7 +585,6 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -632,7 +602,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From 4eaa617f2637cc8a0dbc7ff848379d03d28c8823 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 07:45:42 +0000 Subject: [PATCH 126/248] Bump argcomplete from 2.1.2 to 3.1.1 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 2.1.2 to 3.1.1. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v2.1.2...v3.1.1) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- poetry.lock | 11 +++++------ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8213648..d7777fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,18 +13,17 @@ files = [ [[package]] name = "argcomplete" -version = "2.1.2" +version = "3.1.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.6" files = [ - {file = "argcomplete-2.1.2-py3-none-any.whl", hash = "sha256:4ba9cdaa28c361d251edce884cd50b4b1215d65cdc881bd204426cdde9f52731"}, - {file = "argcomplete-2.1.2.tar.gz", hash = "sha256:fc82ef070c607b1559b5c720529d63b54d9dcf2dcfc2632b10e6372314a34457"}, + {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, + {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, ] [package.extras] -lint = ["flake8", "mypy"] -test = ["coverage", "flake8", "mypy", "pexpect", "wheel"] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "arrow" @@ -616,4 +615,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "adce64fcd9d3a421a927eacf63bfccfed6315e4a6c68188220b519ae76c23b9f" +content-hash = "708b5caaab809e22810e66698ea981afd68848aa9a0cf8e475ba593c399e02fe" diff --git a/pyproject.toml b/pyproject.toml index 67f8df4..f023472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ canvaslms = "canvaslms.cli:main" [tool.poetry.dependencies] python = "^3.8" appdirs = "^1.4.4" -argcomplete = "^2.0.0" +argcomplete = ">=2,<4" cachetools = "^5.3.1" canvasapi = "<3.0.0" keyring = "^24.2.0" From 4a4be6c37ea1cab1fec8a101dc7ccb092b3d9d2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 07:46:05 +0000 Subject: [PATCH 127/248] Bump rich from 13.5.1 to 13.5.2 Bumps [rich](https://github.com/Textualize/rich) from 13.5.1 to 13.5.2. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.5.1...v13.5.2) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8213648..dda5e9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -527,13 +527,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.5.1" +version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.5.1-py3-none-any.whl", hash = "sha256:b97381b204a206e1be618f5e1215a57174a1a7732490b3bf6668cf41d30bc72d"}, - {file = "rich-13.5.1.tar.gz", hash = "sha256:881653ee7037803559d8eae98f145e0a4c4b0ec3ff0300d2cc8d479c71fc6819"}, + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, ] [package.dependencies] From 85c6830c9077bf2ed59c02960a4e5a4cc038b5a9 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Aug 2023 11:47:20 +0200 Subject: [PATCH 128/248] Bugfix: didn't take into account students who did nothing --- src/canvaslms/grades/disjunctmax.nw | 12 +++++++++++- src/canvaslms/grades/maxgradesurvey.nw | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 9c2e3e9..8dd0a3e 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -81,7 +81,17 @@ def summarize(user, assignments_list): grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) dates.append(grade_date) - return (grade_max(grades), max(dates), graders) + if grades: + final_grade = grade_max(grades) + else: + final_grade = "F" + + if dates: + final_date = max(dates) + else: + final_date = None + + return (final_grade, final_date, graders) @ \subsection{Computing the maximum} diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 1f455c1..9eb2d20 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -86,6 +86,11 @@ def summarize(user, assignments_list): else: final_grade = grade_max(grades) - return (final_grade, max(dates), graders) + if dates: + final_date = max(dates) + else: + final_date = None + + return (final_grade, final_date, graders) @ From 174cb26fb7b24c6292dbc160c3ca4992942e5c8c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Aug 2023 13:49:25 +0200 Subject: [PATCH 129/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 67f8df4..bed03d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.18" +version = "2.19" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From bc8eac8454256aa68c6cfb60307665a3bf371ced Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 10 Aug 2023 14:34:33 +0200 Subject: [PATCH 130/248] Bugfix: Can't print for users without --- src/canvaslms/hacks/canvasapi.nw | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/hacks/canvasapi.nw b/src/canvaslms/hacks/canvasapi.nw index 241068d..4bd7464 100644 --- a/src/canvaslms/hacks/canvasapi.nw +++ b/src/canvaslms/hacks/canvasapi.nw @@ -120,7 +120,10 @@ Now, we simply need to define a function to use as a drop-in replacement for the [[__str__]] method. <>= def name_and_login(self): - return f"{self.name} <{self.login_id}>" + try: + return f"{self.name} <{self.login_id}>" + except AttributeError as err: + return f"{self.name} <>" @ Then we simply need to replace the current [[__str__]] method with the new one From 015b4e3c3241a2994d418f16532700dd6ccd2519 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 12:21:01 +0200 Subject: [PATCH 131/248] grades: Handle that a submission resource doesn't exist --- src/canvaslms/grades/conjunctavg.nw | 9 +++++++-- src/canvaslms/grades/disjunctmax.nw | 9 +++++++-- src/canvaslms/grades/maxgradesurvey.nw | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index 8c7ce4b..930f393 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -21,6 +21,7 @@ Conjunctive average means: import datetime as dt from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist <> @@ -62,8 +63,12 @@ def summarize(user, assignments_list): graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user, - include=["submission_history"]) + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + continue + submission.assignment = assignment graders += results.all_graders(submission) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 8dd0a3e..0ac1940 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -29,6 +29,7 @@ This gives the following outline of the module. import datetime as dt from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist <> @@ -63,8 +64,12 @@ def summarize(user, assignments_list): graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user, - include=["submission_history"]) + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + continue + submission.assignment = assignment graders += results.all_graders(submission) diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 9eb2d20..01c475e 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -27,6 +27,7 @@ This gives the following outline of the module. import datetime as dt from canvaslms.grades.disjunctmax import grade_max from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist <> @@ -63,8 +64,12 @@ def summarize(user, assignments_list): graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user, - include=["submission_history"]) + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + continue + submission.assignment = assignment graders += results.all_graders(submission) From 350142c23d0c59422229b0c753fb64b1b5f17b5c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 12:21:42 +0200 Subject: [PATCH 132/248] grades conjunctavgsurvey: Handle nonexisting submission, graders --- src/canvaslms/grades/conjunctavgsurvey.nw | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/grades/conjunctavgsurvey.nw b/src/canvaslms/grades/conjunctavgsurvey.nw index 28b389d..93d1453 100644 --- a/src/canvaslms/grades/conjunctavgsurvey.nw +++ b/src/canvaslms/grades/conjunctavgsurvey.nw @@ -16,6 +16,8 @@ makes this module useful for including mandatory, ungraded surveys. import datetime as dt from canvaslms.grades.conjunctavg import a2e_average +from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist <> @@ -24,8 +26,8 @@ def summarize_group(assignments_list, users_list): users in users_list""" for user in users_list: - grade, grade_date = summarize(user, assignments_list) - yield (user, grade, grade_date) + grade, grade_date, graders = summarize(user, assignments_list) + yield [user, grade, grade_date, *graders] @ @@ -57,10 +59,17 @@ def summarize(user, assignments_list): pf_grades = [] a2e_grades = [] recent_date = dt.date(year=1970, month=1, day=1) + graders = [] for assignment in assignments_list: - submission = assignment.get_submission(user) + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + continue + grade = submission.grade + graders += results.all_graders(submission) if grade is None: grade = "F" @@ -83,9 +92,9 @@ def summarize(user, assignments_list): recent_date = grade_date if not all(map(lambda x: x == "P", pf_grades)): - return ("F", recent_date) + return ("F", recent_date, graders) if a2e_grades: - return (a2e_average(a2e_grades), recent_date) - return ("P", recent_date) + return (a2e_average(a2e_grades), recent_date, graders) + return ("P", recent_date, graders) @ From 29c23a91478c4012fde038ab352025077f9b83a5 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 14:22:33 +0200 Subject: [PATCH 133/248] Skip reporting if there's no grade or date --- src/canvaslms/cli/results.nw | 7 +++++++ src/canvaslms/grades/conjunctavg.nw | 24 +++++++++++++----------- src/canvaslms/grades/disjunctmax.nw | 1 + 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 79a2e48..a29cb98 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -261,6 +261,7 @@ def summarize_assignment_groups(canvas, args): assignment_group, all_assignments)) for user, grade, grade_date, *graders in summary.summarize_group( assignments_list, users_list): + <> yield [ course.course_code, assignment_group.name, @@ -271,6 +272,12 @@ def summarize_assignment_groups(canvas, args): ] @ We will now cover the [[summarize_group]] function in the [[summary]] module. +If a student hasn't done anything, the grade and date will be [[None]]. +There is no point in including this in the result. +<>= +if grade is None or date is None: + continue +@ \subsection{Loading a custom summary module} \label{custom-summary-modules} diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index 930f393..5692a65 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -59,7 +59,7 @@ def summarize(user, assignments_list): pf_grades = [] a2e_grades = [] - recent_date = dt.date(year=1970, month=1, day=1) + dates = [] graders = [] for assignment in assignments_list: @@ -84,20 +84,22 @@ def summarize(user, assignments_list): grade_date = submission.submitted_at or submission.graded_at - if not grade_date: - grade_date = recent_date - else: + if grade_date: grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) - if grade_date > recent_date: - recent_date = grade_date - - if not all(map(lambda x: x == "P", pf_grades)): + if all(map(lambda x: x == "P", pf_grades)): + final_grade = "P" + if a2e_grades: + final_grade = a2e_average(a2e_grades) + else: final_grade = "F" - elif a2e_grades: - final_grade = a2e_average(a2e_grades) + + if dates: + final_date = max(dates) else: - final_grade = "P" + final_date = None + final_grade = None return (final_grade, recent_date, graders) @ diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 0ac1940..60caaa3 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -95,6 +95,7 @@ def summarize(user, assignments_list): final_date = max(dates) else: final_date = None + final_grade = None return (final_grade, final_date, graders) @ From 7794b44e264d08df29b8f3a89788882891384c24 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 14:46:16 +0200 Subject: [PATCH 134/248] Improves the grades example --- src/canvaslms/grades/grades.nw | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index b5137a3..6f5c159 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -44,6 +44,7 @@ and the graders and return the tuple (list) expected. <>= import datetime as dt from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist def summarize_group(assignments, users): """ @@ -51,12 +52,12 @@ def summarize_group(assignments, users): """ for user in users: grades = [] - recent_date = dt.date(year=1970, month=1, day=1) + dates = [] graders = [] <> - yield [user, final_grade(grades), recent_date, *graders] + yield [user, final_grade(grades), max(dates), *graders] @ We leave for the reader to imagine all the possibilities of the @@ -68,15 +69,23 @@ remaining sections of this chapter.) To extract the data we need, we simply iterate through all the assignments and fetch the user's (student's) submission. + Note that we must add the option [[include=["submission_history"]]] to be able to extract everyone who participated in the grading, not just the last one. This is important since the last grader might just check the parts that the previous grader said must be fixed by the student. So both are part of the grading. + +We must also handle the event that the submission doesn't exist. +This happens in very rare cases. +But to get correct behaviour, we must treat it as an F. <>= for assignment in assignments: - submission = assignment.get_submission(user, - include=["submission_history"]) + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + grades.append("F") <> @ @@ -109,8 +118,7 @@ try: except AttributeError: pass else: - if grade_date > recent_date: - recent_date = grade_date + dates.append(date) @ To use this module we would run From 560ce3de9963ae35ae8fbf8892d0d00a277ccdf4 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 15:03:55 +0200 Subject: [PATCH 135/248] Adapts conjunctavg to grades example --- src/canvaslms/grades/conjunctavg.nw | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index 5692a65..3de8943 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -41,8 +41,10 @@ Now we will describe the [[summarize]] helper function. We want to establish three things: the most recent date, a suitable grade and who graded. -For the most recent date, we just check the dates as we iterate through the -submissions. +For the most recent date, we add all dates to a list and then take the +maximum. +If the list is empty, we don't report any grade, but returns [[None]] for both +grade and date. For the grade, as we iterate through we look for P/F and A--E grades. We can then check for Fs among the P/F grades, if we find an F the summarized @@ -67,6 +69,7 @@ def summarize(user, assignments_list): submission = assignment.get_submission(user, include=["submission_history"]) except ResourceDoesNotExist: + pf_grades.append("F") continue submission.assignment = assignment From 13fb29ac59da68af2b6e9a1f09bd8a915265d77f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 15:05:37 +0200 Subject: [PATCH 136/248] disjunctmax: treats nonexisting as an F --- src/canvaslms/grades/disjunctmax.nw | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 60caaa3..8e10929 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -68,6 +68,7 @@ def summarize(user, assignments_list): submission = assignment.get_submission(user, include=["submission_history"]) except ResourceDoesNotExist: + pf_grades.append("F") continue submission.assignment = assignment From 8410b328b2379544f08efb953356d11736d49d81 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 15:10:10 +0200 Subject: [PATCH 137/248] maxgradesurvey: treats missing submission as F --- src/canvaslms/grades/maxgradesurvey.nw | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 01c475e..347a398 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -44,7 +44,8 @@ def summarize_group(assignments_list, users_list): \subsection{Summarizing grades: assignment grades to component grade} Now we will describe the [[summarize]] helper function. -We want to establish two things: the most recent date and a suitable grade. +We want to establish three things: the most recent date, a suitable grade and +the graders. For the most recent date, we just add them to a list as we iterate through the submissions. @@ -68,6 +69,7 @@ def summarize(user, assignments_list): submission = assignment.get_submission(user, include=["submission_history"]) except ResourceDoesNotExist: + pf_grades.append("F") continue submission.assignment = assignment @@ -95,6 +97,7 @@ def summarize(user, assignments_list): final_date = max(dates) else: final_date = None + final_grade = None return (final_grade, final_date, graders) @ From 53eb9ad9c93408f86e37d588f5484e08167ae268 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 15:15:29 +0200 Subject: [PATCH 138/248] Improves date handling in conjunctavgsurvey --- src/canvaslms/grades/conjunctavgsurvey.nw | 37 ++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/canvaslms/grades/conjunctavgsurvey.nw b/src/canvaslms/grades/conjunctavgsurvey.nw index 93d1453..7b47c08 100644 --- a/src/canvaslms/grades/conjunctavgsurvey.nw +++ b/src/canvaslms/grades/conjunctavgsurvey.nw @@ -36,8 +36,9 @@ def summarize_group(assignments_list, users_list): Now we will describe the [[summarize]] helper function. We want to establish two things: the most recent date and a suitable grade. -For the most recent date, we just check the dates as we iterate through the -submissions. +For the most recent date, we just add them to a list as we iterate through +them. +Then we can simply take the maximum. For the grade, as we iterate through we look for P/F and A--E grades. We can then check for Fs among the P/F grades, if we find an F the summarized @@ -58,7 +59,7 @@ def summarize(user, assignments_list): pf_grades = [] a2e_grades = [] - recent_date = dt.date(year=1970, month=1, day=1) + dates = [] graders = [] for assignment in assignments_list: @@ -66,6 +67,7 @@ def summarize(user, assignments_list): submission = assignment.get_submission(user, include=["submission_history"]) except ResourceDoesNotExist: + pf_grades.append("F") continue grade = submission.grade @@ -78,23 +80,30 @@ def summarize(user, assignments_list): a2e_grades.append(grade) elif grade in "PF": pf_grades.append(grade) + elif grade == "Fx": + pf_grades.append("F") else: - pf_grades.append("E") + pf_grades.append("P") grade_date = submission.submitted_at or submission.graded_at - if not grade_date: - return ("F", None) - else: + if grade_date: grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) + + if all(map(lambda x: x == "P", pf_grades)): + final_grade = "P" + if a2e_grades: + final_grade = a2e_average(a2e_grades) - if grade_date > recent_date: - recent_date = grade_date + if dates: + final_date = max(dates) + else: + final_date = None + final_grade = None - if not all(map(lambda x: x == "P", pf_grades)): - return ("F", recent_date, graders) + if len(dates) < len(pf_grades) + len(a2e_grades): + final_grade = "F" - if a2e_grades: - return (a2e_average(a2e_grades), recent_date, graders) - return ("P", recent_date, graders) + return (final_grade, final_date, graders) @ From 7c56eda55767a1a7cf612781f295864ad1f47d2a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 11 Aug 2023 15:20:36 +0200 Subject: [PATCH 139/248] grades.conjunctavg: fixes typo, remaining recent_date --- src/canvaslms/grades/conjunctavg.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/conjunctavg.nw b/src/canvaslms/grades/conjunctavg.nw index 3de8943..e8e4d6c 100644 --- a/src/canvaslms/grades/conjunctavg.nw +++ b/src/canvaslms/grades/conjunctavg.nw @@ -104,7 +104,7 @@ def summarize(user, assignments_list): final_date = None final_grade = None - return (final_grade, recent_date, graders) + return (final_grade, final_date, graders) @ \subsection{Computing averages} From 42ccbb12cfbb174a79e414f5f33eae01811e479d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 14 Aug 2023 10:13:12 +0200 Subject: [PATCH 140/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 35eda06..fbca263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.19" +version = "2.20" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From a9c883b7a561d0f8e44ed2109b61caadbe7af29c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 14 Aug 2023 10:13:39 +0200 Subject: [PATCH 141/248] Bugfix: fixes typo `date` -> `grade_date` --- src/canvaslms/cli/results.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index a29cb98..c128293 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -275,7 +275,7 @@ def summarize_assignment_groups(canvas, args): If a student hasn't done anything, the grade and date will be [[None]]. There is no point in including this in the result. <>= -if grade is None or date is None: +if grade is None or grade_date is None: continue @ From 81799e806b880efa71d97a53e817681c5070a124 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 16 Aug 2023 11:43:29 +0200 Subject: [PATCH 142/248] Bugfix: pf_grades -> grades --- src/canvaslms/grades/maxgradesurvey.nw | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 347a398..029a105 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -69,7 +69,7 @@ def summarize(user, assignments_list): submission = assignment.get_submission(user, include=["submission_history"]) except ResourceDoesNotExist: - pf_grades.append("F") + grades.append("F") continue submission.assignment = assignment @@ -100,5 +100,4 @@ def summarize(user, assignments_list): final_grade = None return (final_grade, final_date, graders) -@ From cccd7fa81c120bebcf5ac1b62776c049db2a5b51 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 16 Aug 2023 11:44:04 +0200 Subject: [PATCH 143/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fbca263..2f1c274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.20" +version = "2.21" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From a40fdca08f71efe708767da0e3fb25105f87192d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 27 Aug 2023 09:25:14 +0200 Subject: [PATCH 144/248] Bumps depends' versions --- poetry.lock | 51 ++++++++++++++++++++++++++++++++++++++++---------- pyproject.toml | 2 +- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index e43b110..dda60ba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = "*" files = [ @@ -15,6 +16,7 @@ files = [ name = "argcomplete" version = "3.1.1" description = "Bash tab completion for argparse" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -29,6 +31,7 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -43,6 +46,7 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -54,6 +58,7 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" +category = "main" optional = false python-versions = "*" files = [ @@ -68,6 +73,7 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -79,6 +85,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -155,6 +162,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -239,6 +247,7 @@ files = [ name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -284,6 +293,7 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -295,6 +305,7 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -312,13 +323,14 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs [[package]] name = "importlib-resources" -version = "6.0.0" +version = "6.0.1" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"}, - {file = "importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"}, + {file = "importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, + {file = "importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, ] [package.dependencies] @@ -332,6 +344,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -350,6 +363,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -365,6 +379,7 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -389,6 +404,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -413,6 +429,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -422,19 +439,21 @@ files = [ [[package]] name = "more-itertools" -version = "10.0.0" +version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.0.0.tar.gz", hash = "sha256:cd65437d7c4b615ab81c0640c0480bc29a550ea032891977681efd28344d51e1"}, - {file = "more_itertools-10.0.0-py3-none-any.whl", hash = "sha256:928d514ffd22b5b0a8fce326d57f423a55d2ff783b093bab217eda71e732330f"}, + {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, + {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -444,13 +463,14 @@ files = [ [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -460,6 +480,7 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -471,6 +492,7 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -485,6 +507,7 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -496,6 +519,7 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -507,6 +531,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -528,6 +553,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -547,6 +573,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -562,6 +589,7 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -573,6 +601,7 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -584,6 +613,7 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -601,6 +631,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ diff --git a/pyproject.toml b/pyproject.toml index 2f1c274..ea6f868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.21" +version = "2.22" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 6d672a5392e429cc455611d1447496d0de29eb21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 07:36:07 +0000 Subject: [PATCH 145/248] Bump argcomplete from 3.1.1 to 3.1.2 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.1.1...v3.1.2) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index dda60ba..00ddde3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -14,14 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.1.1" +version = "3.1.2" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, - {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, + {file = "argcomplete-3.1.2-py3-none-any.whl", hash = "sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99"}, + {file = "argcomplete-3.1.2.tar.gz", hash = "sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b"}, ] [package.extras] @@ -31,7 +29,6 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -46,7 +43,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -58,7 +54,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +68,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +79,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +155,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -247,7 +239,6 @@ files = [ name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -293,7 +284,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -305,7 +295,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -325,7 +314,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -344,7 +332,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -363,7 +350,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -379,7 +365,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +389,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -429,7 +413,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -441,7 +424,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -453,7 +435,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -465,7 +446,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,7 +460,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -492,7 +471,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -507,7 +485,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -519,7 +496,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -531,7 +507,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +528,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -573,7 +547,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -589,7 +562,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -601,7 +573,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -613,7 +584,6 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -631,7 +601,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From 888197a8171f92740075044145835f2ee0ab7d19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:58:34 +0000 Subject: [PATCH 146/248] Bump cryptography from 41.0.3 to 41.0.4 Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.3 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.3...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 81 +++++++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/poetry.lock b/poetry.lock index dda60ba..0611ba4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "3.1.1" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -31,7 +29,6 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -46,7 +43,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -58,7 +54,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +68,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +79,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +155,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -245,35 +237,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -293,7 +284,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -305,7 +295,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -325,7 +314,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -344,7 +332,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -363,7 +350,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -379,7 +365,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +389,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -429,7 +413,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -441,7 +424,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -453,7 +435,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -465,7 +446,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,7 +460,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -492,7 +471,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -507,7 +485,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -519,7 +496,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -531,7 +507,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +528,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -573,7 +547,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -589,7 +562,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -601,7 +573,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -613,7 +584,6 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -631,7 +601,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From c2739e6cf67eae6849ea8afbeeacc8dd59f73c37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 07:57:42 +0000 Subject: [PATCH 147/248] Bump arrow from 1.2.3 to 1.3.0 Bumps [arrow](https://github.com/arrow-py/arrow) from 1.2.3 to 1.3.0. - [Release notes](https://github.com/arrow-py/arrow/releases) - [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst) - [Commits](https://github.com/arrow-py/arrow/compare/1.2.3...1.3.0) --- updated-dependencies: - dependency-name: arrow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 57 ++++++++++++++++++++--------------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/poetry.lock b/poetry.lock index dda60ba..d9a0fd7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "3.1.1" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -29,24 +27,27 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "arrow" -version = "1.2.3" +version = "1.3.0" description = "Better dates & times for Python" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, - {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, ] [package.dependencies] python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] [[package]] name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -58,7 +59,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +73,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +84,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +160,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -247,7 +244,6 @@ files = [ name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -293,7 +289,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -305,7 +300,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -325,7 +319,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -344,7 +337,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -363,7 +355,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -379,7 +370,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +394,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -429,7 +418,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -441,7 +429,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -453,7 +440,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -465,7 +451,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,7 +465,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -492,7 +476,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -507,7 +490,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -519,7 +501,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -531,7 +512,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +533,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -573,7 +552,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -589,7 +567,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -597,11 +574,21 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "types-python-dateutil" +version = "2.8.19.14" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = "*" +files = [ + {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, + {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, +] + [[package]] name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -613,7 +600,6 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -631,7 +617,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From 55018dc685d107685e7ba07e900a1503dc5d2027 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 07:58:19 +0000 Subject: [PATCH 148/248] Bump rich from 13.5.2 to 13.6.0 Bumps [rich](https://github.com/Textualize/rich) from 13.5.2 to 13.6.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.5.2...v13.6.0) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index dda60ba..9b0c017 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "3.1.1" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -31,7 +29,6 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -46,7 +43,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -58,7 +54,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +68,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +79,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +155,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -247,7 +239,6 @@ files = [ name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -293,7 +284,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -305,7 +295,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -325,7 +314,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -344,7 +332,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -363,7 +350,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -379,7 +365,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +389,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -429,7 +413,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -441,7 +424,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -453,7 +435,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -465,7 +446,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,7 +460,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -492,7 +471,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -507,7 +485,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -519,7 +496,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -531,7 +507,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -551,14 +526,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.5.2" +version = "13.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, - {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, + {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, + {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, ] [package.dependencies] @@ -573,7 +547,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -589,7 +562,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -601,7 +573,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -613,7 +584,6 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -631,7 +601,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From e01aa9d03663e1701cb41233c282b3f2925414f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:46:47 +0000 Subject: [PATCH 149/248] Bump urllib3 from 2.0.4 to 2.0.7 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.7. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.7) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index dda60ba..2058308 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "3.1.1" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -31,7 +29,6 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -46,7 +43,6 @@ python-dateutil = ">=2.7.0" name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -58,7 +54,6 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -73,7 +68,6 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +79,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -162,7 +155,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -247,7 +239,6 @@ files = [ name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -293,7 +284,6 @@ test-randomorder = ["pytest-randomly"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -305,7 +295,6 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -325,7 +314,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -344,7 +332,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -363,7 +350,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -379,7 +365,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +389,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -429,7 +413,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -441,7 +424,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -453,7 +435,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -465,7 +446,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,7 +460,6 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.11" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -492,7 +471,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -507,7 +485,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -519,7 +496,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -531,7 +507,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +528,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -573,7 +547,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -589,7 +562,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -601,7 +573,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -611,14 +582,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] @@ -631,7 +601,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From 9605d4eb4e8b492984242e81e79bb08d444dc5e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 07:27:38 +0000 Subject: [PATCH 150/248] Bump pypandoc from 1.11 to 1.12 Bumps [pypandoc](https://github.com/JessicaTegner/pypandoc) from 1.11 to 1.12. - [Release notes](https://github.com/JessicaTegner/pypandoc/releases) - [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md) - [Commits](https://github.com/JessicaTegner/pypandoc/compare/v1.11...v1.12) --- updated-dependencies: - dependency-name: pypandoc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04f192b..87710e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -458,13 +458,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pypandoc" -version = "1.11" +version = "1.12" description = "Thin wrapper for pandoc." optional = false python-versions = ">=3.6" files = [ - {file = "pypandoc-1.11-py3-none-any.whl", hash = "sha256:b260596934e9cfc6513056110a7c8600171d414f90558bf4407e68b209be8007"}, - {file = "pypandoc-1.11.tar.gz", hash = "sha256:7f6d68db0e57e0f6961bec2190897118c4d305fc2d31c22cd16037f22ee084a5"}, + {file = "pypandoc-1.12-py3-none-any.whl", hash = "sha256:efb4f7d68ead8bec32e22b62f02d5608a1700978b51bfc4af286fd6acfe9d218"}, + {file = "pypandoc-1.12.tar.gz", hash = "sha256:8f44740a9f074e121d81b489f073160421611d4ead62d1b306aeb11aab3c32df"}, ] [[package]] From fbbe0bdbeaa989553bb97dab4a8198e19e13f02d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 07:27:52 +0000 Subject: [PATCH 151/248] Bump cachetools from 5.3.1 to 5.3.2 Bumps [cachetools](https://github.com/tkem/cachetools) from 5.3.1 to 5.3.2. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v5.3.1...v5.3.2) --- updated-dependencies: - dependency-name: cachetools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04f192b..54fe3c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -41,13 +41,13 @@ python-dateutil = ">=2.7.0" [[package]] name = "cachetools" -version = "5.3.1" +version = "5.3.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, - {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, ] [[package]] From 552570f53f2b71ae1b201465bd39c85624783436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 07:34:11 +0000 Subject: [PATCH 152/248] Bump argcomplete from 3.1.2 to 3.1.6 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.1.2 to 3.1.6. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.1.2...v3.1.6) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04f192b..4ab884a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.1.2" +version = "3.1.6" description = "Bash tab completion for argparse" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "argcomplete-3.1.2-py3-none-any.whl", hash = "sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99"}, - {file = "argcomplete-3.1.2.tar.gz", hash = "sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b"}, + {file = "argcomplete-3.1.6-py3-none-any.whl", hash = "sha256:71f4683bc9e6b0be85f2b2c1224c47680f210903e23512cfebfe5a41edfd883a"}, + {file = "argcomplete-3.1.6.tar.gz", hash = "sha256:3b1f07d133332547a53c79437527c00be48cca3807b1d4ca5cab1b26313386a6"}, ] [package.extras] From 1f3d355f602874a655fee8b1ba4b7f898d577e2a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 13 Nov 2023 09:23:24 +0100 Subject: [PATCH 153/248] Makes grades module handle Fx --- src/canvaslms/grades/disjunctmax.nw | 19 +++++++++++++------ src/canvaslms/grades/maxgradesurvey.nw | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 8e10929..4fb6e95 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -88,7 +88,7 @@ def summarize(user, assignments_list): dates.append(grade_date) if grades: - final_grade = grade_max(grades) + final_grade = grade_max(grades) or "F" else: final_grade = "F" @@ -109,16 +109,23 @@ We also include P/F here, since we can count them as lower than A--E. <>= def grade_max(grades): """Takes a list of A--E/P--F grades, returns the maximum.""" - num_grades = map(grade_to_int, grades) - max_grade = max(num_grades) - return int_to_grade(max_grade) + num_grades = list(map(grade_to_int, + filter(lambda x: x[0] != "F", grades))) + if not num_grades: + max_grade = max(num_grades) + return int_to_grade(max_grade) + return None def grade_to_int(grade): - grade_map = {"F": -1, "P": 0, "E": 1, "D": 2, "C": 3, "B": 4, "A": 5} + grade_map = {"F": -2, "Fx": -1, + "P": 0, + "E": 1, "D": 2, "C": 3, "B": 4, "A": 5} return grade_map[grade] def int_to_grade(int_grade): - grade_map_inv = {-1: "F", 0: "P", 1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} + grade_map_inv = {-2: "F", -1: "Fx", + 0: "P", + 1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} return grade_map_inv[int_grade] @ diff --git a/src/canvaslms/grades/maxgradesurvey.nw b/src/canvaslms/grades/maxgradesurvey.nw index 029a105..692c440 100644 --- a/src/canvaslms/grades/maxgradesurvey.nw +++ b/src/canvaslms/grades/maxgradesurvey.nw @@ -67,7 +67,7 @@ def summarize(user, assignments_list): for assignment in assignments_list: try: submission = assignment.get_submission(user, - include=["submission_history"]) + include=["submission_history"]) except ResourceDoesNotExist: grades.append("F") continue @@ -91,7 +91,7 @@ def summarize(user, assignments_list): if len(dates) < len(grades): final_grade = "F" else: - final_grade = grade_max(grades) + final_grade = grade_max(grades) or "F" if dates: final_date = max(dates) From ba6316e804ce3c5e521e7ec3f3798192a8e00ecf Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 13 Nov 2023 09:32:39 +0100 Subject: [PATCH 154/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea6f868..e3ea562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.22" +version = "2.23" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 61814826005c745eb7922dfdab4e9169103d4a48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:33:38 +0000 Subject: [PATCH 155/248] Bump rich from 13.6.0 to 13.7.0 Bumps [rich](https://github.com/Textualize/rich) from 13.6.0 to 13.7.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.6.0...v13.7.0) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0b0f892..6c53046 100644 --- a/poetry.lock +++ b/poetry.lock @@ -531,13 +531,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.6.0" +version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, - {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, ] [package.dependencies] From 37e6c3084ef21a041b2c603b7bcf8658dc045943 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:33:50 +0000 Subject: [PATCH 156/248] Bump keyring from 24.2.0 to 24.3.0 Bumps [keyring](https://github.com/jaraco/keyring) from 24.2.0 to 24.3.0. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/keyring/compare/v24.2.0...v24.3.0) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0b0f892..cb958bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -368,13 +368,13 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "24.2.0" +version = "24.3.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-24.2.0-py3-none-any.whl", hash = "sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6"}, - {file = "keyring-24.2.0.tar.gz", hash = "sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509"}, + {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, + {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, ] [package.dependencies] @@ -386,9 +386,9 @@ pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -completion = ["shtab"] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "markdown-it-py" From e2e670f0ddbccce5b0cb144690c20d69a61c9d8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:23:30 +0000 Subject: [PATCH 157/248] Bump cryptography from 41.0.4 to 41.0.6 Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.4 to 41.0.6. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.4...41.0.6) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0b0f892..83522df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "appdirs" @@ -242,34 +242,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.4" +version = "41.0.6" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, + {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, + {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, + {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, + {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, + {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, + {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, ] [package.dependencies] From 2f6dd6526d8bdb3dfec7e35d1503d9b4fd59dfb4 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Dec 2023 13:21:15 +0100 Subject: [PATCH 158/248] Adds option to include submission history --- src/canvaslms/cli/submissions.nw | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 7bdb8ba..2c9ab31 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -143,6 +143,7 @@ submission_parser = subp.add_parser("submission", "including submission and grading time, any text-based attachments.") submission_parser.set_defaults(func=submission_command) add_submission_options(submission_parser) +<> @ We also need the corresponding function. For now, we only print the most relevant data of a submission. <>= @@ -162,7 +163,7 @@ raw markdown. <>= console = rich.console.Console() for submission in submission_list: - output = format_submission(submission) + output = format_submission(submission, history=args.history) if sys.stdout.isatty(): <> @@ -174,6 +175,16 @@ for submission in submission_list: @ Note that we use the theme [[manni]] for the code, as this works in both dark and light terminals. +Now, let's turn to that [[args.history]] argument. +We want to exclude it sometimes, for instance, when we want to get to the +comments only. +So we default to off, since it's only occasionally that we want to see the +history. +<>= +submission_parser.add_argument("-H", "--history", action="store_true", + help="Include submission history.") +@ + \subsection{Check if we should use styles} By default, [[rich.console.Console]] uses the [[pydoc.pager]], which uses the @@ -347,8 +358,12 @@ IDs, instead, we add these as attributes when fetching the objects. So [[submission.assignment]] is the assignment it came from, we don't need to resolve the assignment from the assignmend ID. <>= -def format_submission(submission): - """Formats submission for printing to stdout""" +def format_submission(submission, history=False): + """ + Formats submission for printing to stdout. Returns a string. + + If history is True, also include submission history. + """ student = submission.assignment.course.get_user(submission.user_id) formatted_submission = "" @@ -359,7 +374,8 @@ def format_submission(submission): <> <> <> - <> + if history: + <> return formatted_submission @ From 755fdb6e195a3e115741cda65419aaec7c4aa337 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Dec 2023 13:21:55 +0100 Subject: [PATCH 159/248] Bumps version The submission history option, `canvaslms submission -H`, breaks backwards compatibility. This is because the default output format is changed, it no longer includes the history. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3ea562..a3c2bef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "2.23" +version = "3.0" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 42725d9f250cba4ff6105cc4b66a5611009a9107 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 07:47:41 +0000 Subject: [PATCH 160/248] Bump argcomplete from 3.1.6 to 3.2.1 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.1.6 to 3.2.1. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.1.6...v3.2.1) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index a6fcf1e..21f127d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.1.6" +version = "3.2.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.1.6-py3-none-any.whl", hash = "sha256:71f4683bc9e6b0be85f2b2c1224c47680f210903e23512cfebfe5a41edfd883a"}, - {file = "argcomplete-3.1.6.tar.gz", hash = "sha256:3b1f07d133332547a53c79437527c00be48cca3807b1d4ca5cab1b26313386a6"}, + {file = "argcomplete-3.2.1-py3-none-any.whl", hash = "sha256:30891d87f3c1abe091f2142613c9d33cac84a5e15404489f033b20399b691fec"}, + {file = "argcomplete-3.2.1.tar.gz", hash = "sha256:437f67fb9b058da5a090df505ef9be0297c4883993f3f56cb186ff087778cfb4"}, ] [package.extras] From e905af85fa4910710c6d1ebaa12731ded6c446b2 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 9 Jan 2024 14:59:35 +0100 Subject: [PATCH 161/248] Bugfix: handle missing rubric ratings --- src/canvaslms/cli/submissions.nw | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 2c9ab31..a8c7537 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -559,8 +559,7 @@ def format_rubric(submission): criterion = get_criterion(crit_id, submission.assignment.rubric) rating = get_rating(rating_data["rating_id"], criterion) - result += f"{criterion['description']}: {rating['description']} " \ - f"({rating['points']})\n" + <> if rating_data["comments"]: result += f"Comments: {rating_data['comments']}\n" @@ -570,6 +569,18 @@ def format_rubric(submission): return result.strip() @ +Sometimes Canvas is missing some data, +for instance, an individual rating. +So we add it only if it exists. +<>= +result += f"{criterion['description']}: " +if rating: + result += f"{rating['description']} ({rating['points']})" +else: + result += "-" +result += "\n" +@ + We can get the rating of a rubric from the rubric assessment. We can get this data from [[submission.rubric_assessment]] and it looks like this: From b8c148650455505157de90991c6401eb90cc33a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 07:58:43 +0000 Subject: [PATCH 162/248] Bump argcomplete from 3.2.1 to 3.2.2 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.2.1...v3.2.2) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 21f127d..fae8e32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.2.1" +version = "3.2.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.2.1-py3-none-any.whl", hash = "sha256:30891d87f3c1abe091f2142613c9d33cac84a5e15404489f033b20399b691fec"}, - {file = "argcomplete-3.2.1.tar.gz", hash = "sha256:437f67fb9b058da5a090df505ef9be0297c4883993f3f56cb186ff087778cfb4"}, + {file = "argcomplete-3.2.2-py3-none-any.whl", hash = "sha256:e44f4e7985883ab3e73a103ef0acd27299dbfe2dfed00142c35d4ddd3005901d"}, + {file = "argcomplete-3.2.2.tar.gz", hash = "sha256:f3e49e8ea59b4026ee29548e24488af46e30c9de57d48638e24f54a1ea1000a2"}, ] [package.extras] From 3027a7bf56f4c257c6752077742137f107e359cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 00:43:15 +0000 Subject: [PATCH 163/248] Bump cryptography from 41.0.6 to 42.0.2 Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.6 to 42.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.6...42.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 65 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index 21f127d..7248373 100644 --- a/poetry.lock +++ b/poetry.lock @@ -242,47 +242,56 @@ files = [ [[package]] name = "cryptography" -version = "41.0.6" +version = "42.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, - {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, - {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, - {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, + {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, + {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, + {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, + {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, + {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, + {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] From ec2ef4989d6b281b9c8418b9cbfc4742e30d54b3 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 16:19:24 +0100 Subject: [PATCH 164/248] Updates makefiles submodule --- makefiles | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefiles b/makefiles index a65948d..67d3dff 160000 --- a/makefiles +++ b/makefiles @@ -1 +1 @@ -Subproject commit a65948d6e32472ed4512f0baa8f445a91a7bb35f +Subproject commit 67d3dffd90f19941ad8d95058a8c24450b456757 From 10a00b586f5cb5aa183974ef700bd5bc575ec13f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 16:26:10 +0100 Subject: [PATCH 165/248] Changes filtering of grades in result --- src/canvaslms/cli/results.nw | 58 +++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index c128293..6faa357 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -97,23 +97,38 @@ else: results = summarize_assignments(canvas, args) for result in results: - <> output.writerow(result) @ -We also let the user choose to not include Fs in the output. + +\section{Filtering grades in output} + +We also want to let the user choose to not include Fs (or other grades) in the +output. +By default, we ignore F and Fx grades. +If the user supplies [[-F]], we include all grades. +If the user supplies [[-F regex]], we use regex to filter grades. <>= -results_parser.add_argument("-F", "--include-Fs", - required=False, default=False, action="store_true", - help="Include failing grades (Fs) in output. By default we only output " - "A--Es and Ps.") +passing_regex = r"^([A-EP]|complete)$" +all_grades_regex = r"^([A-F]x?|(in)?complete)$" +results_parser.add_argument("-F", "--filter-grades", + required=False, action="store", nargs="?", + const=all_grades_regex, + default=passing_regex, + help=f"Filter grades. By default we only output " + f"A--Es and Ps ({passing_regex}. " + f"If you want to include Fs ({all_grades_regex}), use this option. " + f"You can also supply an optional regex to this option " + f"to filter grades based on that.") @ -The grade is the third element of the results list. -If we check the first letter of the grade we can detect both F and Fx. -<>= -if not args.include_Fs and result[3][0] == "F": - continue +To make filtering easy, we provide a helper function. +<>= +def filter_grade(grade, regex): + """ + Returns True if the grade matches the regex. + """ + return re.match(regex, grade) @ @@ -146,14 +161,15 @@ def summarize_assignments(canvas, args): for submission in submissions_list: if submission.grade is not None: - yield [ - submission.assignment.course.course_code, - submission.assignment.name, - submission.user.integration_id, - submission.grade, - round_to_day(submission.submitted_at or submission.graded_at), - *all_graders(submission) - ] + if filter_grade(submission.grade, args.filter_grades): + yield [ + submission.assignment.course.course_code, + submission.assignment.name, + submission.user.integration_id, + submission.grade, + round_to_day(submission.submitted_at or submission.graded_at), + *all_graders(submission) + ] @ To create the list of submissions, [[submissions_list]], we have to do the @@ -274,8 +290,10 @@ def summarize_assignment_groups(canvas, args): If a student hasn't done anything, the grade and date will be [[None]]. There is no point in including this in the result. +Similarly, this is a good point to do the filtering of grades. <>= -if grade is None or grade_date is None: +if grade is None or grade_date is None \ + or not filter_grade(grade, args.filter_grades): continue @ From f778f97c8cf1eff584bc8c0adc374281820b982a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 16:53:21 +0100 Subject: [PATCH 166/248] Adds missing option to results command --- src/canvaslms/cli/results.nw | 243 +++++++++++++++++++++++++++++++---- 1 file changed, 217 insertions(+), 26 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 6faa357..88260c8 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -4,11 +4,23 @@ This chapter provides the subcommand [[results]], which lists the results of a course. The purpose of the listing is to export results from Canvas. + +We want to export two types of results. +The first is grades for the students. +We want to turn the assignments in Canvas into grades that can be exported. The format of the listing is compatible with the \texttt{ladok3} package\footnote{% URL: \url{https://github.com/dbosk/ladok3} }. +The second is a listing of all assignments that prevents a student from getting +a grade. +This is useful for reminding students to finish their missing assignments. + +We'll take a general approach and provide an option to switch between these two +cases. + + \section{The [[results]] subcommand and its options} We outline the module: @@ -51,8 +63,9 @@ results_parser = subp.add_parser("results", results_parser.set_defaults(func=results_command) assignments.add_assignment_option(results_parser, ungraded=False) users.add_user_or_group_option(results_parser) -<> <> +<> +<> @ Let's summarize what we want to do. @@ -61,6 +74,13 @@ Lists results of a course for export, for instance to the `ladok report` command. Output format, CSV: + +Can also export a list of missing assignment results (--missing option) that +prevent the student from getting a grade. Output format, CSV: + + + +The reason can be "not submitted" or "not graded". <>= If you specify an assignment group, the results of the assignments in that group will be summarized. You can supply your own function for summarizing @@ -262,8 +282,6 @@ def summarize_assignment_groups(canvas, args): - args is the command-line arguments, as parsed by argparse. """ - <> - courses_list = courses.process_course_option(canvas, args) all_assignments = list(assignments.process_assignment_option(canvas, args)) users_list = list(users.process_user_or_group_option(canvas, args)) @@ -275,17 +293,30 @@ def summarize_assignment_groups(canvas, args): for assignment_group in ag_list: assignments_list = list(assignments.filter_assignments_by_group( assignment_group, all_assignments)) - for user, grade, grade_date, *graders in summary.summarize_group( - assignments_list, users_list): - <> - yield [ - course.course_code, - assignment_group.name, - user.integration_id, - grade, - grade_date, - *graders - ] + if args.missing: + <> + else: + <> +@ + +\subsection{Producing a list of grades} + +Let's start with the case where we want to produce a list of grades. +We simply call the [[summary.summarize_group]] function with the assignments +and users and process the results. +<>= +<> +for user, grade, grade_date, *graders in summary.summarize_group( + assignments_list, users_list): + <> + yield [ + course.course_code, + assignment_group.name, + user.integration_id, + grade, + grade_date, + *graders + ] @ We will now cover the [[summarize_group]] function in the [[summary]] module. If a student hasn't done anything, the grade and date will be [[None]]. @@ -325,24 +356,184 @@ A's. Now to the loader, we first try to load a system module, then we look for a module in the current working directory. -<>= -try: - summary = importlib.import_module(args.summary_module) -except ModuleNotFoundError: - module_path = pathlib.Path.cwd() / args.summary_module - module = module_path.stem - +We provide a helper function to do this. +<>= +def load_module(module_name): + """ + Load a module from the file system or a built-in module. + """ try: + return importlib.import_module(module_name) + except ModuleNotFoundError: + module_path = pathlib.Path.cwd() / module_name + module = module_path.stem + loader = importlib.machinery.SourceFileLoader( module, str(module_path)) spec = importlib.util.spec_from_loader(module, loader) - summary = importlib.util.module_from_spec(spec) - loader.exec_module(summary) - except Exception as err: - canvaslms.cli.err(1, f"Error loading summary module " - f"'{args.summary_module}': {err}") + module_obj = importlib.util.module_from_spec(spec) + loader.exec_module(module_obj) + return module_obj +<>= +try: + summary = load_module(args.summary_module) +except Exception as err: + canvaslms.cli.err(1, f"Error loading summary module " + f"'{args.summary_module}': {err}") @ The available summary functions and the default one can be found in \cref{summary-modules}. +\subsection{Producing a list of missing assignments} + +Now we want to look at the missing option. +If the user supplies this option, we want to produce a list of missing +assignments. +Similarly to summarizing a group, we also want to use different modules to +produce the missing assignments. +We'll use an option missing which takes an optional name of such a module. +<>= +<> +results_parser.add_argument("-M", "--missing", + required=False, nargs="?", + const=default_missing_module, default=None, + help="Produce a list of missing assignments instead of grades. " + "You can supply a custom module to this option, the module must " + "contain a " + "function `missing_assignments(assignments_list, users_list). " + <> + "This option only has effect when working with assignment groups.") +@ + +This lets us load the module and use it to produce the missing assignments, in +a similar fashion as above. +<>= +if args.missing: + try: + missing = load_module(args.missing) + except Exception as err: + canvaslms.cli.err(1, f"Error loading missing module " + f"'{args.missing}': {err}") +@ + +Now, to the main part of the problem. +We simply load the module and call the [[missing_assignments]] function. +It should return a list of tuples, where each tuple is a user, an assignment +and a reason why the assignment is missing. +For instance, the reason could be \enquote{not submitted} or \enquote{not +graded} or \enquote{failed}. + +We output the user's login ID instead of the integration ID, since the login ID +can be used to contact the student (which is probably what we want this data +for). +<>= +<> +<> +for user, assignment, reason in missing_results: + yield [ + course.course_code, + assignment_group.name, + user.login_id, + assignment.name, + reason + ] +@ + +\subsubsection{The default missing module} + +We'll now cover a default function for the missing assignments. +We'll put it in the same module as the [[results]] CLI command, not in a +separate module. +<>= +def missing_assignments(assignments_list, users_list, + <>): + """ + Returns tuples of missing assignments. + + <> + """ + for user in users_list: + for assignment in assignments_list: + <> + <> +<>= +default_missing_module = "canvaslms.cli.results" +@ + +We'll add [[<>]] to the function to make +it accept useful arguments to modify its behaviour. +This way, when someone needs a specific function for their course, they can +just write a function that modifies the default arguments to this function. + +Let's outline what we want this function to do. +The default module checks if all things are graded or submitted. +<>= +"The default module checks if all things are graded or submitted. " +<>= +For each assignment that a student is not done with, we yield a tuple of the +user, the assignment and the reason why the assignment is missing. + +The reason can be "not submitted" or "not graded" or "not a passing grade". + +The only reason to use a different module is if you have optional assignments. +We only want to remind the students of the things they need to pass the course. +We don't want to make it sound like an optional assignment is mandatory. +@ + +This gives us something like this. +<>= +submission = assignment.get_submission(user, include=["submission_history"]) +if submission is None: + yield user, assignment, "not submitted" +elif submission.grade is None: + if submission.submitted_at: + yield user, assignment, \ + f"submitted on {submission.submitted_at}, but not graded" + else: + yield user, assignment, "not done" +elif not filter_grade(submission.grade, passing_regex): + if submission.submitted_at and \ + submission.submitted_at > submission.graded_at: + yield user, assignment, \ + f"not a passing grade ({submission.grade}), resubmission not graded" + else: + yield user, assignment, \ + f"not a passing grade ({submission.grade})" +@ + +Now, we need that [[passing_regex]], so we can add it to the optional +arguments, with a default value (same as above). +<>= +passing_regex=r"^([A-EP]|complete)$", +@ + +Next, if we want to be able to skip optional assignments, we can add an +optional argument for that. +<>= +optional_assignments = None, +@ + +This allows us to make the call to the function as follows. +<>= +missing_results = missing.missing_assignments( + assignments_list, users_list, + passing_regex=args.filter_grades, + optional_assignments=args.optional_assignments) +@ + +All that is missing now is the optional assignments argument for the parser. +<>= +results_parser.add_argument("-O", "--optional-assignments", + required=False, nargs="+", default=None, + help="List of regexes matching optional assignments. The default missing " + "assignments will treat matching assignments as optional.") +@ + +Finally, we can do the skipping too. +<>= +if optional_assignments: + if any(re.match(optional, assignment.name) + for optional in optional_assignments): + continue +@ From bd1a3a4dead6a1a6f748026524ddba8b54fe8541 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 22:40:37 +0100 Subject: [PATCH 167/248] Use re.search instead of re.match --- src/canvaslms/cli/results.nw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 88260c8..195b23a 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -148,7 +148,7 @@ def filter_grade(grade, regex): """ Returns True if the grade matches the regex. """ - return re.match(regex, grade) + return re.search(regex, grade) @ @@ -533,7 +533,7 @@ results_parser.add_argument("-O", "--optional-assignments", Finally, we can do the skipping too. <>= if optional_assignments: - if any(re.match(optional, assignment.name) + if any(re.search(optional, assignment.name) for optional in optional_assignments): continue @ From ca61c3523089f0b8dd86f198bbaf38c16bd6d6d0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 22:41:09 +0100 Subject: [PATCH 168/248] Bugfix: use set instead of list, removes duplicates --- src/canvaslms/cli/results.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 195b23a..e043498 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -284,7 +284,7 @@ def summarize_assignment_groups(canvas, args): courses_list = courses.process_course_option(canvas, args) all_assignments = list(assignments.process_assignment_option(canvas, args)) - users_list = list(users.process_user_or_group_option(canvas, args)) + users_list = set(users.process_user_or_group_option(canvas, args)) for course in courses_list: ag_list = assignments.filter_assignment_groups( From 7fe15b0cd618f67637444d9cc88d41f97454ba66 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 22:43:04 +0100 Subject: [PATCH 169/248] Handles non-existing submissions --- src/canvaslms/cli/results.nw | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index e043498..2f16a23 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -483,7 +483,11 @@ We don't want to make it sound like an optional assignment is mandatory. This gives us something like this. <>= -submission = assignment.get_submission(user, include=["submission_history"]) +try: + submission = assignment.get_submission(user) +except canvasapi.exceptions.ResourceDoesNotExist: + continue + if submission is None: yield user, assignment, "not submitted" elif submission.grade is None: From 3e86659c52252a34a92ab0c1b4878cb336efb2dc Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 22:44:26 +0100 Subject: [PATCH 170/248] Handles different args for built-in missing_assignments --- src/canvaslms/cli/results.nw | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index 2f16a23..cf0dc71 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -519,11 +519,17 @@ optional_assignments = None, @ This allows us to make the call to the function as follows. +We check if it's the default function or not, if it is we can pass additional +arguments from the CLI arguments. <>= -missing_results = missing.missing_assignments( - assignments_list, users_list, - passing_regex=args.filter_grades, - optional_assignments=args.optional_assignments) +if missing.missing_assignments == missing_assignments: + missing_results = missing.missing_assignments( + assignments_list, users_list, + passing_regex=args.filter_grades, + optional_assignments=args.optional_assignments) +else: + missing_results = missing.missing_assignments( + assignments_list, users_list) @ All that is missing now is the optional assignments argument for the parser. From d67f8e9cd4aff4093abd7348c0631dff0fbdaba7 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 18 Feb 2024 22:48:59 +0100 Subject: [PATCH 171/248] Adds pdbpp to dev depends --- poetry.lock | 135 +++++++++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 3 ++ 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 21f127d..9af3ee0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = "*" files = [ @@ -15,6 +16,7 @@ files = [ name = "argcomplete" version = "3.2.1" description = "Bash tab completion for argparse" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -29,6 +31,7 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.3.0" description = "Better dates & times for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -42,12 +45,33 @@ types-python-dateutil = ">=2.8.10" [package.extras] doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] -test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] +test = ["dateparser (>=1.0.0,<2.0.0)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (>=3.0.0,<4.0.0)"] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "cachetools" version = "5.3.2" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -59,6 +83,7 @@ files = [ name = "canvasapi" version = "2.2.0" description = "API wrapper for the Canvas LMS" +category = "main" optional = false python-versions = "*" files = [ @@ -73,6 +98,7 @@ requests = "*" name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -84,6 +110,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -160,6 +187,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -244,6 +272,7 @@ files = [ name = "cryptography" version = "41.0.6" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -285,10 +314,27 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "fancycompleter" +version = "0.9.1" +description = "colorful TAB completion for Python prompt" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, + {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, +] + +[package.dependencies] +pyreadline = {version = "*", markers = "platform_system == \"Windows\""} +pyrepl = ">=0.8.2" + [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -300,6 +346,7 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -319,6 +366,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.1" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -337,6 +385,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -355,6 +404,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -370,6 +420,7 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.3.0" description = "Store and access your passwords safely." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -394,6 +445,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -418,6 +470,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -429,6 +482,7 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -436,10 +490,32 @@ files = [ {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] +[[package]] +name = "pdbpp" +version = "0.10.3" +description = "pdb++, a drop-in replacement for pdb" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, + {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, +] + +[package.dependencies] +fancycompleter = ">=0.8" +pygments = "*" +wmctrl = "*" + +[package.extras] +funcsigs = ["funcsigs"] +testing = ["funcsigs", "pytest"] + [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -451,6 +527,7 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -465,6 +542,7 @@ plugins = ["importlib-metadata"] name = "pypandoc" version = "1.12" description = "Thin wrapper for pandoc." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -472,10 +550,33 @@ files = [ {file = "pypandoc-1.12.tar.gz", hash = "sha256:8f44740a9f074e121d81b489f073160421611d4ead62d1b306aeb11aab3c32df"}, ] +[[package]] +name = "pyreadline" +version = "2.1" +description = "A python implmementation of GNU readline." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, +] + +[[package]] +name = "pyrepl" +version = "0.9.0" +description = "A library for building flexible command line interfaces" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, +] + [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -490,6 +591,7 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -501,6 +603,7 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -512,6 +615,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -533,6 +637,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -552,6 +657,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -567,6 +673,7 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -578,6 +685,7 @@ files = [ name = "types-python-dateutil" version = "2.8.19.14" description = "Typing stubs for python-dateutil" +category = "main" optional = false python-versions = "*" files = [ @@ -589,6 +697,7 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -600,6 +709,7 @@ files = [ name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -613,10 +723,29 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wmctrl" +version = "0.5" +description = "A tool to programmatically control windows inside X" +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ + {file = "wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7"}, + {file = "wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962"}, +] + +[package.dependencies] +attrs = "*" + +[package.extras] +test = ["pytest"] + [[package]] name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -631,4 +760,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "708b5caaab809e22810e66698ea981afd68848aa9a0cf8e475ba593c399e02fe" +content-hash = "a4f8ebd1ca9fe559ad9e4c4a6d3062a140d51b2c618bba6441b7c8359246b8ab" diff --git a/pyproject.toml b/pyproject.toml index a3c2bef..27ebb48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,9 @@ rich = "^13.0.0" [tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] +pdbpp = "^0.10.3" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 7a917c949a165da4bc232c4af43cb8d3d2573dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:20:16 +0000 Subject: [PATCH 172/248] Bump pypandoc from 1.12 to 1.13 Bumps [pypandoc](https://github.com/JessicaTegner/pypandoc) from 1.12 to 1.13. - [Release notes](https://github.com/JessicaTegner/pypandoc/releases) - [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md) - [Commits](https://github.com/JessicaTegner/pypandoc/compare/v1.12...v1.13) --- updated-dependencies: - dependency-name: pypandoc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 54250c3..554dc0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -472,13 +472,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pypandoc" -version = "1.12" +version = "1.13" description = "Thin wrapper for pandoc." optional = false python-versions = ">=3.6" files = [ - {file = "pypandoc-1.12-py3-none-any.whl", hash = "sha256:efb4f7d68ead8bec32e22b62f02d5608a1700978b51bfc4af286fd6acfe9d218"}, - {file = "pypandoc-1.12.tar.gz", hash = "sha256:8f44740a9f074e121d81b489f073160421611d4ead62d1b306aeb11aab3c32df"}, + {file = "pypandoc-1.13-py3-none-any.whl", hash = "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681"}, + {file = "pypandoc-1.13.tar.gz", hash = "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e"}, ] [[package]] From 2972e4bd6aba69ae1df54eb59ff28851af206266 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 19 Feb 2024 15:46:59 +0100 Subject: [PATCH 173/248] Adds new expriment on new quizzes data --- experiments/quiz-new.py | 65 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 experiments/quiz-new.py diff --git a/experiments/quiz-new.py b/experiments/quiz-new.py new file mode 100644 index 0000000..2e7e910 --- /dev/null +++ b/experiments/quiz-new.py @@ -0,0 +1,65 @@ +import canvasapi +import json +import os + +canvas = canvasapi.Canvas(os.environ["CANVAS_SERVER"], + os.environ["CANVAS_TOKEN"]) + +test_course = None + +for course in canvas.get_courses(): + if course.name == "prgm23": + test_course = course + break + +test_quiz = None + +for quiz in course.get_assignments(): + if quiz.name == "Exempelprov": + test_quiz = quiz + break +else: + raise ValueError("No quiz found") + +test_submission = None + +# There is only one in my setup +for quiz_submission in test_quiz.get_submissions(): + if quiz_submission.submitted_at is not None: + test_submission = quiz_submission + break + +print("\n# Quiz submission questions\n") + +for subm_question in test_submission.get_submission_questions(): + #print(subm_question.__dict__.keys()) + #print(subm_question) + print(f"{subm_question.id} " + f"{subm_question.question_name}:\n" + f"{subm_question.question_text}") + try: + print(f"Alternatives: {subm_question.answers}") + print(f"Correct: {subm_question.correct}") + except AttributeError: + pass + + print() + +print("\n# Quiz submission answers\n") + +quiz = None + +for assignment in test_course.get_assignments(): + if assignment.name == "Exempelprov": + quiz = assignment + break + +for submission in quiz.get_submissions(include=["submission_history"]): + for subm in submission.submission_history: + #print(subm) + try: + for data in subm["submission_data"]: + print(json.dumps(data, indent=2)) + except KeyError: + pass + From fe847a18c1a667ebcb66e2ee6458817dec9f25a5 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 19 Feb 2024 15:47:37 +0100 Subject: [PATCH 174/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a3c2bef..9cc3ca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.0" +version = "3.1" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From edbb6fc2b67bbfc0ffc897d8000fde17c1255c7d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 19 Feb 2024 21:06:06 +0100 Subject: [PATCH 175/248] Updates to canvasapi 3.2.0, switch to use File objects --- pyproject.toml | 2 +- src/canvaslms/cli/submissions.nw | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 23cc346..2e0d1c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ python = "^3.8" appdirs = "^1.4.4" argcomplete = ">=2,<4" cachetools = "^5.3.1" -canvasapi = "<3.0.0" +canvasapi = "^3.2.0" keyring = "^24.2.0" pypandoc = "^1.11" arrow = "^1.2.3" diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index a8c7537..d14d89e 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -491,14 +491,14 @@ which case we remove that as well. <>= try: for attachment in submission.attachments: - content_type = ct_to_md(attachment["content-type"]) - if not content_type: + content_type = ct_to_md(getattr(attachment, "content-type")) + if content_type: + contents = attachment.get_contents(binary=True).decode() + else: continue - contents = urllib.request.urlopen(attachment["url"]).read().decode("utf8") - formatted_submission += format_section( - attachment["filename"], + attachment.filename, f"```{content_type}\n{contents}\n```" ) except AttributeError: From 8fd4d2c24eccd6127a9d730b55323b4803f4a973 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 19 Feb 2024 21:06:57 +0100 Subject: [PATCH 176/248] Updates poetry.lock --- poetry.lock | 455 ++++++++++++++++++++++++++-------------------------- 1 file changed, 230 insertions(+), 225 deletions(-) diff --git a/poetry.lock b/poetry.lock index fbc0221..56de1f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -81,103 +81,92 @@ files = [ [[package]] name = "canvasapi" -version = "2.2.0" +version = "3.2.0" description = "API wrapper for the Canvas LMS" category = "main" optional = false python-versions = "*" files = [ - {file = "canvasapi-2.2.0.tar.gz", hash = "sha256:5087db773cac9d92f4f4609b3c160dbeeceb636801421808afee2d438bc43f62"}, + {file = "canvasapi-3.2.0.tar.gz", hash = "sha256:7cf97ad1ddc860e250c3453e229897ed1273095ad061c34baf651bf1b0e5a9c7"}, ] [package.dependencies] +arrow = "*" pytz = "*" requests = "*" [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] @@ -185,129 +174,144 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "cryptography" -version = "42.0.2" +version = "42.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, - {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, - {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, - {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, - {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, - {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, - {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, + {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"}, + {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"}, + {file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"}, + {file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"}, + {file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"}, + {file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"}, + {file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"}, + {file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"}, ] [package.dependencies] @@ -341,73 +345,73 @@ pyrepl = ">=0.8.2" [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.0.1" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" -version = "6.0.1" +version = "6.1.1" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, - {file = "importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, + {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, + {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] [[package]] name = "jaraco-classes" -version = "3.3.0" +version = "3.3.1" description = "Utility functions for Python class constructs" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, - {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, + {file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"}, + {file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"}, ] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jeepney" @@ -489,14 +493,14 @@ files = [ [[package]] name = "more-itertools" -version = "10.1.0" +version = "10.2.0" description = "More routines for operating on iterables, beyond itertools" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, - {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, + {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, + {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, ] [[package]] @@ -534,18 +538,19 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pypandoc" @@ -598,14 +603,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3" +version = "2024.1" description = "World timezone definitions, modern and historical" category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -692,43 +697,43 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.8.19.14" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, - {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -752,21 +757,21 @@ test = ["pytest"] [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "a4f8ebd1ca9fe559ad9e4c4a6d3062a140d51b2c618bba6441b7c8359246b8ab" +content-hash = "bdb63b5ca41671ae10aff6fc52edda928d2076e581ba1320ca4408c09e3260e5" From 423abc005211d85deac9a85fc2807b3f3da23402 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 19 Feb 2024 23:52:51 +0100 Subject: [PATCH 177/248] Make submission command handle any attachment --- src/canvaslms/cli/submissions.nw | 105 +++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index d14d89e..30f6580 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -6,6 +6,7 @@ handles an individual submission. We outline the module: <>= +import canvasapi.file import canvasapi.submission import canvaslms.cli.assignments as assignments @@ -16,11 +17,14 @@ import argparse import csv import json import os +import pathlib +import subprocess import pypandoc import re import rich.console import rich.markdown import sys +import tempfile import urllib.request <> @@ -482,44 +486,57 @@ except AttributeError: \subsection{Attachments} -For the attachment, we want to add it as a Markdown code block. -This means that we can set what type of data the code block contains. -We compute this text from the content type of the attachment. -For instance, Python source code is [[text/x-python]]. -We remove the [[text/]] prefix and check if there is any [[x-]] prefix left, in -which case we remove that as well. +For the attachment, we want to add it to the output. +In the case of Python code we want to have it as a Markdown code block. +If it's a text file, we want to have it as part of the plain Markdown. +We will try to convert the attachment to Markdown using [[pypandoc]]. <>= try: for attachment in submission.attachments: - content_type = ct_to_md(getattr(attachment, "content-type")) - if content_type: - contents = attachment.get_contents(binary=True).decode() - else: - continue - - formatted_submission += format_section( - attachment.filename, - f"```{content_type}\n{contents}\n```" - ) + try: + contents = convert_to_md(attachment) + formatted_submission += format_section(attachment.filename, + contents) + except Exception as err: + <> except AttributeError: pass @ +Let's look at the conversion. +<>= +def convert_to_md(attachment: canvasapi.file.File) -> str: + """ + Converts `attachment` to Markdown. Returns the Markdown string. + """ + content_type = getattr(attachment, "content-type") + <> + <> +@ + +If the content type is text, we can just decode it and use it in a Markdown +code block with a suitable (Markdown) content type. +This means that we can set what type of data the code block contains. +We compute this text from the content type of the attachment. +For instance, Python source code is [[text/x-python]]. +We remove the [[text/]] prefix and check if there is any [[x-]] prefix left, in +which case we remove that as well. + Now, we want to change the content type to the format expected by Markdown to do proper syntax highlighting. This requires some ugly processing since one [[.py]] file might have content type [[text/x-python]] and another [[.py]] file might have [[text/python-script]]. <>= -def ct_to_md(content_type): +def text_to_md(content_type): """ - Takes a content type, returns Markdown code block type. - Returns None if not possible. + Takes a text-based content type, returns Markdown code block type. + Raises ValueError if not possible. """ if content_type.startswith("text/"): content_type = content_type[len("text/"):] else: - return None + raise ValueError(f"Not text-based content type: {content_type}") if content_type.startswith("x-"): content_type = content_type[2:] @@ -529,6 +546,54 @@ def ct_to_md(content_type): return content_type @ +This leaves us with the following. +<>= +try: + md_type = text_to_md(content_type) + contents = attachment.get_contents(binary=True).decode() + return f"```{md_type}\n{contents}\n```" +except ValueError: + pass +@ + +If the content type is not text, we use [[pypandoc]] to convert it to Markdown. +Here we'll use Pandoc's ability to infer the file type on its own. +This means we'll have to download the attachment as a file in a temporary +location and let Pandoc convert the file to Markdown. +<>= +with tempfile.TemporaryDirectory() as tmpdir: + tmpfile = pathlib.Path(tmpdir) / attachment.filename + attachment.download(tmpfile) + return pypandoc.convert_file(tmpfile, "markdown") +@ + +Now, if that fails, we'll not have anything to add to the output. +That means that if we're to have anything to show to the user, we'll need to +show the native file. +However, we note this in the output. +<>= +error_msg = f""" +Unsupported filetype, showing in external viewer. + +Error message from pandoc: {err} +""".strip() +formatted_submission += format_section(attachment.filename, error_msg) +@ + +Now we want to open download and then open the file. +We want to make sure the file remains, so we can't use the same temporary +directory as before (that was destroyed when we left the [[with]] block). +We can't use [[tempfile.TemporaryDirectory]] since its [[delete]] parameter is +available only from Python 3.12. +So we'll need to use [[tempfile.mkdtemp]] instead. +We don't delete anything, but leave it for system cleanup. +<>= +tmpdir = tempfile.mkdtemp() +tmpfile = pathlib.Path(tmpdir) / attachment.filename +attachment.download(tmpfile) +subprocess.run(["open", tmpfile]) +@ + \subsection{Submission history} <>= From 40b5446edde8b02f9b4e587f193373f593252bfb Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Feb 2024 19:39:38 +0100 Subject: [PATCH 178/248] Puts submission files in dedicated directory --- src/canvaslms/cli/submissions.nw | 184 ++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 51 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 30f6580..382538b 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -140,14 +140,20 @@ def speedgrader(submission): Here we provide a subcommand [[submission]] which deals with an individual submission. +<>= +Prints data about matching submissions, including submission and grading time, +and any text-based attachments. + <>= submission_parser = subp.add_parser("submission", help="Prints information about a submission", - description="Prints data about matching submissions, " - "including submission and grading time, any text-based attachments.") + description=""" +<> +""") submission_parser.set_defaults(func=submission_command) add_submission_options(submission_parser) <> +<> @ We also need the corresponding function. For now, we only print the most relevant data of a submission. <>= @@ -166,18 +172,24 @@ If stdout is not a terminal, we don't use [[rich]], then we simply print the raw markdown. <>= console = rich.console.Console() +<> for submission in submission_list: - output = format_submission(submission, history=args.history) - - if sys.stdout.isatty(): + <> + output = format_submission(submission, + history=args.history, + tmpdir=tmpdir/subdir) + + if args.output_dir: + <> + elif sys.stdout.isatty(): <> with console.pager(styles=styles): console.print(rich.markdown.Markdown(output, code_theme="manni")) else: print(output) -@ Note that we use the theme [[manni]] for the code, as this works in both dark -and light terminals. +@ Note that we use the theme [[manni]] for the code, as this works well in both +dark and light terminals. Now, let's turn to that [[args.history]] argument. We want to exclude it sometimes, for instance, when we want to get to the @@ -189,6 +201,65 @@ submission_parser.add_argument("-H", "--history", action="store_true", help="Include submission history.") @ +Next we have the [[args.output_dir]] argument. +If we specify the [[output_dir]] we want to write the output to files and not +have it printed to stdout. +<>= +submission_parser.add_argument("-o", "--output-dir", + required=False, default=None, + help="Write output to files in directory the given directory.") +@ + +If the user specified an output directory, we will not create a temporary +directory, but rather let [[tmpdir]] be the output directory. +<>= +if args.output_dir: + tmpdir = pathlib.Path(args.output_dir) +else: + tmpdir = pathlib.Path(tempfile.mkdtemp()) +@ + +Finally, we can then write the output to a file in the output directory. +We need to structure the files in some way. +We have a list of submissions for various students. +The submissions can be submissions for various assignments (in assignment +groups) in various courses. +The two most interesting options are: +\begin{enumerate} +\item to group by student, that is the student is the top level directory, +[[student/course/assignment]]; or +\item to group by course, that is we get [[course/assignment/student]]. +\end{enumerate} +This affects both +[[<>]] +and +[[<<[[tmpdir]] for [[format_submission]]>>]]. + +We'll introduce an option that lets us choose between these two options. +<>= +submission_parser.add_argument("--sort-order", required=False, + choices=["student", "course"], default="student", + help="Determines the order in which directories are created " + "in `output_dir`. `student` results in `student/course/assignment` " + "and `course` results in `course/assignment/student`. " + "Default: %(default)s") +<>= +if args.sort_order == "student": + subdir = f"{submission.user.login_id}" \ + f"/{submission.assignment.course.course_code}" \ + f"/{submission.assignment.name}" +else: + subdir = f"{submission.assignment.course.course_code}" \ + f"/{submission.assignment.name}" \ + f"/{submission.user.login_id}" + +(tmpdir / subdir).mkdir(parents=True, exist_ok=True) +<>= +with open(tmpdir/subdir/"metadata.md", "w") as f: + f.write(output) +@ + + \subsection{Check if we should use styles} By default, [[rich.console.Console]] uses the [[pydoc.pager]], which uses the @@ -207,6 +278,10 @@ elif "PAGER" in os.environ: styles = False if "less" in pager and ("-R" in pager or "-r" in pager): styles = True +<>= +Uses MANPAGER or PAGER environment variables for the pager to page output. If +the `-r` or `-R` flag is passed to `less`, it uses colours in the output. + @ @@ -362,7 +437,8 @@ IDs, instead, we add these as attributes when fetching the objects. So [[submission.assignment]] is the assignment it came from, we don't need to resolve the assignment from the assignmend ID. <>= -def format_submission(submission, history=False): +def format_submission(submission, history=False, + <<[[format_submission]] args>>): """ Formats submission for printing to stdout. Returns a string. @@ -492,26 +568,54 @@ If it's a text file, we want to have it as part of the plain Markdown. We will try to convert the attachment to Markdown using [[pypandoc]]. <>= try: + <> for attachment in submission.attachments: - try: - contents = convert_to_md(attachment) - formatted_submission += format_section(attachment.filename, - contents) - except Exception as err: - <> + <> + formatted_submission += format_section(attachment.filename, + contents) except AttributeError: pass @ Let's look at the conversion. +If it's a text-based format, we want to include it as a Markdown code block. +Otherwise, we'll try to convert it to Markdown using [[pypandoc]]. +If the latter fails, we want to add a pointer to the file in the output, so +that the user can open it in an external viewer. + +In fact, having a copy of all the files locally is useful. +We need to download it anyways, so we might just as well put a copy in a local +temporary directory too. +We'll let the caller specify the directory to use, so we can use the same +directory for all attachments and potentially all users. +<<[[format_submission]] args>>= +tmpdir = None, +<>= +tmpdir = pathlib.Path(tmpdir or tempfile.mkdtemp()) +@ + +We'll use [[tmpdir]] as the temporary directory to store the files when we try +to convert them. <>= -def convert_to_md(attachment: canvasapi.file.File) -> str: +def convert_to_md(attachment: canvasapi.file.File, + tmpdir: pathlib.Path) -> str: """ Converts `attachment` to Markdown. Returns the Markdown string. + + Store a file version in `tmpdir`. """ + <> content_type = getattr(attachment, "content-type") - <> - <> + <> + <> +<>= +contents = convert_to_md(attachment, tmpdir) +@ + +The download is simple. +<>= +outfile = tmpdir / attachment.filename +attachment.download(outfile) @ If the content type is text, we can just decode it and use it in a Markdown @@ -547,10 +651,13 @@ def text_to_md(content_type): @ This leaves us with the following. -<>= +The advantage of reading the content from the file is that Python will solve +the encoding for us. +<>= try: md_type = text_to_md(content_type) - contents = attachment.get_contents(binary=True).decode() + with open(outfile, "r") as f: + contents = f.read() return f"```{md_type}\n{contents}\n```" except ValueError: pass @@ -560,40 +667,15 @@ If the content type is not text, we use [[pypandoc]] to convert it to Markdown. Here we'll use Pandoc's ability to infer the file type on its own. This means we'll have to download the attachment as a file in a temporary location and let Pandoc convert the file to Markdown. -<>= -with tempfile.TemporaryDirectory() as tmpdir: - tmpfile = pathlib.Path(tmpdir) / attachment.filename - attachment.download(tmpfile) - return pypandoc.convert_file(tmpfile, "markdown") -@ - -Now, if that fails, we'll not have anything to add to the output. -That means that if we're to have anything to show to the user, we'll need to -show the native file. -However, we note this in the output. -<>= -error_msg = f""" -Unsupported filetype, showing in external viewer. - -Error message from pandoc: {err} -""".strip() -formatted_submission += format_section(attachment.filename, error_msg) -@ - -Now we want to open download and then open the file. -We want to make sure the file remains, so we can't use the same temporary -directory as before (that was destroyed when we left the [[with]] block). -We can't use [[tempfile.TemporaryDirectory]] since its [[delete]] parameter is -available only from Python 3.12. -So we'll need to use [[tempfile.mkdtemp]] instead. -We don't delete anything, but leave it for system cleanup. -<>= -tmpdir = tempfile.mkdtemp() -tmpfile = pathlib.Path(tmpdir) / attachment.filename -attachment.download(tmpfile) -subprocess.run(["open", tmpfile]) +<>= +try: + return pypandoc.convert_file(outfile, "markdown") +except Exception as err: + return f"Pandoc cannot convert this file. " \ + f"The file is located at\n\n {outfile}\n\n" @ + \subsection{Submission history} <>= From 832fdd38caf91455f29d5b7c8790cc53ee14795c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Feb 2024 21:39:11 +0100 Subject: [PATCH 179/248] Add --open option to submission command --- src/canvaslms/cli/submissions.nw | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 382538b..66d4784 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -170,6 +170,14 @@ It also adds syntax highlighting for the source code attachments. However, we need to adapt the use of styles to the pager to be used. If stdout is not a terminal, we don't use [[rich]], then we simply print the raw markdown. + +However, we might want to write all data to the output directory. +In that case, we don't print anything to stdout. + +Finally, if we use a pager, we'll first open the directory with the files, so +that the user can explore the files while reading the output (containing the +grading and comments). +That way, the user can try to run the Python code too, if necessary. <>= console = rich.console.Console() <> @@ -183,6 +191,7 @@ for submission in submission_list: <> elif sys.stdout.isatty(): <> + <> with console.pager(styles=styles): console.print(rich.markdown.Markdown(output, code_theme="manni")) @@ -191,6 +200,8 @@ for submission in submission_list: @ Note that we use the theme [[manni]] for the code, as this works well in both dark and light terminals. +\subsection{Optional history} + Now, let's turn to that [[args.history]] argument. We want to exclude it sometimes, for instance, when we want to get to the comments only. @@ -201,6 +212,8 @@ submission_parser.add_argument("-H", "--history", action="store_true", help="Include submission history.") @ +\subsection{Specifying an output directory} + Next we have the [[args.output_dir]] argument. If we specify the [[output_dir]] we want to write the output to files and not have it printed to stdout. @@ -259,6 +272,19 @@ with open(tmpdir/subdir/"metadata.md", "w") as f: f.write(output) @ +\subsection{Opening the directory with the files} + +We want to open the directory with the files if we use a pager. +However, we provide this as an option. +<>= +submission_parser.add_argument("--open", required=False, default=False, + action="store_true", + help="Open the directory with the files in the default file manager.") +<>= +if args.open: + subprocess.run(["open", tmpdir/subdir]) +@ + \subsection{Check if we should use styles} From bb8e70bca14989d27e09e9826e24aa8d2565e361 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 20 Feb 2024 21:45:38 +0100 Subject: [PATCH 180/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2e0d1c2..a9cc7fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.1" +version = "3.2" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 0fb390cbc188596fe060b07e7244cf1b0dd09f97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:50:55 +0000 Subject: [PATCH 181/248] Bump cryptography from 42.0.3 to 42.0.4 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.3 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.3...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 108 +++++++++++++++++----------------------------------- 1 file changed, 35 insertions(+), 73 deletions(-) diff --git a/poetry.lock b/poetry.lock index 56de1f8..0c80baf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "argcomplete" version = "3.2.2" description = "Bash tab completion for argparse" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -31,7 +29,6 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "arrow" version = "1.3.0" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -45,13 +42,12 @@ types-python-dateutil = ">=2.8.10" [package.extras] doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] -test = ["dateparser (>=1.0.0,<2.0.0)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (>=3.0.0,<4.0.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] [[package]] name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -71,7 +67,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "cachetools" version = "5.3.2" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -83,7 +78,6 @@ files = [ name = "canvasapi" version = "3.2.0" description = "API wrapper for the Canvas LMS" -category = "main" optional = false python-versions = "*" files = [ @@ -99,7 +93,6 @@ requests = "*" name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -111,7 +104,6 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -176,7 +168,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -274,44 +265,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.3" +version = "42.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"}, - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"}, - {file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"}, - {file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"}, - {file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"}, - {file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"}, - {file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"}, - {file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, + {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, + {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, + {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, + {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, + {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, + {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, ] [package.dependencies] @@ -331,7 +321,6 @@ test-randomorder = ["pytest-randomly"] name = "fancycompleter" version = "0.9.1" description = "colorful TAB completion for Python prompt" -category = "dev" optional = false python-versions = "*" files = [ @@ -347,7 +336,6 @@ pyrepl = ">=0.8.2" name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -359,7 +347,6 @@ files = [ name = "importlib-metadata" version = "7.0.1" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -379,7 +366,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.1.1" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -398,7 +384,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jaraco-classes" version = "3.3.1" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -417,7 +402,6 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -433,7 +417,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.3.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -458,7 +441,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -483,7 +465,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -495,7 +476,6 @@ files = [ name = "more-itertools" version = "10.2.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -507,7 +487,6 @@ files = [ name = "pdbpp" version = "0.10.3" description = "pdb++, a drop-in replacement for pdb" -category = "dev" optional = false python-versions = "*" files = [ @@ -528,7 +507,6 @@ testing = ["funcsigs", "pytest"] name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -540,7 +518,6 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -556,7 +533,6 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pypandoc" version = "1.13" description = "Thin wrapper for pandoc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -568,7 +544,6 @@ files = [ name = "pyreadline" version = "2.1" description = "A python implmementation of GNU readline." -category = "dev" optional = false python-versions = "*" files = [ @@ -579,7 +554,6 @@ files = [ name = "pyrepl" version = "0.9.0" description = "A library for building flexible command line interfaces" -category = "dev" optional = false python-versions = "*" files = [ @@ -590,7 +564,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -605,7 +578,6 @@ six = ">=1.5" name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -617,7 +589,6 @@ files = [ name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -629,7 +600,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -651,7 +621,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -671,7 +640,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -687,7 +655,6 @@ jeepney = ">=0.6" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -699,7 +666,6 @@ files = [ name = "types-python-dateutil" version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -711,7 +677,6 @@ files = [ name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -723,7 +688,6 @@ files = [ name = "urllib3" version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -741,7 +705,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "wmctrl" version = "0.5" description = "A tool to programmatically control windows inside X" -category = "dev" optional = false python-versions = ">=2.7" files = [ @@ -759,7 +722,6 @@ test = ["pytest"] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ From dc065364b3ecda480e53429ce818732b9067be31 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Feb 2024 23:02:05 +0100 Subject: [PATCH 182/248] Adds possibility to spawn shell in submission directory This fixes #133. --- src/canvaslms/cli/submissions.nw | 80 +++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 66d4784..931cfba 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -187,11 +187,16 @@ for submission in submission_list: history=args.history, tmpdir=tmpdir/subdir) + <> + if args.output_dir: - <> + pass + elif args.open == "shell": + <> elif sys.stdout.isatty(): <> - <> + if args.open == "open": + <> with console.pager(styles=styles): console.print(rich.markdown.Markdown(output, code_theme="manni")) @@ -200,29 +205,31 @@ for submission in submission_list: @ Note that we use the theme [[manni]] for the code, as this works well in both dark and light terminals. -\subsection{Optional history} - -Now, let's turn to that [[args.history]] argument. -We want to exclude it sometimes, for instance, when we want to get to the -comments only. -So we default to off, since it's only occasionally that we want to see the -history. -<>= -submission_parser.add_argument("-H", "--history", action="store_true", - help="Include submission history.") -@ - -\subsection{Specifying an output directory} - -Next we have the [[args.output_dir]] argument. If we specify the [[output_dir]] we want to write the output to files and not have it printed to stdout. <>= submission_parser.add_argument("-o", "--output-dir", required=False, default=None, - help="Write output to files in directory the given directory.") + help="Write output to files in directory the given directory. " + "If not specified, print to stdout. " + "If specified, do not print to stdout.") +@ + +We also have the open option, that has a choice of two alternatives. +<>= +submission_parser.add_argument("--open", required=False, + nargs="?", default=None, const="open", choices=["open", "shell"], + help="Open the directory containing the files using " + "the default file manager (`open`) or " + "spawn a shell in the directory (`shell`). " + "With `open`, the pager will be used to display the output as usual. " + "With `shell`, we just drop into the shell, the output can be " + "found in the metadata.md file in the shell's working directory. " + "Default: %(default)s") @ +\subsection{Specifying an output directory} + If the user specified an output directory, we will not create a temporary directory, but rather let [[tmpdir]] be the output directory. <>= @@ -272,17 +279,22 @@ with open(tmpdir/subdir/"metadata.md", "w") as f: f.write(output) @ -\subsection{Opening the directory with the files} - -We want to open the directory with the files if we use a pager. -However, we provide this as an option. -<>= -submission_parser.add_argument("--open", required=False, default=False, - action="store_true", - help="Open the directory with the files in the default file manager.") +\subsection{Opening the directory containing the files} + +Sometimes we want to open the directory. +There are several ways we can do this. +We can open the directory with the system file explorer, that way the user can +open files while reading the stdout output using a pager. +We can also spawn a shell in the directory so that the user can run the Python +code. +<>= +print(f"---> Opening a shell ({os.environ['SHELL']}) in {tmpdir/subdir}") +subprocess.run([ + "sh", "-c", f"cd '{tmpdir/subdir}' && exec {os.environ['SHELL']}" +]) +print(f"<--- canvaslms submission shell terminated.") <>= -if args.open: - subprocess.run(["open", tmpdir/subdir]) +subprocess.run(["open", tmpdir/subdir]) @ @@ -310,6 +322,18 @@ the `-r` or `-R` flag is passed to `less`, it uses colours in the output. @ +\subsection{Optional history} + +Now, let's turn to that [[args.history]] argument. +We want to exclude it sometimes, for instance, when we want to get to the +comments only. +So we default to off, since it's only occasionally that we want to see the +history. +<>= +submission_parser.add_argument("-H", "--history", action="store_true", + help="Include submission history.") +@ + \section{Selecting a submission on the command line}% \label{submission-options} From ffa3200f397ec0eec18805e42450235339126060 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Feb 2024 23:03:32 +0100 Subject: [PATCH 183/248] Improves submission history Particularly, makes it compatible with storing the submission files in the output directory. --- src/canvaslms/cli/submissions.nw | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 931cfba..5b909ab 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -492,7 +492,7 @@ def format_submission(submission, history=False, """ Formats submission for printing to stdout. Returns a string. - If history is True, also include submission history. + If history is True, include all submission versions from history. """ student = submission.assignment.course.get_user(submission.user_id) @@ -501,11 +501,12 @@ def format_submission(submission, history=False, <> <> <> - <> - <> - <> if history: <> + else: + <> + <> + <> return formatted_submission @ @@ -622,7 +623,7 @@ try: for attachment in submission.attachments: <> formatted_submission += format_section(attachment.filename, - contents) + contents) except AttributeError: pass @ @@ -642,6 +643,7 @@ directory for all attachments and potentially all users. tmpdir = None, <>= tmpdir = pathlib.Path(tmpdir or tempfile.mkdtemp()) +tmpdir.mkdir(parents=True, exist_ok=True) @ We'll use [[tmpdir]] as the temporary directory to store the files when we try @@ -728,15 +730,35 @@ except Exception as err: \subsection{Submission history} +The history contains all the student's submissions, including the current +submission. +We want to keep track of what belongs to the different versions. +This becomes important when we rely on the [[tmpdir]] to store the files. +We don't want the files of one version overwrite the files of another. +Then we can't be sure which version we're looking at. + +To produce the history, we'll modify [[tmpdir]] to create a subdirectory for +each version. +We'll write a [[metadata.md]] file for each version. +Then we'll return all of those concatenated. <>= try: if submission.submission_history: - for prev_submission in submission.submission_history: + for version, prev_submission in enumerate(submission.submission_history): + version = str(version) prev_submission = canvasapi.submission.Submission( submission._requester, prev_submission) prev_submission.assignment = submission.assignment - formatted_submission += "\n\n" + format_submission(prev_submission) + version_dir = tmpdir / f"version-{version}" + + prev_metadata = format_submission(prev_submission, + tmpdir=version_dir) + + with open(version_dir/"metadata.md", "w") as f: + f.write(prev_metadata) + + formatted_submission += "\n\n" + prev_metadata except AttributeError: pass @ From 9df76056720413bfa8541c353513e7210631e660 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Feb 2024 23:05:53 +0100 Subject: [PATCH 184/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9cc7fe..ddaf65d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.2" +version = "3.3" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 5d41ea345d8b59394d217b9413c44916b1d7781f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 21 Feb 2024 23:06:43 +0100 Subject: [PATCH 185/248] Bumps year in LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8cdf029..9f4f1ea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020--2023 Daniel Bosk +Copyright (c) 2020--2024 Daniel Bosk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5464aeee10af818e20880c1b15ed7acd905c48ea Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 22 Feb 2024 14:27:45 +0100 Subject: [PATCH 186/248] Adds option to output submission in JSON format --- src/canvaslms/cli/submissions.nw | 302 ++++++++++++++++++++++++------- 1 file changed, 237 insertions(+), 65 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 5b909ab..e8c0c7e 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -23,6 +23,7 @@ import pypandoc import re import rich.console import rich.markdown +import rich.json import sys import tempfile import urllib.request @@ -185,7 +186,8 @@ for submission in submission_list: <> output = format_submission(submission, history=args.history, - tmpdir=tmpdir/subdir) + tmpdir=tmpdir/subdir, + json_format=args.json) <> @@ -198,8 +200,7 @@ for submission in submission_list: if args.open == "open": <> with console.pager(styles=styles): - console.print(rich.markdown.Markdown(output, - code_theme="manni")) + <> else: print(output) @ Note that we use the theme [[manni]] for the code, as this works well in both @@ -224,10 +225,24 @@ submission_parser.add_argument("--open", required=False, "spawn a shell in the directory (`shell`). " "With `open`, the pager will be used to display the output as usual. " "With `shell`, we just drop into the shell, the output can be " - "found in the metadata.md file in the shell's working directory. " + "found in the metadata.{json,md} file in the shell's working " + "directory. " "Default: %(default)s") @ +Finally, we can add the [[json]] option to the [[submission]] command. +<>= +submission_parser.add_argument("--json", required=False, + action="store_true", default=False, + help="Print output as JSON, otherwise Markdown.") +<>= +if args.json: + console.print(rich.json.JSON(output)) +else: + console.print(rich.markdown.Markdown(output, + code_theme="manni")) +@ + \subsection{Specifying an output directory} If the user specified an output directory, we will not create a temporary @@ -275,7 +290,12 @@ else: (tmpdir / subdir).mkdir(parents=True, exist_ok=True) <>= -with open(tmpdir/subdir/"metadata.md", "w") as f: +if args.json: + filename = "metadata.json" + output = json.dumps(output, indent=2) +else: + filename = "metadata.md" +with open(tmpdir/subdir/filename, "w") as f: f.write(output) @ @@ -486,17 +506,34 @@ We no longer need the [[canvas]] object to resolve course, assignment and user IDs, instead, we add these as attributes when fetching the objects. So [[submission.assignment]] is the assignment it came from, we don't need to resolve the assignment from the assignmend ID. + +We have a [[md_title_level]] argument to specify the level of the title in the +Markdown version of the output. +We want this to be able to recursively use [[format_submission]] to format the +submission history. +This must then be passed to the recursive call and the section formatting. <>= -def format_submission(submission, history=False, +def format_submission(submission, history=False, json_format=False, + md_title_level="#", <<[[format_submission]] args>>): """ Formats submission for printing to stdout. Returns a string. If history is True, include all submission versions from history. + + If json_format is True, return a JSON string, otherwise Markdown. + + `md_title_level` is the level of the title in Markdown, by default `#`. This + is used to create a hierarchy of sections in the output. + + <<[[format_submission]] doc>> """ student = submission.assignment.course.get_user(submission.user_id) - formatted_submission = "" + if json_format: + formatted_submission = {} + else: + formatted_submission = "" <> <> @@ -514,9 +551,32 @@ def format_submission(submission, history=False, \subsection{Some helper functions} We want to format some sections. +The section has a title and a body. +<<[[format_section]] doc>>= +In the case of Markdown (default), we format the title as a header and the body +as a paragraph. If we don't do JSON, but receive a dictionary as the body, we +format it as a list of key-value pairs. + +`md_title_level` is the level of the title in Markdown, by default `#`. +We'll use this to create a hierarchy of sections in the output. + +In the case of JSON, we return a dictionary with the title as the key and the +body as the value. <>= -def format_section(title, body): - return f"\n# {title}\n\n{body}\n\n" +def format_section(title, body, json_format=False, md_title_level="#"): + """ + <<[[format_section]] doc>> + """ + if json_format: + return {title: body} + + if isinstance(body, dict): + return "\n".join([ + f" - {key.capitalize().replace('_', ' ')}: {value}" + for key, value in body.items() + ]) + + return f"\n{md_title_level} {title}\n\n{body}\n\n" @ \subsection{Metadata} @@ -524,18 +584,25 @@ def format_section(title, body): To format the metadata section, we simply pass the right strings to the section formatting function. <>= -formatted_submission += format_section( - "Metadata", - f"{submission.assignment.course.course_code} > {submission.assignment.name}" - f"\n\n" - f" - Student: {student.name} " - f"({student.login_id or None}, {submission.user_id})\n" - f" - Submission ID: {submission.id}\n" - f" - Submitted (graded): {submission.submitted_at} " - f"({submission.graded_at})\n" - f" - Grade: {submission.grade} ({submission.score})\n" - f" - Graded by: {resolve_grader(submission)}\n" - f" - SpeedGrader: {speedgrader(submission)}") +metadata = { + "course": submission.assignment.course.course_code, + "assignment": submission.assignment.name, + "student": str(student), + "submission_id": submission.id, + "submitted_at": submission.submitted_at, + "graded_at": submission.graded_at, + "grade": submission.grade, + "graded_by": str(resolve_grader(submission)), + "speedgrader": speedgrader(submission) +} + +if json_format: + formatted_submission.update(format_section("metadata", metadata, + json_format=True, + md_title_level=md_title_level)) +else: + formatted_submission += format_section("Metadata", metadata, + md_title_level=md_title_level) @ Now to resolve the grader, we need to look up a user ID. @@ -568,9 +635,15 @@ def resolve_grader(submission): <>= try: if submission.rubric_assessment: - formatted_submission += format_section( - "Rubric assessment", - format_rubric(submission)) + if json_format: + formatted_submission.update(format_section( + "rubric_assessment", + format_rubric(submission, json_format=True)), + json_format=True) + else: + formatted_submission += format_section( + "Rubric assessment", + format_rubric(submission)) except AttributeError: pass @ @@ -580,11 +653,16 @@ except AttributeError: <>= try: if submission.submission_comments: - body = "" - for comment in submission.submission_comments: - body += f"{comment['author_name']} ({comment['created_at']}):\n\n" - body += comment["comment"] + "\n\n" - formatted_submission += format_section("Comments", body) + if json_format: + formatted_submission.update(format_section( + "comments", submission.submission_comments, + json_format=True)) + else: + body = "" + for comment in submission.submission_comments: + body += f"{comment['author_name']} ({comment['created_at']}):\n\n" + body += comment["comment"] + "\n\n" + formatted_submission += format_section("Comments", body) except AttributeError: pass @ @@ -594,7 +672,13 @@ except AttributeError: <>= try: if submission.body: - formatted_submission += format_section("Body", submission.body) + if json_format: + formatted_submission.update(format_section( + "body", submission.body, json_format=True, + md_title_level=md_title_level)) + else: + formatted_submission += format_section("Body", submission.body, + md_title_level=md_title_level) except AttributeError: pass @ @@ -604,9 +688,15 @@ except AttributeError: <>= try: if submission.submission_data: - formatted_submission += format_section( - "Quiz answers", - json.dumps(submission.submission_data, indent=2)) + if json_format: + formatted_submission.update(format_section( + "quiz_answers", submission.submission_data, + json_format=True, md_title_level=md_title_level)) + else: + formatted_submission += format_section( + "Quiz answers", + json.dumps(submission.submission_data, indent=2), + md_title_level=md_title_level) except AttributeError: pass @ @@ -617,13 +707,33 @@ For the attachment, we want to add it to the output. In the case of Python code we want to have it as a Markdown code block. If it's a text file, we want to have it as part of the plain Markdown. We will try to convert the attachment to Markdown using [[pypandoc]]. + +Since we want the files to be their own sections, for easy navigation, we must +bumps [[md_title_level]] by one to make the files a subsection of the main +submission. + +In the JSON version, we want to introduce one more level: the attachments +should be found under the key [[attachments]]. <>= try: <> + if json_format: + attachments = {} for attachment in submission.attachments: <> - formatted_submission += format_section(attachment.filename, - contents) + formatted_attachment = format_section(attachment.filename, contents, + json_format=json_format, + md_title_level=md_title_level+"#") + + if json_format: + attachments.update(formatted_attachment) + else: + formatted_submission += formatted_attachment + + if json_format and attachments: + formatted_submission.update(format_section("attachments", attachments, + json_format=True, + md_title_level=md_title_level)) except AttributeError: pass @ @@ -641,6 +751,9 @@ We'll let the caller specify the directory to use, so we can use the same directory for all attachments and potentially all users. <<[[format_submission]] args>>= tmpdir = None, +<<[[format_submission]] doc>>= +`tmpdir` is the directory to store all the submission files. Defaults to None, +which creates a temporary directory. <>= tmpdir = pathlib.Path(tmpdir or tempfile.mkdtemp()) tmpdir.mkdir(parents=True, exist_ok=True) @@ -739,28 +852,71 @@ Then we can't be sure which version we're looking at. To produce the history, we'll modify [[tmpdir]] to create a subdirectory for each version. -We'll write a [[metadata.md]] file for each version. -Then we'll return all of those concatenated. +We'll write a metadata file for each version. +Then we'll return all of those in one main metadata file too. <>= try: - if submission.submission_history: + submission_history = submission.submission_history +except AttributeError: + pass +else: + if submission_history: + versions = {} for version, prev_submission in enumerate(submission.submission_history): version = str(version) - prev_submission = canvasapi.submission.Submission( - submission._requester, prev_submission) - prev_submission.assignment = submission.assignment - version_dir = tmpdir / f"version-{version}" - prev_metadata = format_submission(prev_submission, - tmpdir=version_dir) - - with open(version_dir/"metadata.md", "w") as f: - f.write(prev_metadata) - - formatted_submission += "\n\n" + prev_metadata -except AttributeError: - pass + <> + <> + + if json_format: + formatted_submission.update(format_section( + "submission_history", versions, json_format=True, + md_title_level=md_title_level)) + else: + formatted_versions = "" + for version, prev_metadata in versions.items(): + formatted_versions += format_section(f"Version {version}", + prev_metadata, + md_title_level=md_title_level+"#") + formatted_submission += format_section( + "Submission history", formatted_versions, + md_title_level=md_title_level) +@ + +The [[prev_submission]] that we get is in raw JSON format from Canvas. +We'll turn it into a [[Submission]] object and add the extra assignment +attribute to it. +Now we can reuse the [[format_submission]] function to format this version's +submission metadata. + +Note that we can't pass on the [[history]] argument to [[format_submission]], +we need to let it know that it's formatting a history version in another way to +get the sectioning levels right. +We'll use an argument [[md_title_level]]. +<>= +prev_submission = canvasapi.submission.Submission( + submission._requester, prev_submission) +prev_submission.assignment = submission.assignment + +prev_metadata = format_submission(prev_submission, + tmpdir=version_dir, + json_format=json_format, + md_title_level=md_title_level+"#") + +versions[version] = prev_metadata +@ + +When we write the metadata to a file, we'll either just write a string or JSON +data. +We can use [[json.dump]] to write the JSON data to a file. +<>= +if json_format: + with open(version_dir/"metadata.json", "w") as f: + json.dump(prev_metadata, f, indent=2) +else: + with open(version_dir/"metadata.md", "w") as f: + f.write(prev_metadata) @ @@ -769,21 +925,30 @@ except AttributeError: For assignments that use rubrics, we want to format those rubrics so that we can read the results instead of just the cumulated grade. <>= -def format_rubric(submission): - """Format the rubric assessment of the `submission` in readable form.""" +def format_rubric(submission, json_format=False): + """ + Format the rubric assessment of the `submission` in readable form. + + If `json_format` is True, return a JSON string, otherwise Markdown. + """ - result = "" + if json_format: + result = {} + else: + result = "" for crit_id, rating_data in submission.rubric_assessment.items(): criterion = get_criterion(crit_id, submission.assignment.rubric) rating = get_rating(rating_data["rating_id"], criterion) + try: + comments = rating_data["comments"] + except KeyError: + comments = "" - <> + <> - if rating_data["comments"]: - result += f"Comments: {rating_data['comments']}\n" - - result += "\n" + if not json_format: + result += "\n" return result.strip() @ @@ -791,13 +956,20 @@ def format_rubric(submission): Sometimes Canvas is missing some data, for instance, an individual rating. So we add it only if it exists. -<>= -result += f"{criterion['description']}: " -if rating: - result += f"{rating['description']} ({rating['points']})" +<>= +if json_format: + result[criterion["description"]] = { + "rating": rating["description"] if rating else None, + "points": rating["points"] if rating else None, + "comments": comments + } else: - result += "-" -result += "\n" + result += f"{criterion['description']}: " + if rating: + result += f"{rating['description']} ({rating['points']})" + else: + result += "-" + result += "\n" @ We can get the rating of a rubric from the rubric assessment. From 27bd396eca8ce6ab5602d17ea2b4c3efa090af9c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 22 Feb 2024 14:30:18 +0100 Subject: [PATCH 187/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ddaf65d..f37b83c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.3" +version = "3.4" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From b0263c850baa7179a618017e13ef9cb995abf1f8 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 23 Feb 2024 14:33:03 +0100 Subject: [PATCH 188/248] Makes opening run even if submission data is piped --- src/canvaslms/cli/submissions.nw | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index e8c0c7e..7458e73 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -175,10 +175,15 @@ raw markdown. However, we might want to write all data to the output directory. In that case, we don't print anything to stdout. -Finally, if we use a pager, we'll first open the directory with the files, so -that the user can explore the files while reading the output (containing the -grading and comments). -That way, the user can try to run the Python code too, if necessary. +Finally, we might also want to open the directory of files, either in a shell +or in the system file manager. +We'll open the directory in the file manager so that the user can explore the +files while reading the output (containing the grading and comments). +So we must open this first, then we can proceed. + +When we spawn a shell, we don't want to do anything else. +But open in the file manager we can do no matter what the user wants to do, if +they pipe the output or skip the output altogether. <>= console = rich.console.Console() <> @@ -191,14 +196,15 @@ for submission in submission_list: <> - if args.output_dir: - pass - elif args.open == "shell": + if args.open == "open": + <> + + if args.open == "shell": <> + elif args.output_dir: + pass elif sys.stdout.isatty(): <> - if args.open == "open": - <> with console.pager(styles=styles): <> else: From d463ca11a2f5492e40e1dff20ea0fc563779d704 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 23 Feb 2024 14:36:57 +0100 Subject: [PATCH 189/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f37b83c..ed3b593 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.4" +version = "3.5" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 578e9ef4cbf9949c75031e34b65df9a6d938c899 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 23 Feb 2024 15:29:44 +0100 Subject: [PATCH 190/248] Bugfix: Output comments in Markdown formatted submission rubric --- src/canvaslms/cli/submissions.nw | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 7458e73..01ce053 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -26,6 +26,7 @@ import rich.markdown import rich.json import sys import tempfile +import textwrap import urllib.request <> @@ -970,12 +971,16 @@ if json_format: "comments": comments } else: - result += f"{criterion['description']}: " + result += f"- {criterion['description']}: " if rating: result += f"{rating['description']} ({rating['points']})" else: result += "-" result += "\n" + if comments: + result += textwrap.indent(textwrap.fill(f"- Comment: {comments}"), + " ") + result += "\n" @ We can get the rating of a rubric from the rubric assessment. From fc5a1dd54e30ab3e659a068f758392ae7d7995ec Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 23 Feb 2024 15:30:34 +0100 Subject: [PATCH 191/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ed3b593..0ff55ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.5" +version = "3.6" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From dfa8b9e3306530e024e76ab9d3766eb49c80de11 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 1 Mar 2024 21:39:29 +0100 Subject: [PATCH 192/248] Adds support for Docker for submission command --- src/canvaslms/cli/submissions.nw | 126 +++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 14 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 01ce053..e06361d 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -24,11 +24,13 @@ import re import rich.console import rich.markdown import rich.json +import shlex import sys import tempfile import textwrap import urllib.request +<> <> def add_command(subp): @@ -200,7 +202,7 @@ for submission in submission_list: if args.open == "open": <> - if args.open == "shell": + if <>: <> elif args.output_dir: pass @@ -226,15 +228,16 @@ submission_parser.add_argument("-o", "--output-dir", We also have the open option, that has a choice of two alternatives. <>= submission_parser.add_argument("--open", required=False, - nargs="?", default=None, const="open", choices=["open", "shell"], + nargs="?", default=None, const="open", choices=["open"]+choices_for_shells, help="Open the directory containing the files using " - "the default file manager (`open`) or " - "spawn a shell in the directory (`shell`). " + "the default file manager (`open`). " "With `open`, the pager will be used to display the output as usual. " - "With `shell`, we just drop into the shell, the output can be " - "found in the metadata.{json,md} file in the shell's working " - "directory. " - "Default: %(default)s") + <> + "Default: %(const)s") +<>= +args.open in choices_for_shells +<>= +<> @ Finally, we can add the [[json]] option to the [[submission]] command. @@ -312,16 +315,111 @@ Sometimes we want to open the directory. There are several ways we can do this. We can open the directory with the system file explorer, that way the user can open files while reading the stdout output using a pager. -We can also spawn a shell in the directory so that the user can run the Python -code. +<>= +subprocess.run(["open", tmpdir/subdir]) +@ + +We can also spawn a shell in the directory so that the user can work with the +files, for instance run the Python code in the case of a Python lab submission. +Now, we could spawn a sub-shell of the user's shell, +we'll let this be the [[shell]] option. +Another approach would be to run a Docker image and mount the directory in the +container. +This would be the [[docker]] option. +<>= +choices_for_shells = ["shell", "docker"] +<>= +"With `shell`, we just drop into the shell (as set by $SHELL), " +"the output can be found in the metadata.{json,md} file in " +"the shell's working directory. " +"With `docker`, we run a Docker container with the " +"directory mounted in the container. " +"This way we can run the code in the submission in a " +"controlled environment. " +"Note that this requires Docker to be installed and running. " <>= -print(f"---> Opening a shell ({os.environ['SHELL']}) in {tmpdir/subdir}") +if args.open == "shell": + <> +elif args.open == "docker": + <> +@ + +In both cases, we want to print some useful info for the user, so that they can +more easily orient themselves. +In the case of the sub-shell, we print a message and then spawn the shell in +the directory. +At exit, we print a message that the shell has terminated and that the files +are left in the directory, so the user can go back without executing the +command again. +<>= +print(f"---> Spawning a shell ({os.environ['SHELL']}) in {tmpdir/subdir}") + subprocess.run([ "sh", "-c", f"cd '{tmpdir/subdir}' && exec {os.environ['SHELL']}" ]) -print(f"<--- canvaslms submission shell terminated.") -<>= -subprocess.run(["open", tmpdir/subdir]) + +print(f"<--- canvaslms submission shell terminated.\n" + f"---- Files left in {tmpdir/subdir}.") +@ + +We want to do the same for Docker. +However, this is a bit more complicated. +We need to know which image to use and which command to run in the container. +We also need to know any other options that we might want to pass to Docker. +<>= +print(f"---> Running a Docker container, files mounted in /mnt.") + +cmd = [ + "docker", "run", "-it", "--rm" +] +if args.docker_args: + cmd += args.docker_args +cmd += [ + "-v", f"{tmpdir/subdir}:/mnt", + args.docker_image, args.docker_cmd +] + +subprocess.run(cmd) + +print(f"<--- canvaslms submission Docker container terminated.\n" + f"---- Files left in {tmpdir/subdir}.\n" + f"---- To rerun the container, run:\n" + f"`{' '.join(map(shlex.quote, cmd))}`") +@ + +This requires us to add an option for the Docker image to use and an option for +the command to run in the Docker container. +<>= +submission_parser.add_argument("--docker-image", required=False, + default="ubuntu", + help="The Docker image to use when running a Docker container. " + "This is used with the `docker` option for `--open`. " + "Default: %(default)s") +submission_parser.add_argument("--docker-cmd", required=False, + default="bash", + help="The command to run in the Docker container. " + "This is used with the `docker` option for `--open`. " + "Default: %(default)s") +@ + +For the last argument, [[args.docker_args]], we want to be able to pass any +arguments to the Docker command. +This should be a list of strings, so we can just pass it on to the +[[subprocess.run]] function. + +Using the [[argparse.REMAINDER]] option, we can pass the rest of the command +line to the Docker command. +This is useful since it saves us a lot of problems with escaping options that +we want to pass to Docker, instead of our argparser to parse it. +Normally, if we want to pass [[-e LADOK_USER]] to Docker, our argparser would +pick up that [[-e]] as an option, unless escaped. +<>= +submission_parser.add_argument("--docker-args", required=False, + default=[], nargs=argparse.REMAINDER, + help="Any additional arguments to pass to the Docker command. " + "This is used with the `docker` option for `--open`. " + "Note that this must be the last option on the command line, it takes " + "the rest of the line as arguments for Docker.") @ From a44f6fc6570e26374bda931266ff38fca21cba2a Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 1 Mar 2024 21:41:00 +0100 Subject: [PATCH 193/248] Clarifies how to set coloured pager for submission command --- src/canvaslms/cli/submissions.nw | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index e06361d..717e441 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -443,7 +443,9 @@ if "less" in pager and ("-R" in pager or "-r" in pager): styles = True <>= Uses MANPAGER or PAGER environment variables for the pager to page output. If -the `-r` or `-R` flag is passed to `less`, it uses colours in the output. +the `-r` or `-R` flag is passed to `less`, it uses colours in the output. That +is, set `PAGER=less -r` or `PAGER=less -R` to get coloured output from this +command. @ From ad429c38fff9e241a9ff0956e7f2e1c9f8016b3f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 1 Mar 2024 21:43:09 +0100 Subject: [PATCH 194/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0ff55ee..6dca709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.6" +version = "3.7" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 09e8e9f8c024a92253ee54f14ce69a2d029dd07f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:01:03 +0000 Subject: [PATCH 195/248] Bump rich from 13.7.0 to 13.7.1 Bumps [rich](https://github.com/Textualize/rich) from 13.7.0 to 13.7.1. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.7.0...v13.7.1) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c80baf..2e5241b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -619,13 +619,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [package.dependencies] From 2b0556d8507fd2cad2a18f89545be4d288b06b50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:01:38 +0000 Subject: [PATCH 196/248] Bump keyring from 24.3.0 to 24.3.1 Bumps [keyring](https://github.com/jaraco/keyring) from 24.3.0 to 24.3.1. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/keyring/compare/v24.3.0...v24.3.1) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c80baf..45e5afc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -415,13 +415,13 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "24.3.0" +version = "24.3.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, - {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, + {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, + {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, ] [package.dependencies] @@ -435,7 +435,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "markdown-it-py" From 610df41e22e2f1d151dd480a63b05ee3b286e956 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:02:07 +0000 Subject: [PATCH 197/248] Bump cachetools from 5.3.2 to 5.3.3 Bumps [cachetools](https://github.com/tkem/cachetools) from 5.3.2 to 5.3.3. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v5.3.2...v5.3.3) --- updated-dependencies: - dependency-name: cachetools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c80baf..f4abcc5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,13 +65,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "cachetools" -version = "5.3.2" +version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] From d0394f40305d3a083d57afd770f026572bbe7d12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:13:01 +0000 Subject: [PATCH 198/248] Bump argcomplete from 3.2.2 to 3.2.3 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.2.2 to 3.2.3. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.2.2...v3.2.3) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c80baf..a247338 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.2.2" +version = "3.2.3" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.2.2-py3-none-any.whl", hash = "sha256:e44f4e7985883ab3e73a103ef0acd27299dbfe2dfed00142c35d4ddd3005901d"}, - {file = "argcomplete-3.2.2.tar.gz", hash = "sha256:f3e49e8ea59b4026ee29548e24488af46e30c9de57d48638e24f54a1ea1000a2"}, + {file = "argcomplete-3.2.3-py3-none-any.whl", hash = "sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c"}, + {file = "argcomplete-3.2.3.tar.gz", hash = "sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23"}, ] [package.extras] From a7a9545d6764b193d7479180dcb784e4237d17a6 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 11 Mar 2024 08:50:58 +0100 Subject: [PATCH 199/248] Adds all alternative for submission --open option --- src/canvaslms/cli/submissions.nw | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 717e441..615572c 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -201,6 +201,8 @@ for submission in submission_list: if args.open == "open": <> + elif args.open == "all": + <> if <>: <> @@ -225,13 +227,16 @@ submission_parser.add_argument("-o", "--output-dir", "If specified, do not print to stdout.") @ -We also have the open option, that has a choice of two alternatives. +We also have the open option, that has a choice of a few alternatives. <>= submission_parser.add_argument("--open", required=False, - nargs="?", default=None, const="open", choices=["open"]+choices_for_shells, + nargs="?", default=None, const="open", + choices=["open", "all"]+choices_for_shells, help="Open the directory containing the files using " "the default file manager (`open`). " "With `open`, the pager will be used to display the output as usual. " + "With `all`, all files (not the directory containing them) will be " + "opened in the default application for the file type. " <> "Default: %(const)s") <>= @@ -319,6 +324,13 @@ open files while reading the stdout output using a pager. subprocess.run(["open", tmpdir/subdir]) @ +If we instead want to open all files contained in the directory, we can need to +iterate all the files and open them one by one. +<>= +for file in (tmpdir/subdir).iterdir(): + subprocess.run(["open", file]) +@ + We can also spawn a shell in the directory so that the user can work with the files, for instance run the Python code in the case of a Python lab submission. Now, we could spawn a sub-shell of the user's shell, From eec2d2fbe760efeb1cfdf1c302ce568840c9df12 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 11 Mar 2024 08:52:33 +0100 Subject: [PATCH 200/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dca709..2b3cfef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.7" +version = "3.8" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 60d0a5d28d3e918ba01c34ef87fcd67eced15085 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 15 Mar 2024 00:13:52 +0100 Subject: [PATCH 201/248] Adds grades.tilkryLAB1 summary module --- doc/preamble.tex | 4 + src/canvaslms/grades/.gitignore | 2 + src/canvaslms/grades/Makefile | 11 +- src/canvaslms/grades/grades.nw | 1 + src/canvaslms/grades/tilkryLAB1.nw | 300 +++++++++++++++++++++++++++++ 5 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 src/canvaslms/grades/tilkryLAB1.nw diff --git a/doc/preamble.tex b/doc/preamble.tex index 62c7867..d5b397e 100644 --- a/doc/preamble.tex +++ b/doc/preamble.tex @@ -35,11 +35,15 @@ \usepackage{amsmath} \usepackage{amssymb} \usepackage{mathtools} +\DeclarePairedDelimiter{\ceil}{\lceil}{\rceil} + \usepackage{amsthm} \usepackage{thmtools} \usepackage[unq]{unique} \DeclareMathOperator{\powerset}{\mathcal{P}} +\usepackage{longtable} + \usepackage[binary-units]{siunitx} \usepackage[capitalize]{cleveref} diff --git a/src/canvaslms/grades/.gitignore b/src/canvaslms/grades/.gitignore index a0f351b..4f0fda4 100644 --- a/src/canvaslms/grades/.gitignore +++ b/src/canvaslms/grades/.gitignore @@ -9,3 +9,5 @@ maxgradesurvey.py maxgradesurvey.tex conjunctavgsurvey.py conjunctavgsurvey.tex +tilkryLAB1.py +tilkryLAB1.tex diff --git a/src/canvaslms/grades/Makefile b/src/canvaslms/grades/Makefile index 4465ede..9f4332a 100644 --- a/src/canvaslms/grades/Makefile +++ b/src/canvaslms/grades/Makefile @@ -5,19 +5,22 @@ MODULES+= conjunctavg.py MODULES+= disjunctmax.py MODULES+= maxgradesurvey.py MODULES+= conjunctavgsurvey.py +MODULES+= tilkryLAB1.py .PHONY: all all: grades.tex -all: conjunctavg.py conjunctavg.tex -all: disjunctmax.py disjunctmax.tex -all: maxgradesurvey.py maxgradesurvey.tex -all: conjunctavgsurvey.py conjunctavgsurvey.tex +all: conjunctavg.tex +all: disjunctmax.tex +all: maxgradesurvey.tex +all: conjunctavgsurvey.tex +all: tilkryLAB1.tex all: ${MODULES} grades.tex: conjunctavg.tex grades.tex: disjunctmax.tex grades.tex: maxgradesurvey.tex grades.tex: conjunctavgsurvey.tex +grades.tex: tilkryLAB1.tex __init__.py: init.py ${MV} $^ $@ diff --git a/src/canvaslms/grades/grades.nw b/src/canvaslms/grades/grades.nw index 6f5c159..6e5eef7 100644 --- a/src/canvaslms/grades/grades.nw +++ b/src/canvaslms/grades/grades.nw @@ -134,3 +134,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead. \input{../src/canvaslms/grades/disjunctmax.tex} \input{../src/canvaslms/grades/maxgradesurvey.tex} \input{../src/canvaslms/grades/conjunctavgsurvey.tex} +\input{../src/canvaslms/grades/tilkryLAB1.tex} diff --git a/src/canvaslms/grades/tilkryLAB1.nw b/src/canvaslms/grades/tilkryLAB1.nw new file mode 100644 index 0000000..d1798e8 --- /dev/null +++ b/src/canvaslms/grades/tilkryLAB1.nw @@ -0,0 +1,300 @@ +\section{The Applied Crypto course} + +In this module we'll describe a more complicated way of calculating the grades. +This is used for the LAB1 module in DD2520 Applied Crypto at KTH. + +We have the following grading criteria for the intended learning outcomes. +\begin{longtable}[]{% +>{\raggedright}p{0.25\columnwidth}% +>{\raggedright}p{0.25\columnwidth}% +>{\raggedright}p{0.25\columnwidth}% +>{\raggedright}p{0.25\columnwidth}% +} +\caption{Grading criteria for the intended learning outcomes in DD2520 Applied +Crypto at KTH.} \tabularnewline +\toprule +ILO & +E/P & +C & +A \tabularnewline +\midrule +\endhead +describe cryptographic concepts and explain their security properties, & +from simple examples & +from simple system descriptions & +from complex system descriptions \tabularnewline +\midrule +%ASSESSMENT +% +%written assignments, formative: seminar exercises +use basic terminology in computer security and cryptography correctly & +with few mistakes & +with few and only minor mistakes & +with clear and concise explanations \tabularnewline +\midrule +%ASSESSMENT +% +%written assignments, labs submissions, lab solution presentations; +%formative: seminar exercises +find and use documentation of cryptographic libraries and standards, & +enough to solve labs and cover basics for discussions and hand-ins with +some scientific resources \tabularnewline +\midrule +%ASSESSMENT +% +%written assignments, lab submissions; formative: seminar exercises +identify and categorise threats against a cryptographic IT-system at a +conceptual level, suggest appropriate countermeasures and present the +reasoning to others & +with some appropriate counter-measures and basic argumentation, with +sufficient clarity for fellow students and for teachers to understand, +with few mistakes & +with some appropriate counter-measures and basic argumentation, with +demonstrated correct understanding, with enough relevant detail and few +tangents & +with arguably most appropriate counter-measures and nuanced +argumentation, with logical and pedagogical flow and concise expression +of all (and only) relevant and correct details \tabularnewline +%ASSESSMENT +% +%written assignments, labs submissions, lab solution presentations; +%formative: seminar exercises +\bottomrule +\end{longtable} + +Then we have some mandatory and some optional assignments. +\begin{itemize} +\item Cryptanalysis of Ciphertexts +\item Optional: Cryptopals (C, B, A) +\item Implement AES (Kattis Problem) +\item Optional: AES presentation (C, A) +\item MANDATORY Seminar (pick 1/2 or 2/2): usability (Sonja) ON CAMPUS +\item MANDATORY Seminar (pick 15/2 or 16/2): Impact considerations around crypto systems (Sonja) ON CAMPUS +\item MANDATORY Design Considerations (after the impact considerations seminar) +\item MANDATORY Lab (pick 23/2 or 1/3): Introduction to ProVerif (Karl and Jesper) ON CAMPUS +\item Optional: Side channels (C, B, A) +\item Optional: Secure multi-party computation (C, B, A) +\end{itemize} +The assignments are designed in such a way that the optional assignments let +the students show that they fulfil the higher criteria. +The mandatory ones just ensure the grading criteria for E. + +Each assignment in turn has assignment-specific grading criteria to map grading +to the general grading criteria above. +The assignment-specific grading criteria are mapped to points. + +\begin{longtable}[]{% +>{\raggedright}p{0.3\columnwidth}% +>{\raggedright}p{0.2\columnwidth}% +>{\raggedright}p{0.2\columnwidth}% +>{\raggedright}p{0.2\columnwidth}% +>{\raggedright}p{0.2\columnwidth}% +} +\caption{Assignment-oriented grading criteria for the assignments in DD2520 +Applied Crypto at KTH.}\tabularnewline +\toprule +Higher LAB1 grades given E of mandatory & +Optional assignments for D & +Optional assignments for C & +Optional assignments for B & +Optional assignments for A\tabularnewline +\midrule +\endhead +\(\ceil{\sum}\) is the rounded up sum of points from optional assignments, +where \(1A=2.5, 1B=1.5, 1C=1.\) & +\(\ceil{\sum} = 1\) & +\(\ceil{\sum} = 2\) & +\(\ceil{\sum} = 3\) & +\(\sum \geq 4\)\tabularnewline +\midrule +possible instantiations & 1C & 2C or 1B & 2B or 1A or (1B+1C) & 2A or +(1A+1B) or (1A+2C)\tabularnewline +\bottomrule +\end{longtable} + +We let the mandatory assignments have a grading scale that translates to P +whenever a mandatory assignment is passed. +The optional assignments will not have any grading scale, but we'll use the +points. +This way we can simply sum up the points of the optional assignments and +translate the points to a grade using the table above. + +We should not that this translation is not perfect. +As is pointed out in by the possible instantiations above: technically one can +get an A by doing all the four optional assignments at the lowest level (\(C = +1\)). +However, for now I'm stuck with this system that I inherited with the course. + +In this module we want to provide a summarizer for this grading system. +<>= +Summarizes the assignments of LAB1 in the course DD2520 Applied Crypto at KTH. + +There are some mandatory assignments graded P/F. They must all be P. + +There are also some optional assigments. They're given scores between 0 and +2.5. The sum of the scores is used to determine the grade. If the sum is more +than 4, the grade is A. +@ + +We have one requirement on the summary module: it must contain a function +[[summarize_group]] that takes two arguments; +the first being a list of assignments, +the second being a list of users. +The [[summarize_group]] function is the function that the above code will call. +This gives the following outline of the module. +<>= +""" +<> +""" + +import datetime as dt +from canvaslms.cli import results +from canvasapi.exceptions import ResourceDoesNotExist +import logging +from math import ceil + +<> + +def summarize_group(assignments_list, users_list): + """ + Summarizes a particular set of assignments (assignments_list) for all + users in users_list + """ + + for user in users_list: + grade, grade_date, graders = summarize(user, assignments_list) + yield [user, grade, grade_date, *graders] +@ + + +\subsection{Summarizing grades: assignment grades to component grade} + +Now we will describe the [[summarize]] helper function. +We want to establish three things: the most recent date, a suitable grade and +the graders. + +For the most recent date, we just add them to a list as we iterate through the +submissions. +Then we'll pick the most recent date from the list. + +We do the same for grades, as we iterate through we add any grade to either of +two lists: [[mandatory]] or [[optional]]. +The mandatory assignments have P/F grades. +The optional assignments have grades between 0 and 2.5. +<>= +def summarize(user, assignments_list): + """ + Extracts user's submissions for assignments in assingments_list to summarize + results into one grade and a grade date. + + Summarize according to tilkry grading scheme. + """ + + mandatory = [] + optional = [] + dates = [] + graders = [] + + for assignment in assignments_list: + try: + submission = assignment.get_submission(user, + include=["submission_history"]) + except ResourceDoesNotExist: + <> + + submission.assignment = assignment + graders += results.all_graders(submission) + + grade = submission.grade + + <> + + grade_date = submission.submitted_at or submission.graded_at + + if grade_date: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) + + if not all(grade == "P" for grade in mandatory): + final_grade = "F" + else: + <> + + if dates: + final_date = max(dates) + else: + final_date = None + final_grade = None + + return (final_grade, final_date, graders) +@ + +\subsection{Sorting out mandatory and optional assignments} + +If an assignment is mandatory or optional determines what to do if there is no +submission. +This means that we can't use the grade of the submission (float or P/F) to +determine if the assignment is mandatory or optional. +Based on the names listed above, the system will be that an optional assignment +has its name prefixed with \enquote{Optional:}. +(However, we'll include a few different prefixes.) +<>= +if is_optional(assignment): + <> +else: + <> +<>= +def is_optional(assignment): + assignment_name = assignment.name.casefold() + return ( + assignment_name.startswith("optional:") + or assignment_name.startswith("(optional)") + ) +@ + +If the assignment is mandatory, we'll add the grade to [[mandatory]]. +If there is no grade or submission, we'll treat it as an F. +<>= +if grade is None: + grade = "F" +elif grade not in "PF": + logging.warning(f"Invalid grade {grade} for {user} in {assignment}, " + "using F") + grade = "F" +mandatory.append(grade) +<>= +if not is_optional(assignment): + logging.warning(f"No submission for {user} in {assignment}, using F") + mandatory.append("F") + continue +@ + +If the assignment is optional, we'll add the grade to [[optional]]---if there +is any. +If there is no grade or submission, we'll just skip it. +<>= +if grade is None: + continue +try: + grade = float(grade) + optional.append(grade) +except ValueError: + logging.warning(f"Invalid grade {grade} for {user} in {assignment}, " + "skipping.") + continue +@ + +\subsection{Calculating the final grade} + +The final grade is calculated based on the sum of the optional assignments. +<>= +if sum(optional) >= 4: + final_grade = "A" +elif ceil(sum(optional)) >= 3: + final_grade = "B" +elif ceil(sum(optional)) >= 2: + final_grade = "C" +elif ceil(sum(optional)) >= 1: + final_grade = "D" +else: + final_grade = "E" From 3c0e59321fe11c4c663837c1627a797db98b276c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 15 Mar 2024 00:19:46 +0100 Subject: [PATCH 202/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2b3cfef..92bf7d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.8" +version = "3.9" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 1b883066ac2280ee18b8f6862edd82f2be2229e4 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Fri, 15 Mar 2024 11:33:36 +0100 Subject: [PATCH 203/248] Adds the complete module to the doc (grades.tillkryLAB1) --- src/canvaslms/grades/tilkryLAB1.nw | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/canvaslms/grades/tilkryLAB1.nw b/src/canvaslms/grades/tilkryLAB1.nw index d1798e8..7306a93 100644 --- a/src/canvaslms/grades/tilkryLAB1.nw +++ b/src/canvaslms/grades/tilkryLAB1.nw @@ -298,3 +298,9 @@ elif ceil(sum(optional)) >= 1: final_grade = "D" else: final_grade = "E" +@ + +\subsection{The complete module} + +All in all, the module looks like this. +\inputminted{python}{../src/canvaslms/grades/tilkryLAB1.py} From 8e91865cbe8329bf286030dbb0acec077517c352 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Mar 2024 11:13:39 +0100 Subject: [PATCH 204/248] Fixes assignments output data This makes it more like the other commands' output, namely containing the course code as well. We also add the due date, unlock at and lock at to the output. --- src/canvaslms/cli/assignments.nw | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index 7e2b024..c70cf02 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -141,7 +141,7 @@ We add the subparser for [[assignments]]. assignments_parser = subp.add_parser("assignments", help="Lists assignments of a course", description="Lists assignments of a course. " - "Output, CSV-format: ") + <>) assignments_parser.set_defaults(func=assignments_command) add_assignment_option(assignments_parser) @ Now, that [[assignments_command]] function must take three arguments: @@ -157,11 +157,21 @@ def assignments_command(config, canvas, args): We then simply get the filtered list from the processing of the assignment options, stored in [[assignment_list]] above. -Then we will print the most useful attributes (mentioned in the help text -above) of an assignment in CSV format. +Then we will print the most useful attributes. +<>= +"Output, CSV-format: " +" " +" " <>= for assignment in assignment_list: - output.writerow([assignment.assignment_group.name, assignment.name]) + output.writerow([ + assignment.course.course_code, + assignment.assignment_group.name, + assignment.name, + assignment.due_at, + assignment.unlock_at, + assignment.lock_at + ]) @ From 809d303357a8529356730b93b42bac576eebdbfc Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Mar 2024 11:55:41 +0100 Subject: [PATCH 205/248] Makes assignment print raw JSON data when there is no instruction This is useful for viewing assignments using LTI integrations. --- src/canvaslms/cli/assignments.nw | 83 +++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/cli/assignments.nw b/src/canvaslms/cli/assignments.nw index c70cf02..ba9cb69 100644 --- a/src/canvaslms/cli/assignments.nw +++ b/src/canvaslms/cli/assignments.nw @@ -17,6 +17,7 @@ import canvasapi import canvaslms.cli.courses as courses import canvaslms.hacks.canvasapi import csv +import json import os import pypandoc import re @@ -262,15 +263,85 @@ The assignment contents given by Canvas is HTML, we want to pipe that through <>= def format_assignment(assignment): """Returns an assignment formatted for the terminal""" - instruction = pypandoc.convert_text( - assignment.description, "md", format="html") + text = f""" +<> - return f"""# {assignment.name} +""" -{instruction} + if assignment.description: + instruction = pypandoc.convert_text( + assignment.description, "md", format="html") + text += f"## Instruction\n\n{instruction}\n\n" + <> + else: + <> + text += f"## Assignment data\n\n```json\n{format_json(assignment)}\n```\n" -URL: {assignment.html_url} -Submissions: {assignment.submissions_download_url}""" + return text +@ + +\subsection{Assignment metadata} + +Now let's look at the metadata to add. +<>= +# {assignment.name} + +## Metadata + +- Unlocks: {assignment.unlock_at if assignment.unlock_at else None} +- Due: {assignment.due_at if assignment.due_at else None} +- Locks: {assignment.lock_at if assignment.lock_at else None} +- Ungraded submissions: {assignment.needs_grading_count} +- Submission type: {assignment.submission_types} +- URL: {assignment.html_url} +- Submissions: {assignment.submissions_download_url} +@ + +\subsection{Assignment rubric} + +We want to format the rubric as well. +<>= +try: + text += f"## Rubric\n\n{format_rubric(assignment.rubric)}\n\n" +except AttributeError: + pass +@ + +We'll do this with [[format_rubric]]. +It should output a markdown representation of the rubric. +<>= +def format_rubric(rubric): + """ + Returns a markdown representation of the rubric + """ + if not rubric: + return "No rubric set." + + text = "" + for criterion in rubric: + text += f"- {criterion['description']}\n" + text += f" - Points: {criterion['points']}\n" + text += f" - Ratings: " + text += "; ".join([ + f"{rating['description'].strip()} ({rating['points']})" + for rating in criterion["ratings"] + ]) + "\n" + text += f"\n```\n{criterion['long_description']}\n```\n\n" + + return text +@ + +\subsection{Assignment data as raw JSON} + +We also want to format the assignment data as JSON. +We must extract all attributes from the assignment object. +<>= +def format_json(assignment): + """Returns a JSON representation of the assignment""" + return json.dumps({ + key: str(value) for key, value in assignment.__dict__.items() + if not key.startswith("_") + }, indent=2) @ From 8d7cbdbba14bfc3b0e2361359786f4161e23ca30 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Mar 2024 12:06:19 +0100 Subject: [PATCH 206/248] Makes groups output CSV format consistent with other CSV outputs The course code is used instead of the Canvas ID. --- src/canvaslms/cli/users.nw | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index f9bb8cd..babf4a3 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -260,7 +260,7 @@ def add_groups_command(subp): groups_parser = subp.add_parser("groups", help="Lists groups of a course", description="Lists groups of a course(s). Output, CSV-format: " - " <#members>") + " <#members>") groups_parser.set_defaults(func=groups_command) courses.add_course_option(groups_parser) <> @@ -298,7 +298,10 @@ else: for category in categories: for group in filter_groups([category], args.regex): - row = [group.id, category.name, group.name, group.members_count] + row = [ + category.course.course_code, category.name, + group.name, group.members_count + ] output.writerow(row) @ @@ -313,8 +316,10 @@ we can filter out groups; or filter out groups directly from courses. We want to filter out the group categories from a list of courses. <>= def filter_group_categories(course_list, regex): - """Filters out the group categories whose names match regex - in the courses in course_list""" + """ + Filters out the group categories whose names match regex + in the courses in course_list + """ name = re.compile(regex or ".*") @@ -334,9 +339,11 @@ method [[.get_groups()]], so thanks to Python's duck typing we can write short code. <>= def filter_groups(items, regex): - """Items is a list of either courses or group categories, + """ + Items is a list of either courses or group categories, regex is a regular expression. - Returns all groups whose name match regex.""" + Returns all groups whose name match regex. + """ name = re.compile(regex or ".*") From aa9964a200eacc541800a896d387ee09021e5331 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Mar 2024 12:07:17 +0100 Subject: [PATCH 207/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 92bf7d6..29d7ada 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "3.9" +version = "4.0" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 1aa35f489d4dc46457ea964db0f323b89efee4a1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 26 Mar 2024 22:52:13 +0100 Subject: [PATCH 208/248] Bugfix: `disjunctmax.grade_max` had `not` in the wrong place There was a `not` where it shouldn't have been. This resulted in results never being reported. --- src/canvaslms/grades/disjunctmax.nw | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/canvaslms/grades/disjunctmax.nw b/src/canvaslms/grades/disjunctmax.nw index 4fb6e95..1a51a98 100644 --- a/src/canvaslms/grades/disjunctmax.nw +++ b/src/canvaslms/grades/disjunctmax.nw @@ -104,16 +104,17 @@ def summarize(user, assignments_list): \subsection{Computing the maximum} To compute the maximum for the A--E grades; we will convert the grades into -integers, compute the maximum, round the value to an integer and convert back. +integers, compute the maximum, and convert back. We also include P/F here, since we can count them as lower than A--E. <>= def grade_max(grades): """Takes a list of A--E/P--F grades, returns the maximum.""" - num_grades = list(map(grade_to_int, - filter(lambda x: x[0] != "F", grades))) - if not num_grades: + num_grades = list(map(grade_to_int, grades)) + + if num_grades: max_grade = max(num_grades) return int_to_grade(max_grade) + return None def grade_to_int(grade): @@ -127,5 +128,4 @@ def int_to_grade(int_grade): 0: "P", 1: "E", 2: "D", 3: "C", 4: "B", 5: "A"} return grade_map_inv[int_grade] -@ From eb8d69f68166fb0fd7357ee9bb2e5dbab29ee622 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 08:00:48 +0000 Subject: [PATCH 209/248] Bump argcomplete from 3.2.3 to 3.3.0 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.2.3 to 3.3.0. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.2.3...v3.3.0) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index b322256..9dafad0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "appdirs" @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.2.3" +version = "3.3.0" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.2.3-py3-none-any.whl", hash = "sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c"}, - {file = "argcomplete-3.2.3.tar.gz", hash = "sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23"}, + {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, + {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, ] [package.extras] From afbbf3653f9a15bcb3d78964863d5724fc617fbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 07:36:24 +0000 Subject: [PATCH 210/248] Bump keyring from 24.3.1 to 25.2.1 Bumps [keyring](https://github.com/jaraco/keyring) from 24.3.1 to 25.2.1. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/keyring/compare/v24.3.1...v25.2.1) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- poetry.lock | 67 ++++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index b322256..8225a1d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "appdirs" @@ -63,6 +63,21 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "backports-tarfile" +version = "1.1.1" +description = "Backport of CPython tarfile module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "backports.tarfile-1.1.1-py3-none-any.whl", hash = "sha256:73e0179647803d3726d82e76089d01d8549ceca9bace469953fcb4d97cf2d417"}, + {file = "backports_tarfile-1.1.1.tar.gz", hash = "sha256:9c2ef9696cb73374f7164e17fc761389393ca76777036f5aad42e8b93fcd8009"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] + [[package]] name = "cachetools" version = "5.3.3" @@ -398,6 +413,42 @@ more-itertools = "*" docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +[[package]] +name = "jaraco-context" +version = "5.3.0" +description = "Useful decorators and context managers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266"}, + {file = "jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"}, +] + +[package.dependencies] +"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-functools" +version = "4.0.1" +description = "Functools like those found in stdlib" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"}, + {file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.classes", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [[package]] name = "jeepney" version = "0.8.0" @@ -415,27 +466,29 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "24.3.1" +version = "25.2.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, - {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, + {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"}, + {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"}, ] [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} importlib-resources = {version = "*", markers = "python_version < \"3.9\""} "jaraco.classes" = "*" +"jaraco.context" = "*" +"jaraco.functools" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "markdown-it-py" @@ -736,4 +789,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "bdb63b5ca41671ae10aff6fc52edda928d2076e581ba1320ca4408c09e3260e5" +content-hash = "0e04cb6c380fa3066ca03f44309fd06d530c96efb5c42abe0d648af0c1d267a3" diff --git a/pyproject.toml b/pyproject.toml index 29d7ada..aeb2f5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ appdirs = "^1.4.4" argcomplete = ">=2,<4" cachetools = "^5.3.1" canvasapi = "^3.2.0" -keyring = "^24.2.0" +keyring = ">=24.2,<26.0" pypandoc = "^1.11" arrow = "^1.2.3" rich = "^13.0.0" From 8d800cf8cda910ad396df5408e2c7ac28c24f237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 05:46:28 +0000 Subject: [PATCH 211/248] --- updated-dependencies: - dependency-name: idna dependency-type: indirect dependency-group: pip - dependency-name: requests dependency-type: indirect dependency-group: pip ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index b322256..ad8abe5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "appdirs" @@ -334,13 +334,13 @@ pyrepl = ">=0.8.2" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -598,13 +598,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, + {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, ] [package.dependencies] From e8643d90b96dfc20b5483cbad43ee6ab78e2da59 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 23 May 2024 09:58:04 +0200 Subject: [PATCH 212/248] Makes user matching case insensitive --- src/canvaslms/cli/users.nw | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index babf4a3..e753c82 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -390,6 +390,11 @@ def list_users(courses, roles): Second, we provide the most general function, [[filter_users]], which takes a list of courses, a list of Canvas roles and a regex as arguments. It returns the matching users. + +We compile the regex to a pattern. +This is because it's slightly faster, since we reuse the regex for several +searches. +We'll ignore case. <>= def filter_users(course_list, regex, roles=[]): """ @@ -400,7 +405,7 @@ def filter_users(course_list, regex, roles=[]): - integration id (exact match), - SIS user ID (exact match). """ - pattern = re.compile(regex or ".*") + pattern = re.compile(regex or ".*", re.IGNORECASE) for user in list_users(course_list, roles): <> @@ -408,6 +413,12 @@ def filter_users(course_list, regex, roles=[]): Now to check if the user matches, we want to match some things by regular expression, but other things exactly. +The reasoning is this: +If we use the name, we might match on parts of it; if the user has a unique +enough name. +But if we give the integration ID, we wouldn't try to give part of it; it's not +predicable how much of it will give a unique match, and it doesn't make sense +to look for students with a common prefix of their integration IDs. <>= if str(user.id) == regex: yield user From a8b754a1cc991b7e4e5437c4fe37ea22b639ad2f Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 13 Jun 2024 14:20:27 +0200 Subject: [PATCH 213/248] Adds -i/--id option to courses command --- src/canvaslms/cli/courses.nw | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/courses.nw b/src/canvaslms/cli/courses.nw index ec93dff..8dd486e 100644 --- a/src/canvaslms/cli/courses.nw +++ b/src/canvaslms/cli/courses.nw @@ -21,7 +21,7 @@ def add_command(subp): courses_parser = subp.add_parser("courses", help="Lists your courses", description="Lists your courses. Output, CSV-format: " - " ") + "<>") courses_parser.set_defaults(func=courses_command) <> @ @@ -82,13 +82,26 @@ if not args.all: We have the course data in a [[course]] object. Now we just print the interesting data about it. +<>= +* <>= -output.writerow([ +row = [] +if args.id: + row.append(course.id) +row.extend([ course.course_code, course.name, course.start_at, course.end_at ]) +output.writerow(row) +@ + +We add the option [[--id]] to show the Canvas ID of the course. +<>= +courses_parser.add_argument("-i", "--id", + action="store_true", default=False, + help="Include Canvas ID of the course as first column.") @ From 9cca1ef107fdbb7cd6945533b222ed07aa3d99aa Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 13 Jun 2024 14:23:15 +0200 Subject: [PATCH 214/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aeb2f5e..d56ae9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.0" +version = "4.1" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From ad669f06ba79315f683b45ac7d36da259f3a3837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:02:16 +0000 Subject: [PATCH 215/248] Bump argcomplete from 3.3.0 to 3.4.0 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.3.0...v3.4.0) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 02babe6..9fa1b7f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.3.0" +version = "3.4.0" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, - {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, + {file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"}, + {file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"}, ] [package.extras] From 725656a5c2132f6c736ea3d88c048c6ba631d549 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 17 Jun 2024 11:33:07 +0200 Subject: [PATCH 216/248] Adds missing P in the help text for the `results -F` option --- src/canvaslms/cli/results.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/cli/results.nw b/src/canvaslms/cli/results.nw index cf0dc71..131a7b1 100644 --- a/src/canvaslms/cli/results.nw +++ b/src/canvaslms/cli/results.nw @@ -130,7 +130,7 @@ If the user supplies [[-F]], we include all grades. If the user supplies [[-F regex]], we use regex to filter grades. <>= passing_regex = r"^([A-EP]|complete)$" -all_grades_regex = r"^([A-F]x?|(in)?complete)$" +all_grades_regex = r"^([A-FP]x?|(in)?complete)$" results_parser.add_argument("-F", "--filter-grades", required=False, action="store", nargs="?", const=all_grades_regex, From 73d0589b85eaef664b587b5f47454e7b5fcdc8a2 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 17 Jun 2024 11:33:45 +0200 Subject: [PATCH 217/248] Removes newclude to make doc compile with TeXLive 2024 --- doc/preamble.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/preamble.tex b/doc/preamble.tex index d5b397e..aa98bff 100644 --- a/doc/preamble.tex +++ b/doc/preamble.tex @@ -10,7 +10,7 @@ \renewcommand{\foreignfullfont}{} \renewcommand{\foreignabbrfont}{} -\usepackage{newclude} +%\usepackage{newclude} \usepackage{import} \usepackage[strict]{csquotes} From d474956b7ed6b25e71372aabd309b7069b805d41 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 17 Jun 2024 11:34:36 +0200 Subject: [PATCH 218/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d56ae9a..b678f26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.1" +version = "4.2" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 945099d0cbe042c4f6de9c10dd99c2519dd071f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jul 2024 01:34:44 +0000 Subject: [PATCH 219/248] Bump the pip group with 2 updates Bumps the pip group with 2 updates: [certifi](https://github.com/certifi/python-certifi) and [urllib3](https://github.com/urllib3/urllib3). Updates `certifi` from 2024.2.2 to 2024.7.4 - [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04) Updates `urllib3` from 2.2.1 to 2.2.2 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect dependency-group: pip - dependency-name: urllib3 dependency-type: indirect dependency-group: pip ... Signed-off-by: dependabot[bot] --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9fa1b7f..0aa300f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "appdirs" @@ -106,13 +106,13 @@ requests = "*" [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -739,13 +739,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] From b5060ec85a28b7bc01e3ba4679bf00ae866dc23b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:04:43 +0000 Subject: [PATCH 220/248] Bump zipp from 3.17.0 to 3.19.1 in the pip group Bumps the pip group with 1 update: [zipp](https://github.com/jaraco/zipp). Updates `zipp` from 3.17.0 to 3.19.1 - [Release notes](https://github.com/jaraco/zipp/releases) - [Changelog](https://github.com/jaraco/zipp/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/zipp/compare/v3.17.0...v3.19.1) --- updated-dependencies: - dependency-name: zipp dependency-type: indirect dependency-group: pip ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0aa300f..64b9d7f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -773,18 +773,18 @@ test = ["pytest"] [[package]] name = "zipp" -version = "3.17.0" +version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, + {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" From 6c1204efb29c925775af7b4f32f95c0ba6740011 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 07:47:36 +0000 Subject: [PATCH 221/248] Bump keyring from 25.2.1 to 25.3.0 Bumps [keyring](https://github.com/jaraco/keyring) from 25.2.1 to 25.3.0. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/keyring/compare/v25.2.1...v25.3.0) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 64b9d7f..2ac9dfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -466,13 +466,13 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "25.2.1" +version = "25.3.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"}, - {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"}, + {file = "keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae"}, + {file = "keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef"}, ] [package.dependencies] @@ -487,8 +487,8 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "markdown-it-py" From 397a621fbc5a35905308357a0d2b6016f47d5a5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 07:38:32 +0000 Subject: [PATCH 222/248] Bump argcomplete from 3.4.0 to 3.5.0 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 64b9d7f..e3afbca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.4.0" +version = "3.5.0" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"}, - {file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"}, + {file = "argcomplete-3.5.0-py3-none-any.whl", hash = "sha256:d4bcf3ff544f51e16e54228a7ac7f486ed70ebf2ecfe49a63a91171c76bf029b"}, + {file = "argcomplete-3.5.0.tar.gz", hash = "sha256:4349400469dccfb7950bb60334a680c58d88699bff6159df61251878dc6bf74b"}, ] [package.extras] From 9fdcd5dda150ca914a91668c70ee6392c83dc602 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 07:43:34 +0000 Subject: [PATCH 223/248] Bump cachetools from 5.3.3 to 5.5.0 Bumps [cachetools](https://github.com/tkem/cachetools) from 5.3.3 to 5.5.0. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v5.3.3...v5.5.0) --- updated-dependencies: - dependency-name: cachetools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 64b9d7f..fe40d27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,13 +80,13 @@ testing = ["jaraco.test", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "p [[package]] name = "cachetools" -version = "5.3.3" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] From 2ca9dedb3d404055df87cc18eaa3b50deff228f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:47:52 +0000 Subject: [PATCH 224/248] Bump rich from 13.7.1 to 13.8.0 Bumps [rich](https://github.com/Textualize/rich) from 13.7.1 to 13.8.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.7.1...v13.8.0) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index c5052fe..ff8ac60 100644 --- a/poetry.lock +++ b/poetry.lock @@ -672,13 +672,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.1" +version = "13.8.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, + {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, ] [package.dependencies] From 50cf2aca315729ff68c0c0e1b63f18066dd3e59f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:48:04 +0000 Subject: [PATCH 225/248] Bump canvasapi from 3.2.0 to 3.3.0 Bumps [canvasapi](https://github.com/ucfopen/canvasapi) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/ucfopen/canvasapi/releases) - [Changelog](https://github.com/ucfopen/canvasapi/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ucfopen/canvasapi/compare/v3.2.0...v3.3.0) --- updated-dependencies: - dependency-name: canvasapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index c5052fe..7dee991 100644 --- a/poetry.lock +++ b/poetry.lock @@ -91,12 +91,13 @@ files = [ [[package]] name = "canvasapi" -version = "3.2.0" +version = "3.3.0" description = "API wrapper for the Canvas LMS" optional = false python-versions = "*" files = [ - {file = "canvasapi-3.2.0.tar.gz", hash = "sha256:7cf97ad1ddc860e250c3453e229897ed1273095ad061c34baf651bf1b0e5a9c7"}, + {file = "canvasapi-3.3.0-py3-none-any.whl", hash = "sha256:d7b85a9abf149ed7729002d9bfe2c30bbfcdcf14517f10234b7bdd630a1a8217"}, + {file = "canvasapi-3.3.0.tar.gz", hash = "sha256:86f2e930acc87c9a360575b969687f107ab4e1f3c0ee4556df30d1757eaf5ef0"}, ] [package.dependencies] From 7afdf246bd7fe7ffca4694f23f8939a75e5d3ffb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:04:03 +0000 Subject: [PATCH 226/248] Bump cryptography from 42.0.4 to 43.0.1 in the pip group Bumps the pip group with 1 update: [cryptography](https://github.com/pyca/cryptography). Updates `cryptography` from 42.0.4 to 43.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...43.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect dependency-group: pip ... Signed-off-by: dependabot[bot] --- poetry.lock | 63 ++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/poetry.lock b/poetry.lock index c5052fe..2303c33 100644 --- a/poetry.lock +++ b/poetry.lock @@ -280,43 +280,38 @@ files = [ [[package]] name = "cryptography" -version = "42.0.4" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, - {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, - {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, - {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, - {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, - {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, - {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, - {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -329,7 +324,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] From 4bf2e6211a4d86b2caba62584d2f0c197f3c9b81 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 15 Sep 2024 14:20:23 +0200 Subject: [PATCH 227/248] Improves error handling when listing users --- src/canvaslms/cli/users.nw | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/canvaslms/cli/users.nw b/src/canvaslms/cli/users.nw index babf4a3..fb07bd6 100644 --- a/src/canvaslms/cli/users.nw +++ b/src/canvaslms/cli/users.nw @@ -19,6 +19,7 @@ We outline the module: <>= import argparse import canvasapi.course +import canvasapi.exceptions import canvasapi.group import canvaslms.cli import canvaslms.cli.courses as courses @@ -373,13 +374,23 @@ We provide the following functions: First, we provide [[list_users]], which takes a list of courses and a list of Canvas roles as arguments. +Here we must be careful. +For some courses we can't list the users, because of lacking permissions. +Whenever we're searching for a particular user, we might look in all courses, +and then we must skip those where we can't list the users. +This way we can search through all other courses instead of stopping at the +first error. <>= def list_users(courses, roles): """List users in courses with roles""" users = list() for course in courses: - course_users = list(course.get_users(enrollment_type=roles)) + try: + course_users = list(course.get_users(enrollment_type=roles)) + except canvasapi.exceptions.CanvasException as err: + canvaslms.cli.warn(f"skipped {course}: {err}") + continue for user in course_users: user.course = course users.extend(course_users) @@ -403,12 +414,14 @@ def filter_users(course_list, regex, roles=[]): pattern = re.compile(regex or ".*") for user in list_users(course_list, roles): - <> + <> @ Now to check if the user matches, we want to match some things by regular expression, but other things exactly. -<>= +Some attributes might not exist, so we must try-except them and issue a warning +if they don't exist. +<>= if str(user.id) == regex: yield user continue From fed7719d217eb8624f51ae1b529509c9ab713598 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 07:17:30 +0000 Subject: [PATCH 228/248] Bump argcomplete from 3.5.0 to 3.5.1 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.5.0...v3.5.1) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0065670..c88c7dc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.0" +version = "3.5.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.5.0-py3-none-any.whl", hash = "sha256:d4bcf3ff544f51e16e54228a7ac7f486ed70ebf2ecfe49a63a91171c76bf029b"}, - {file = "argcomplete-3.5.0.tar.gz", hash = "sha256:4349400469dccfb7950bb60334a680c58d88699bff6159df61251878dc6bf74b"}, + {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, + {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, ] [package.extras] From 7d43d41cf21a838c81540380ed8a8293b8de7e79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 07:53:34 +0000 Subject: [PATCH 229/248] Bump pypandoc from 1.13 to 1.14 Bumps [pypandoc](https://github.com/JessicaTegner/pypandoc) from 1.13 to 1.14. - [Release notes](https://github.com/JessicaTegner/pypandoc/releases) - [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md) - [Commits](https://github.com/JessicaTegner/pypandoc/compare/v1.13...v1.14) --- updated-dependencies: - dependency-name: pypandoc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0065670..b276c80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -580,13 +580,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pypandoc" -version = "1.13" +version = "1.14" description = "Thin wrapper for pandoc." optional = false python-versions = ">=3.6" files = [ - {file = "pypandoc-1.13-py3-none-any.whl", hash = "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681"}, - {file = "pypandoc-1.13.tar.gz", hash = "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e"}, + {file = "pypandoc-1.14-py3-none-any.whl", hash = "sha256:1315c7ad7fac7236dacf69a05b521ed2c3f1d0177f70e9b92bfffce6c023df22"}, + {file = "pypandoc-1.14.tar.gz", hash = "sha256:6b4c45f5f1b9fb5bb562079164806bdbbc3e837b5402bcf3f1139edc5730a197"}, ] [[package]] From f850df21d9115a0b68066a2ad306915e63b04050 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:54:36 +0000 Subject: [PATCH 230/248] Bump keyring from 25.3.0 to 25.5.0 Bumps [keyring](https://github.com/jaraco/keyring) from 25.3.0 to 25.5.0. - [Release notes](https://github.com/jaraco/keyring/releases) - [Changelog](https://github.com/jaraco/keyring/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/keyring/compare/v25.3.0...v25.5.0) --- updated-dependencies: - dependency-name: keyring dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0065670..fc856aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -462,13 +462,13 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "25.3.0" +version = "25.5.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae"}, - {file = "keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef"}, + {file = "keyring-25.5.0-py3-none-any.whl", hash = "sha256:e67f8ac32b04be4714b42fe84ce7dad9c40985b9ca827c592cc303e7c26d9741"}, + {file = "keyring-25.5.0.tar.gz", hash = "sha256:4c753b3ec91717fe713c4edd522d625889d8973a349b0e582622f49766de58e6"}, ] [package.dependencies] @@ -482,9 +482,13 @@ pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] completion = ["shtab (>=1.1.0)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] +type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"] [[package]] name = "markdown-it-py" From 079d18d6cc93811ad8cbc12644eb5c13cbc35023 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:54:47 +0000 Subject: [PATCH 231/248] Bump rich from 13.8.0 to 13.9.3 Bumps [rich](https://github.com/Textualize/rich) from 13.8.0 to 13.9.3. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.8.0...v13.9.3) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0065670..7975af0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -668,19 +668,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.8.0" +version = "13.9.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, - {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, + {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, + {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] From 5ed3d751b661ce4f8e6b56b8c49159272c570a71 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Wed, 30 Oct 2024 09:17:01 +0100 Subject: [PATCH 232/248] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 5fa215d..fc3f71f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ is `canvaslms` and it has several subcommands in the same style as Git. `canvaslms` provides output in a format useful for POSIX tools, this makes automating tasks much easier. +## Getting started + +Start by login to your Canvas server + +``` {.text} +canvaslms login +``` + Let's consider how to grade students logging into the student-shell SSH server. We store the list of students' Canvas and KTH IDs in a file. From cfadbb1fd24417faca538f2c84caa99e4ac018a0 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Sun, 3 Nov 2024 08:11:37 +0100 Subject: [PATCH 233/248] Handles non-existing preview_url attribute in submissions --- src/canvaslms/cli/submissions.nw | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 615572c..ed996a8 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -109,6 +109,8 @@ submissions_parser.add_argument("-l", "--login-id", Sometimes we want to compute the URL for SpeedGrader for a submission. The [[Submission]] objects come with an attribute [[preview_url]]. +(However, previous submissions don't have this, so if it doesn't exist, we use +[[None]].) We want to turn that one into the SpeedGrader URL. The [[preview_url]] looks like this: \begin{minted}{text} @@ -124,7 +126,10 @@ We use Python's regex abilities to rewrite the URL. <>= def speedgrader(submission): """Returns the SpeedGrader URL of the submission""" - speedgrader_url = submission.preview_url + try: + speedgrader_url = submission.preview_url + except AttributeError: + return None speedgrader_url = re.sub("assignments/", "gradebook/speed_grader?assignment_id=", From 9cdb8a27cdb67cc09a8f1c49bcb7a30c6e7486f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:15:28 +0000 Subject: [PATCH 234/248] Bump rich from 13.9.3 to 13.9.4 Bumps [rich](https://github.com/Textualize/rich) from 13.9.3 to 13.9.4. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v13.9.3...v13.9.4) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index c6d54c7..23fdf12 100644 --- a/poetry.lock +++ b/poetry.lock @@ -672,13 +672,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.9.3" +version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ - {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, - {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] From afded9735bf61b1f76b7765ac84da969c11e6a61 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Nov 2024 10:19:52 +0100 Subject: [PATCH 235/248] Adds error handling for unknown graders --- src/canvaslms/cli/submissions.nw | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index 615572c..e4087b4 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -6,9 +6,11 @@ handles an individual submission. We outline the module: <>= +import canvasapi.exceptions import canvasapi.file import canvasapi.submission +import canvaslms import canvaslms.cli.assignments as assignments import canvaslms.cli.users as users import canvaslms.hacks.canvasapi @@ -732,6 +734,9 @@ The grader ID is negative if it was graded automatically, \eg by a quiz or LTI integration. If negative, it's either the quiz ID or LTI tool ID. (Negate to get the ID.) +Finally, we look up the user object from the course. +In some rare cases, we might not find the user, in which case we return the +grader ID as a string. <>= def resolve_grader(submission): """ @@ -746,7 +751,13 @@ def resolve_grader(submission): if submission.grader_id < 0: return "autograded" - return submission.assignment.course.get_user(submission.grader_id) + + try: + return submission.assignment.course.get_user(submission.grader_id) + except canvasapi.exceptions.ResourceDoesNotExist: + canvaslms.warn(f"unknown grader {submission.grader_id} " + f"for submission {submission}.") + return f"unknown grader {submission.grader_id}" @ \subsection{Rubric data} From eff3a8cb96bb0a074783ff1fddd90ef43bd92324 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Nov 2024 10:22:56 +0100 Subject: [PATCH 236/248] Minor clarification in the code of tilkryLAB1 --- src/canvaslms/grades/tilkryLAB1.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/tilkryLAB1.nw b/src/canvaslms/grades/tilkryLAB1.nw index 7306a93..dbe9b23 100644 --- a/src/canvaslms/grades/tilkryLAB1.nw +++ b/src/canvaslms/grades/tilkryLAB1.nw @@ -201,6 +201,7 @@ def summarize(user, assignments_list): include=["submission_history"]) except ResourceDoesNotExist: <> + continue submission.assignment = assignment graders += results.all_graders(submission) @@ -266,7 +267,6 @@ mandatory.append(grade) if not is_optional(assignment): logging.warning(f"No submission for {user} in {assignment}, using F") mandatory.append("F") - continue @ If the assignment is optional, we'll add the grade to [[optional]]---if there From 02d634795207164b58a939e444d12d4f06b5221d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Nov 2024 10:24:27 +0100 Subject: [PATCH 237/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b678f26..03aa50e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.2" +version = "4.3" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From cf504ab70539b046ae593f54452358eeaa2c9e9d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Nov 2024 12:45:59 +0100 Subject: [PATCH 238/248] Adds missing `cli` in import statement The `warn` function is not available in the `canvaslms` module, but in the `canvaslms.cli` module. This commit fixes the import statement to import the `warn` function from the correct module. --- src/canvaslms/cli/submissions.nw | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index e4087b4..9133e5a 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -10,7 +10,7 @@ import canvasapi.exceptions import canvasapi.file import canvasapi.submission -import canvaslms +import canvaslms.cli import canvaslms.cli.assignments as assignments import canvaslms.cli.users as users import canvaslms.hacks.canvasapi @@ -755,8 +755,8 @@ def resolve_grader(submission): try: return submission.assignment.course.get_user(submission.grader_id) except canvasapi.exceptions.ResourceDoesNotExist: - canvaslms.warn(f"unknown grader {submission.grader_id} " - f"for submission {submission}.") + canvaslms.cli.warn(f"unknown grader {submission.grader_id} " + f"for submission {submission}.") return f"unknown grader {submission.grader_id}" @ From 6c17049ed074e035a8789bea7bb642aa5bee3c32 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Mon, 4 Nov 2024 12:47:32 +0100 Subject: [PATCH 239/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03aa50e..c311a25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.3" +version = "4.4" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From c33f4b9f1fb93fca893c20fc905b146b672b6fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 07:54:09 +0000 Subject: [PATCH 240/248] Bump argcomplete from 3.5.1 to 3.5.2 Bumps [argcomplete](https://github.com/kislyuk/argcomplete) from 3.5.1 to 3.5.2. - [Release notes](https://github.com/kislyuk/argcomplete/releases) - [Changelog](https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst) - [Commits](https://github.com/kislyuk/argcomplete/compare/v3.5.1...v3.5.2) --- updated-dependencies: - dependency-name: argcomplete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 23fdf12..b5a3ef7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.1" +version = "3.5.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, - {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, + {file = "argcomplete-3.5.2-py3-none-any.whl", hash = "sha256:036d020d79048a5d525bc63880d7a4b8d1668566b8a76daf1144c0bbe0f63472"}, + {file = "argcomplete-3.5.2.tar.gz", hash = "sha256:23146ed7ac4403b70bd6026402468942ceba34a6732255b9edf5b7354f68a6bb"}, ] [package.extras] From 005d7a860dc42aed817605e6287074b163897b92 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 17 Dec 2024 11:49:28 +0100 Subject: [PATCH 241/248] Don't write unknown grader warning to stderr --- src/canvaslms/cli/submissions.nw | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index d4566c4..a20be10 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -760,8 +760,6 @@ def resolve_grader(submission): try: return submission.assignment.course.get_user(submission.grader_id) except canvasapi.exceptions.ResourceDoesNotExist: - canvaslms.cli.warn(f"unknown grader {submission.grader_id} " - f"for submission {submission}.") return f"unknown grader {submission.grader_id}" @ From 16492c30bff9d256956e50a7f1f25d1882d14187 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 17 Dec 2024 11:54:36 +0100 Subject: [PATCH 242/248] Don't warn whenever a student has no submission --- src/canvaslms/grades/tilkryLAB1.nw | 1 - 1 file changed, 1 deletion(-) diff --git a/src/canvaslms/grades/tilkryLAB1.nw b/src/canvaslms/grades/tilkryLAB1.nw index dbe9b23..21aea10 100644 --- a/src/canvaslms/grades/tilkryLAB1.nw +++ b/src/canvaslms/grades/tilkryLAB1.nw @@ -265,7 +265,6 @@ elif grade not in "PF": mandatory.append(grade) <>= if not is_optional(assignment): - logging.warning(f"No submission for {user} in {assignment}, using F") mandatory.append("F") @ From 3618c2617006f1c6fc08f88f04b122fe34406185 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 17 Dec 2024 13:21:27 +0100 Subject: [PATCH 243/248] Converts PDFs to text using pdf2txt --- src/canvaslms/cli/submissions.nw | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/canvaslms/cli/submissions.nw b/src/canvaslms/cli/submissions.nw index a20be10..6e0eae6 100644 --- a/src/canvaslms/cli/submissions.nw +++ b/src/canvaslms/cli/submissions.nw @@ -905,6 +905,7 @@ def convert_to_md(attachment: canvasapi.file.File, <> content_type = getattr(attachment, "content-type") <> + <> <> <>= contents = convert_to_md(attachment, tmpdir) @@ -951,6 +952,8 @@ def text_to_md(content_type): This leaves us with the following. The advantage of reading the content from the file is that Python will solve the encoding for us. +Instead of using an [[if]] statement, we'll go all Python and use a +[[try-except]] block. <>= try: md_type = text_to_md(content_type) @@ -961,7 +964,22 @@ except ValueError: pass @ -If the content type is not text, we use [[pypandoc]] to convert it to Markdown. +Now we'll do the same for PDF files. +We'll use [[pdf2txt]] to convert the PDF to text. +However, here we'll use an if statement. +We'll check for the content type to end with [[pdf]], that will capture also +[[x-pdf]]. +<>= +if content_type.endswith("pdf"): + try: + return subprocess.check_output(["pdf2txt", str(outfile)], + text=True) + except subprocess.CalledProcessError: + pass +@ + +Finally, as a last attempt, we use [[pypandoc]] to try to convert it to +Markdown. Here we'll use Pandoc's ability to infer the file type on its own. This means we'll have to download the attachment as a file in a temporary location and let Pandoc convert the file to Markdown. @@ -969,7 +987,7 @@ location and let Pandoc convert the file to Markdown. try: return pypandoc.convert_file(outfile, "markdown") except Exception as err: - return f"Pandoc cannot convert this file. " \ + return f"Cannot convert this file. " \ f"The file is located at\n\n {outfile}\n\n" @ From b379e58a2de32fbbce6fdff1d9c5de52978f5f49 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 17 Dec 2024 13:24:15 +0100 Subject: [PATCH 244/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c311a25..033b1e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.4" +version = "4.5" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 51d7606c73bd948f18d6837b8e4d44f3703fb8c1 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 7 Jan 2025 11:46:59 +0100 Subject: [PATCH 245/248] Bugfix conjunctavgsurvey: submission needs assignment Also rewrote the code to be more readable (and literate). --- src/canvaslms/grades/conjunctavgsurvey.nw | 90 ++++++++++++++++------- 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/src/canvaslms/grades/conjunctavgsurvey.nw b/src/canvaslms/grades/conjunctavgsurvey.nw index 7b47c08..b8a1011 100644 --- a/src/canvaslms/grades/conjunctavgsurvey.nw +++ b/src/canvaslms/grades/conjunctavgsurvey.nw @@ -65,7 +65,8 @@ def summarize(user, assignments_list): for assignment in assignments_list: try: submission = assignment.get_submission(user, - include=["submission_history"]) + include=["submission_history"]) + submission.assignment = assignment except ResourceDoesNotExist: pf_grades.append("F") continue @@ -76,34 +77,73 @@ def summarize(user, assignments_list): if grade is None: grade = "F" - if grade in "ABCDE": - a2e_grades.append(grade) - elif grade in "PF": - pf_grades.append(grade) - elif grade == "Fx": - pf_grades.append("F") - else: - pf_grades.append("P") + <> + <> - grade_date = submission.submitted_at or submission.graded_at + <> + <> - if grade_date: - grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) - dates.append(grade_date) + <> - if all(map(lambda x: x == "P", pf_grades)): - final_grade = "P" - if a2e_grades: - final_grade = a2e_average(a2e_grades) + return (final_grade, final_date, graders) +@ - if dates: - final_date = max(dates) - else: - final_date = None - final_grade = None +We look at the grade and add it to the appropriate list. +The grade Fx is treated as an F. +It's the only grade that should be more than one letter. +<>= +if grade in "ABCDE": + a2e_grades.append(grade) +elif grade in "PF": + pf_grades.append(grade) +elif grade == "Fx": + pf_grades.append("F") +else: + pf_grades.append("P") +@ - if len(dates) < len(pf_grades) + len(a2e_grades): - final_grade = "F" +When we check, we check that all the P/F grades are P. +If that's the case, we can compute the average of the A--E grades---if there +are any. +If there are no A--E grades, the final grade is P. +<>= +if all(map(lambda x: x == "P", pf_grades)): + final_grade = "P" + if a2e_grades: + final_grade = a2e_average(a2e_grades) +else: + final_grade = "F" +@ - return (final_grade, final_date, graders) +When it comes to the date, we want primarily the submission date. +If there is no submission date, we use the grade date. +(However, when we require the student to present their work, we should probably +use the grade date as that best represents the date of presenting.) +<> +grade_date = submission.submitted_at or submission.graded_at + +if grade_date: + grade_date = dt.date.fromisoformat(grade_date.split("T")[0]) + dates.append(grade_date) +@ + +When we check the dates, we want the final date to be the most recent date. +If there are no dates, the student hasn't done anything, then we set the final +grade (and date) to [[None]] instead of F. +<>= +if dates: + final_date = max(dates) +else: + final_date = None + final_grade = None @ + +Finally, as a check, we can check that the number of dates and number of grades +are the same. +Otherwise, they have passed everything they have done, but simply not done some +assignment. +<>= +if len(dates) < len(pf_grades) + len(a2e_grades): + final_grade = "F" +@ + From bb1be075bcf0d06dc8d4a27f1a9865566ab5ef7d Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 7 Jan 2025 12:00:00 +0100 Subject: [PATCH 246/248] Fixes typo in conjunctavgsurvey.nw --- src/canvaslms/grades/conjunctavgsurvey.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvaslms/grades/conjunctavgsurvey.nw b/src/canvaslms/grades/conjunctavgsurvey.nw index b8a1011..ed6ccf3 100644 --- a/src/canvaslms/grades/conjunctavgsurvey.nw +++ b/src/canvaslms/grades/conjunctavgsurvey.nw @@ -119,7 +119,7 @@ When it comes to the date, we want primarily the submission date. If there is no submission date, we use the grade date. (However, when we require the student to present their work, we should probably use the grade date as that best represents the date of presenting.) -<> +<>= grade_date = submission.submitted_at or submission.graded_at if grade_date: From fce1e2964a75b05e1331879d1761bcbbba404e7c Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 7 Jan 2025 12:01:02 +0100 Subject: [PATCH 247/248] Bumps version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 033b1e3..63879ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "canvaslms" -version = "4.5" +version = "4.6" description = "Command-line interface to Canvas LMS" authors = ["Daniel Bosk "] license = "MIT" From 2eadffd51d807d2544050c9037cbf13369afe387 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Tue, 14 Jan 2025 20:50:04 +0100 Subject: [PATCH 248/248] Adds P+ grade to tilkryLAB1 --- src/canvaslms/grades/tilkryLAB1.nw | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/canvaslms/grades/tilkryLAB1.nw b/src/canvaslms/grades/tilkryLAB1.nw index 21aea10..d3676fe 100644 --- a/src/canvaslms/grades/tilkryLAB1.nw +++ b/src/canvaslms/grades/tilkryLAB1.nw @@ -255,9 +255,14 @@ def is_optional(assignment): If the assignment is mandatory, we'll add the grade to [[mandatory]]. If there is no grade or submission, we'll treat it as an F. + +We also added a grade P+, for the fun of it, on one assignment. +We treat it as a normal P though. <>= if grade is None: grade = "F" +elif grade == "P+": + grade = "P" elif grade not in "PF": logging.warning(f"Invalid grade {grade} for {user} in {assignment}, " "using F")