ladok3 4.9__tar.gz → 4.11__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.9 → ladok3-4.11}/PKG-INFO +22 -4
  2. {ladok3-4.9 → ladok3-4.11}/README.md +21 -3
  3. {ladok3-4.9 → ladok3-4.11}/doc/preamble.tex +1 -1
  4. {ladok3-4.9 → ladok3-4.11}/makefiles/noweb.mk +14 -12
  5. {ladok3-4.9 → ladok3-4.11}/pyproject.toml +1 -1
  6. {ladok3-4.9 → ladok3-4.11}/src/ladok3/cli.nw +28 -10
  7. {ladok3-4.9 → ladok3-4.11}/src/ladok3/ladok3.nw +118 -59
  8. {ladok3-4.9 → ladok3-4.11}/src/ladok3/report.nw +1 -1
  9. {ladok3-4.9 → ladok3-4.11}/LICENSE +0 -0
  10. {ladok3-4.9 → ladok3-4.11}/doc/Makefile +0 -0
  11. {ladok3-4.9 → ladok3-4.11}/doc/abstract.tex +0 -0
  12. {ladok3-4.9 → ladok3-4.11}/doc/ladok3.tex +0 -0
  13. {ladok3-4.9 → ladok3-4.11}/makefiles/doc.mk +0 -0
  14. {ladok3-4.9 → ladok3-4.11}/makefiles/exam.mk +0 -0
  15. {ladok3-4.9 → ladok3-4.11}/makefiles/haskell.mk +0 -0
  16. {ladok3-4.9 → ladok3-4.11}/makefiles/miun.course.mk +0 -0
  17. {ladok3-4.9 → ladok3-4.11}/makefiles/miun.depend.mk +0 -0
  18. {ladok3-4.9 → ladok3-4.11}/makefiles/miun.docs.mk +0 -0
  19. {ladok3-4.9 → ladok3-4.11}/makefiles/miun.port.mk +0 -0
  20. {ladok3-4.9 → ladok3-4.11}/makefiles/miun.pub.mk +0 -0
  21. {ladok3-4.9 → ladok3-4.11}/makefiles/pkg.mk +0 -0
  22. {ladok3-4.9 → ladok3-4.11}/makefiles/portability.mk +0 -0
  23. {ladok3-4.9 → ladok3-4.11}/makefiles/pub.mk +0 -0
  24. {ladok3-4.9 → ladok3-4.11}/makefiles/results.mk +0 -0
  25. {ladok3-4.9 → ladok3-4.11}/makefiles/subdir.mk +0 -0
  26. {ladok3-4.9 → ladok3-4.11}/makefiles/tex.mk +0 -0
  27. {ladok3-4.9 → ladok3-4.11}/makefiles/transform.mk +0 -0
  28. {ladok3-4.9 → ladok3-4.11}/src/ladok3/.gitignore +0 -0
  29. {ladok3-4.9 → ladok3-4.11}/src/ladok3/Makefile +0 -0
  30. {ladok3-4.9 → ladok3-4.11}/src/ladok3/api.nw +0 -0
  31. {ladok3-4.9 → ladok3-4.11}/src/ladok3/data.nw +0 -0
  32. {ladok3-4.9 → ladok3-4.11}/src/ladok3/student.nw +0 -0
  33. {ladok3-4.9 → ladok3-4.11}/src/ladok3/undoc.nw +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ladok3
3
- Version: 4.9
3
+ Version: 4.11
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
@@ -69,9 +69,17 @@ command-line tool `ladok`.
69
69
  Let's assume that we have a student with personnummer 123456-1234.
70
70
  Let's also assume that this student has taken a course with course code AB1234
71
71
  and finished the module LAB1 on date 2021-03-15.
72
+ Say also that the student's assignments were graded by the teacher and two TAs:
73
+
74
+ - Daniel Bosk <dbosk@kth.se> (teacher)
75
+ - Teaching Assistantsdotter <tad@kth.se>
76
+ - Teaching Assistantsson <tas@kth.se>
77
+
72
78
  Then we can report this result like this:
