Commit 186cc76ce18f343cfcb4078ddcf2b9ec3c3871db
1 parent
1841cc32
Exists in
master
and in
1 other branch
adds config/logger.yaml files as a base for customization.
Showing
8 changed files
with
131 additions
and
36 deletions
Show diff stats
BUGS.md
README.md
... | ... | @@ -85,8 +85,9 @@ At this point aprendizations is installed in |
85 | 85 | ~/.local/bin # FreeBSD/Linux |
86 | 86 | ``` |
87 | 87 | |
88 | -and can be run with the command `aprendizations` (make sure this directory is | |
89 | -in your `$PATH`). | |
88 | +Make sure this directory is in your `$PATH`. | |
89 | + | |
90 | +The server can be run with the command `aprendizations` from the terminal. | |
90 | 91 | |
91 | 92 | ## Configuration |
92 | 93 | |
... | ... | @@ -121,7 +122,7 @@ have a registered publicly accessible domain name. |
121 | 122 | |
122 | 123 | #### Selfsigned |
123 | 124 | |
124 | -Generate a selfsigned certificate and place it in `aprendizations/certs`. | |
125 | +Generate a selfsigned certificate and place it in `~/.local/share/certs`. | |
125 | 126 | |
126 | 127 | ```sh |
127 | 128 | openssl req -x509 -newkey rsa:4096 -keyout privkey.pem -out cert.pem -days 365 -nodes |
... | ... | @@ -141,7 +142,7 @@ sudo certbot certonly --standalone -d www.example.com |
141 | 142 | sudo service pf start # enable pf firewall |
142 | 143 | ``` |
143 | 144 | |
144 | -Certificates are saved under `/usr/local/etc/letsencrypt/live/www.example.com/`. Copy them to `aprendizations/certs` and change permissions to be readable: | |
145 | +Certificates are saved under `/usr/local/etc/letsencrypt/live/www.example.com/`. Copy them to `~/.local/share/certs` and change permissions to be readable: | |
145 | 146 | |
146 | 147 | ```sh |
147 | 148 | sudo cp /usr/local/etc/letsencrypt/live/www.example.com/cert.pem . |
... | ... | @@ -157,7 +158,7 @@ sudo certbot renew |
157 | 158 | sudo service pf start # start firewall |
158 | 159 | ``` |
159 | 160 | |
160 | -and then copy the `cert.pem` and `privkey.pem` files to `aprendizations/certs` directory. Change permissions and ownership as appropriate. | |
161 | +and then copy the `cert.pem` and `privkey.pem` files to `~/.local/share/certs` directory. Change permissions and ownership as appropriate. | |
161 | 162 | |
162 | 163 | |
163 | 164 | ### Testing |
... | ... | @@ -169,7 +170,8 @@ cd demo |
169 | 170 | aprendizations demo.yaml |
170 | 171 | ``` |
171 | 172 | |
172 | -and open a browser at [https://127.0.0.1:8443](). If it everything looks good, | |
173 | +and open a browser at [https://127.0.0.1:8443](https://127.0.0.1:8443). | |
174 | +If it everything looks good, | |
173 | 175 | check at the correct address `https://www.example.com` (requires port forward |
174 | 176 | in the firewall). The option `--debug` provides more verbose logging and might |
175 | 177 | be useful during testing. The option `--check` generates all the questions once |
... | ... | @@ -220,6 +222,7 @@ errors in the browser. |
220 | 222 | Logging levels can be adjusted in `~/.config/aprendizations/logger.yaml` and |
221 | 223 | `~/.config/aprendizations/logger-debug.yaml`. |
222 | 224 | |
225 | +If these files do not yet exist, there are examples in `aprendizations/config` that can be copied to `~/.config/aprendizations`. | |
223 | 226 | |
224 | 227 | #### UnicodeEncodeError |
225 | 228 | ... | ... |
aprendizations/knowledge.py
... | ... | @@ -69,13 +69,14 @@ class StudentKnowledge(object): |
69 | 69 | # ------------------------------------------------------------------------ |
70 | 70 | async def start_topic(self, topic): |
71 | 71 | logger.debug('StudentKnowledge.start_topic()') |
72 | + | |
72 | 73 | if self.current_topic == topic: |
73 | - logger.info(' Restarting current topic is not allowed.') | |
74 | + logger.info('Restarting current topic is not allowed.') | |
74 | 75 | return False |
75 | 76 | |
76 | 77 | # do not allow locked topics |
77 | 78 | if self.is_locked(topic): |
78 | - logger.debug(f' Topic {topic} is locked') | |
79 | + logger.debug(f'Topic {topic} is locked') | |
79 | 80 | return False |
80 | 81 | |
81 | 82 | # starting new topic |
... | ... | @@ -96,23 +97,16 @@ class StudentKnowledge(object): |
96 | 97 | # synchronous: |
97 | 98 | # self.questions = [self.factory[ref].generate() for ref in questions] |
98 | 99 | |
99 | - loop = asyncio.get_running_loop() | |
100 | - | |
101 | 100 | # async: |
102 | - # generators = [loop.run_in_executor(None, self.factory[qref].generate) | |
103 | - # for qref in questions] | |
104 | - # self.questions = await asyncio.gather(*generators) | |
105 | - | |
106 | - # another async: | |
107 | - self.questions = [] | |
108 | - for qref in questions: | |
109 | - q = await loop.run_in_executor(None, self.factory[qref].generate) | |
110 | - self.questions.append(q) | |
111 | - | |
112 | - logger.debug(f'Generated {len(self.questions)} questions') | |
101 | + loop = asyncio.get_running_loop() | |
102 | + generators = [loop.run_in_executor(None, self.factory[qref].generate) | |
103 | + for qref in questions] | |
104 | + self.questions = await asyncio.gather(*generators) | |
113 | 105 | |
114 | 106 | # get first question |
115 | 107 | self.next_question() |
108 | + | |
109 | + logger.debug(f'Generated {len(self.questions)} questions') | |
116 | 110 | return True |
117 | 111 | |
118 | 112 | # ------------------------------------------------------------------------ | ... | ... |
aprendizations/learnapp.py
aprendizations/questions.py
... | ... | @@ -28,9 +28,9 @@ class QuestionException(Exception): |
28 | 28 | # =========================================================================== |
29 | 29 | class Question(dict): |
30 | 30 | ''' |
31 | - Classes derived from this base class are meant to instantiate a question | |
32 | - to a student. | |
33 | - Instances can shuffle options, or automatically generate questions. | |
31 | + Classes derived from this base class are meant to instantiate questions | |
32 | + for each student. | |
33 | + Instances can shuffle options or automatically generate questions. | |
34 | 34 | ''' |
35 | 35 | def __init__(self, q: QDict) -> None: |
36 | 36 | super().__init__(q) |
... | ... | @@ -125,7 +125,7 @@ class QuestionRadio(Question): |
125 | 125 | self['correct'] = [float(correct[i]) for i in perm] |
126 | 126 | |
127 | 127 | # ------------------------------------------------------------------------ |
128 | - # can return negative values for wrong answers | |
128 | + # can assign negative grades for wrong answers | |
129 | 129 | def correct(self) -> None: |
130 | 130 | super().correct() |
131 | 131 | ... | ... |
aprendizations/serve.py
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | |
3 | 3 | # python standard library |
4 | -from os import path | |
5 | -import os | |
6 | 4 | import sys |
7 | 5 | import base64 |
8 | 6 | import uuid |
... | ... | @@ -13,12 +11,12 @@ import signal |
13 | 11 | import functools |
14 | 12 | import ssl |
15 | 13 | import asyncio |
16 | -# from typing import NoReturn | |
14 | +from os import path, environ | |
17 | 15 | |
18 | 16 | # third party libraries |
19 | 17 | import tornado.ioloop |
20 | -import tornado.web | |
21 | 18 | import tornado.httpserver |
19 | +import tornado.web | |
22 | 20 | from tornado.escape import to_unicode |
23 | 21 | |
24 | 22 | # this project |
... | ... | @@ -294,7 +292,7 @@ class QuestionHandler(BaseHandler): |
294 | 292 | answer_qid = self.get_body_arguments('qid')[0] |
295 | 293 | current_qid = self.learn.get_current_question_id(user) |
296 | 294 | if answer_qid != current_qid: |
297 | - logging.debug(f'User {user} desynchronized questions') | |
295 | + logging.info(f'User {user} desynchronized questions') | |
298 | 296 | self.write({ |
299 | 297 | 'method': 'invalid', |
300 | 298 | 'params': { |
... | ... | @@ -428,16 +426,16 @@ def get_logger_config(debug=False): |
428 | 426 | filename = 'logger.yaml' |
429 | 427 | level = 'INFO' |
430 | 428 | |
431 | - config_dir = os.environ.get('XDG_CONFIG_HOME', '~/.config/') | |
429 | + config_dir = environ.get('XDG_CONFIG_HOME', '~/.config/') | |
432 | 430 | config_file = path.join(path.expanduser(config_dir), APP_NAME, filename) |
433 | 431 | |
434 | 432 | default_config = { |
435 | 433 | 'version': 1, |
436 | 434 | 'formatters': { |
437 | 435 | 'standard': { |
438 | - 'format': '%(asctime)s %(name)-24s %(levelname)-8s ' | |
436 | + 'format': '%(asctime)s %(name)-24s %(levelname)-10s - ' | |
439 | 437 | '%(message)s', |
440 | - 'datefmt': '%H:%M:%S', | |
438 | + 'datefmt': '%Y-%m-%d %H:%M:%S', | |
441 | 439 | }, |
442 | 440 | }, |
443 | 441 | 'handlers': { |
... | ... | @@ -503,8 +501,8 @@ def main(): |
503 | 501 | sys.exit(1) |
504 | 502 | |
505 | 503 | # --- get SSL certificates |
506 | - if 'XDG_DATA_HOME' in os.environ: | |
507 | - certs_dir = path.join(os.environ['XDG_DATA_HOME'], 'certs') | |
504 | + if 'XDG_DATA_HOME' in environ: | |
505 | + certs_dir = path.join(environ['XDG_DATA_HOME'], 'certs') | |
508 | 506 | else: |
509 | 507 | certs_dir = path.expanduser('~/.local/share/certs') |
510 | 508 | |
... | ... | @@ -523,7 +521,12 @@ def main(): |
523 | 521 | logging.critical('Certificates cert.pem and privkey.pem not found') |
524 | 522 | sys.exit(1) |
525 | 523 | |
526 | - httpserver.listen(arg.port) | |
524 | + try: | |
525 | + httpserver.listen(arg.port) | |
526 | + except OSError: | |
527 | + logging.critical(f'Cannot bind port {arg.port}. Already in use?') | |
528 | + sys.exit(1) | |
529 | + | |
527 | 530 | logging.info(f'Listening on port {arg.port}.') |
528 | 531 | |
529 | 532 | # --- run webserver | ... | ... |
... | ... | @@ -0,0 +1,46 @@ |
1 | +--- | |
2 | +version: 1 | |
3 | + | |
4 | +formatters: | |
5 | + void: | |
6 | + format: '' | |
7 | + standard: | |
8 | + format: '%(asctime)s | %(levelname)-9s | %(name)-24s | %(thread)-15d | %(message)s' | |
9 | + datefmt: '%H:%M:%S' | |
10 | + | |
11 | +handlers: | |
12 | + default: | |
13 | + level: 'DEBUG' | |
14 | + class: 'logging.StreamHandler' | |
15 | + formatter: 'standard' | |
16 | + stream: 'ext://sys.stdout' | |
17 | + | |
18 | +loggers: | |
19 | + '': | |
20 | + handlers: ['default'] | |
21 | + level: 'DEBUG' | |
22 | + | |
23 | + 'aprendizations.factory': | |
24 | + handlers: ['default'] | |
25 | + level: 'DEBUG' | |
26 | + propagate: false | |
27 | + | |
28 | + 'aprendizations.knowledge': | |
29 | + handlers: ['default'] | |
30 | + level: 'DEBUG' | |
31 | + propagate: false | |
32 | + | |
33 | + 'aprendizations.learnapp': | |
34 | + handlers: ['default'] | |
35 | + level: 'DEBUG' | |
36 | + propagate: false | |
37 | + | |
38 | + 'aprendizations.questions': | |
39 | + handlers: ['default'] | |
40 | + level: 'DEBUG' | |
41 | + propagate: false | |
42 | + | |
43 | + 'aprendizations.tools': | |
44 | + handlers: ['default'] | |
45 | + level: 'DEBUG' | |
46 | + propagate: false | ... | ... |
... | ... | @@ -0,0 +1,46 @@ |
1 | +--- | |
2 | +version: 1 | |
3 | + | |
4 | +formatters: | |
5 | + void: | |
6 | + format: '' | |
7 | + standard: | |
8 | + format: '%(asctime)s | %(thread)-15d | %(levelname)-9s | %(message)s' | |
9 | + datefmt: '%Y-%m-%d %H:%M:%S' | |
10 | + | |
11 | +handlers: | |
12 | + default: | |
13 | + level: 'INFO' | |
14 | + class: 'logging.StreamHandler' | |
15 | + formatter: 'standard' | |
16 | + stream: 'ext://sys.stdout' | |
17 | + | |
18 | +loggers: | |
19 | + '': | |
20 | + handlers: ['default'] | |
21 | + level: 'INFO' | |
22 | + | |
23 | + 'aprendizations.factory': | |
24 | + handlers: ['default'] | |
25 | + level: 'INFO' | |
26 | + propagate: false | |
27 | + | |
28 | + 'aprendizations.knowledge': | |
29 | + handlers: ['default'] | |
30 | + level: 'INFO' | |
31 | + propagate: false | |
32 | + | |
33 | + 'aprendizations.learnapp': | |
34 | + handlers: ['default'] | |
35 | + level: 'INFO' | |
36 | + propagate: false | |
37 | + | |
38 | + 'aprendizations.questions': | |
39 | + handlers: ['default'] | |
40 | + level: 'INFO' | |
41 | + propagate: false | |
42 | + | |
43 | + 'aprendizations.tools': | |
44 | + handlers: ['default'] | |
45 | + level: 'INFO' | |
46 | + propagate: false | ... | ... |