Commit 1f734486117453b2dc7a15af9ed8fe01675d28d5

Authored by Miguel Barão
1 parent 7b668cb1
Exists in master and in 1 other branch dev

split serve.py in two files:

- main.py to setupt and start the application
- serve.py to handle requests
aprendizations/main.py 0 → 100644
... ... @@ -0,0 +1,231 @@
  1 +#!/usr/bin/env python3
  2 +
  3 +# python standard library
  4 +import argparse
  5 +import logging
  6 +from os import environ, path
  7 +import signal
  8 +import ssl
  9 +import sys
  10 +
  11 +# third party libraries
  12 +import tornado
  13 +
  14 +# this project
  15 +from .learnapp import LearnApp, DatabaseUnusableException
  16 +from .serve import WebApplication
  17 +from .tools import load_yaml
  18 +from . import APP_NAME, APP_VERSION
  19 +
  20 +
  21 +# ----------------------------------------------------------------------------
  22 +# Signal handler to catch Ctrl-C and abort server
  23 +# ----------------------------------------------------------------------------
  24 +def signal_handler(signal, frame):
  25 + r = input(' --> Stop webserver? (yes/no) ').lower()
  26 + if r == 'yes':
  27 + tornado.ioloop.IOLoop.current().stop()
  28 + logging.critical('Webserver stopped.')
  29 + sys.exit(0)
  30 + else:
  31 + logging.info('Abort canceled...')
  32 +
  33 +
  34 +# ----------------------------------------------------------------------------
  35 +def parse_cmdline_arguments():
  36 + argparser = argparse.ArgumentParser(
  37 + description='Server for online learning. Students and topics '
  38 + 'have to be previously configured. Please read the documentation '
  39 + 'included with this software before running the server.'
  40 + )
  41 +
  42 + argparser.add_argument(
  43 + 'conffile', type=str, nargs='*',
  44 + help='Topics configuration file in YAML format.'
  45 + )
  46 +
  47 + argparser.add_argument(
  48 + '--prefix', type=str, default='.',
  49 + help='Path where the topic directories can be found (default: .)'
  50 + )
  51 +
  52 + argparser.add_argument(
  53 + '--port', type=int, default=8443,
  54 + help='Port to be used by the HTTPS server (default: 8443)'
  55 + )
  56 +
  57 + argparser.add_argument(
  58 + '--db', type=str, default='students.db',
  59 + help='SQLite3 database file (default: students.db)'
  60 + )
  61 +
  62 + argparser.add_argument(
  63 + '--check', action='store_true',
  64 + help='Sanity check questions (can take awhile)'
  65 + )
  66 +
  67 + argparser.add_argument(
  68 + '--debug', action='store_true',
  69 + help='Enable debug mode'
  70 + )
  71 +
  72 + argparser.add_argument(
  73 + '--version', action='store_true',
  74 + help='Print version information'
  75 + )
  76 +
  77 + return argparser.parse_args()
  78 +
  79 +
  80 +# ----------------------------------------------------------------------------
  81 +def get_logger_config(debug=False):
  82 + if debug:
  83 + filename, level = 'logger-debug.yaml', 'DEBUG'
  84 + else:
  85 + filename, level = 'logger.yaml', 'INFO'
  86 +
  87 + config_dir = environ.get('XDG_CONFIG_HOME', '~/.config/')
  88 + config_file = path.join(path.expanduser(config_dir), APP_NAME, filename)
  89 +
  90 + default_config = {
  91 + 'version': 1,
  92 + 'formatters': {
  93 + 'standard': {
  94 + 'format': '%(asctime)s | %(levelname)-10s | %(message)s',
  95 + 'datefmt': '%Y-%m-%d %H:%M:%S',
  96 + },
  97 + },
  98 + 'handlers': {
  99 + 'default': {
  100 + 'level': level,
  101 + 'class': 'logging.StreamHandler',
  102 + 'formatter': 'standard',
  103 + 'stream': 'ext://sys.stdout',
  104 + },
  105 + },
  106 + 'loggers': {
  107 + '': { # configuration for serve.py
  108 + 'handlers': ['default'],
  109 + 'level': level,
  110 + },
  111 + },
  112 + }
  113 + default_config['loggers'].update({
  114 + APP_NAME+'.'+module: {
  115 + 'handlers': ['default'],
  116 + 'level': level,
  117 + 'propagate': False,
  118 + } for module in ['learnapp', 'models', 'factory', 'questions',
  119 + 'knowledge', 'tools']})
  120 +
  121 + return load_yaml(config_file, default=default_config)
  122 +
  123 +
  124 +# ----------------------------------------------------------------------------
  125 +# Tornado web server
  126 +# ----------------------------------------------------------------------------
  127 +def main():
  128 + # --- Commandline argument parsing
  129 + arg = parse_cmdline_arguments()
  130 +
  131 + if arg.version:
  132 + print(f'{APP_NAME} - {APP_VERSION}\nPython {sys.version}')
  133 + sys.exit(0)
  134 +
  135 + # --- Setup logging
  136 + logger_config = get_logger_config(arg.debug)
  137 + logging.config.dictConfig(logger_config)
  138 +
  139 + try:
  140 + logging.config.dictConfig(logger_config)
  141 + except Exception:
  142 + print('An error ocurred while setting up the logging system.')
  143 + sys.exit(1)
  144 +
  145 + logging.info('====================== Start Logging ======================')
  146 +
  147 + # --- start application
  148 + logging.info('Starting App...')
  149 + try:
  150 + learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db,
  151 + check=arg.check)
  152 + except DatabaseUnusableException:
  153 + logging.critical('Failed to start application.')
  154 + print('--------------------------------------------------------------')
  155 + print('Could not find a usable database. Use one of the follwing ')
  156 + print('commands to initialize: ')
  157 + print(' ')
  158 + print(' initdb-aprendizations --admin # add admin ')
  159 + print(' initdb-aprendizations -a 86 "Max Smart" # add student ')
  160 + print(' initdb-aprendizations students.csv # add many students')
  161 + print('--------------------------------------------------------------')
  162 + sys.exit(1)
  163 + except Exception:
  164 + logging.critical('Failed to start application.')
  165 + sys.exit(1)
  166 +
  167 + # --- create web application
  168 + logging.info('Starting Web App (tornado)...')
  169 + try:
  170 + webapp = WebApplication(learnapp, debug=arg.debug)
  171 + except Exception:
  172 + logging.critical('Failed to start web application.')
  173 + sys.exit(1)
  174 +
  175 + # --- get SSL certificates
  176 + if 'XDG_DATA_HOME' in environ:
  177 + certs_dir = path.join(environ['XDG_DATA_HOME'], 'certs')
  178 + else:
  179 + certs_dir = path.expanduser('~/.local/share/certs')
  180 +
  181 + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
  182 + try:
  183 + ssl_ctx.load_cert_chain(path.join(certs_dir, 'cert.pem'),
  184 + path.join(certs_dir, 'privkey.pem'))
  185 + except FileNotFoundError:
  186 + logging.critical(f'SSL certificates missing in {certs_dir}')
  187 + print('--------------------------------------------------------------')
  188 + print('Certificates should be issued by a certificate authority (CA),')
  189 + print('such as https://letsencrypt.org, and then copied to: ')
  190 + print(' ')
  191 + print(f' {certs_dir:<62}')
  192 + print(' ')
  193 + print('For testing purposes a selfsigned certificate can be generated')
  194 + print('locally by running: ')
  195 + print(' ')
  196 + print(' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ')
  197 + print(' -out cert.pem -days 365 -nodes ')
  198 + print(' ')
  199 + print('--------------------------------------------------------------')
  200 + sys.exit(1)
  201 +
  202 + # --- create webserver
  203 + try:
  204 + httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl_ctx)
  205 + except ValueError:
  206 + logging.critical('Certificates cert.pem and privkey.pem not found')
  207 + sys.exit(1)
  208 +
  209 + try:
  210 + httpserver.listen(arg.port)
  211 + except OSError:
  212 + logging.critical(f'Cannot bind port {arg.port}. Already in use?')
  213 + sys.exit(1)
  214 +
  215 + logging.info(f'Listening on port {arg.port}.')
  216 +
  217 + # --- run webserver
  218 + signal.signal(signal.SIGINT, signal_handler)
  219 + logging.info('Webserver running. (Ctrl-C to stop)')
  220 +
  221 + try:
  222 + tornado.ioloop.IOLoop.current().start() # running...
  223 + except Exception:
  224 + logging.critical('Webserver stopped.')
  225 + tornado.ioloop.IOLoop.current().stop()
  226 + raise
  227 +
  228 +
  229 +# ----------------------------------------------------------------------------
  230 +if __name__ == "__main__":
  231 + main()