73
79
  ```bash
74
- ladok report 123456-1234 AB1234 LAB1 -d 2021-03-15 -f
80
+ ladok report 123456-1234 AB1234 LAB1 -d 2021-03-15 -f \
81
+ "Daniel Bosk <dbosk@kth.se>" "Teaching Assistantsdotter <tad@kth.se>" \
82
+ "Teaching Assistantsson <tas@kth.se>"
75
83
  ```
76
84
 
77
85
  If we use Canvas for all results, we can even report all results for a
@@ -84,13 +92,24 @@ canvaslms results -c AB1234 -A LAB1 | ladok report -v
84
92
  The `canvaslms results` command will export the results in CSV format, this
85
93
  will be piped to `ladok report` that can read it and report it in bulk.
86
94
 
95
+ Most likely you'll need to pass the CSV through `sed` to change the column
96
+ containing the course identifier to just contain the course code. At KTH, the
97
+ course code attribute in Canvas contains course code and the semester. So I
98
+ have to `sed` away the semester part.
99
+
87
100
  ### As a Python package
88
101
 
89
102
  To use the package, it's just to import the package as usual.
90
103
  ```python
91
104
  import ladok3
92
105
 
93
- ls = ladok3.kth.LadokSession("user", "password")
106
+ credentials = {
107
+ "username": "dbosk@ug.kth.se",
108
+ "password": "password ..."
109
+ }
110
+
111
+ ls = ladok3.LadokSession("KTH Royal Institute of Technology",
112
+ vars=credentials)
94
113
 
95
114
  student = ls.get_student("123456-1234")
96
115
 
@@ -208,7 +227,6 @@ Purpose: Use the data in a Canvas course room together with the data from Ladok3
208
227
 
209
228
  Input:
210
229
  ```
211
- Input
212
230
  cl_user_info.py Canvas_user_id|KTHID|Ladok_id [course_id]
213
231
  ```
214
232
  The course_id can be a Canvas course_id **or** if you have dashboard cards, you can specific a course code, a nickname, unique part of the short name or original course name.
@@ -33,9 +33,17 @@ command-line tool `ladok`.
33
33
  Let's assume that we have a student with personnummer 123456-1234.
34
34
  Let's also assume that this student has taken a course with course code AB1234
35
35
  and finished the module LAB1 on date 2021-03-15.
36
+ Say also that the student's assignments were graded by the teacher and two TAs:
37
+
38
+ - Daniel Bosk <dbosk@kth.se> (teacher)
39
+ - Teaching Assistantsdotter <tad@kth.se>
40
+ - Teaching Assistantsson <tas@kth.se>
41
+
36
42
  Then we can report this result like this:
37
43
  ```bash
38
- ladok report 123456-1234 AB1234 LAB1 -d 2021-03-15 -f
44
+ ladok report 123456-1234 AB1234 LAB1 -d 2021-03-15 -f \
45
+ "Daniel Bosk <dbosk@kth.se>" "Teaching Assistantsdotter <tad@kth.se>" \
46
+ "Teaching Assistantsson <tas@kth.se>"
39
47
  ```
40
48
 
41
49
  If we use Canvas for all results, we can even report all results for a
@@ -48,13 +56,24 @@ canvaslms results -c AB1234 -A LAB1 | ladok report -v
48
56
  The `canvaslms results` command will export the results in CSV format, this
49
57
  will be piped to `ladok report` that can read it and report it in bulk.
50
58
 
59
+ Most likely you'll need to pass the CSV through `sed` to change the column
60
+ containing the course identifier to just contain the course code. At KTH, the
61
+ course code attribute in Canvas contains course code and the semester. So I
62
+ have to `sed` away the semester part.
63
+
51
64
  ### As a Python package
52
65
 
53
66
  To use the package, it's just to import the package as usual.
54
67
  ```python
