Simplify Config setup and move some functions to dedicated files

This commit is contained in:
Patrick Jentsch 2021-09-15 12:31:53 +02:00
parent 8a69d6364a
commit 52c25fd563
11 changed files with 81 additions and 136 deletions

View File

@ -91,9 +91,8 @@ MAIL_USERNAME=
# Flask-SQLAlchemy # # Flask-SQLAlchemy #
# https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/ # # https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/ #
################################################################################ ################################################################################
# DEFAULT with development config: postgresql://nopaque:nopaque@db/nopaque_dev # DEFAULT: 'sqlite:///<nopaque-root-dir>/app.db'
# DEFAULT with production config: postgresql://nopaque:nopaque@db/nopaque # NOTE: Use `.` as <nopaque-root-dir>
# DEFAULT with testing config: postgresql://nopaque:nopaque@db/nopaque_test
# SQLALCHEMY_DATABASE_URI= # SQLALCHEMY_DATABASE_URI=
@ -105,10 +104,6 @@ MAIL_USERNAME=
# EXAMPLE: admin.nopaque@example.com # EXAMPLE: admin.nopaque@example.com
NOPAQUE_ADMIN= NOPAQUE_ADMIN=
# DEFAULT: development
# CHOOSE ONE: development, production, testing
# NOPAQUE_CONFIG=
# This email adress is used for the contact button in the nopaque footer. If # This email adress is used for the contact button in the nopaque footer. If
# not set, no contact button is displayed. # not set, no contact button is displayed.
# DEFAULT: None # DEFAULT: None
@ -124,24 +119,13 @@ NOPAQUE_ADMIN=
# DEFAULT: True # DEFAULT: True
# NOPAQUE_DAEMON_ENABLED= # NOPAQUE_DAEMON_ENABLED=
# The hostname or IP address for the server to listen on.
# DEFAULT: 0.0.0.0
# NOTES: To use a domain locally, add any names that should route to the app to your hosts file.
# If nopaque is running in a Docker container, you propably want to use the default value.
# NOPAQUE_HOST=
# The port number for the server to listen on.
# DEFAULT: 5000
# NOTE: If nopaque is running in a Docker container, you propably want to use the default value.
# NOPAQUE_PORT=
# transport://[userid:password]@hostname[:port]/[virtual_host] # transport://[userid:password]@hostname[:port]/[virtual_host]
NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI= NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI=
# DEFAULT: %Y-%m-%d %H:%M:%S # DEFAULT: %Y-%m-%d %H:%M:%S
# NOPAQUE_LOG_DATE_FORMAT= # NOPAQUE_LOG_DATE_FORMAT=
# DEFAULT: <nopaque-root-dir>/nopaque.log ~ /home/nopaque/nopaque.log # DEFAULT: <nopaque-root-dir>/nopaque.log
# NOTE: Use `.` as <nopaque-root-dir> # NOTE: Use `.` as <nopaque-root-dir>
# NOPAQUE_LOG_FILE= # NOPAQUE_LOG_FILE=

1
.flaskenv Normal file
View File

@ -0,0 +1 @@
FLASK_APP=nopaque.py

View File

@ -1,7 +1,8 @@
from config import config from config import Config
from flask import Flask from flask import Flask
from flask_login import LoginManager from flask_login import LoginManager
from flask_mail import Mail from flask_mail import Mail
from flask_migrate import Migrate
from flask_paranoid import Paranoid from flask_paranoid import Paranoid
from flask_socketio import SocketIO from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
@ -10,23 +11,26 @@ import flask_assets
assets = flask_assets.Environment() assets = flask_assets.Environment()
db = SQLAlchemy() db = SQLAlchemy()
login_manager = LoginManager() login = LoginManager()
login_manager.login_view = 'auth.login' login.login_view = 'auth.login'
login.login_message = 'Please log in to access this page.'
mail = Mail() mail = Mail()
migrate = Migrate()
paranoid = Paranoid() paranoid = Paranoid()
paranoid.redirect_view = '/' paranoid.redirect_view = '/'
socketio = SocketIO() socketio = SocketIO()
def create_app(config_name): def create_app(config_class=Config):
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config[config_name]) app.config.from_object(config_class)
assets.init_app(app) assets.init_app(app)
config[config_name].init_app(app) config_class.init_app(app)
db.init_app(app) db.init_app(app)
login_manager.init_app(app) login.init_app(app)
mail.init_app(app) mail.init_app(app)
migrate.init_app(app, db)
paranoid.init_app(app) paranoid.init_app(app)
socketio.init_app( socketio.init_app(
app, message_queue=app.config['NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI']) app, message_queue=app.config['NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI'])

