Commit 186cc76ce18f343cfcb4078ddcf2b9ec3c3871db

Authored by Miguel Barão
1 parent 1841cc32
Exists in master and in 1 other branch dev

adds config/logger.yaml files as a base for customization.

BUGS.md
1 1  
2 2 # BUGS
3 3  
  4 +- permitir configuracao para escolher entre static files locais ou remotos
4 5 - sqlalchemy.pool.impl.NullPool: Exception during reset or similar
5 6 sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread.
6 7  
... ...
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
... ... @@ -459,3 +459,5 @@ class LearnApp(object):
459 459 for uid, name in students if uid != '0']
460 460  
461 461 return sorted(rankings, key=lambda x: x[2], reverse=True)
  462 +
  463 + # ------------------------------------------------------------------------
... ...
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
... ...
config/logger-debug.yaml 0 → 100644
... ... @@ -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
... ...
config/logger.yaml 0 → 100644
... ... @@ -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
... ...