55
68
  import ladok3
56
69
 
57
- ls = ladok3.kth.LadokSession("user", "password")
70
+ credentials = {
71
+ "username": "dbosk@ug.kth.se",
72
+ "password": "password ..."
73
+ }
74
+
75
+ ls = ladok3.LadokSession("KTH Royal Institute of Technology",
76
+ vars=credentials)
58
77
 
59
78
  student = ls.get_student("123456-1234")
60
79
 
@@ -172,7 +191,6 @@ Purpose: Use the data in a Canvas course room together with the data from Ladok3
172
191
 
173
192
  Input:
174
193
  ```
175
- Input
176
194
  cl_user_info.py Canvas_user_id|KTHID|Ladok_id [course_id]
177
195
  ```
178
196
  The course_id can be a Canvas course_id **or** if you have dashboard cards, you can specific a course code, a nickname, unique part of the short name or original course name.
@@ -13,7 +13,7 @@
13
13
  \renewcommand{\foreignfullfont}{}
14
14
  \renewcommand{\foreignabbrfont}{}
15
15
 
16
- \usepackage{newclude}
16
+ %\usepackage{newclude}
17
17
  \usepackage{import}
18
18
 
19
19
  \usepackage[strict]{csquotes}
@@ -9,51 +9,53 @@ NOWEAVE.pdf?= \
9
9
  NOWEAVEFLAGS.pdf?= ${NOWEAVEFLAGS} -x -t2
10
10
  NOTANGLEFLAGS?=
11
11
  NOTANGLE?= notangle ${NOTANGLEFLAGS} -R$(notdir $@) $(filter %.nw,$^) | \
12
- ${CPIF} $@
12
+ ${CPIF} $@ && noroots $(filter %.nw,$^)
13
13
  CPIF?= cpif
14
14
  NOWEB_SUFFIXES+= .c .cc .cpp .cxx
15
15
  NOTANGLEFLAGS.c?= -L
16
16
  NOTANGLE.c?= notangle ${NOTANGLEFLAGS.c} -R$(notdir $@) \
17
- $(filter %.nw,$^) | ${CPIF} $@
17
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
18
18
  NOTANGLEFLAGS.cc?= ${NOTANGLEFLAGS.c}
19
19
  NOTANGLE.cc?= notangle ${NOTANGLEFLAGS.cc} -R$(notdir $@) \
20
- $(filter %.nw,$^) | ${CPIF} $@
20
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
21
21
  NOTANGLEFLAGS.cpp?= ${NOTANGLEFLAGS.c}
22
22
  NOTANGLE.cpp?= notangle ${NOTANGLEFLAGS.cpp} -R$(notdir $@) \
23
- $(filter %.nw,$^) | ${CPIF} $@
23
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
24
24
  NOTANGLEFLAGS.cxx?= ${NOTANGLEFLAGS.c}
25
25
  NOTANGLE.cxx?= notangle ${NOTANGLEFLAGS.cxx} -R$(notdir $@) \
26
- $(filter %.nw,$^) | ${CPIF} $@
26
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
27
27
  NOWEB_SUFFIXES+= .h .hh .hpp .hxx
28
28
  NOTANGLEFLAGS.h?= -L
29
29
  NOTANGLE.h?= notangle ${NOTANGLEFLAGS.h} -R$(notdir $@) \
30
- $(filter %.nw,$^) | ${CPIF} $@
30
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
31
31
  NOTANGLEFLAGS.hh?= ${NOTANGLEFLAGS.h}
32
32
  NOTANGLE.hh?= notangle ${NOTANGLEFLAGS.hh} -R$(notdir $@) \
33
- $(filter %.nw,$^) | ${CPIF} $@
33
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
34
34
  NOTANGLEFLAGS.hpp?= ${NOTANGLEFLAGS.h}
