Source code for backend.config

import os
import redis

from appdirs import AppDirs
from datetime import timedelta

from backend.utils.date import utcnow

APP_NAME = 'flask-react-spa'
app_dirs = AppDirs(APP_NAME)
APP_CACHE_FOLDER = app_dirs.user_cache_dir
APP_DATA_FOLDER = app_dirs.user_data_dir

PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
TEMPLATE_FOLDER = os.path.join(PROJECT_ROOT, 'backend', 'templates')
STATIC_FOLDER = os.environ.get('FLASK_STATIC_FOLDER',
                               os.path.join(PROJECT_ROOT, 'static'))
STATIC_URL_PATH = '/static'  # serve asset files in static/ at /static/

# blog articles configuration
ARTICLES_FOLDER = os.path.join(PROJECT_ROOT, 'articles')
ARTICLE_PREVIEW_LENGTH = 400
FRONTMATTER_LIST_DELIMETER = ','
MARKDOWN_EXTENSIONS = ['extra']
DEFAULT_ARTICLE_AUTHOR_EMAIL = 'a@a.com'
SERIES_FILENAME = 'series.md'
ARTICLE_FILENAME = 'article.md'
ARTICLE_STYLESHEET_FILENAME = 'styles.css'

# list of bundle modules to register with the app, in dot notation
BUNDLES = [
    'backend.admin',
    'backend.blog',
    'backend.security',
    'backend.site',
]

# ordered list of extensions to register before the bundles
# syntax is import.name.in.dot.module.notation:extension_instance_name
EXTENSIONS = [
    'backend.extensions:session',               # should be first
    'backend.extensions:csrf',                  # should be second
    'backend.extensions:db',
    'backend.extensions:alembic',               # must come after db
    'backend.extensions.celery:celery',
    'backend.extensions.mail:mail',
    'backend.extensions.marshmallow:ma',        # must come after db
    'backend.extensions.security:security',     # must come after celery and mail
]

# list of extensions to register after the bundles
# syntax is import.name.in.dot.module.notation:extension_instance_name
DEFERRED_EXTENSIONS = [
    'backend.extensions.api:api',
    'backend.extensions.admin:admin',
]

# Declare role inheritances
# Keys here correspond to roles a user explicitly has (as set in the database).
# Values should be a list of "inherited" roles. There is also a special flag,
# __CRUD__, which expands into the standard CREATE, VIEW, EDIT and DELETE roles.
# Role inheritances are loaded recursively, so, for example given the following:
# ROLE_HIERARCHY = {
#     'ROLE_ADMIN': ['ROLE_USER'],
#     'ROLE_USER': ['ROLE_POST'],
#     'ROLE_POST': ['__CRUD__'],
#     'ROLE_GUEST': ['ROLE_POST_VIEW']
# }
# Then ROLE_ADMIN users will also get ROLE_USER, ROLE_POST, ROLE_POST_CREATE,
# ROLE_POST_VIEW, ROLE_POST_EDIT, and ROLE_POST_DELETE roles.
# Likewise, ROLE_USER users will inherit the ROLE_POST, ROLE_POST_CREATE,
# ROLE_POST_VIEW, ROLE_POST_EDIT, and ROLE_POST_DELETE roles.
# However, ROLE_GUEST users will only inherit the ROLE_POST_VIEW role. (note
# that if you want unauthenticated users to have the ROLE_GUEST role, you'll
# need to implement and register a custom AnonymousUser class)
ROLE_HIERARCHY = {
    'ROLE_ADMIN': ['ROLE_USER'],
}


def get_boolean_env(name, default):
    default = 'true' if default else 'false'
    return os.getenv(name, default).lower() in ['true', 'yes', '1']


