ladok3 3.6__tar.gz → 3.8__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-3.6 → ladok3-3.8}/PKG-INFO +1 -1
  2. {ladok3-3.6 → ladok3-3.8}/pyproject.toml +1 -1
  3. {ladok3-3.6 → ladok3-3.8}/src/ladok3/ladok3.nw +45 -8
  4. {ladok3-3.6 → ladok3-3.8}/src/ladok3/report.nw +48 -17
  5. {ladok3-3.6 → ladok3-3.8}/src/ladok3/student.nw +22 -4
  6. {ladok3-3.6 → ladok3-3.8}/LICENSE +0 -0
  7. {ladok3-3.6 → ladok3-3.8}/README.md +0 -0
  8. {ladok3-3.6 → ladok3-3.8}/doc/Makefile +0 -0
  9. {ladok3-3.6 → ladok3-3.8}/doc/abstract.tex +0 -0
  10. {ladok3-3.6 → ladok3-3.8}/doc/ladok3.tex +0 -0
  11. {ladok3-3.6 → ladok3-3.8}/doc/preamble.tex +0 -0
  12. {ladok3-3.6 → ladok3-3.8}/makefiles/doc.mk +0 -0
  13. {ladok3-3.6 → ladok3-3.8}/makefiles/exam.mk +0 -0
  14. {ladok3-3.6 → ladok3-3.8}/makefiles/haskell.mk +0 -0
  15. {ladok3-3.6 → ladok3-3.8}/makefiles/miun.course.mk +0 -0
  16. {ladok3-3.6 → ladok3-3.8}/makefiles/miun.depend.mk +0 -0
  17. {ladok3-3.6 → ladok3-3.8}/makefiles/miun.docs.mk +0 -0
  18. {ladok3-3.6 → ladok3-3.8}/makefiles/miun.port.mk +0 -0
  19. {ladok3-3.6 → ladok3-3.8}/makefiles/miun.pub.mk +0 -0
  20. {ladok3-3.6 → ladok3-3.8}/makefiles/noweb.mk +0 -0
  21. {ladok3-3.6 → ladok3-3.8}/makefiles/pkg.mk +0 -0
  22. {ladok3-3.6 → ladok3-3.8}/makefiles/portability.mk +0 -0
  23. {ladok3-3.6 → ladok3-3.8}/makefiles/pub.mk +0 -0
  24. {ladok3-3.6 → ladok3-3.8}/makefiles/results.mk +0 -0
  25. {ladok3-3.6 → ladok3-3.8}/makefiles/subdir.mk +0 -0
  26. {ladok3-3.6 → ladok3-3.8}/makefiles/tex.mk +0 -0
  27. {ladok3-3.6 → ladok3-3.8}/makefiles/transform.mk +0 -0
  28. {ladok3-3.6 → ladok3-3.8}/src/ladok3/.gitignore +0 -0
  29. {ladok3-3.6 → ladok3-3.8}/src/ladok3/Makefile +0 -0
  30. {ladok3-3.6 → ladok3-3.8}/src/ladok3/api.nw +0 -0
  31. {ladok3-3.6 → ladok3-3.8}/src/ladok3/cli.nw +0 -0
  32. {ladok3-3.6 → ladok3-3.8}/src/ladok3/data.nw +0 -0
  33. {ladok3-3.6 → ladok3-3.8}/src/ladok3/undoc.nw +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ladok3
3
- Version: 3.6
3
+ Version: 3.8
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ladok3"
3
- version = "3.6"
3
+ version = "3.8"
4
4
  description = "Python wrapper and CLI for the LADOK3 REST API."