35
35
  NOTANGLE.hpp?= notangle ${NOTANGLEFLAGS.hpp} -R$(notdir $@) \
36
- $(filter %.nw,$^) | ${CPIF} $@
36
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
37
37
  NOTANGLEFLAGS.hxx?= ${NOTANGLEFLAGS.h}
38
38
  NOTANGLE.hxx?= notangle ${NOTANGLEFLAGS.hxx} -R$(notdir $@) \
39
- $(filter %.nw,$^) | ${CPIF} $@
39
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
40
40
  NOWEB_SUFFIXES+= .hs
41
41
  NOTANGLEFLAGS.hs?= -L
42
42
  NOTANGLE.hs?= notangle ${NOTANGLEFLAGS.hs} -R$(notdir $@) \
43
- $(filter %.nw,$^) | ${CPIF} $@
43
+ $(filter %.nw,$^) | ${CPIF} $@ && noroots $(filter %.nw,$^)
44
44
  NOWEB_SUFFIXES+= .mk
45
45
  NOTANGLEFLAGS.mk?= -t2
46
46
  NOTANGLE.mk?= notangle ${NOTANGLEFLAGS.mk} -R$(notdir $@) \
47
- $(filter %.nw,$^) > $@
47
+ $(filter %.nw,$^) > $@ && noroots $(filter %.nw,$^)
48
48
  NOWEB_SUFFIXES+= .py .sty .cls .sh .go
49
49
 
50
50
  define default_tangling
51
51
  NOTANGLEFLAGS$(1)?=
52
52
  NOTANGLE$(1)?= notangle $${NOTANGLEFLAGS$(1)} -R$$(notdir $$@) \
53
- $$(filter %.nw,$$^) > $$@
53
+ $$(filter %.nw,$$^) > $$@ && noroots $$(filter %.nw,$$^)
54
54
  endef
55
55
 
56
56
  $(foreach suffix,${NOWEB_SUFFIXES},$(eval $(call default_tangling,${suffix})))
57
+ NOTANGLE.py+= && ${NOWEB_PYCODEFMT}
58
+ NOWEB_PYCODEFMT?= black $@
57
59
 
58
60
  INCLUDE_MAKEFILES?=.
59
61
  MAKEFILES_DIR?=${INCLUDE_MAKEFILES}
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ladok3"
3
- version = "4.9"
3
+ version = "4.11"
4
4
  description = "Python wrapper and CLI for the LADOK3 REST API."
