ladok3 4.9__py3-none-any.whl → 4.11__py3-none-any.whl

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

Potentially problematic release.


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

ladok3/report.py CHANGED
@@ -3,123 +3,160 @@ import datetime
3
3
  import ladok3
4
4
  import sys
5
5
 
6
+
6
7
  def report_one_result(ladok, args):
7
- if not (args.course_code and args.component_code and
8
- args.student_id and args.grade):
9
- print(f"{sys.argv[0]} report: "
10
- "not all positional args given: course_code, component, student, grade",
11
- file=sys.stderr)
12
- sys.exit(1)
13
- try:
14
- student = ladok.get_student(args.student_id)
15
- try:
16
- course = student.courses(code=args.course_code)[0]
17
- except IndexError:
18
- raise Exception("f{args.course_code}: No such course for {student}")
19
- try:
20
- result = course.results(component=args.component_code)[0]
21
- except IndexError:
22
- raise Exception(f"{args.component_code}: "
23
- f"No such component for {args.course_code}")
24
- result.set_grade(args.grade, args.date)
25
- if args.finalize:
26
- result.finalize(args.graders)
27
- except Exception as err:
8
+ if not (
9
+ args.course_code and args.component_code and args.student_id and args.grade
10
+ ):
11
+ print(
12
+ f"{sys.argv[0]} report: "
13
+ "not all positional args given: course_code, component, student, grade",
14
+ file=sys.stderr,
15
+ )
16
+ sys.exit(1)
28
17
  try:
29
- print(f"{student}: {err}")
30
- except ValueError as verr:
31
- print(f"{verr}: {args.student_id}: {err}")
18
+ student = ladok.get_student(args.student_id)
19
+ try:
20
+ course = student.courses(code=args.course_code)[0]
21
+ except IndexError:
22
+ raise Exception("f{args.course_code}: No such course for {student}")
23
+ try:
24
+ result = course.results(component=args.component_code)[0]
25
+ except IndexError:
26
+ raise Exception(
27
+ f"{args.component_code}: " f"No such component for {args.course_code}"
28
+ )
29
+ result.set_grade(args.grade, args.date)
30
+ if args.finalize:
31
+ result.finalize(args.graders)
32
+ except Exception as err:
33
+ try:
34
+ print(f"{student}: {err}")
35
+ except ValueError as verr:
36
+ print(f"{verr}: {args.student_id}: {err}")
37
+
32
38
 
33
39
  def report_many_results(ladok, args):
34
- data_reader = csv.reader(sys.stdin, delimiter=args.delimiter)
35
- for course_code, component_code, student_id, grade, date, *graders in data_reader:
36
- try:
37
- student = ladok.get_student(student_id)
40
+ data_reader = csv.reader(sys.stdin, delimiter=args.delimiter)
41
+ for course_code, component_code, student_id, grade, date, *graders in data_reader:
42
+ try:
43
+ student = ladok.get_student(student_id)
38
44
 
39
- try:
40
- course = student.courses(code=course_code)[0]
41
- except IndexError:
42
- raise Exception(f"{course_code}: No such course for {student}")
45
+ try:
46
+ course = student.courses(code=course_code)[0]
47
+ except IndexError:
48
+ raise Exception(f"{course_code}: No such course for {student}")
43
49
 
44
- try:
45
- component = course.results(component=component_code)[0]
46
- except IndexError:
47
- raise Exception(f"{component_code}: no such component for {course_code}")
50
+ try:
51
+ component = course.results(component=component_code)[0]
52
+ except IndexError:
53
+ raise Exception(
54
+ f"{component_code}: no such component for {course_code}"
55
+ )
56
+
57
+ if not component.attested and component.grade != grade:
58
+ component.set_grade(grade, date)
59
+ if args.finalize:
60
+ component.finalize(graders)
61
+ if args.verbose:
62
+ print(
63
+ f"{course_code} {student}: reported "
64
+ f"{component.component} = {component.grade} ({date}) "
65
+ f"by {', '.join(graders)}."
66
+ )
67
+ elif component.grade != grade:
68
+ print(
69
+ f"{course_code} {student}: attested {component.component} "
70
+ f"result {component.grade} ({component.date}) "
71
+ f"is different from {grade} ({date})."
72
+ )
73
+ except Exception as err:
74
+ try:
75
+ print(
76
+ f"{course_code} {component_code}={grade} ({date}) {student}: {err}",
77
+ file=sys.stderr,
78
+ )
79
+ except ValueError as verr:
80
+ print(
81
+ f"{verr}: "
82
+ f"{course_code} {component_code}={grade} ({date}) {student_id}: {err}",
83
+ file=sys.stderr,
84
+ )
48
85
 
49
- if not component.attested and component.grade != grade:
50
- component.set_grade(grade, date)
51
- if args.finalize:
52
- component.finalize(graders)
53
- if args.verbose:
54
- print(f"{course_code} {student}: reported "
55
- f"{component.component} = {component.grade} ({date}) "
56
- f"by {', '.join(graders)}.")
57
- elif component.grade != grade:
58
- print(f"{course_code} {student}: attested {component.component} "
59
- f"result {component.grade} ({component.date}) "
60
- f"is different from {grade} ({date}).")
61
- except Exception as err:
62
- try:
63
- print(f"{course_code} {component_code}={grade} ({date}) {student}: {err}",
64
- file=sys.stderr)
65
- except ValueError as verr:
66
- print(f"{verr}: "
67
- f"{course_code} {component_code}={grade} ({date}) {student_id}: {err}",
68
- file=sys.stderr)
69
86
 
70
87
  def add_command_options(parser):
71
- report_parser = parser.add_parser("report",
72
- help="Reports course results to LADOK",
73
- description="Reports course results to LADOK"
74
- )
75
- report_parser.set_defaults(func=command)
76
- one_parser = report_parser.add_argument_group(
77
- "one result as positional args, only date is optional")
78
- one_parser.add_argument("course_code", nargs="?",
79
- help="The course code (e.g. DD1315) for which the grade is for"
80
- )
88
+ report_parser = parser.add_parser(
89
+ "report",
90
+ help="Reports course results to LADOK",
91
+ description="Reports course results to LADOK",
92
+ )
93
+ report_parser.set_defaults(func=command)
94
+ one_parser = report_parser.add_argument_group(
95
+ "one result as positional args, only date is optional"
96
+ )
97
+ one_parser.add_argument(
98
+ "course_code",
99
+ nargs="?",
100
+ help="The course code (e.g. DD1315) for which the grade is for",
101
+ )
81
102
 
82
- one_parser.add_argument("component_code", nargs="?",
83
- help="The component code (e.g. LAB1) for which the grade is for"
84
- )
103
+ one_parser.add_argument(
104
+ "component_code",
105
+ nargs="?",
106
+ help="The component code (e.g. LAB1) for which the grade is for",
107
+ )
85
108
 
86
- one_parser.add_argument("student_id", nargs="?",
87
- help="Student identifier (personnummer or LADOK ID)"
88
- )
109
+ one_parser.add_argument(
110
+ "student_id", nargs="?", help="Student identifier (personnummer or LADOK ID)"
111
+ )
89
112
 
90
- one_parser.add_argument("grade", nargs="?",
91
- help="The grade (e.g. A or P)"
92
- )
93
- one_parser.add_argument("date", nargs="?",
94
- help="Date on ISO format (e.g. 2021-03-18), "
95
- f"defaults to today's date ({datetime.date.today()})",
96
- type=datetime.date.fromisoformat,
97
- default=datetime.date.today()
98
- )
99
- one_parser.add_argument("graders", nargs="*",
100
- help="Space separated list of who did the grading, "
101
- "give each grader as 'First Last <email@instutution.se>'.")
102
- many_parser = report_parser.add_argument_group(
103
- "many results read from standard input as CSV, columns: "
104
- "course, component, student, grade, date, grader 1, ..., grader N")
105
- many_parser.add_argument("-d", "--delimiter",
106
- default="\t",
107
- help="The delimiter for the CSV input; "
108
- "default is a tab character to be compatible with POSIX commands, "
109
- "use `-d,` or `-d ,` to get comma-separated values.")
110
- many_parser.add_argument("-v", "--verbose",
111
- action="count", default=0,
112
- help="Increases the verbosity of the output: -v will print results that "
113
- "were reported to standard out. Otherwise only errors are printed.")
114
- report_parser.add_argument("-f", "--finalize",
115
- help="""Finalize the grade (mark as ready/klarmarkera) for examiner to attest.
113
+ one_parser.add_argument("grade", nargs="?", help="The grade (e.g. A or P)")
114
+ one_parser.add_argument(
115
+ "date",
116
+ nargs="?",
117
+ help="Date on ISO format (e.g. 2021-03-18), "
118
+ f"defaults to today's date ({datetime.date.today()})",
119
+ type=datetime.date.fromisoformat,
120
+ default=datetime.date.today(),
121
+ )
122
+ one_parser.add_argument(
123
+ "graders",
124
+ nargs="*",
125
+ help="Space separated list of who did the grading, "
126
+ "give each grader as 'First Last <email@institution.se>'.",
127
+ )
128
+ many_parser = report_parser.add_argument_group(
129
+ "many results read from standard input as CSV, columns: "
130
+ "course, component, student, grade, date, grader 1, ..., grader N"
131
+ )
132
+ many_parser.add_argument(
133
+ "-d",
134
+ "--delimiter",
135
+ default="\t",
136
+ help="The delimiter for the CSV input; "
137
+ "default is a tab character to be compatible with POSIX commands, "
138
+ "use `-d,` or `-d ,` to get comma-separated values.",
139
+ )
140
+ many_parser.add_argument(
141
+ "-v",
142
+ "--verbose",
143
+ action="count",
144
+ default=0,
145
+ help="Increases the verbosity of the output: -v will print results that "
146
+ "were reported to standard out. Otherwise only errors are printed.",
147
+ )
148
+ report_parser.add_argument(
149
+ "-f",
150
+ "--finalize",
151
+ help="""Finalize the grade (mark as ready/klarmarkera) for examiner to attest.
116
152
  Note that without this option, no graders will be registered in LADOK.""",
117
- action="store_true",
118
- default=False
119
- )
153
+ action="store_true",
154
+ default=False,
155
+ )
156
+
120
157
 
121
158
  def command(ladok, args):
122
- if args.course_code:
123
- report_one_result(ladok, args)
124
- else:
125
- report_many_results(ladok, args)
159
+ if args.course_code:
160
+ report_one_result(ladok, args)
161
+ else:
162
+ report_many_results(ladok, args)
@@ -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.
@@ -0,0 +1,21 @@
1
+ doc/ltxobj/ladok3.pdf,sha256=WZ3PoQ8-IPNLUNiOwv9P0pxsFHaRx2vTTgbQjtfGajo,1407824
2
+ ladok3/.gitignore,sha256=QOcCshtjIsFasi4vaqFcooBWPJkxVWaoYEWOBBtdY_w,81
3
+ ladok3/Makefile,sha256=Jy6OFjoVLU9YivnVxctxI_zrUOK9ysEOiistJ3ST6Nw,557
4
+ ladok3/__init__.py,sha256=3z1Lys2ygnuDyYFvm4O4Um6vgYud470W3xX4gYJ4bMc,245725
5
+ ladok3/api.nw,sha256=pHFU8T2tr2N4kJfyo7uKzNd1lpkd7KICK8lHzV6b1-Y,43304
6
+ ladok3/cli.nw,sha256=cwooD_hc9ZpgnRhQ9TebbeWp_ZjKDTWjcV9Qqiqxu28,22378
7
+ ladok3/cli.py,sha256=pLAVt2XhaGJ50knOlRNX_lcS3NFExu5ChHPgItRRGvI,12093
8
+ ladok3/data.nw,sha256=3o6-kmeMtCGoSJ5yL8qFCuIINQeym_WtW_2mhItuR-s,11785
9
+ ladok3/data.py,sha256=kPRO9l5DTQb9lGnN2kU-YYPSyg31t0bq5HCw986hbPk,6747
10
+ ladok3/ladok.bash,sha256=zGfTFdtos2zLjV13pzfK-1uCy2b_lF2qUKMoL2ExW7c,1441
11
+ ladok3/ladok3.nw,sha256=6er9_eF1ErShimmpmNh4UkC-EHoLn0HGK8NjzEz_fVM,52289
12
+ ladok3/report.nw,sha256=Bp0-yNVb3NZ0WIj5xn9cpsnY6M60MWMzpX3BC9mXqN0,8972
13
+ ladok3/report.py,sha256=1K7cRaedemiOGDDAMI9wqnctLeic5ZlMYHw5hzhnvQw,5627
14
+ ladok3/student.nw,sha256=zayn9_b9jCKeMnZxSGS_EuSmF3ojOBHQDMUMMkpRssI,3747
15
+ ladok3/student.py,sha256=TaYn2rpbQnzummB-8xz-sUEV31Gh0CUmU0QkF6VgEic,1703
16
+ ladok3/undoc.nw,sha256=NyHuVIzrRqJPM39MyAlZNEE7PbXdUDJFQ2kJ0NfdwQI,180333
17
+ ladok3-4.11.dist-info/LICENSE,sha256=s_C5qznXAvDRrzU7vRd4eqzshyIkAfPwGyVBihGeOdM,1155
18
+ ladok3-4.11.dist-info/METADATA,sha256=b_1jNYsFOsiXR11udNkWFRzBZepydHwWfRAu9PTRlFo,8822
19
+ ladok3-4.11.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88
20
+ ladok3-4.11.dist-info/entry_points.txt,sha256=pi-KKP5Obo0AyuDjXQUpadS9kIvAY2_5ORhPgEYlJv8,41
21
+ ladok3-4.11.dist-info/RECORD,,
ladok3/kth.py DELETED
@@ -1,117 +0,0 @@
1
- import html
2
- import ladok3
3
- import re
4
-
5
- class LadokSession(ladok3.LadokSession):
6
- def __init__(self, username, password, test_environment=False):
7
- """Initialize KTH version of LadokSession"""
8
- super().__init__(test_environment=test_environment)
9
- self.__username = username
10
- self.__password = password
11
-
12
- def ladok_run_shib_login(self, url):
13
- response = self.session.get(
14
- url=url+'&entityID=https://saml.sys.kth.se/idp/shibboleth')
15
-
16
- action = re.search(
17
- '<form ?[^>]* action="(.*?)"',
18
- response.text).group(1)
19
-
20
- csrf_token = re.search(
21
- '<input ?[^>]* name="csrf_token" ?[^>]* value="(.*?)"',
22
- response.text).group(1)
23
-
24
- post_data = {
25
- 'csrf_token': csrf_token,
26
- 'shib_idp_ls_exception.shib_idp_session_ss': '',
27
- 'shib_idp_ls_success.shib_idp_session_ss': 'true',
28
- 'shib_idp_ls_value.shib_idp_session_ss': '',
29
- 'shib_idp_ls_exception.shib_idp_persistent_ss': '',
30
- 'shib_idp_ls_success.shib_idp_persistent_ss': 'true',
31
- 'shib_idp_ls_value.shib_idp_persistent_ss': '',
32
- 'shib_idp_ls_supported': 'true',
33
- '_eventId_proceed': ''
34
- }
35
-
36
- response = self.session.post(
37
- url="https://saml-5.sys.kth.se" + action,
38
- data=post_data)
39
-
40
- return response
41
- def ug_post_user_pass(self, shib_response):
42
- action = re.search('<form ?[^>]* id="loginForm" ?[^>]* action="(.*?)"',
43
- shib_response.text).group(1)
44
-
45
- post_data = {
46
- 'username': self.__username if "@" in self.__username \
47
- else self.__username + "@ug.kth.se",
48
- 'password': self.__password,
49
- 'Kmsi': True,
50
- 'AuthMethod': "FormsAuthentication"
51
- }
52
-
53
- response = self.session.post(
54
- url='https://login.ug.kth.se' + action,
55
- data=post_data)
56
-
57
- return response
58
- def perform_redirects_back_to_ladok(self, ug_response):
59
- action = re.search('<form ?[^>]* action="(.*?)"',
60
- ug_response.text)
61
- if action is None:
62
- raise Exception('Invalid username or password OR possibly the SAML \
63
- configuration has changed, manually login an accept the changed \
64
- information.')
65
- action = html.unescape(action.group(1))
66
-
67
- relay_state = re.search(
68
- '<input ?[^>]* name="RelayState" ?[^>]* value="(.*?)"',
69
- ug_response.text)
70
- try:
71
- relay_state = html.unescape(relay_state.group(1))
72
- except AttributeError:
73
- raise Exception(
74
- "Try to log in using a web browser and accept sharing data.")
75
-
76
- saml_response = re.search(
77
- '<input ?[^>]* name="SAMLResponse" ?[^>]* value="(.*?)"',
78
- ug_response.text)
79
- saml_response = html.unescape(saml_response.group(1))
80
-
81
- post_data = {
82
- 'RelayState': relay_state,
83
- 'SAMLResponse': saml_response
84
- }
85
-
86
- response = self.session.post(url=action, data=post_data)
87
-
88
- ladok_action = re.search(
89
- '<form ?[^>]* action="(.*?)"',
90
- response.text)
91
- ladok_action = html.unescape(ladok_action.group(1))
92
-
93
- relay_state = re.search(
94
- '<input ?[^>]* name="RelayState" ?[^>]* value="([^"]+)"',
95
- response.text)
96
- relay_state = html.unescape(relay_state.group(1))
97
-
98
- saml_response = re.search(
99
- '<input ?[^>]* name="SAMLResponse" ?[^>]* value="(.*?)"',
100
- response.text)
101
- saml_response = html.unescape(saml_response.group(1))
102
-
103
- post_data = {
104
- 'RelayState': relay_state,
105
- 'SAMLResponse': saml_response
106
- }
107
-
108
- response = self.session.post(url=ladok_action, data=post_data)
109
-
110
- return response
111
-
112
- def saml_login(self, url):
113
- """Do the SSO login"""
114
- response = self.ladok_run_shib_login(url)
115
- response = self.ug_post_user_pass(response)
116
- response = self.perform_redirects_back_to_ladok(response)
117
- return response
ladok3/test.py DELETED
File without changes
@@ -1,23 +0,0 @@
1
- doc/ltxobj/ladok3.pdf,sha256=HKjdxKpz0xw17z-2-P6Cyxx-9lRSjp__GR9YEcCLdvw,1338675
2
- ladok3/.gitignore,sha256=QOcCshtjIsFasi4vaqFcooBWPJkxVWaoYEWOBBtdY_w,81
3
- ladok3/Makefile,sha256=Jy6OFjoVLU9YivnVxctxI_zrUOK9ysEOiistJ3ST6Nw,557
4
- ladok3/__init__.py,sha256=q-pAOMPN_jGms0iIyfX8i73oIRaRKwcOmwVqNIS56Ro,229002
5
- ladok3/api.nw,sha256=pHFU8T2tr2N4kJfyo7uKzNd1lpkd7KICK8lHzV6b1-Y,43304
6
- ladok3/cli.nw,sha256=M6Rq1YCEGs3dvYh1E_ERAsDL7s229gcphmFyed1I2v8,21682
7
- ladok3/cli.py,sha256=_Q3noEL5rRxsbqm43HT2x0GXDXv4Mu8tOMOKrOnqOvU,10883
8
- ladok3/data.nw,sha256=3o6-kmeMtCGoSJ5yL8qFCuIINQeym_WtW_2mhItuR-s,11785
9
- ladok3/data.py,sha256=mCHySFtQL_oh6mkfMbgRM4T1bnM8HR45eumgoKFwomk,5893
10
- ladok3/kth.py,sha256=aoodYd2bLQcPNQiFwyP0mppJO399UcqUgUhN9wzVwBA,3720
11
- ladok3/ladok.bash,sha256=zGfTFdtos2zLjV13pzfK-1uCy2b_lF2qUKMoL2ExW7c,1441
12
- ladok3/ladok3.nw,sha256=V9QStMwA2dwR-DgWQHCfUEXirJN_y8kauVi2PXYRoHE,50196
13
- ladok3/report.nw,sha256=vU_z1ZYgf8fV7xeA5R8w6DnOyhdEaNpXtPWa0l07o3A,8972
14
- ladok3/report.py,sha256=xNKx4lurcJR5DkUIvyCGqt2p_YffchLiHLAOhoAvw8w,4750
15
- ladok3/student.nw,sha256=zayn9_b9jCKeMnZxSGS_EuSmF3ojOBHQDMUMMkpRssI,3747
16
- ladok3/student.py,sha256=TaYn2rpbQnzummB-8xz-sUEV31Gh0CUmU0QkF6VgEic,1703
17
- ladok3/test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- ladok3/undoc.nw,sha256=NyHuVIzrRqJPM39MyAlZNEE7PbXdUDJFQ2kJ0NfdwQI,180333
19
- ladok3-4.9.dist-info/LICENSE,sha256=s_C5qznXAvDRrzU7vRd4eqzshyIkAfPwGyVBihGeOdM,1155
20
- ladok3-4.9.dist-info/METADATA,sha256=cAC-y1Rj228nb-47obZQlDjdxQBZgER-U4BGsvft-ks,8098
21
- ladok3-4.9.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88
22
- ladok3-4.9.dist-info/entry_points.txt,sha256=pi-KKP5Obo0AyuDjXQUpadS9kIvAY2_5ORhPgEYlJv8,41
23
- ladok3-4.9.dist-info/RECORD,,
File without changes
File without changes