ladok3 4.18__tar.gz → 5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ladok3 might be problematic. Click here for more details.

Files changed (33) hide show
  1. {ladok3-4.18 → ladok3-5.0}/PKG-INFO +1 -1
  2. {ladok3-4.18 → ladok3-5.0}/doc/ladok3.tex +62 -25
  3. {ladok3-4.18 → ladok3-5.0}/pyproject.toml +1 -1
  4. {ladok3-4.18 → ladok3-5.0}/src/ladok3/data.nw +26 -8
  5. {ladok3-4.18 → ladok3-5.0}/src/ladok3/report.nw +139 -117
  6. {ladok3-4.18 → ladok3-5.0}/LICENSE +0 -0
  7. {ladok3-4.18 → ladok3-5.0}/README.md +0 -0
  8. {ladok3-4.18 → ladok3-5.0}/doc/Makefile +0 -0
  9. {ladok3-4.18 → ladok3-5.0}/doc/abstract.tex +0 -0
  10. {ladok3-4.18 → ladok3-5.0}/doc/preamble.tex +0 -0
  11. {ladok3-4.18 → ladok3-5.0}/makefiles/doc.mk +0 -0
  12. {ladok3-4.18 → ladok3-5.0}/makefiles/exam.mk +0 -0
  13. {ladok3-4.18 → ladok3-5.0}/makefiles/haskell.mk +0 -0
  14. {ladok3-4.18 → ladok3-5.0}/makefiles/miun.course.mk +0 -0
  15. {ladok3-4.18 → ladok3-5.0}/makefiles/miun.depend.mk +0 -0
  16. {ladok3-4.18 → ladok3-5.0}/makefiles/miun.docs.mk +0 -0
  17. {ladok3-4.18 → ladok3-5.0}/makefiles/miun.port.mk +0 -0
  18. {ladok3-4.18 → ladok3-5.0}/makefiles/miun.pub.mk +0 -0
  19. {ladok3-4.18 → ladok3-5.0}/makefiles/noweb.mk +0 -0
  20. {ladok3-4.18 → ladok3-5.0}/makefiles/pkg.mk +0 -0
  21. {ladok3-4.18 → ladok3-5.0}/makefiles/portability.mk +0 -0
  22. {ladok3-4.18 → ladok3-5.0}/makefiles/pub.mk +0 -0
  23. {ladok3-4.18 → ladok3-5.0}/makefiles/results.mk +0 -0
  24. {ladok3-4.18 → ladok3-5.0}/makefiles/subdir.mk +0 -0
  25. {ladok3-4.18 → ladok3-5.0}/makefiles/tex.mk +0 -0
  26. {ladok3-4.18 → ladok3-5.0}/makefiles/transform.mk +0 -0
  27. {ladok3-4.18 → ladok3-5.0}/src/ladok3/.gitignore +0 -0
  28. {ladok3-4.18 → ladok3-5.0}/src/ladok3/Makefile +0 -0
  29. {ladok3-4.18 → ladok3-5.0}/src/ladok3/api.nw +0 -0
  30. {ladok3-4.18 → ladok3-5.0}/src/ladok3/cli.nw +0 -0
  31. {ladok3-4.18 → ladok3-5.0}/src/ladok3/ladok3.nw +0 -0
  32. {ladok3-4.18 → ladok3-5.0}/src/ladok3/student.nw +0 -0
  33. {ladok3-4.18 → ladok3-5.0}/src/ladok3/undoc.nw +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ladok3
3
- Version: 4.18
3
+ Version: 5.0
4
4
  Summary: Python wrapper and CLI for the LADOK3 REST API.
5
5
  Home-page: https://github.com/dbosk/ladok3
6
6
  License: MIT
@@ -46,10 +46,10 @@ higher education in Sweden.
46
46
  This is the documented source code of \texttt{ladok3}, a LADOK3 API wrapper for
47
47
  Python.
48
48
 
49
- The \texttt{ladok3} library provides a non-GUI application that, similar to
50
- access via a web browser, only provides the user with access to the LADOK data
51
- and functionality that they would actually have based on their specific user
52
- permissions in LADOK.
49
+ The \texttt{ladok3} library provides a non-GUI application (a command-line
50
+ interface, CLI) that, similar to access via a web browser, only provides the
51
+ user with access to the LADOK data and functionality that they would actually
52
+ have based on their specific user permissions in LADOK.
53
53
  It can be seem as a very streamlined web browser just for LADOK's web