5
5
  authors = [
6
6
  "Daniel Bosk <dbosk@kth.se>",
@@ -504,7 +504,11 @@ If all fail, the function will return [[None]] for both.
504
504
  (This is due to how we handle the [[login]] command.)
505
505
  <<functions>>=
506
506
  def load_credentials(filename="config.json"):
507
- """Load credentials from environment or file named filename"""
507
+ """
508
+ Loads credentials from environment or file named filename.
509
+ Returns the tuple (instituation, credential dictionary) that
510
+ can be passed to `LadokSession(instiution, credential dictionary)`.
511
+ """
508
512
 
509
513
  <<fetch vars from keyring>>
510
514
  <<fetch username and password from keyring>>
@@ -518,6 +522,9 @@ def load_credentials(filename="config.json"):
518
522
 
519
523
  First we try the newest format.
520
524
  We try to fetch the institution and vars from the keyring.
525
+
526
+ Note that [[keyring]] returns [[None]] if the key doesn't exist, it doesn't
527
+ raise an exception.
521
528
  <<fetch vars from keyring>>=
522
529
  try:
523
530
  institution = keyring.get_password("ladok3", "institution")
@@ -525,7 +532,9 @@ try:
525
532
 
526
533
  vars = {}
527
534
  for key in vars_keys.split(";"):
528
- vars[key] = keyring.get_password("ladok3", key)
535
+ value = keyring.get_password("ladok3", key)
536
+ if value:
537
+ vars[key] = value
529
538
 
530
539
  if institution and vars:
531
540
  return institution, vars
@@ -538,12 +547,10 @@ supported KTH.
538
547
  <<fetch username and password from keyring>>=
539
548
  try:
540
549
  institution = "KTH Royal Institute of Technology"
541
- vars = {
542
- "username": keyring.get_password("ladok3", "username"),
543
- "password": keyring.get_password("ladok3", "password")
544
- }
545
- if vars:
546
- return institution, vars
550
+ username = keyring.get_password("ladok3", "username")
551
+ password = keyring.get_password("ladok3", "password")
552
+ if username and password:
553
+ return institution, {"username": username, "password": password}
547
554
  except:
548
555
  pass
549
556
  @
@@ -577,7 +584,10 @@ try:
577
584
 
578
585
  vars = {}
579
586
  for key in vars_keys.split(":"):
580
- vars[key] = os.environ[key]
587
+ try:
588
+ vars[key] = os.environ[key]
589
+ except KeyError:
590
+ <<print warning about missing variable in [[LADOK_VARS]]>>
581
591
 
582
592
  if institution and vars:
583
593
  return institution, vars
@@ -585,6 +595,14 @@ except:
585
595
  pass
586
596
  @
587
597
 
598
+ Unlike in the other cases, we don't just ignore the exception of the key not
599
+ existing.
600
+ Since the user has explicitly specified the variable, we should warn them that
601
+ it doesn't exist.
602
+ <<print warning about missing variable in [[LADOK_VARS]]>>=
603
+ warn(f"Variable {key} not set, ignoring.")
604
+ @
605
+
588
606
  If none of the above worked, the last resort is to try to read the
589
607
  configuration file.
590
608
  We pop the institution from the configuration file (a dictionary), because then
@@ -595,7 +613,7 @@ try:
595
613
  config = json.load(conf_file)
596
614
 
597
615
  institution = config.pop("institution",
598
- "KTH Royal Institute of Technology")
616
+ "KTH Royal Institute of Technology")
599
617
  return institution, config
600
618
  except:
601
619
  pass
@@ -46,6 +46,17 @@ It uses the [[requests]] module.
46
46
  We can use the class as follows.
47
47
  \inputminted{python}{../examples/example_LadokSession.py}
48
48
 
49
+ An alternative way would be to use the general authentication procedure that
50
+ seems to work at most Swedish universities.
51
+ \begin{minted}[linenos]{python}
52
+ import ladok3
53
+
54
+ ladok = ladok3.LadokSession("KTH Royal Institute of Technology",
55
+ vars={"username": os.environ["KTH_LOGIN"],
56
+ "password": os.environ["KTH_PASSWD"]},
57
+ test_environment=True) # for experiments
58
+ \end{minted}
59
+
49
60
  This chapter covers how the [[LadokSession]] class work.
50
61
  The remaining chapters cover what the [[ladok]] object can be used for.
51
62
  \Cref{StudentClasses} covers how we can work with student data.
@@ -1034,27 +1045,126 @@ This is essentially an \enquote{instance of a course syllabus}.
1034
1045
  We know from above that it inherits from [[LadokRemoteData]] and that it must
1035
1046
  be initialized with the keywords [[ladok]] (for its parent) and
1036
1047
  [[UtbildningsinstansUID]] (for itself) and optionally some data.
1037
- This leaves the following methods.
1038
1048
  <<CourseInstance methods>>=
1039
1049
  def __init__(self, /, **kwargs):
1040
1050
  self.__instance_id = kwargs.pop("UtbildningsinstansUID")
1041
- super().__init__(**kwargs)
1051
+ super().__init__(**kwargs) # sets self.ladok
1042
1052
 
