Browse Source

Prefer the instance path over the projects root directory for the config

Peter Justin 7 years ago
parent
commit
cbef5ee6a4
2 changed files with 86 additions and 15 deletions
  1. 33 10
      flaskbb/app.py
  2. 53 5
      flaskbb/utils/helpers.py

+ 33 - 10
flaskbb/app.py

@@ -40,7 +40,8 @@ from flaskbb.utils.helpers import (time_utcnow, format_date, time_since,
                                    crop_title, is_online, mark_online,
                                    forum_is_unread, topic_is_unread,
                                    render_template, render_markup,
-                                   app_config_from_env, get_alembic_locations)
+                                   app_config_from_env, get_flaskbb_config,
+                                   get_alembic_locations)
 from flaskbb.utils.translations import FlaskBBDomain
 # permission checks (here they are used for the jinja filters)
 from flaskbb.utils.requirements import (IsAdmin, IsAtleastModerator,
@@ -63,16 +64,31 @@ from flaskbb.plugins import spec
 logger = logging.getLogger(__name__)
 
 
-def create_app(config=None):
+def create_app(config=None, instance_path=None):
     """Creates the app.
 
+    :param instance_path: An alternative instance path for the application.
+                          By default the folder ``'instance'`` next to the
+                          package or module is assumed to be the instance
+                          path.
+                          See :ref:`Instance Folders <flask:instance-folders>`.
     :param config: The configuration file or object.
                    The environment variable is weightet as the heaviest.
                    For example, if the config is specified via an file
                    and a ENVVAR, it will load the config via the file and
                    later overwrite it from the ENVVAR.
     """
-    app = Flask("flaskbb")
+
+    app = Flask(
+        "flaskbb",
+        instance_path=instance_path,
+        instance_relative_config=True
+    )
+
+    # instance folders are not automatically created by flask
+    if not os.path.exists(app.instance_path):
+        os.makedirs(app.instance_path)
+
     configure_app(app, config)
     configure_celery_app(app, celery)
     configure_extensions(app)
@@ -94,18 +110,20 @@ def configure_app(app, config):
     """Configures FlaskBB."""
     # Use the default config and override it afterwards
     app.config.from_object('flaskbb.configs.default.DefaultConfig')
-
-    if isinstance(config, string_types) and \
-            os.path.exists(os.path.abspath(config)):
-        config = os.path.abspath(config)
+    config = get_flaskbb_config(app, config)
+    # Path
+    if isinstance(config, string_types):
         app.config.from_pyfile(config)
+    # Module
     else:
         # try to update the config from the object
         app.config.from_object(config)
+
     # Add the location of the config to the config
     app.config["CONFIG_PATH"] = config
 
-    # try to update the config via the environment variable
+    # Environment
+    # Get config file from envvar
     app.config.from_envvar("FLASKBB_SETTINGS", silent=True)
 
     # Parse the env for FLASKBB_ prefixed env variables and set
@@ -115,6 +133,13 @@ def configure_app(app, config):
     # Setting up logging as early as possible
     configure_logging(app)
 
+    if isinstance(config, string_types):
+        config_name = config
+    else:
+        config_name = "{}.{}".format(config.__module__, config.__name__)
+
+    logger.info("Using config from: {}".format(config_name))
+
     app.pluggy = FlaskBBPluginManager('flaskbb', implprefix='flaskbb_')
 
 
@@ -259,7 +284,6 @@ def configure_context_processors(app):
         """Injects the ``flaskbb_config`` config variable into the
         templates.
         """
-
         return dict(flaskbb_config=flaskbb_config, format_date=format_date)
 
 
@@ -270,7 +294,6 @@ def configure_before_handlers(app):
     def update_lastseen():
         """Updates `lastseen` before every reguest if the user is
         authenticated."""
-
         if current_user.is_authenticated:
             current_user.lastseen = time_utcnow()
             db.session.add(current_user)

+ 53 - 5
flaskbb/utils/helpers.py

@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import ast
-import glob
 import itertools
 import logging
 import operator
@@ -26,13 +25,14 @@ import requests
 import unidecode
 from babel.core import get_locale_identifier
 from babel.dates import format_timedelta as babel_format_timedelta
+from werkzeug.utils import import_string, ImportStringError
 from flask import flash, g, redirect, request, session, url_for
 from flask_allows import Permission
 from flask_babelplus import lazy_gettext as _
 from flask_login import current_user
 from flask_themes2 import get_themes_list, render_theme_template
-from flaskbb._compat import (iteritems, range_method, text_type, to_bytes,
-                             to_unicode)
+from flaskbb._compat import (iteritems, range_method, text_type, string_types,
+                             to_bytes, to_unicode)
 from flaskbb.extensions import babel, redis_store
 from flaskbb.utils.markup import markdown
 from flaskbb.utils.settings import flaskbb_config
@@ -347,10 +347,10 @@ def get_online_users(guest=False):  # pragma: no cover
     minutes = range_method(flaskbb_config['ONLINE_LAST_MINUTES'])
     if guest:
         users = redis_store.sunion(['online-guests/%d' % (current - x)
-                                   for x in minutes])
+                                    for x in minutes])
     else:
         users = redis_store.sunion(['online-users/%d' % (current - x)
-                                   for x in minutes])
+                                    for x in minutes])
 
     return [to_unicode(u) for u in users]
 
@@ -589,6 +589,54 @@ def app_config_from_env(app, prefix="FLASKBB_"):
     return app
 
 
+def get_flaskbb_config(app, config_file):
+    """Returns ``None`` or the config which is either the path to a config file
+    or an object. They can be used by ``app.config.from_pyfile`` or
+    by ``app.config.from_object``.
+
+    :param app: The app instance.
+    :param config_file: A string which is either a module that can be
+                        imported, a path to a config file or an object.
+                        If the provided config_file can't be found, it will
+                        search for a 'flaskbb.cfg' file in the instance
+                        directory and in the project's root directory.
+    """
+    if config_file is not None:
+        # config is an object
+        if not isinstance(config_file, string_types):
+            return config_file
+
+        # config is a file
+        if (
+            os.path.exists(os.path.join(app.instance_path, config_file)) or
+            os.path.exists(os.path.abspath(config_file))
+        ):
+            return config_file
+
+        # conifg is an importable string
+        try:
+            return import_string(config_file)
+        except ImportStringError:
+            return None
+    else:
+        # this would be so much nicer and cleaner if we wouldn't
+        # support the root/project dir.
+        # this walks back to flaskbb/ from flaskbb/flaskbb/cli/main.py
+        project_dir = os.path.dirname(
+            os.path.dirname(os.path.dirname(__file__))
+        )
+        project_config = os.path.join(project_dir, "flaskbb.cfg")
+
+        # instance config doesn't exist
+        instance_config = os.path.join(app.instance_path, "flaskbb.cfg")
+        if os.path.exists(instance_config):
+            return instance_config
+
+        # config in root directory doesn't exist
+        if os.path.exists(project_config):
+            return project_config
+
+
 class ReverseProxyPathFix(object):
     """Wrap the application in this middleware and configure the
     front-end server to add these headers, to let you quietly bind