... ...
aprendizations/serve.py
1   -#!/usr/bin/env python3
2 1  
3 2 # python standard library
4   -import argparse
5 3 import asyncio
6 4 import base64
7 5 import functools
8 6 import logging.config
9 7 import mimetypes
10   -from os import path, environ
11   -import signal
12   -import ssl
13   -import sys
  8 +from os import path
14 9 import uuid
15 10  
16   -
17 11 # third party libraries
18   -import tornado.ioloop
19   -import tornado.httpserver
20 12 import tornado.web
21 13 from tornado.escape import to_unicode
22 14  
23 15 # this project
24   -from .learnapp import LearnApp, DatabaseUnusableException
25   -from .tools import load_yaml, md_to_html
26   -from . import APP_NAME, APP_VERSION
  16 +from .tools import md_to_html
  17 +from . import APP_NAME
27 18  
28 19  
29 20 # ----------------------------------------------------------------------------
... ... @@ -187,7 +178,6 @@ class RootHandler(BaseHandler):
187 178 # ----------------------------------------------------------------------------
188 179 # /topic/...
189 180 # Start a given topic
190   -# FIXME should not change state...
191 181 # ----------------------------------------------------------------------------
192 182 class TopicHandler(BaseHandler):
193 183 @tornado.web.authenticated
... ... @@ -198,12 +188,12 @@ class TopicHandler(BaseHandler):
198 188 await self.learn.start_topic(uid, topic)
199 189 except KeyError:
200 190 self.redirect('/')
201   - else:
202   - self.render('topic.html',
203   - appname=APP_NAME,
204   - uid=uid,
205   - name=self.learn.get_student_name(uid),
206   - )
  191 +
  192 + self.render('topic.html',
  193 + appname=APP_NAME,
  194 + uid=uid,
  195 + name=self.learn.get_student_name(uid),
  196 + )
