From 52c25fd563f2f84b033b97a8694aa2f8a353f4fa Mon Sep 17 00:00:00 2001
From: Patrick Jentsch
Date: Wed, 15 Sep 2021 12:31:53 +0200
Subject: [PATCH] Simplify Config setup and move some functions to dedicated
files
---
.env.tpl | 22 +++-----------------
.flaskenv | 1 +
app/__init__.py | 18 +++++++++-------
app/cli.py | 25 ++++++++++++++++++++++
app/models.py | 25 +++-------------------
config.py | 44 +++++++--------------------------------
nopaque.py | 45 +++++-----------------------------------
tests/__init__.py | 6 ++++++
tests/test_basics.py | 7 ++++---
tests/test_client.py | 17 +++++++++------
tests/test_user_model.py | 7 ++++---
11 files changed, 81 insertions(+), 136 deletions(-)
create mode 100644 .flaskenv
create mode 100644 app/cli.py
diff --git a/.env.tpl b/.env.tpl
index ad27fb60..d05e16ae 100644
--- a/.env.tpl
+++ b/.env.tpl
@@ -91,9 +91,8 @@ MAIL_USERNAME=
# Flask-SQLAlchemy #
# https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/ #
################################################################################
-# DEFAULT with development config: postgresql://nopaque:nopaque@db/nopaque_dev
-# DEFAULT with production config: postgresql://nopaque:nopaque@db/nopaque
-# DEFAULT with testing config: postgresql://nopaque:nopaque@db/nopaque_test
+# DEFAULT: 'sqlite:////app.db'
+# NOTE: Use `.` as
# SQLALCHEMY_DATABASE_URI=
@@ -105,10 +104,6 @@ MAIL_USERNAME=
# EXAMPLE: admin.nopaque@example.com
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
# not set, no contact button is displayed.
# DEFAULT: None
@@ -124,24 +119,13 @@ NOPAQUE_ADMIN=
# DEFAULT: True
# 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]
NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI=
# DEFAULT: %Y-%m-%d %H:%M:%S
# NOPAQUE_LOG_DATE_FORMAT=
-# DEFAULT: /nopaque.log ~ /home/nopaque/nopaque.log
+# DEFAULT: /nopaque.log
# NOTE: Use `.` as
# NOPAQUE_LOG_FILE=
diff --git a/.flaskenv b/.flaskenv
new file mode 100644
index 00000000..1fd672d3
--- /dev/null
+++ b/.flaskenv
@@ -0,0 +1 @@
+FLASK_APP=nopaque.py
diff --git a/app/__init__.py b/app/__init__.py
index 4649a090..c03a25ea 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -1,7 +1,8 @@
-from config import config
+from config import Config
from flask import Flask
from flask_login import LoginManager
from flask_mail import Mail
+from flask_migrate import Migrate
from flask_paranoid import Paranoid
from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy
@@ -10,23 +11,26 @@ import flask_assets
assets = flask_assets.Environment()
db = SQLAlchemy()
-login_manager = LoginManager()
-login_manager.login_view = 'auth.login'
+login = LoginManager()
+login.login_view = 'auth.login'
+login.login_message = 'Please log in to access this page.'
mail = Mail()
+migrate = Migrate()
paranoid = Paranoid()
paranoid.redirect_view = '/'
socketio = SocketIO()
-def create_app(config_name):
+def create_app(config_class=Config):
app = Flask(__name__)
- app.config.from_object(config[config_name])
+ app.config.from_object(config_class)
assets.init_app(app)
- config[config_name].init_app(app)
+ config_class.init_app(app)
db.init_app(app)
- login_manager.init_app(app)
+ login.init_app(app)
mail.init_app(app)
+ migrate.init_app(app, db)
paranoid.init_app(app)
socketio.init_app(
app, message_queue=app.config['NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI'])
diff --git a/app/cli.py b/app/cli.py
new file mode 100644
index 00000000..7c0782b2
--- /dev/null
+++ b/app/cli.py
@@ -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)
diff --git a/app/models.py b/app/models.py
index abc6b9f2..5f79cb59 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,11 +1,11 @@
from datetime import datetime, timedelta
from flask import current_app, url_for
-from flask_login import UserMixin, AnonymousUserMixin
+from flask_login import UserMixin
from itsdangerous import BadSignature, TimedJSONWebSignatureSerializer
from time import sleep
from werkzeug.security import generate_password_hash, check_password_hash
import xml.etree.ElementTree as ET
-from . import db, login_manager
+from . import db, login
import base64
import logging
import os
@@ -285,18 +285,6 @@ class User(UserMixin, db.Model):
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 to define JobInputs.
@@ -722,13 +710,6 @@ class QueryResult(db.Model):
return ''.format(self.title)
-'''
-' Flask-Login is told to use the application’s custom anonymous user by setting
-' its class in the login_manager.anonymous_user attribute.
-'''
-login_manager.anonymous_user = AnonymousUser
-
-
-@login_manager.user_loader
+@login.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
diff --git a/config.py b/config.py
index 913229cb..2c99b412 100644
--- a/config.py
+++ b/config.py
@@ -1,9 +1,11 @@
+from dotenv import load_dotenv
from werkzeug.middleware.proxy_fix import ProxyFix
import logging
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:
@@ -33,6 +35,10 @@ class Config:
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'false').lower() == 'true'
''' # Flask-SQLAlchemy # '''
+ SQLALCHEMY_DATABASE_URI = os.environ.get(
+ 'SQLALCHEMY_DATABASE_URI',
+ 'sqlite:///' + os.path.join(basedir, 'app.db')
+ )
SQLALCHEMY_RECORD_QUERIES = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -52,7 +58,7 @@ class Config:
'datefmt': os.environ.get('NOPAQUE_LOG_DATE_FORMAT',
'%Y-%m-%d %H:%M:%S'),
'filename': os.environ.get('NOPAQUE_LOG_FILE',
- os.path.join(ROOT_DIR, 'nopaque.log')),
+ os.path.join(basedir, 'nopaque.log')),
'format': os.environ.get(
'NOPAQUE_LOG_FORMAT',
'[%(asctime)s] %(levelname)s in '
@@ -72,37 +78,3 @@ class Config:
'x_proto': int(os.environ.get('NOPAQUE_PROXY_FIX_X_PROTO', '0'))
}
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}
diff --git a/nopaque.py b/nopaque.py
index 53c75e29..1e32e381 100644
--- a/nopaque.py
+++ b/nopaque.py
@@ -6,23 +6,13 @@ import eventlet
eventlet.monkey_patch()
-from dotenv import load_dotenv # 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 import db, cli, create_app, socketio # noqa
from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult,
QueryResult, Role, User) # noqa
-from flask_migrate import Migrate, upgrade # noqa
-app = create_app(os.environ.get('NOPAQUE_CONFIG', 'development'))
-migrate = Migrate(app, db, compare_type=True)
+app = create_app()
+cli.register(app)
@app.shell_context_processor
@@ -38,32 +28,7 @@ def make_shell_context():
'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__':
- host = os.environ.get('NOPAQUE_HOST', '0.0.0.0')
- port = int(os.environ.get('NOPAQUE_PORT', '5000'))
+ host = '0.0.0.0'
+ port = 5000
socketio.run(app, host=host, port=port)
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29b..b36bebf9 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,6 @@
+from config import Config
+
+
+class TestConfig(Config):
+ TESTING = True
+ SQLALCHEMY_DATABASE_URI = 'sqlite://'
diff --git a/tests/test_basics.py b/tests/test_basics.py
index e52943de..df9d0ff5 100644
--- a/tests/test_basics.py
+++ b/tests/test_basics.py
@@ -1,11 +1,12 @@
-import unittest
-from flask import current_app
from app import create_app, db
+from flask import current_app
+from . import TestConfig
+import unittest
class BasicsTestCase(unittest.TestCase):
def setUp(self):
- self.app = create_app('testing')
+ self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
diff --git a/tests/test_client.py b/tests/test_client.py
index e4c0738c..89aaa74a 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1,10 +1,11 @@
-import unittest
from app import create_app, db
+from . import TestConfig
+import unittest
class FlaskClientTestCase(unittest.TestCase):
def setUp(self):
- self.app = create_app('testing')
+ self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
@@ -48,7 +49,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat'
})
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):
# register a new account with wrong username
@@ -59,7 +61,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat'
})
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):
# tries to register an account that has already been registered
@@ -78,7 +81,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat'
})
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={
'email': 'john@example.com',
'username': 'johnsmith',
@@ -86,7 +90,8 @@ class FlaskClientTestCase(unittest.TestCase):
'password2': 'cat'
})
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):
response = self.client.post('/auth/login', data={
diff --git a/tests/test_user_model.py b/tests/test_user_model.py
index adc650bd..30066087 100644
--- a/tests/test_user_model.py
+++ b/tests/test_user_model.py
@@ -1,12 +1,13 @@
-import unittest
-import time
from app import create_app, db
from app.models import User, AnonymousUser, Role, Permission
+from . import TestConfig
+import time
+import unittest
class UserModelTestCase(unittest.TestCase):
def setUp(self):
- self.app = create_app('testing')
+ self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()