54
54
  interface.
55
55
  While the library exploits caching to reduce the load on the LADOK server, this
@@ -61,12 +61,23 @@ You can install the \texttt{ladok3} package by running
61
61
  pip3 install ladok3
62
62
  \end{minted}
63
63
  in the terminal.
64
- You can find a quick reference by running
64
+
65
+ Then you can use the CLI by running \mintinline{bash}{ladok} in the terminal.
66
+ The command has built-in help, simply run \mintinline{bash}{ladok -h} to see
67
+ the available commands.
68
+ However, the first thing you want to run after installing the package is
65
69
  \begin{minted}[firstnumber=last]{bash}
66
- pydoc ladok3
70
+ ladok login
71
+ \end{minted}
72
+ This will set up your credentials for the CLI.
73
+ However, if you want to use the library in a script, you can read
74
+ \begin{minted}[firstnumber=last]{bash}
75
+ ladok login -h
67
76
  \end{minted}
77
+ for alternative ways of providing your credentials.
68
78
 
69
- We provide the main class \texttt{LadokSession} (\cref{LadokSession}).
79
+ For use in Python scripts,
80
+ we provide the main class \texttt{LadokSession} (\cref{LadokSession}).
70
81
  The \texttt{LadokSession} class acts like \enquote{factories} and will return
71
82
  objects representing various LADOK data.
72
83
  These data objects' classes inherit the \texttt{LadokData} (\cref{LadokData})
@@ -83,17 +94,10 @@ We do this by caching all factory methods of any \texttt{LadokSession}.
83
94
  The \texttt{LadokSession} itself is also designed to be cacheable: if the session to
84
95
  LADOK expires, it will automatically reauthenticate to establish a new session.
85
96
 
86
-
87
-
88
- \part{The library}
89
-
90
- \input{../src/ladok3/ladok3.tex}
91
-
92
-
93
- \part{API calls}
94
-
95
- \input{../src/ladok3/api.tex}
96
- \input{../src/ladok3/undoc.tex}
97
+ You can find a quick reference by running
98
+ \begin{minted}[firstnumber=last]{bash}
99
+ pydoc ladok3
100
+ \end{minted}
97
101
 
98
102
 
99
103
 
@@ -103,7 +107,7 @@ LADOK expires, it will automatically reauthenticate to establish a new session.
103
107
 
104
108
  \input{../src/ladok3/cli.tex}
105
109
 
106
- \chapter{The \texttt{data} command}
110
+ \chapter{The \texttt{course} command}
107
111
 
108
112
  \input{../src/ladok3/data.tex}
109
113
 
@@ -119,19 +123,52 @@ LADOK expires, it will automatically reauthenticate to establish a new session.
119
123
 
120
124
  \part{Other example applications}
121
125
 
122
- \chapter{Transfer results from KTH Canvas to LADOK}
126
+ \chapter{Transfer results from Canvas to LADOK}
123
127
 
124
128
  Here we provide an example program~\texttt{canvas2ladok.py} which exports
125
- results from KTH Canvas to LADOK for the introductory programming course~prgi
129
+ results from Canvas to LADOK for the introductory programming course~prgi
126
130
  (DD1315).