5
5
  authors = [
6
6
  "Daniel Bosk <dbosk@kth.se>",
@@ -908,6 +908,24 @@ for course in self.ladok.registrations_JSON(self.ladok_id):
908
908
  @
909
909
 
910
910
 
911
+ \section{Is the student suspended?}
912
+
913
+ We also want to know if a student is suspended or not.
914
+ We can get a list of suspensions for a student.
915
+ However, since suspended students are rare, we haven't found a specimen to
916
+ study; so we simply don't know what the list contains.
917
+ But we can return it nonetheless.
918
+ We never cache this result since we always want the most up-to-date version.
919
+ <<student attribute methods>>=
920
+ @property
921
+ def suspensions(self):
922
+ """
923
+ The list of the students' suspensions.
924
+ """
925
+ return self.ladok.get_student_suspensions_JSON(self.ladok_id)["Avstangning"]
926
+ @
927
+
928
+
911
929
  \chapter{Courses}\label{CourseClasses}
912
930
 
913
931
  We can use the course-related classes as follows.
@@ -1099,15 +1117,20 @@ print(r"\end{minted}")
1099
1117
 
1100
1118
  Now that we have the [[data]] object, we can assign its values to the private
1101
1119
  attributes.
1120
+ We note, however, that some course instances lack both [[Versionsnummer]] and
1121
+ [[Omfattning]].
1122
+ It seems like faux courses are created to document when students go on
1123
+ Exchanges.
1124
+ These faux courses have a name and code, but no credits or version of syllabus.
1102
1125
  <<assign CourseInstance data to private attributes>>=
1103
1126
  self.__education_id = data.pop("UtbildningUID")
1104
1127
 
1105
1128
  self.__code = data.pop("Utbildningskod")
1106
1129
  self.__name = data.pop("Benamning")
1107
- self.__version = data.pop("Versionsnummer")
1130
+ self.__version = data.pop("Versionsnummer", None)
1108
1131
 
1109
- self.__credits = data.pop("Omfattning")
1110
- self.__unit = data.pop("Enhet")
1132
+ self.__credits = data.pop("Omfattning", None)
1133
+ self.__unit = data.pop("Enhet", None)
1111
1134
 
1112
1135
  self.__grade_scale = self.ladok.get_grade_scales(
1113
1136
  id=data.pop("BetygsskalaID"))
@@ -1126,6 +1149,10 @@ else:
1126
1149
  The [[CourseComponent]] class will make the attributes available.
1127
1150
  We specify the most interesting ones and let the [[LadokData]] constructor turn
1128
1151
  the rest into properties as well, so that they are available for the curious.
1152
+
1153
+ Turns out that some course components don't have any credits.
1154
+ This is probably a consequence of being a component in a course with no
1155
+ credits, \eg one of those faux courses to register exchange studies.
1129
1156
  <<classes>>=
1130
1157
  class CourseComponent(LadokData):
1131
1158
  """Represents a course component of a course registration"""
@@ -1148,8 +1175,8 @@ class CourseComponent(LadokData):
1148
1175
  else:
1149
1176
  self.__description = description
1150
1177
 
1151
- self.__credits = kwargs.pop("Omfattning")
1152
- self.__unit = kwargs.pop("Enhet")
1178
+ self.__credits = kwargs.pop("Omfattning", None)
1179
+ self.__unit = kwargs.pop("Enhet", None)
1153
1180
 
1154
1181
  ladok = kwargs.pop("ladok")
1155
1182
  grade_scale_id = kwargs.pop("BetygsskalaID")
@@ -1260,6 +1287,12 @@ def __fetch_participants(self):
1260
1287
  Students register for a course.
1261
1288
  The [[CourseRegistration]] class represents this data from LADOK.
1262
1289
  This is the object that must be used to get results for a student.
1290
+ This object keeps more info than the course instance.
1291
+
1292
+ We note that some registrations lack the \enquote{Utbildningstillfalleskod}
1293
+ attribute.
1294
+ This happens on \enquote{custom courses} like Erasmus exchanges.
1295
+ If that's the case, we use the value [[None]] in its place.
1263
1296
  <<classes>>=
1264
1297
  class CourseRegistration(CourseInstance):
1265
1298
  """Represents a student's participation in a course instance"""
@@ -1268,7 +1301,7 @@ class CourseRegistration(CourseInstance):
1268
1301
 
1269
1302
  # ett Ladok-ID för kursomgången
1270
1303
  self.__round_id = kwargs.pop("UtbildningstillfalleUID")
1271
- self.__round_code = kwargs.pop("Utbildningstillfalleskod")
1304
+ self.__round_code = kwargs.pop("Utbildningstillfalleskod", None)
1272
1305
 
1273
1306
  dates = kwargs.pop("Studieperiod")
1274
1307
  self.__start = datetime.date.fromisoformat(dates["Startdatum"])
@@ -1295,10 +1328,10 @@ class CourseRegistration(CourseInstance):
1295
1328
  return self.__end
1296
1329
 
1297
1330
  def __str__(self):
1298
- return f"{self.code} {self.round_code} ({self.start}--{self.end})"
1331
+ return f"{self.code} {self.round_code or ''} ({self.start}--{self.end})"
1299
1332
 
1300
1333
  def __repr__(self):
1301
- return f"{self.code}:{self.round_code}:{self.start}--{self.end}"
1334
+ return f"{self.code}:{self.round_code or ''}:{self.start}--{self.end}"
1302
1335
 
1303
1336
  def results(self, /, **kwargs):
1304
1337
  """Returns the student's results on the course, filtered on keywords"""
@@ -1444,6 +1477,10 @@ class CourseResult(LadokRemoteData):
1444
1477
  """Returns True if the grade has been attested in LADOK"""
1445
1478
  return self.__attested
1446
1479
 
1480
+ def __str__(self):
1481
+ return f"{self.component} {self.grade} " \
1482
+ f"{self.date}{'*' if not self.attested else ''}"
1483
+
1447
1484
  def push(self):
1448
1485
  if self.__uid:
1449
1486
  <<push the existing CourseResult grade data to LADOK>>
@@ -129,8 +129,8 @@ them.
129
129
  <<check that we got all positional arguments>>
130
130
  try:
131
131
  student = ladok.get_student(args.student_id)
132
- course = student.courses(code=args.course_code)[0]
133
- result = course.results(component=args.component_code)[0]
132
+ <<look up [[course]] from [[student]]>>
133
+ <<look up [[result]] from [[course]]>>
134
134
  result.set_grade(args.grade, args.date)
135
135
  if args.finalize:
136
136
  result.finalize()
@@ -139,9 +139,25 @@ except Exception as err:
139
139
  print(f"{student}: {err}")
140
140
  except ValueError as verr:
141
141
  print(f"{verr}: {args.student_id}: {err}")
142
- @ This means that we need the following command-line arguments.
143
- The option [[args.finalize]] is already set above, so we don't need to add that
144
- one here.
142
+ @ The option [[args.finalize]] is already set above, so we don't need to add
143
+ that one here.
144
+
145
+ Both looking up the course and the component can yield index errors.
146
+ We'd like to distinguish these.
147
+ We will catch the exception, the reraise the same exception but with a better
148
+ error message.
149
+ <<look up [[course]] from [[student]]>>=
150
+ try:
151
+ course = student.courses(code=args.course_code)[0]
152
+ except IndexError:
153
+ raise Exception("f{args.course_code}: No such course for {student}")
154
+ <<look up [[result]] from [[course]]>>=
155
+ try:
156
+ result = course.results(component=args.component_code)[0]
157
+ except IndexError:
158
+ raise Exception(f"{args.component_code}: "
159
+ f"No such component for {args.course_code}")
160
+ @
145
161
 
146
162
 
147
163
  \section{Report many results given in standard input}
@@ -180,23 +196,14 @@ try:
180
196
  try:
181
197
  course = student.courses(code=course_code)[0]
182
198
  except IndexError:
183
- raise Exception(f"{course_code}: no such course.")
199
+ raise Exception(f"{course_code}: No such course for {student}")
184
200
 
185
201
  try:
186
202
  component = course.results(component=component_code)[0]
187
203
  except IndexError:
188
- raise Exception(f"{course_code} has no component {component_code}.")
204
+ raise Exception(f"{component_code}: no such component for {course_code}")
189
205
 
190
- if not component.attested and component.grade != grade:
191
- component.set_grade(grade, date)
192
- component.finalize()
193
- if args.verbose:
194
- print(f"{course_code} {student}: reported "
195
- f"{component.component} = {component.grade} ({date}).")
196
- elif component.grade != grade:
197
- print(f"{course_code} {student}: attested {component.component} "
198
- f"result {component.grade} ({component.date}) "
199
- f"is different from {grade} ({date}).")
206
+ <<set [[grade]] to [[component]], output if verbose>>
200
207
  except Exception as err:
201
208
  try:
202
209
  print(f"{course_code} {component_code}={grade} ({date}) {student}: {err}",
@@ -207,6 +214,30 @@ except Exception as err:
207
214
  file=sys.stderr)
208
215
  @
209
216
 
217
+ Now, when we set the grade, there are a few cases that should be handled.
218
+ If the grade isn't attested, we try to change it.
219
+ (This might still fail if the grade is finalized but not attested.)
220
+ If we've selected the verbose option, then we print what we have reported.
221
+
222
+ If the grade was attested, then we check if it's different.
223
+ If it's different, we output this.
224
+ If it's the same, we silently ignore it.
225
+ This is best for bulk reporting, because then we can always try to report for
226
+ all students.
227
+ <<set [[grade]] to [[component]], output if verbose>>=
228
+ if not component.attested and component.grade != grade:
229
+ component.set_grade(grade, date)
230
+ if args.finalize:
231
+ component.finalize()
232
+ if args.verbose:
233
+ print(f"{course_code} {student}: reported "
234
+ f"{component.component} = {component.grade} ({date}).")
235
+ elif component.grade != grade:
236
+ print(f"{course_code} {student}: attested {component.component} "
237
+ f"result {component.grade} ({component.date}) "
238
+ f"is different from {grade} ({date}).")
239
+ @
240
+
210
241
 
211
242
  \section{Determine which function to run}
212
243
 
@@ -63,6 +63,17 @@ student_parser.add_argument("-c", "--course",
63
63
  )
64
64
  @
65
65
 
66
+ \subsection{A results flag}
67
+
68
+ We also want a flag for specifying whether or not to include the results of
69
+ each course.
70
+ <<add student command arguments to student parser>>=
71
+ student_parser.add_argument("-r", "--results",
72
+ action="store_true", default=False,
73
+ help="Set to include results for each course listed."
74
+ )
75
+ @
76
+
66
77
  \section{Print the student data}
67
78
 
68
79
  Now that we have the student identifier, we can simply use that to fetch the
@@ -79,7 +90,8 @@ except Exception as err:
79
90
  print_student_data(student)
80
91
 
81
92
  if args.course:
82
- print_course_data(student, args.course)
93
+ print()
94
+ print_course_data(student, args)
83
95
  @
84
96
 
85
97
  \subsection{Printing personal student data}
@@ -94,16 +106,22 @@ def print_student_data(student):
94
106
  print(f"Personnummer: {student.personnummer}")
95
107
  print(f"LADOK ID: {student.ladok_id}")
96
108
  print(f"Alive: {student.alive}")
109
+ print(f"Suspended: {student.suspensions}")
97
110
  @
98
111
 
99
112
  \subsection{Printing student's course data}
100
113
 
101
114
  To print the student's course data, we simply filter the courses on the option
102
115
  that the user supplies.
116
+ We then print all courses and, if the flag is set, we also print the results
117
+ for each course.
103
118
  <<functions>>=
104
- def print_course_data(student, course):
119
+ def print_course_data(student, args):
105
120
  """Prints the courses"""
106
121
  print("Courses:")
107
- for course in student.courses(code=course):
108
- print(f"- {course}")
122
+ for course in student.courses(code=args.course):
123
+ print(f"{course}")
124
+ if args.results:
125
+ for result in course.results():
126
+ print(f" {result}")
109
127
  @
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