1043
- try:
1044
- self.__assign_attr(kwargs)
1045
- except:
1046
- self.__pull_attributes()
1053
+ <<CourseInstance constructor body>>
1054
+ @
1055
+
1056
+ We can fetch the data from LADOK as follows.
1057
+ Note that we need to use the [[round_id]] from the [[CourseRound]] object.
1058
+ This works since we never have a [[CourseInstance]] object on its own, it's
1059
+ always parent of a [[CourseRound]] object.
1060
+ (And that makes this kind of ugly from an OOP perspective.)
1061
+ <<fetch CourseInstance data from LADOK>>=
1062
+ data = self.ladok.course_round_components_JSON(self.round_id)
1063
+ @
1064
+
1065
+ Then data will be populated with the following values:
1066
+ \begin{pycode}
1067
+ import json
1068
+ import ladok3
1069
+ import os
1047
1070
 
1071
+ ladok = ladok3.LadokSession(
1072
+ os.environ["LADOK_INST"],
1073
+ vars={"username": os.environ["LADOK_USER"],
1074
+ "password": os.environ["LADOK_PASS"]},
1075
+ test_environment=True)
1076
+
1077
+ print(r"\begin{minted}{JSON}")
1078
+ data = ladok.course_round_components_JSON(
1079
+ "cf7045a7-3e1c-11eb-b960-5f936a674375")
1080
+ ladok3.clean_data(data)
1081
+ print(json.dumps(data, indent=2))
1082
+ print(r"\end{minted}")
1083
+ \end{pycode}
1084
+
1085
+ Now that we have all data in [[data]], we can assign its values to the private
1086
+ attributes.
1087
+ We note, however, that some course instances lack both [[Versionsnummer]] and
1088
+ [[Omfattning]].
1089
+ It seems like faux courses are created to document when students go on
1090
+ Exchanges.
1091
+ These faux courses have a name and code, but no credits or version of syllabus.
1092
+ Consequently they don't have any grading scales either.
1093
+
1094
+ Our approach will be to try to assign the attributes using [[kwargs]], and if
1095
+ that fails, we will fetch the data (pull from LADOK).
1096
+ If that pull fails with missing data, then we will assume that it's one of
1097
+ these faux courses.
1098
+ <<CourseInstance constructor body>>=
1099
+ try:
1100
+ self.__assign_attr(kwargs)
1101
+ except:
1102
+ self.__pull_attributes()
1103
+ <<CourseInstance methods>>=
1048
1104
  def __assign_attr(self, data):
1049
1105
  <<assign CourseInstance data to private attributes>>
1050
1106
 
1051
1107
  def __pull_attributes(self):
1052
- <<fetch CourseInstance data object from LADOK>>
1053
- self.__assign_attr(data)
1108
+ <<fetch CourseInstance data from LADOK>>
1109
+ try:
1110
+ self.__assign_attr(data)
1111
+ except:
1112
+ self.__assign_faux(data)
1054
1113
 
1055
1114
  def pull(self):
1056
1115
  self.__pull_attributes()
1057
1116
 