25
app/cli.py Normal file
View File

@ -0,0 +1,25 @@
from .models import Role
from flask_migrate import upgrade
def register(app):
@app.cli.command()
def deploy():
"""Run deployment tasks."""
# migrate database to latest revision
upgrade()
# create or update user roles
Role.insert_roles()
@app.cli.command()
def tasks():
from app.tasks import TaskRunner
task_runner = TaskRunner()
task_runner.run()
@app.cli.command()
def test():
"""Run the unit tests."""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)

View File

@ -1,11 +1,11 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import current_app, url_for from flask import current_app, url_for
from flask_login import UserMixin, AnonymousUserMixin from flask_login import UserMixin
from itsdangerous import BadSignature, TimedJSONWebSignatureSerializer from itsdangerous import BadSignature, TimedJSONWebSignatureSerializer
from time import sleep from time import sleep
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from . import db, login_manager from . import db, login
import base64 import base64
import logging import logging
import os import os
@ -285,18 +285,6 @@ class User(UserMixin, db.Model):
return user return user
class AnonymousUser(AnonymousUserMixin):
'''
Model replaces the default AnonymousUser.
'''
def can(self, permissions):
return False
def is_administrator(self):
return False
class JobInput(db.Model): class JobInput(db.Model):
''' '''
Class to define JobInputs. Class to define JobInputs.
@ -722,13 +710,6 @@ class QueryResult(db.Model):
return '<QueryResult {}>'.format(self.title) return '<QueryResult {}>'.format(self.title)
''' @login.user_loader
' Flask-Login is told to use the applications custom anonymous user by setting
' its class in the login_manager.anonymous_user attribute.
'''
login_manager.anonymous_user = AnonymousUser
@login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return User.query.get(int(user_id)) return User.query.get(int(user_id))

View File

@ -1,9 +1,11 @@
from dotenv import load_dotenv
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
import logging import logging
import os import os
ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config: class Config:
@ -33,6 +35,10 @@ class Config:
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'false').lower() == 'true' MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'false').lower() == 'true'
''' # Flask-SQLAlchemy # ''' ''' # Flask-SQLAlchemy # '''
SQLALCHEMY_DATABASE_URI = os.environ.get(
'SQLALCHEMY_DATABASE_URI',
'sqlite:///' + os.path.join(basedir, 'app.db')
)
SQLALCHEMY_RECORD_QUERIES = True SQLALCHEMY_RECORD_QUERIES = True
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
@ -52,7 +58,7 @@ class Config:
'datefmt': os.environ.get('NOPAQUE_LOG_DATE_FORMAT', 'datefmt': os.environ.get('NOPAQUE_LOG_DATE_FORMAT',
'%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S'),
'filename': os.environ.get('NOPAQUE_LOG_FILE', 'filename': os.environ.get('NOPAQUE_LOG_FILE',
os.path.join(ROOT_DIR, 'nopaque.log')), os.path.join(basedir, 'nopaque.log')),
'format': os.environ.get( 'format': os.environ.get(
'NOPAQUE_LOG_FORMAT', 'NOPAQUE_LOG_FORMAT',
'[%(asctime)s] %(levelname)s in ' '[%(asctime)s] %(levelname)s in '
@ -72,37 +78,3 @@ class Config:
'x_proto': int(os.environ.get('NOPAQUE_PROXY_FIX_X_PROTO', '0')) 'x_proto': int(os.environ.get('NOPAQUE_PROXY_FIX_X_PROTO', '0'))
} }
app.wsgi_app = ProxyFix(app.wsgi_app, **proxy_fix_kwargs) app.wsgi_app = ProxyFix(app.wsgi_app, **proxy_fix_kwargs)
class DevelopmentConfig(Config):
''' # Flask # '''
DEBUG = True
''' # Flask-SQLAlchemy # '''
SQLALCHEMY_DATABASE_URI = os.environ.get(
'SQLALCHEMY_DATABASE_URI',
'postgresql://nopaque:nopaque@db/nopaque_dev'
)
class ProductionConfig(Config):
''' # Flask-SQLAlchemy # '''
SQLALCHEMY_DATABASE_URI = os.environ.get(
'SQLALCHEMY_DATABASE_URI', 'postgresql://nopaque:nopaque@db/nopaque')
class TestingConfig(Config):
''' # Flask # '''
TESTING = True
WTF_CSRF_ENABLED = False
''' # Flask-SQLAlchemy # '''
SQLALCHEMY_DATABASE_URI = os.environ.get(
'SQLALCHEMY_DATABASE_URI',
'postgresql://nopaque:nopaque@db/nopaque_test'
)
config = {'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig}

View File

@ -6,23 +6,13 @@ import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
from dotenv import load_dotenv # noqa from app import db, cli, create_app, socketio # noqa
import os # noqa
# Load environment variables
DOTENV_FILE = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(DOTENV_FILE):
load_dotenv(DOTENV_FILE)
from app import create_app, db, socketio # noqa
from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult, from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult,
QueryResult, Role, User) # noqa QueryResult, Role, User) # noqa
from flask_migrate import Migrate, upgrade # noqa
app = create_app(os.environ.get('NOPAQUE_CONFIG', 'development')) app = create_app()
migrate = Migrate(app, db, compare_type=True) cli.register(app)
@app.shell_context_processor @app.shell_context_processor
@ -38,32 +28,7 @@ def make_shell_context():
'User': User} 'User': User}
@app.cli.command()
def deploy():
"""Run deployment tasks."""
# migrate database to latest revision
upgrade()
# create or update user roles
Role.insert_roles()
@app.cli.command()
def tasks():
from app.tasks import TaskRunner
task_runner = TaskRunner()
task_runner.run()
@app.cli.command()
def test():
"""Run the unit tests."""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
if __name__ == '__main__': if __name__ == '__main__':
host = os.environ.get('NOPAQUE_HOST', '0.0.0.0') host = '0.0.0.0'
port = int(os.environ.get('NOPAQUE_PORT', '5000')) port = 5000
socketio.run(app, host=host, port=port) socketio.run(app, host=host, port=port)