[docs]class BaseConfig(object): ########################################################################## # flask # ########################################################################## DEBUG = get_boolean_env('FLASK_DEBUG', False) SECRET_KEY = os.environ.get('FLASK_SECRET_KEY', 'not-secret-key') # FIXME STRICT_SLASHES = False BUNDLES = BUNDLES ########################################################################## # session/cookies # ########################################################################## SESSION_TYPE = 'redis' SESSION_REDIS = redis.Redis( host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'), port=int(os.getenv('FLASK_REDIS_PORT', 6379)), ) SESSION_PROTECTION = 'strong' SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = True REMEMBER_COOKIE_HTTPONLY = True # SECURITY_TOKEN_MAX_AGE is fixed from time of token generation; # it does not update on refresh like a session timeout would. for that, # we set (the ironically named) PERMANENT_SESSION_LIFETIME PERMANENT_SESSION_LIFETIME = timedelta(minutes=60) ########################################################################## # database # ########################################################################## SQLALCHEMY_TRACK_MODIFICATIONS = False ALEMBIC = { 'script_location': os.path.join(PROJECT_ROOT, 'migrations'), } ########################################################################## # celery # ########################################################################## CELERY_BROKER_URL = 'redis://{host}:{port}/0'.format( host=os.getenv('FLASK_REDIS_HOST', '127.0.0.1'), port=os.getenv('FLASK_REDIS_PORT', 6379), ) CELERY_RESULT_BACKEND = CELERY_BROKER_URL CELERY_ACCEPT_CONTENT = ('json', 'pickle') ########################################################################## # mail # ########################################################################## MAIL_ADMINS = ('admin@example.com',) # FIXME MAIL_SERVER = os.environ.get('FLASK_MAIL_HOST', 'localhost') MAIL_PORT = int(os.environ.get('FLASK_MAIL_PORT', 25)) MAIL_USE_TLS = get_boolean_env('FLASK_MAIL_USE_TLS', False) MAIL_USE_SSL = get_boolean_env('FLASK_MAIL_USE_SSL', False) MAIL_USERNAME = os.environ.get('FLASK_MAIL_USERNAME', None) MAIL_PASSWORD = os.environ.get('FLASK_MAIL_PASSWORD', None) MAIL_DEFAULT_SENDER = ( os.environ.get('FLASK_MAIL_DEFAULT_SENDER_NAME', 'Flask React SPA'), os.environ.get('FLASK_MAIL_DEFAULT_SENDER_EMAIL', f"noreply@{os.environ.get('FLASK_DOMAIN', 'localhost')}") ) ########################################################################## # security # ########################################################################## SECURITY_DATETIME_FACTORY = utcnow # specify which user field attributes can be used for login SECURITY_USER_IDENTITY_ATTRIBUTES = ['email', 'username'] # NOTE: itsdangerous "salts" are not normal salts in the cryptographic # sense, see https://pythonhosted.org/itsdangerous/#the-salt SECURITY_PASSWORD_SALT = os.environ.get('FLASK_SECURITY_PASSWORD_SALT', 'security-password-salt') # disable flask-security's use of .txt templates (instead we # generate the plain text from the html message) SECURITY_EMAIL_PLAINTEXT = False # enable forgot password functionality SECURITY_RECOVERABLE = True # enable email confirmation before allowing login SECURITY_CONFIRMABLE = True # this setting is parsed as a kwarg to timedelta, so the time unit must # always be plural SECURITY_CONFIRM_EMAIL_WITHIN = '7 days' # default 5 days # urls for the *frontend* router SECURITY_CONFIRM_ERROR_VIEW = '/sign-up/resend-confirmation-email' SECURITY_POST_CONFIRM_VIEW = '/?welcome'
[docs]class ProdConfig(BaseConfig): ########################################################################## # flask # ########################################################################## ENV = 'prod' DEBUG = get_boolean_env('FLASK_DEBUG', False) ########################################################################## # database # ########################################################################## SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{db_name}'.format( user=os.environ.get('FLASK_DATABASE_USER', 'flask_api'), password=os.environ.get('FLASK_DATABASE_PASSWORD', 'flask_api'), host=os.environ.get('FLASK_DATABASE_HOST', '127.0.0.1'), port=os.environ.get('FLASK_DATABASE_PORT', 5432), db_name=os.environ.get('FLASK_DATABASE_NAME', 'flask_api'), ) ########################################################################## # session/cookies # ########################################################################## SESSION_COOKIE_DOMAIN = os.environ.get('FLASK_DOMAIN', 'example.com') # FIXME SESSION_COOKIE_SECURE = get_boolean_env('SESSION_COOKIE_SECURE', True)
[docs]class DevConfig(BaseConfig): ########################################################################## # flask # ########################################################################## ENV = 'dev' DEBUG = get_boolean_env('FLASK_DEBUG', True) # EXPLAIN_TEMPLATE_LOADING = True ########################################################################## # session/cookies # ########################################################################## SESSION_COOKIE_SECURE = False ########################################################################## # database # ########################################################################## SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{db_name}'.format( user=os.environ.get('FLASK_DATABASE_USER', 'flask_api'), password=os.environ.get('FLASK_DATABASE_PASSWORD', 'flask_api'), host=os.environ.get('FLASK_DATABASE_HOST', '127.0.0.1'), port=os.environ.get('FLASK_DATABASE_PORT', 5432), db_name=os.environ.get('FLASK_DATABASE_NAME', 'flask_api'), ) # SQLALCHEMY_ECHO = True ########################################################################## # mail # ########################################################################## MAIL_PORT = 1025 # MailHog MAIL_DEFAULT_SENDER = ('Flask React SPA', 'noreply@localhost') ########################################################################## # security # ########################################################################## SECURITY_CONFIRMABLE = True SECURITY_CONFIRM_EMAIL_WITHIN = '1 minutes' # for testing
[docs]class TestConfig(BaseConfig): TESTING = True DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite://' # :memory: WTF_CSRF_ENABLED = False SECURITY_PASSWORD_HASH_OPTIONS = dict(bcrypt={'rounds': 4}) SECURITY__SEND_MAIL_TASK = None