207 197  
208 198  
209 199 # ----------------------------------------------------------------------------
... ... @@ -362,217 +352,3 @@ class QuestionHandler(BaseHandler):
362 352 logging.error(f'Unknown action: {action}')
363 353  
364 354 self.write(response)
365   -
366   -
367   -# ----------------------------------------------------------------------------
368   -# Signal handler to catch Ctrl-C and abort server
369   -# ----------------------------------------------------------------------------
370   -def signal_handler(signal, frame):
371   - r = input(' --> Stop webserver? (yes/no) ').lower()
372   - if r == 'yes':
373   - tornado.ioloop.IOLoop.current().stop()
374   - logging.critical('Webserver stopped.')
375   - sys.exit(0)
376   - else:
377   - logging.info('Abort canceled...')
378   -
379   -
380   -# ----------------------------------------------------------------------------
381   -def parse_cmdline_arguments():
382   - argparser = argparse.ArgumentParser(
383   - description='Server for online learning. Students and topics '
384   - 'have to be previously configured. Please read the documentation '
385   - 'included with this software before running the server.'
386   - )
387   -
388   - argparser.add_argument(
389   - 'conffile', type=str, nargs='*',
390   - help='Topics configuration file in YAML format.'
391   - )
392   -
393   - argparser.add_argument(
394   - '--prefix', type=str, default='.',
395   - help='Path where the topic directories can be found (default: .)'
396   - )
397   -
398   - argparser.add_argument(
399   - '--port', type=int, default=8443,
400   - help='Port to be used by the HTTPS server (default: 8443)'
401   - )
402   -
403   - argparser.add_argument(
404   - '--db', type=str, default='students.db',
405   - help='SQLite3 database file (default: students.db)'
406   - )
407   -
408   - argparser.add_argument(
409   - '--check', action='store_true',
410   - help='Sanity check questions (can take awhile)'
411   - )
412   -
413   - argparser.add_argument(
414   - '--debug', action='store_true',
415   - help='Enable debug mode'
416   - )
417   -
418   - argparser.add_argument(
419   - '--version', action='store_true',
420   - help='Print version information'
421   - )
422   -
423   - return argparser.parse_args()
424   -
425   -
426   -# ----------------------------------------------------------------------------
427   -def get_logger_config(debug=False):
428   - if debug:
429   - filename, level = 'logger-debug.yaml', 'DEBUG'
430   - else:
431   - filename, level = 'logger.yaml', 'INFO'
432   -
433   - config_dir = environ.get('XDG_CONFIG_HOME', '~/.config/')
434   - config_file = path.join(path.expanduser(config_dir), APP_NAME, filename)
435   -
436   - default_config = {
437   - 'version': 1,
438   - 'formatters': {
439   - 'standard': {
440   - 'format': '%(asctime)s | %(levelname)-10s | %(message)s',
441   - 'datefmt': '%Y-%m-%d %H:%M:%S',
442   - },
443   - },
444   - 'handlers': {
445   - 'default': {
446   - 'level': level,
447   - 'class': 'logging.StreamHandler',
448   - 'formatter': 'standard',
449   - 'stream': 'ext://sys.stdout',
450   - },
451   - },
452   - 'loggers': {
453   - '': { # configuration for serve.py
454   - 'handlers': ['default'],
455   - 'level': level,
456   - },
457   - },
458   - }
459   - default_config['loggers'].update({
460   - APP_NAME+'.'+module: {
461   - 'handlers': ['default'],
462   - 'level': level,
463   - 'propagate': False,
464   - } for module in ['learnapp', 'models', 'factory', 'questions',
465   - 'knowledge', 'tools']})
466   -
467   - return load_yaml(config_file, default=default_config)
468   -
469   -
470   -# ----------------------------------------------------------------------------
471   -# Tornado web server
472   -# ----------------------------------------------------------------------------
473   -def main():
474   - # --- Commandline argument parsing
475   - arg = parse_cmdline_arguments()
476   -
477   - if arg.version:
478   - print(APP_NAME + ' ' + APP_VERSION)
479   - print('Python ' + sys.version)
480   - sys.exit(0)
481   -
482   - # --- Setup logging
483   - logger_config = get_logger_config(arg.debug)
484   - logging.config.dictConfig(logger_config)
485   -
486   - try:
487   - logging.config.dictConfig(logger_config)
488   - except Exception:
489   - print('An error ocurred while setting up the logging system.')
490   - sys.exit(1)
491   -
492   - logging.info('====================== Start Logging ======================')
493   -
494   - # --- start application
495   - logging.info('Starting App...')
496   - try:
497   - learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db,
498   - check=arg.check)
499   - except DatabaseUnusableException:
500   - logging.critical('Failed to start application.')
501   - print('--------------------------------------------------------------')
502   - print('Could not find a usable database. Use one of the follwing ')
503   - print('commands to initialize: ')
504   - print(' ')
505   - print(' initdb-aprendizations --admin # add admin ')
506   - print(' initdb-aprendizations -a 86 "Max Smart" # add student ')
507   - print(' initdb-aprendizations students.csv # add many students')
508   - print('--------------------------------------------------------------')
509   - sys.exit(1)
510   - except Exception:
511   - logging.critical('Failed to start application.')
512   - sys.exit(1)
513   -
514   - # --- create web application
515   - logging.info('Starting Web App (tornado)...')
516   - try:
517   - webapp = WebApplication(learnapp, debug=arg.debug)
518   - except Exception:
519   - logging.critical('Failed to start web application.')
520   - sys.exit(1)
521   -
522   - # --- get SSL certificates
523   - if 'XDG_DATA_HOME' in environ:
524   - certs_dir = path.join(environ['XDG_DATA_HOME'], 'certs')
525   - else:
526   - certs_dir = path.expanduser('~/.local/share/certs')
527   -
528   - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
529   - try:
530   - ssl_ctx.load_cert_chain(path.join(certs_dir, 'cert.pem'),
531   - path.join(certs_dir, 'privkey.pem'))
532   - except FileNotFoundError:
533   - logging.critical(f'SSL certificates missing in {certs_dir}')
534   - print('--------------------------------------------------------------')
535   - print('Certificates should be issued by a certificate authority (CA),')
536   - print('such as https://letsencrypt.org, and then copied to: ')
537   - print(' ')
538   - print(f' {certs_dir:<62}')
539   - print(' ')
540   - print('For testing purposes a selfsigned certificate can be generated')
541   - print('locally by running: ')
542   - print(' ')
543   - print(' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ')
544   - print(' -out cert.pem -days 365 -nodes ')
545   - print(' ')
546   - print('--------------------------------------------------------------')
547   - sys.exit(1)
548   -
549   - # --- create webserver
550   - try:
551   - httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl_ctx)
552   - except ValueError:
553   - logging.critical('Certificates cert.pem and privkey.pem not found')
554   - sys.exit(1)
555   -
556   - try:
557   - httpserver.listen(arg.port)
558   - except OSError:
559   - logging.critical(f'Cannot bind port {arg.port}. Already in use?')
560   - sys.exit(1)
561   -
562   - logging.info(f'Listening on port {arg.port}.')
563   -
564   - # --- run webserver
565   - signal.signal(signal.SIGINT, signal_handler)
566   - logging.info('Webserver running. (Ctrl-C to stop)')
567   -
568   - try:
569   - tornado.ioloop.IOLoop.current().start() # running...
570   - except Exception:
571   - logging.critical('Webserver stopped.')
572   - tornado.ioloop.IOLoop.current().stop()
573   - raise
574   -
575   -
576   -# ----------------------------------------------------------------------------
577   -if __name__ == "__main__":
578   - main()
... ...
setup.py
... ... @@ -25,9 +25,9 @@ setup(
25 25 ],
26 26 entry_points={
27 27 'console_scripts': [
28   - 'aprendizations = aprendizations.serve:main',
  28 + 'aprendizations = aprendizations.main:main',
29 29 'initdb-aprendizations = aprendizations.initdb:main',
30   - 'redirect = aprendizations.redirect:main',
  30 + # 'redirect = aprendizations.redirect:main',
31 31 ]
32 32 },
33 33 classifiers=[
... ...