1117
+ def __assign_faux(self, data):
1118
+ <<assign faux CourseInstance data to private attributes>>
1119
+ @
1120
+
1121
+ By trial-and-error, it seems like the faux courses has none of the attributes
1122
+ that the real courses have.
1123
+ However, we try our best.
1124
+ <<assign common CourseInstance data to private attributes>>=
1125
+ if "IngaendeMoment" in data:
1126
+ self.__components = [CourseComponent(
1127
+ ladok=self.ladok, course=self,
1128
+ **component) for component in data["IngaendeMoment"]]
1129
+ else:
1130
+ self.__components = []
1131
+ <<assign CourseInstance data to private attributes>>=
1132
+ <<assign common CourseInstance data to private attributes>>
1133
+
1134
+ self.__name = data.pop("Benamning")
1135
+ self.__code = data.pop("Utbildningskod")
1136
+
1137
+ self.__credits = data.pop("Omfattning")
1138
+ self.__unit = data.pop("Enhet")
1139
+
1140
+ self.__version = data.pop("Versionsnummer")
1141
+
1142
+ self.__education_id = data.pop("UtbildningUID")
1143
+
1144
+ self.__grade_scale = self.ladok.get_grade_scales(
1145
+ id=data.pop("BetygsskalaID"))
1146
+ <<assign faux CourseInstance data to private attributes>>=
1147
+ <<assign common CourseInstance data to private attributes>>
1148
+
1149
+ self.__name = data.pop("Benamning", None)
1150
+ self.__code = data.pop("Utbildningskod", None)
1151
+
1152
+ self.__credits = data.pop("Omfattning", None)
1153
+ self.__unit = data.pop("Enhet", None)
1154
+
1155
+ self.__version = data.pop("Versionsnummer", None)
1156
+
1157
+ self.__education_id = data.pop("UtbildningUID", None)
1158
+
1159
+ try:
1160
+ self.__grade_scale = self.ladok.get_grade_scales(
1161
+ id=data.pop("BetygsskalaID"))
1162
+ except KeyError:
1163
+ self.__grade_scale = None
1164
+ @
1165
+
1166
+ Finally, we want to have properties to access the private attributes.
1167
+ <<CourseInstance methods>>=
1058
1168
  @property
1059
1169
  def instance_id(self):
1060
1170
  return self.__instance_id
@@ -1092,57 +1202,6 @@ def components(self, /, **kwargs):
1092
1202
  return filter_on_keys(self.__components, **kwargs)
1093
1203
  @
1094
1204
 
1095
- Now we must fetch data from LADOK.
1096
- <<fetch CourseInstance data object from LADOK>>=
1097
- data = self.ladok.course_round_components_JSON(self.round_id)
1098
- @ Then data will be populated with the following values:
1099
- \begin{pycode}
1100
- import json
1101
- import ladok3
1102
- import os
1103
-
1104
- ladok = ladok3.LadokSession(
1105
- os.environ["LADOK_INST"],
1106
- vars={"username": os.environ["LADOK_USER"],
1107
- "password": os.environ["LADOK_PASS"]},
1108
- test_environment=True)
1109
-
1110
- print(r"\begin{minted}{JSON}")
1111
- data = ladok.course_round_components_JSON(
1112
- "cf7045a7-3e1c-11eb-b960-5f936a674375")
1113
- ladok3.clean_data(data)
1114
- print(json.dumps(data, indent=2))
1115
- print(r"\end{minted}")
1116
- \end{pycode}
1117
-
1118
- Now that we have the [[data]] object, we can assign its values to the private
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.
1125
- <<assign CourseInstance data to private attributes>>=
1126
- self.__education_id = data.pop("UtbildningUID")
1127
-
1128
- self.__code = data.pop("Utbildningskod")
1129
- self.__name = data.pop("Benamning")
1130
- self.__version = data.pop("Versionsnummer", None)
1131
-
1132
- self.__credits = data.pop("Omfattning", None)
1133
- self.__unit = data.pop("Enhet", None)
1134
-
1135
- self.__grade_scale = self.ladok.get_grade_scales(
1136
- id=data.pop("BetygsskalaID"))
1137
-
1138
- if "IngaendeMoment" in data:
1139
- self.__components = [CourseComponent(
1140
- ladok=self.ladok, course=self,
1141
- **component) for component in data["IngaendeMoment"]]
1142
- else:
1143
- self.__components = []
1144
- @
1145
-
1146
1205
 
1147
1206
  \section{Course components}
1148
1207
 
@@ -133,7 +133,7 @@ Finally, we have the list of graders.
133
133
  <<add one result group arguments>>=
134
134
  one_parser.add_argument("graders", nargs="*",
135
135
  help="Space separated list of who did the grading, "
136
- "give each grader as 'First Last <email@instutution.se>'.")
136
+ "give each grader as 'First Last <email@institution.se>'.")
137
137
  @
138
138
 
139
139
  Now that we have the arguments, we can just execute the following code using
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