127
- You can find an up-to-date version of this chapter at
128
- \begin{center}
129
- \url{https://github.com/dbosk/intropy/tree/master/adm/reporting}.
130
- \end{center}
131
+
132
+ However, a better way to do this is by using the CLI:
133
+ \begin{minted}{bash}
134
+ #!/bin/bash
135
+
136
+ . ${HOME}/.profile
137
+
138
+ year=22
139
+ courses="DD13(10HT${year}|17HT${year})"
140
+ components="(LAB[123]|MAT1|KAL1)"
141
+
142
+ canvaslms results -c "$courses" -A "$components" \
143
+ | sed -E "s/ ?[HV]T[0-9]{2}( \(.*\))?//" \
144
+ | ladok report -fv
145
+ \end{minted}
146
+ The advantage to using this command is that it will automatically report the
147
+ correct dates and everyone who has participated in the grading of each
148
+ student---as required by regulation.
149
+ The official tools, like KTH Transfer to Ladok or SUNET's version of the same,
150
+ don't do this---they don't set the dates correctly of each individual, but one
151
+ for all, and they don't register the graders in LADOK.
152
+
153
+ But now we'll have a look at how we can do this (well, a simpler version) in
154
+ Python.
155
+
131
156
 
132
157
  \input{../examples/canvas2ladok.tex}
133
158
 
134
159
 
160
+ \part{The library}
161
+
162
+ \input{../src/ladok3/ladok3.tex}
163
+
164
+
165
+ \part{API calls}
166
+
167
+ \input{../src/ladok3/api.tex}
168
+ \input{../src/ladok3/undoc.tex}
169
+
170
+
171
+
135
172
  \backmatter
136
173
  \printbibliography
137
174
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ladok3"
3
- version = "4.18"
3
+ version = "5.0"
4
4
  description = "Python wrapper and CLI for the LADOK3 REST API."
5
5
  authors = [
6
6
  "Daniel Bosk <dbosk@kth.se>",
@@ -16,7 +16,7 @@ from LADOK (\cref{DataCommand}).
16
16
  This is a subcommand for the [[ladok]] command-line interface.
17
17
  It can be used like this:
18
18
  \begin{minted}{bash}
19
- ladok data DD1315 > DD1315.csv
19
+ ladok course DD1315 > DD1315.csv
20
20
  \end{minted}
21
21
  This program will produce CSV-formated data to answer the questions above.
22
22
  The data is formated like this:
@@ -26,11 +26,11 @@ The data is formated like this:
26
26
  \item component,
27
27
  \item student (pseudonym),
28
28
  \item grade,
29
- \item normalized time.
29
+ \item either absolute date or normalized to the course start and finish.
30
30
  \end{itemize}
31
31
 
32
32
 
33
- \section{The [[data]] subcommand}\label{DataCommand}
33
+ \section{The [[course]] subcommand}\label{DataCommand}
34
34
 
35
35
  This is a subcommand run as part of the [[ladok3.cli]] module.
36
36
  We provide a function [[add_command_options]] that adds the subcommand options
@@ -59,7 +59,7 @@ def command(ladok, args):
59
59
  We add a subparser.
60
60
  We set it up to use the function [[command]].
61
61
  <<add data parser to parser>>=
62
- data_parser = parser.add_parser("data",
62
+ data_parser = parser.add_parser("course",
63
63
  help="Returns course results data in CSV form",
64
64
  description="""
65
65
  Returns the results in CSV form for all first-time registered students.
@@ -79,9 +79,10 @@ course_rounds = filter_rounds(
79
79
  ladok.search_course_rounds(code=args.course_code),
80
80
  args.rounds)
81
81
 
82
- data_writer.writerow([
83
- "Course", "Round", "Component", "Student", "Grade", "Time"
84
- ])
82
+ if args.header:
83
+ data_writer.writerow([
84
+ "Course", "Round", "Component", "Student", "Grade", "Time"
85
+ ])
85
86
  for course_round in course_rounds:
86
87
  data = extract_data_for_round(ladok, course_round, args)
87
88
 
@@ -91,6 +92,7 @@ for course_round in course_rounds:
91
92
  student, grade, time]
92
93
  )
93
94
  @ We must take a course code and a delimiter as arguments.
95
+ We also want to know if we want a header or not.
94
96
  <<add data command arguments to data parser>>=
95
97
  data_parser.add_argument("course_code",
96
98
  help="The course code of the course for which to export data")
@@ -100,6 +102,9 @@ data_parser.add_argument("-d", "--delimiter",
100
102
  help="The delimiter for the CSV output; "
101
103
  "default is a tab character to be compatible with POSIX commands, "
102
104
  "use `-d,` or `-d ,` to get comma-separated values.")
105
+
106
+ data_parser.add_argument("-H", "--header", action="store_true",
107
+ help="Print a header line with the column names.")
103
108
  @
104
109
 
105
110
  We filter the rounds.
@@ -154,7 +159,20 @@ def extract_data_for_round(ladok, course_round, args):
154
159
  else:
155
160
  <<extract grade and normalized date from result data>>
156
161
 
157
- yield student, component, grade, normalized_date
162
+ <<yield [[student, component, grade]] and date>>
163
+ @
164
+
165
+ We want to yield the data in CSV form, so we simply yield a tuple.
166
+ The date is either the normalized date or the date from the result data.
167
+ <<yield [[student, component, grade]] and date>>=
168
+ if args.normalize_date:
169
+ yield student, component, grade, normalized_date
170
+ elif result_data:
171
+ yield student, component, grade, result_data["Examinationsdatum"]
172
+ <<add data command arguments to data parser>>=
173
+ data_parser.add_argument("-n", "--normalize-date", action="store_true",
174
+ help="Normalize the date to the start of the course, "
175
+ "otherwise the date is printed as is.")
158
176
  @
159
177
 
160
178
  \subsection{Get round data}
@@ -83,6 +83,123 @@ report_parser.add_argument("-f", "--finalize",
83
83
  @
84
84
 
85
85
 
86
+ \section{Report many results given in standard input}
87
+
88
+ We want to read CSV data from standard input.
89
+ <<report results given in stdin>>=
90
+ data_reader = csv.reader(sys.stdin, delimiter=args.delimiter)
91
+ for course_code, component_code, student_id, grade, date, *graders in data_reader:
92
+ <<report a result read from stdin>>
93
+ @ We need to add an argument for the delimiter.
94
+ <<add many results group arguments>>=
95
+ many_parser.add_argument("-d", "--delimiter",
96
+ default="\t",
97
+ help="The delimiter for the CSV input; "
98
+ "default is a tab character to be compatible with POSIX commands, "
99
+ "use `-d,` or `-d ,` to get comma-separated values.")
100
+ @
101
+
102
+ We also want to handle errors and confirmations.
103
+ When reporting in bulk, we don't want unnecessary errors.
104
+ We also want to have a summary of the changes.
105
+ <<add many results group arguments>>=
106
+ many_parser.add_argument("-v", "--verbose",
107
+ action="count", default=0,
108
+ help="Increases the verbosity of the output: -v will print results that "
109
+ "were reported to standard out. Otherwise only errors are printed.")
110
+ @
111
+
112
+ Then we can actually report the result using the values read from [[stdin]].
113
+ <<report a result read from stdin>>=
114
+ try:
115
+ set_grade(ladok, args,
116
+ student_id, course_code, component_code, grade, date, graders)
117
+ except Exception as err:
118
+ <<try to resolve [[student]] from [[ladok]] using [[student_id]]>>
119
+ print(f"{course_code} {component_code}={grade} ({date}) {student}: "
120
+ f"{err}",
121
+ file=sys.stderr)
122
+ @
123
+
124
+ The reason we want to resolve the student from LADOK is that the [[student_id]]
125
+ might be a personnummer or a LADOK ID---if the latter, it's not particularly
126
+ readable for a human and we can't use the LADOK ID in the LADOK web interface
127
+ when we want to deal with the errors manually.
128
+ But if we resolve the student, then we get a readable name.
129
+ <<try to resolve [[student]] from [[ladok]] using [[student_id]]>>=
130
+ try:
131
+ student = ladok.get_student(student_id)
132
+ except Exception:
133
+ student = student_id
134
+ @
135
+
136
+ When we set the grade, there are a few cases that should be handled.
137
+ If the grade isn't attested, we try to change it.
138
+ (This might still fail if the grade is finalized but not attested.)
139
+ If we've selected the verbose option, then we print what we have reported.
140
+
141
+ If the grade was attested, then we check if it's different.
142
+ If it's different, we output this.
143
+ If it's the same, we silently ignore it.
144
+ This is best for bulk reporting, because then we can always try to report for
145
+ all students.
146
+
147
+ We want to report errors as exceptions.
148
+ <<functions>>=
149
+ def set_grade(ladok, args,
150
+ student_id, course_code, component_code, grade, date, graders):
151
+ student = ladok.get_student(student_id)
152
+ <<get [[course]] from [[student]] and [[course_code]]>>
153
+ <<get [[component]] from [[course]] and [[component_code]]>>
154
+
155
+ if not component.attested and component.grade != grade:
156
+ <<ensure [[date]] is a valid date for [[course]]>>
157
+ component.set_grade(grade, date)
158
+ if args.finalize:
159
+ component.finalize(graders)
160
+ if args.verbose:
161
+ print(f"{course_code} {student}: reported "
162
+ f"{component.component} = {component.grade} ({date}) "
163
+ f"by {', '.join(graders)}.")
164
+ elif component.grade != grade:
165
+ raise Exception(f"attested {component.component} "
166
+ f"result {component.grade} ({component.date}) "
167
+ f"is different from {grade} ({date}).")
168
+ @
169
+
170
+ Now we simply want to set those objects up.
171
+ We want to throw exceptions that explain what the problem is if these don't
172
+ exist.
173
+ <<get [[course]] from [[student]] and [[course_code]]>>=
174
+ try:
175
+ course = student.courses(code=course_code)[0]
176
+ except IndexError:
177
+ raise Exception(f"{course_code}: No such course for {student}")
178
+ <<get [[component]] from [[course]] and [[component_code]]>>=
179
+ try:
180
+ component = course.results(component=component_code)[0]
181
+ except IndexError:
182
+ raise Exception(f"{component_code}: no such component for {course_code}")
183
+ @
184
+
185
+ Finally, we want to ensure the date is correct.
186
+ The date must be at the earliest the start of the course.
187
+ The student can't finish any results before the course has started.
188
+ LADOK will not accept that.
189
+ <<ensure [[date]] is a valid date for [[course]]>>=
190
+ if not isinstance(date, datetime.date):
191
+ date = datetime.date.fromisoformat(date)
192
+
193
+ if date < course.start:
194
+ print(f"{course_code} {component_code}={grade} "
195
+ f"({date}) {student}: "
196
+ f"Grade date ({date}) is before "
197
+ f"course start date ({course.start}), "
198
+ f"using course start date instead.")
199
+ date = course.start
200
+ @
201
+
202
+
86
203
  \section{Report a result given on command line}
87
204
 
88
205
  If we've chosen to give one result on the command line, then we'll need the
@@ -91,28 +208,33 @@ following arguments.
91
208
  We start with the course, component code, the student's ID and grade.
92
209
  <<add one result group arguments>>=
93
210
  one_parser.add_argument("course_code", nargs="?",
94
- help="The course code (e.g. DD1315) for which the grade is for"
211
+ help="The course code (e.g. DD1315) for which the grade is for."
95
212
  )
96
213
 
97
214
  one_parser.add_argument("component_code", nargs="?",
98
- help="The component code (e.g. LAB1) for which the grade is for"
215
+ help="The component code (e.g. LAB1) for which the grade is for. "
216
+ "This can be set to the course code (e.g. DD1315) to set the "
217
+ "final grade for the course. But all components must be "
218
+ "certified (attested) before the course grade can be set."
99
219
  )
100
220
 
101
221
  one_parser.add_argument("student_id", nargs="?",
102
- help="Student identifier (personnummer or LADOK ID)"
222
+ help="Student identifier (personnummer or LADOK ID)."
103
223
  )
104
224
 
105
225
  one_parser.add_argument("grade", nargs="?",
106
- help="The grade (e.g. A or P)"
226
+ help="The grade (e.g. A or P)."
107
227
  )
108
- @ We must make them optional like this to make it work with out second
109
- alternative, so we must check ourselves that we got the arguments.
228
+ @ We must make them optional like this to make it work with our second
229
+ alternative (bulk reporting through [[stdin]]), so we must check ourselves that
230
+ we got the arguments.
110
231
  <<check that we got all positional arguments>>=
111
232
  if not (args.course_code and args.component_code and
112
233
  args.student_id and args.grade):
113
234
  print(f"{sys.argv[0]} report: "
114
- "not all positional args given: course_code, component, student, grade",
115
- file=sys.stderr)
235
+ "not all positional args given: "
236
+ "course_code, component, student, grade",
237
+ file=sys.stderr)
116
238
  sys.exit(1)
117
239
  @
118
240
 
@@ -123,7 +245,7 @@ If it's not provided, we let [[argparse]] set it to today's date.
123
245
  <<add one result group arguments>>=
124
246
  one_parser.add_argument("date", nargs="?",
125
247
  help="Date on ISO format (e.g. 2021-03-18), "
126
- f"defaults to today's date ({datetime.date.today()})",
248
+ f"defaults to today's date ({datetime.date.today()}).",
127
249
  type=datetime.date.fromisoformat,
128
250
  default=datetime.date.today()
129
251
  )
@@ -141,115 +263,15 @@ them.
141
263
  <<report results given in args>>=
142
264
  <<check that we got all positional arguments>>
143
265
  try:
144
- student = ladok.get_student(args.student_id)
145
- <<look up [[course]] from [[student]]>>
146
- <<look up [[result]] from [[course]]>>
147
- result.set_grade(args.grade, args.date)
148
- if args.finalize:
149
- result.finalize(args.graders)
266
+ set_grade(ladok, args,
267
+ args.student_id, args.course_code, args.component_code,
268
+ args.grade, args.date, args.graders)
150
269
  except Exception as err:
151
- try:
152
- print(f"{student}: {err}")
153
- except ValueError as verr:
154
- print(f"{verr}: {args.student_id}: {err}")
155
- @ The option [[args.finalize]] is already set above, so we don't need to add
156
- that one here.
157
-
158
- Both looking up the course and the component can yield index errors.
159
- We'd like to distinguish these.
160
- We will catch the exception, the reraise the same exception but with a better
161
- error message.
162
- <<look up [[course]] from [[student]]>>=
163
- try:
164
- course = student.courses(code=args.course_code)[0]
165
- except IndexError:
166
- raise Exception(f"{args.course_code}: No such course for {student}")
167
- <<look up [[result]] from [[course]]>>=
168
- try:
169
- result = course.results(component=args.component_code)[0]
170
- except IndexError:
171
- raise Exception(f"{args.component_code}: "
172
- f"No such component for {args.course_code}")
173
- @
174
-
175
-
176
- \section{Report many results given in standard input}
177
-
178
- We want to read CSV data from standard input.
179
- <<report results given in stdin>>=
180
- data_reader = csv.reader(sys.stdin, delimiter=args.delimiter)
181
- for course_code, component_code, student_id, grade, date, *graders in data_reader:
182
- <<report a result read from stdin>>
183
- @ We need to add an argument for the delimiter.
184
- <<add many results group arguments>>=
185
- many_parser.add_argument("-d", "--delimiter",
186
- default="\t",
187
- help="The delimiter for the CSV input; "
188
- "default is a tab character to be compatible with POSIX commands, "
189
- "use `-d,` or `-d ,` to get comma-separated values.")
190
- @
191
-
192
- We also want to handle errors and confirmations differently.
193
- When reporting in bulk, we don't want unnecessary errors.
194
- We also want to have a summary of the changes.
195
- <<add many results group arguments>>=
196
- many_parser.add_argument("-v", "--verbose",
197
- action="count", default=0,
198
- help="Increases the verbosity of the output: -v will print results that "
199
- "were reported to standard out. Otherwise only errors are printed.")
200
- @
201
-
202
- When we actually report a result, this is similar to how we did it above.
203
- The difference is that we've read the variables from the CSV data in [[stdin]]
204
- instead of from [[args]].
205
- <<report a result read from stdin>>=
206
- try:
207
- student = ladok.get_student(student_id)
208
-
209
- try:
210
- course = student.courses(code=course_code)[0]
211
- except IndexError:
212
- raise Exception(f"{course_code}: No such course for {student}")
213
-
214
- try:
215
- component = course.results(component=component_code)[0]
216
- except IndexError:
217
- raise Exception(f"{component_code}: no such component for {course_code}")
218
-
219
- <<set [[grade]] to [[component]], output if verbose>>
220
- except Exception as err:
221
- try:
222
- print(f"{course_code} {component_code}={grade} ({date}) {student}: {err}",
223
- file=sys.stderr)
224
- except ValueError as verr:
225
- print(f"{verr}: "
226
- f"{course_code} {component_code}={grade} ({date}) {student_id}: {err}",
227
- file=sys.stderr)
228
- @
229
-
230
- Now, when we set the grade, there are a few cases that should be handled.
231
- If the grade isn't attested, we try to change it.
232
- (This might still fail if the grade is finalized but not attested.)
233
- If we've selected the verbose option, then we print what we have reported.
234
-
235
- If the grade was attested, then we check if it's different.
236
- If it's different, we output this.
237
- If it's the same, we silently ignore it.
238
- This is best for bulk reporting, because then we can always try to report for
239
- all students.
240
- <<set [[grade]] to [[component]], output if verbose>>=
241
- if not component.attested and component.grade != grade:
242
- component.set_grade(grade, date)
243
- if args.finalize:
244
- component.finalize(graders)
245
- if args.verbose:
246
- print(f"{course_code} {student}: reported "
247
- f"{component.component} = {component.grade} ({date}) "
248
- f"by {', '.join(graders)}.")
249
- elif component.grade != grade:
250
- print(f"{course_code} {student}: attested {component.component} "
251
- f"result {component.grade} ({component.date}) "
252
- f"is different from {grade} ({date}).")
270
+ student_id = args.student_id
271
+ <<try to resolve [[student]] from [[ladok]] using [[student_id]]>>
272
+ print(f"{args.course_code} {args.component_code}={args.grade} ({args.date}) "
273
+ f"{student}: {err}",
274
+ file=sys.stderr)
253
275
  @
254
276
 
255
277
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes