ladok3 4.10__py3-none-any.whl → 5.4__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.
- doc/ltxobj/ladok3.pdf +0 -0
- ladok3/Makefile +6 -0
- ladok3/__init__.py +1511 -3386
- ladok3/api.nw +1653 -225
- ladok3/cli.nw +118 -53
- ladok3/cli.py +323 -252
- ladok3/data.nw +92 -15
- ladok3/data.py +79 -3
- ladok3/ladok.bash +35 -17
- ladok3/ladok3.nw +288 -16
- ladok3/report.nw +183 -117
- ladok3/report.py +135 -63
- ladok3/scripts.nw +244 -0
- ladok3/student.nw +69 -4
- ladok3/student.py +98 -42
- ladok3/undoc.nw +62 -3119
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/LICENSE +1 -1
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/METADATA +39 -17
- ladok3-5.4.dist-info/RECORD +21 -0
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/WHEEL +1 -1
- ladok3/.gitignore +0 -10
- ladok3-4.10.dist-info/RECORD +0 -21
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/entry_points.txt +0 -0
ladok3/cli.nw
CHANGED
|
@@ -71,10 +71,21 @@ We will use the function [[err]] for errors and [[warn]] for warnings, both
|
|
|
71
71
|
inspired by err(3) and warn(3) in the BSD world.
|
|
72
72
|
<<functions>>=
|
|
73
73
|
def err(rc, msg):
|
|
74
|
+
"""Print error message to stderr and exit with given return code.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
rc (int): Return code to exit with.
|
|
78
|
+
msg (str): Error message to display.
|
|
79
|
+
"""
|
|
74
80
|
print(f"{sys.argv[0]}: error: {msg}", file=sys.stderr)
|
|
75
81
|
sys.exit(rc)
|
|
76
82
|
|
|
77
83
|
def warn(msg):
|
|
84
|
+
"""Print warning message to stderr.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
msg (str): Warning message to display.
|
|
88
|
+
"""
|
|
78
89
|
print(f"{sys.argv[0]}: {msg}", file=sys.stderr)
|
|
79
90
|
@
|
|
80
91
|
|
|
@@ -188,6 +199,18 @@ But we also want to encrypt the stored object using authenticated encryption.
|
|
|
188
199
|
That way, we know that we can trust the pickle (which is otherwise a problem).
|
|
189
200
|
<<functions>>=
|
|
190
201
|
def store_ladok_session(ls, credentials):
|
|
202
|
+
"""Store a LadokSession object to disk with encryption.
|
|
203
|
+
|
|
204
|
+
Saves the session object as an encrypted pickle file in the user's cache directory.
|
|
205
|
+
The credentials are used to derive an encryption key for security.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
ls (LadokSession): The session object to store.
|
|
209
|
+
credentials (tuple): Tuple of (institution, vars) used for key derivation.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If credentials are missing or invalid.
|
|
213
|
+
"""
|
|
191
214
|
if not os.path.isdir(dirs.user_cache_dir):
|
|
192
215
|
os.makedirs(dirs.user_cache_dir)
|
|
193
216
|
|
|
@@ -200,6 +223,17 @@ def store_ladok_session(ls, credentials):
|
|
|
200
223
|
file.write(encrypted_ls)
|
|
201
224
|
|
|
202
225
|
def restore_ladok_session(credentials):
|
|
226
|
+
"""Restore a LadokSession object from disk.
|
|
227
|
+
|
|
228
|
+
Attempts to load and decrypt a previously stored session object. Returns None
|
|
229
|
+
if no cached session exists or decryption fails.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
credentials (tuple): Tuple of (institution, vars) used for key derivation.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
LadokSession or None: The restored session object, or None if unavailable.
|
|
236
|
+
"""
|
|
203
237
|
file_path = dirs.user_cache_dir + "/LadokSession"
|
|
204
238
|
|
|
205
239
|
if os.path.isfile(file_path):
|
|
@@ -299,21 +333,17 @@ the user.
|
|
|
299
333
|
Manages the user's LADOK login credentials. There are three ways to supply the
|
|
300
334
|
login credentials, in order of priority:
|
|
301
335
|
|
|
302
|
-
1) Through the
|
|
303
|
-
enter the credentials and they will be stored in the keyring. Note that for
|
|
304
|
-
this to work on the WSL platform (and possibly on Windows), you need to
|
|
305
|
-
install the `keyrings.alt` package: `python3 -m pip install keyrings.alt`.
|
|
336
|
+
1) Through the environment: Just set the environment variables
|
|
306
337
|
|
|
307
|
-
|
|
338
|
+
a) LADOK_INST, the name of the institution, e.g. KTH Royal Institute of
|
|
339
|
+
Technology;
|
|
308
340
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
LADOK_USER (the username, e.g. dbosk@ug.kth.se) and
|
|
314
|
-
LADOK_PASS (the password) instead.
|
|
341
|
+
b) LADOK_VARS, a colon-separated list of environment variables, similarly to
|
|
342
|
+
what's done in `ladok login` --- most don't need this, but can rather set
|
|
343
|
+
LADOK_USER (the username, e.g. dbosk@ug.kth.se) and LADOK_PASS (the
|
|
344
|
+
password) instead.
|
|
315
345
|
|
|
316
|
-
|
|
346
|
+
2) Through the configuration file: Just write
|
|
317
347
|
|
|
318
348
|
{{
|
|
319
349
|
"institution": "the name of the university"
|
|
@@ -325,6 +355,17 @@ login credentials, in order of priority:
|
|
|
325
355
|
option). (The keys 'username' and 'password' can be renamed to correspond to
|
|
326
356
|
the necessary values if the university login system uses other names.)
|
|
327
357
|
|
|
358
|
+
3) Through the system keyring: Just run `ladok login` and you'll be asked to
|
|
359
|
+
enter the credentials and they will be stored in the keyring. Note that for
|
|
360
|
+
this to work on the WSL platform (and possibly on Windows), you need to
|
|
361
|
+
install the `keyrings.alt` package: `python3 -m pip install keyrings.alt`.
|
|
362
|
+
|
|
363
|
+
The keyring is the most secure. However, sometimes one want to try different
|
|
364
|
+
credentials, so the environment should override the keyring. Also, on WSL the
|
|
365
|
+
keyring might require you to enter a password in the terminal---this is very
|
|
366
|
+
inconvenient in scripts. However, when logging in, we first try to store the
|
|
367
|
+
credentials in the keyring.
|
|
368
|
+
|
|
328
369
|
<<add subparsers to subp>>=
|
|
329
370
|
login_parser = subp.add_parser("login",
|
|
330
371
|
help="Manage login credentials",
|
|
@@ -510,49 +551,17 @@ def load_credentials(filename="config.json"):
|
|
|
510
551
|
can be passed to `LadokSession(instiution, credential dictionary)`.
|
|
511
552
|
"""
|
|
512
553
|
|
|
513
|
-
<<fetch vars from keyring>>
|
|
514
|
-
<<fetch username and password from keyring>>
|
|
515
554
|
<<fetch institution from environment>>
|
|
516
555
|
<<fetch username and password from environment>>
|
|
517
556
|
<<fetch vars from environment>>
|
|
518
557
|
<<fetch vars from config file>>
|
|
558
|
+
<<fetch vars from keyring>>
|
|
559
|
+
<<fetch username and password from keyring>>
|
|
519
560
|
|
|
520
561
|
return None, None
|
|
521
562
|
@
|
|
522
563
|
|
|
523
|
-
First
|
|
524
|
-
We try to fetch the institution and vars from the keyring.
|
|
525
|
-
<<fetch vars from keyring>>=
|
|
526
|
-
try:
|
|
527
|
-
institution = keyring.get_password("ladok3", "institution")
|
|
528
|
-
vars_keys = keyring.get_password("ladok3", "vars")
|
|
529
|
-
|
|
530
|
-
vars = {}
|
|
531
|
-
for key in vars_keys.split(";"):
|
|
532
|
-
vars[key] = keyring.get_password("ladok3", key)
|
|
533
|
-
|
|
534
|
-
if institution and vars:
|
|
535
|
-
return institution, vars
|
|
536
|
-
except:
|
|
537
|
-
pass
|
|
538
|
-
@
|
|
539
|
-
|
|
540
|
-
However, if that fails, we fall back on the previous format, that only
|
|
541
|
-
supported KTH.
|
|
542
|
-
<<fetch username and password from keyring>>=
|
|
543
|
-
try:
|
|
544
|
-
institution = "KTH Royal Institute of Technology"
|
|
545
|
-
vars = {
|
|
546
|
-
"username": keyring.get_password("ladok3", "username"),
|
|
547
|
-
"password": keyring.get_password("ladok3", "password")
|
|
548
|
-
}
|
|
549
|
-
if vars:
|
|
550
|
-
return institution, vars
|
|
551
|
-
except:
|
|
552
|
-
pass
|
|
553
|
-
@
|
|
554
|
-
|
|
555
|
-
Next in priority is to read from the environment.
|
|
564
|
+
First in priority is to read from the environment.
|
|
556
565
|
We try to read the institution.
|
|
557
566
|
If that fails, we assume we're using the old format that only supported KTH.
|
|
558
567
|
<<fetch institution from environment>>=
|
|
@@ -560,14 +569,13 @@ try:
|
|
|
560
569
|
institution = os.environ["LADOK_INST"]
|
|
561
570
|
except:
|
|
562
571
|
institution = "KTH Royal Institute of Technology"
|
|
563
|
-
|
|
564
572
|
<<fetch username and password from environment>>=
|
|
565
573
|
try:
|
|
566
574
|
vars = {
|
|
567
575
|
"username": os.environ["LADOK_USER"],
|
|
568
576
|
"password": os.environ["LADOK_PASS"]
|
|
569
577
|
}
|
|
570
|
-
if institution and vars:
|
|
578
|
+
if institution and vars["username"] and vars["password"]:
|
|
571
579
|
return institution, vars
|
|
572
580
|
except:
|
|
573
581
|
pass
|
|
@@ -575,13 +583,20 @@ except:
|
|
|
575
583
|
|
|
576
584
|
If we couldn't read the old [[LADOK_USER]] and [[LADOK_PASS]], we try to read
|
|
577
585
|
the [[vars]] from the environment using [[LADOK_VARS]].
|
|
586
|
+
Note that we need the [[institution]] to be set from [[LADOK_INST]] above for
|
|
587
|
+
this.
|
|
578
588
|
<<fetch vars from environment>>=
|
|
579
589
|
try:
|
|
580
590
|
vars_keys = os.environ["LADOK_VARS"]
|
|
581
591
|
|
|
582
592
|
vars = {}
|
|
583
593
|
for key in vars_keys.split(":"):
|
|
584
|
-
|
|
594
|
+
try:
|
|
595
|
+
value = os.environ[key]
|
|
596
|
+
if value:
|
|
597
|
+
vars[key] = value
|
|
598
|
+
except KeyError:
|
|
599
|
+
<<print warning about missing variable in [[LADOK_VARS]]>>
|
|
585
600
|
|
|
586
601
|
if institution and vars:
|
|
587
602
|
return institution, vars
|
|
@@ -589,8 +604,15 @@ except:
|
|
|
589
604
|
pass
|
|
590
605
|
@
|
|
591
606
|
|
|
592
|
-
|
|
593
|
-
|
|
607
|
+
Unlike in the other cases, we don't just ignore the exception of the key not
|
|
608
|
+
existing.
|
|
609
|
+
Since the user has explicitly specified the variable, we should warn them that
|
|
610
|
+
it doesn't exist.
|
|
611
|
+
<<print warning about missing variable in [[LADOK_VARS]]>>=
|
|
612
|
+
warn(f"Variable {key} not set, ignoring.")
|
|
613
|
+
@
|
|
614
|
+
|
|
615
|
+
If none of the above worked, we try the config file next.
|
|
594
616
|
We pop the institution from the configuration file (a dictionary), because then
|
|
595
617
|
the remaining entries will be used as [[vars]].
|
|
596
618
|
<<fetch vars from config file>>=
|
|
@@ -599,12 +621,46 @@ try:
|
|
|
599
621
|
config = json.load(conf_file)
|
|
600
622
|
|
|
601
623
|
institution = config.pop("institution",
|
|
602
|
-
|
|
624
|
+
"KTH Royal Institute of Technology")
|
|
603
625
|
return institution, config
|
|
604
626
|
except:
|
|
605
627
|
pass
|
|
606
628
|
@
|
|
607
629
|
|
|
630
|
+
Lastly, if nothing else worked, we try to fetch the institution and vars from
|
|
631
|
+
the keyring.
|
|
632
|
+
Note that [[keyring]] returns [[None]] if the key doesn't exist, it doesn't
|
|
633
|
+
raise an exception.
|
|
634
|
+
<<fetch vars from keyring>>=
|
|
635
|
+
try:
|
|
636
|
+
institution = keyring.get_password("ladok3", "institution")
|
|
637
|
+
vars_keys = keyring.get_password("ladok3", "vars")
|
|
638
|
+
|
|
639
|
+
vars = {}
|
|
640
|
+
for key in vars_keys.split(";"):
|
|
641
|
+
value = keyring.get_password("ladok3", key)
|
|
642
|
+
if value:
|
|
643
|
+
vars[key] = value
|
|
644
|
+
|
|
645
|
+
if institution and vars:
|
|
646
|
+
return institution, vars
|
|
647
|
+
except:
|
|
648
|
+
pass
|
|
649
|
+
@
|
|
650
|
+
|
|
651
|
+
However, if that fails, we fall back on the previous format, that only
|
|
652
|
+
supported KTH.
|
|
653
|
+
<<fetch username and password from keyring>>=
|
|
654
|
+
try:
|
|
655
|
+
institution = "KTH Royal Institute of Technology"
|
|
656
|
+
username = keyring.get_password("ladok3", "username")
|
|
657
|
+
password = keyring.get_password("ladok3", "password")
|
|
658
|
+
if username and password:
|
|
659
|
+
return institution, {"username": username, "password": password}
|
|
660
|
+
except:
|
|
661
|
+
pass
|
|
662
|
+
@
|
|
663
|
+
|
|
608
664
|
|
|
609
665
|
\section{Managing the cache: the \texttt{cache} command and subcommands}
|
|
610
666
|
|
|
@@ -643,6 +699,15 @@ If we don't exit using [[sys.exit]], the main program will write the cache back
|
|
|
643
699
|
again on its exit.
|
|
644
700
|
<<functions>>=
|
|
645
701
|
def clear_cache(ls, args):
|
|
702
|
+
"""Clear the cached LADOK session data.
|
|
703
|
+
|
|
704
|
+
Removes the stored encrypted session file from the user's cache directory.
|
|
705
|
+
Silently ignores if the file doesn't exist.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
ls (LadokSession): The LADOK session (unused but required by interface).
|
|
709
|
+
args: Command line arguments (unused).
|
|
710
|
+
"""
|
|
646
711
|
try:
|
|
647
712
|
os.remove(dirs.user_cache_dir + "/LadokSession")
|
|
648
713
|
except FileNotFoundError as err:
|