View File

@ -0,0 +1,6 @@
from config import Config
class TestConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'

View File

@ -1,11 +1,12 @@
import unittest
from flask import current_app
from app import create_app, db from app import create_app, db
from flask import current_app
from . import TestConfig
import unittest
class BasicsTestCase(unittest.TestCase): class BasicsTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.app = create_app('testing') self.app = create_app(TestConfig)
self.app_context = self.app.app_context() self.app_context = self.app.app_context()
self.app_context.push() self.app_context.push()
db.create_all() db.create_all()

View File

@ -1,10 +1,11 @@
import unittest
from app import create_app, db from app import create_app, db
from . import TestConfig
import unittest
class FlaskClientTestCase(unittest.TestCase): class FlaskClientTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.app = create_app('testing') self.app = create_app(TestConfig)
self.app_context = self.app.app_context() self.app_context = self.app.app_context()
self.app_context.push() self.app_context.push()
db.create_all() db.create_all()
@ -48,7 +49,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat' 'password2': 'cat'
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue('Usernames must have only letters, numbers, dots or underscores' in response.get_data(as_text=True)) self.assertTrue(
'Usernames must have only letters, numbers, dots or underscores' in response.get_data(as_text=True))
def test_register_false_email(self): def test_register_false_email(self):
# register a new account with wrong username # register a new account with wrong username
@ -59,7 +61,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat' 'password2': 'cat'
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue('Invalid email address.' in response.get_data(as_text=True)) self.assertTrue(
'Invalid email address.' in response.get_data(as_text=True))
def test_duplicates(self): def test_duplicates(self):
# tries to register an account that has already been registered # tries to register an account that has already been registered
@ -78,7 +81,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat' 'password2': 'cat'
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue('Username already in use.' in response.get_data(as_text=True)) self.assertTrue(
'Username already in use.' in response.get_data(as_text=True))
response = self.client.post('/auth/register', data={ response = self.client.post('/auth/register', data={
'email': 'john@example.com', 'email': 'john@example.com',
'username': 'johnsmith', 'username': 'johnsmith',
@ -86,7 +90,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat' 'password2': 'cat'
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue('Email already registered.' in response.get_data(as_text=True)) self.assertTrue(
'Email already registered.' in response.get_data(as_text=True))
def test_admin_forbidden(self): def test_admin_forbidden(self):
response = self.client.post('/auth/login', data={ response = self.client.post('/auth/login', data={

View File

@ -1,12 +1,13 @@
import unittest
import time
from app import create_app, db from app import create_app, db
from app.models import User, AnonymousUser, Role, Permission from app.models import User, AnonymousUser, Role, Permission
from . import TestConfig
import time
import unittest
class UserModelTestCase(unittest.TestCase): class UserModelTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.app = create_app('testing') self.app = create_app(TestConfig)
self.app_context = self.app.app_context() self.app_context = self.app.app_context()
self.app_context.push() self.app_context.push()
db.create_all() db.create_all()