main.py 5 KB
#!/usr/bin/env python3

# python standard library
import argparse
import logging
import os
from os import environ, path
import ssl
import sys
# from typing import Any, Dict

# this project
from .app import App
from .serve import run_webserver
from .tools import load_yaml
from . import APP_NAME, APP_VERSION


# ----------------------------------------------------------------------------
def parse_cmdline_arguments():
    '''
    Get command line arguments
    '''
    parser = argparse.ArgumentParser(
        description='Server for online tests. Enrolled students and tests '
        'have to be previously configured. Please read the documentation '
        'included with this software before running the server.')
    parser.add_argument('testfile',
                        type=str, nargs='?',
                        help='tests in YAML format')
    parser.add_argument('--allow-all',
                        action='store_true',
                        help='Allow all students to login immediately')
    parser.add_argument('--debug',
                        action='store_true',
                        help='Enable debug messages')
    parser.add_argument('--show-ref',
                        action='store_true',
                        help='Show question references')
    parser.add_argument('--review',
                        action='store_true',
                        help='Review mode: doesn\'t generate test')
    parser.add_argument('--port',
                        type=int, default=8443,
                        help='port for the HTTPS server (default: 8443)')
    parser.add_argument('-v', '--version', action='store_true',
                        help='Show version information and exit')
    return parser.parse_args()


# ----------------------------------------------------------------------------
def get_logger_config(debug=False):
    '''
    Load logger configuration from ~/.config directory if exists,
    otherwise set default paramenters.
    '''
    if debug:
        filename = 'logger-debug.yaml'
        level = 'DEBUG'
    else:
        filename = 'logger.yaml'
        level = 'INFO'

    config_dir = environ.get('XDG_CONFIG_HOME', '~/.config/')
    config_file = path.join(path.expanduser(config_dir), APP_NAME, filename)

    default_config = {
        'version': 1,
        'formatters': {
            'standard': {
                'format': '%(asctime)s %(levelname)-8s %(message)s',
                'datefmt': '%H:%M',
                },
            },
        'handlers': {
            'default': {
                'level': level,
                'class': 'logging.StreamHandler',
                'formatter': 'standard',
                'stream': 'ext://sys.stdout',
                },
            },
        'loggers': {
            '': {  # configuration for serve.py
                'handlers': ['default'],
                'level': level,
                },
            },
        }
    default_config['loggers'].update({
        APP_NAME+'.'+module: {
            'handlers': ['default'],
            'level': level,
            'propagate': False,
            } for module in ['app', 'models', 'factory', 'questions',
                             'test', 'tools']})

    return load_yaml(config_file, default=default_config)


# ----------------------------------------------------------------------------
def main():
    '''
    Tornado web server
    '''
    args = parse_cmdline_arguments()

    if args.version:
        print(f'{APP_NAME} {APP_VERSION}\nPython {sys.version}')
        sys.exit(0)

    # --- Setup logging ------------------------------------------------------
    logging.config.dictConfig(get_logger_config(args.debug))
    logging.info('====================== Start Logging ======================')

    # --- start application --------------------------------------------------
    config = {
        'testfile': args.testfile,
        'debug':    args.debug,
        'allow_all': args.allow_all,
        'show_ref': args.show_ref,
        'review':   args.review,
        }

    # testapp = App(config)
    try:
        testapp = App(config)
    except Exception:
        logging.critical('Failed to start application.')
        sys.exit(-1)

    # --- get SSL certificates -----------------------------------------------
    if 'XDG_DATA_HOME' in os.environ:
        certs_dir = path.join(os.environ['XDG_DATA_HOME'], 'certs')
    else:
        certs_dir = path.expanduser('~/.local/share/certs')

    ssl_opt = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    try:
        ssl_opt.load_cert_chain(path.join(certs_dir, 'cert.pem'),
                                path.join(certs_dir, 'privkey.pem'))
    except FileNotFoundError:
        logging.critical('SSL certificates missing in %s', certs_dir)
        sys.exit(-1)

    # --- run webserver ----------------------------------------------------
    run_webserver(app=testapp, ssl_opt=ssl_opt, port=args.port,
                  debug=args.debug)


# ----------------------------------------------------------------------------
if __name__ == "__main__":
    main()