import sys import os from flask_migrate import Migrate from flask import Flask, send_from_directory, request, redirect from flask_cors import CORS from flask_compress import Compress from utils import DebugUtils from database.db import lookup_db_for_binding, init_all_dbs_with_app, init_binding_to_db_for_bindings from config.config import DEBUG_MODE, get_app_config, get_static_url # The environment variable whose value is the PORT this application will listen on PORT_ENV_VAR_KEY = "PORT" # Create and configure our Flask app app = Flask(__name__, static_url_path=get_static_url()) app.url_map.strict_slashes = False # Enable Gzip Compression on all responses Compress(app) # Enable CORS for all routes CORS(app) # NOTE: if there is an error in your route, e.g.: printing a variable that doesn't # exist, then you will get a CORS related error which has nothing to do with CORS # Source: https://stackoverflow.com/questions/25594893/how-to-enable-cors-in-flask # Load config flask_app_config = get_app_config() app.config.from_object(flask_app_config) init_binding_to_db_for_bindings(flask_app_config.SQLALCHEMY_BINDS.keys()) # Initialise all databases init_all_dbs_with_app(app) # The import below registers all models to their respective database from models import * # Register all blueprints/routes from blueprints import home_blueprint, persons_blueprint # from blueprints.auth import auth_blueprint app.register_blueprint(home_blueprint) app.register_blueprint(persons_blueprint) # app.register_blueprint(auth_blueprint) # ===================== MIGRATION SETUP ===================== APP_CONFIG_BINDINGS_DICT_KEY = "SQLALCHEMY_BINDS" # The below environment variables should be set by the script that initializes the migration # This environment variable is used by "Flask_Migrate" and should not be changed FLASK_MIGRATE_FLASK_APP_ENV_VAR_KEY = "FLASK_APP" # This environment variable is a hack to specify the DB we want to migrate, since we have multiple dbs # and only require/want to migrate one at a time. FLASK_DB_BINDING_ENV_VAR_KEY = "FLASK_DB_BINDING_TO_MIGRATE" MIGRATION_MODE = os.environ.get(FLASK_MIGRATE_FLASK_APP_ENV_VAR_KEY, None) is not None if MIGRATION_MODE: binding_name = os.environ.get(FLASK_DB_BINDING_ENV_VAR_KEY, None) if not binding_name: print("Invalid binding name specified:", binding_name) exit(-1) with app.app_context(): print("Initializing migration") # Source: https://flask-migrate.readthedocs.io/en/latest/ # After the extension is initialized, a "db" group will be added to the command-line options with several # sub-commands bindings_dict = app.config[APP_CONFIG_BINDINGS_DICT_KEY] # Set the main db app.config["SQLALCHEMY_DATABASE_URI"] = bindings_dict[binding_name] # engine = db.get_engine(flask_app, bind_name) migrate = Migrate() # "db" arg below needs to be the database you want the above main db to become/migrate to db_to_migrate = lookup_db_for_binding(binding_name) migrate.init_app(app, db_to_migrate) # ===================== End MIGRATION SETUP. ===================== # Get arguments to this program call if not MIGRATION_MODE: # The Flask Migrate CLI passes arguments from its own CLI, and we don't want to handle those. from cli_handler import handle_cli cli_args = sys.argv[1:] if len(cli_args) > 0: if DEBUG_MODE: with app.app_context(): handle_cli(app, lookup_db_for_binding, cli_args) exit(1) else: DebugUtils.print_warning("Production application should not be run with CLI arguments, " "if you intended to run the CLI please run in development mode.") exit(-1) # Serve all static assets for the frontend @app.route('/static/<path:path>') def serve_static_files(path): return send_from_directory('static', path) # Force secure connection @app.before_request def before_request(): if not DEBUG_MODE: scheme = request.headers.get('X-Forwarded-Proto') if scheme and scheme == 'http' and request.url.startswith('http://'): url = request.url.replace('http://', 'https://', 1) code = 301 return redirect(url, code=code) # Hook any custom Jinja templating functions from config import CUSTOM_TEMPLATE_FUNCTIONS app.jinja_env.globals.update(CUSTOM_TEMPLATE_FUNCTIONS) if __name__ == '__main__': host = '127.0.0.1' port = int(os.environ.get(PORT_ENV_VAR_KEY, 5000)) if DEBUG_MODE: mode_desc = "debug" else: mode_desc = "production" # NOTE: it will say "Environment: production", but if DEBUG_MODE=True, then ignore that. DebugUtils.print_warning(f"Running in {mode_desc} mode") app.run(debug=DEBUG_MODE, host=host, port=port)