Sakuten/backend

View on GitHub
api/app.py

Summary

Maintainability
A
25 mins
Test Coverage
from flask import Flask, current_app
from flask_cors import CORS
import sqlalchemy
from .routes import auth, api
from .swagger import swag
from .models import db, User
from cards.id import load_id_json_file, decode_public_id
import os
import sys
import json
import base64

config = {
    "development": "api.config.DevelopmentConfig",
    "testing": "api.config.TestingConfig",
    "preview": "api.config.PreviewDeploymentConfig",
    "deployment": "api.config.DeploymentConfig",
    "default": "api.config.DevelopmentConfig"
}


def create_app():
    """create base flask application
        1. generate flask application
        2. set config based on 'FLASK_CONFIGURATION'
        3. initialize DB
        4. some other settings

        Args:
            no-args needed

        ENVIRONMENT_VALIABLES:
            FLASK_CONFIGURATION (str): define config type.
                ('defalut'|'development'|'testing'|'preview'|'deployment')
        Return:
            app (Flask): generated flask application
        Exit Status:
            4 : SQLALCHEMY_DATABASE_URI or SECRET_KEY is not set.

    """
    app = Flask(__name__, instance_relative_config=True)

    # Allow to access with or without trailing slash
    app.url_map.strict_slashes = False

    CORS(app, supports_credentials=True)

    # load app sepcified configuration
    config_name = os.getenv('FLASK_CONFIGURATION', 'default')

    app.config.from_object(config[config_name])
    app.config.from_pyfile('config.cfg', silent=True)

    if app.config.get('SQLALCHEMY_DATABASE_URI', None) is None:
        app.logger.error(
            "SQLALCHEMY_DATABASE_URI is not set."
            "Didn't you forget to set it in "
            "DATABASE_URL environmental variable?")
        sys.exit(4)  # Return 4 to exit gunicorn

    if app.config.get('SECRET_KEY', None) is None:
        app.logger.error(
            "SECRET_KEY is not set."
            "Didn't you forget to set it in "
            "SECRET_KEY environmental variable?")
        sys.exit(4)  # Return 4 to exit gunicorn

    db.init_app(app)
    swag.init_app(app)

    app.register_blueprint(auth.bp)
    app.register_blueprint(api.bp)

    app.before_first_request(init_and_generate)

    return app


def init_and_generate():
    """
        Intialize and generate DB if needed,
        depends on DB_GEN_POLICY and DB_FORCE_INIT.

        application context is required.

        * DB_GEN_POLICY=[always|first_time|never]
          * always: Generates initial data every time.
                    (existing User, Classroom, Lottery, Error is deleted)
          * first_time: When initial data is not generated yes, generates.
          * never: Never generates initial data (for deployments)

        * DB_FORCE_INIT=[true|false]
          * true: Deletes all tables and re-creates
          * false: If there is no tables, creates them

        Args:
        Return:
    """
    policy = current_app.config['DB_GEN_POLICY']
    force_init = current_app.config['DB_FORCE_INIT']
    is_empty = len(sqlalchemy.inspect(db.engine).get_table_names()) == 0
    if force_init and not is_empty:
        current_app.logger.warning('Dropping all tables because '
                                   'DB_FORCE_INIT == true')
        db.drop_all()
    if force_init or is_empty:
        current_app.logger.warning('Creating all tables')
        db.create_all()
    if policy == 'always' or \
            (policy == 'first_time' and len(User.query.all()) == 0):
        current_app.logger.warning(
            'Generating Initial Data for Database '
            f'(DB_GEN_POLICY: {policy})')
        generate()
    elif policy != 'never' and policy != 'first_time':
        current_app.logger.warning(
            f'Unknown DB_GEN_POLICY: {policy}. Treated as \'never\'.')


def initdb(app, db):
    from api.models import db
    db.create_all()


def generate():
    """generate DB contents
        1. generate classrooms
        2. generate lotteries
        3. generate users

        Args:
            no-args needed
        Return:
            no-return given
    """
    from .models import Lottery, Classroom, User, Error, db
    total_index = 4

    Classroom.query.delete()
    Lottery.query.delete()
    cl_list_path = current_app.config['CLASSROOM_TABLE_FILE']
    classroom_list = load_id_json_file(cl_list_path)
    for class_data in classroom_list:
        # add classroom
        title_enc = base64.b64encode(class_data['title'].encode('utf-8'))
        room = Classroom(grade=class_data['grade'],
                         index=class_data['index'],
                         title=title_enc.decode('utf-8'))
        db.session.add(room)

        # add lotteries
        # `room` has no id yet. so, get new_classroom from DB
        new_room = Classroom.query.filter_by(grade=class_data['grade'],
                                             index=class_data['index']).first()
        for perf_index in range(total_index):
            lottery = Lottery(classroom_id=new_room.id, index=perf_index)
            db.session.add(lottery)

    User.query.delete()
    json_path = current_app.config['ID_LIST_FILE']
    id_list = load_id_json_file(json_path)
    for ids in id_list:
        user = User(secret_id=ids['secret_id'],
                    public_id=decode_public_id(ids['public_id']),
                    authority=ids['authority'],
                    kind=ids['kind'])
        db.session.add(user)

    db.session.commit()

    Error.query.delete()
    json_path = current_app.config['ERROR_TABLE_FILE']
    with open(json_path, 'r') as f:
        error_list = json.load(f)
    for (code, desc) in error_list.items():
        error = Error(code=int(code, 10),
                      message=desc['message'], http_code=desc['status'])
        db.session.add(error)

    db.session.commit()