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,
                                    crop_title, is_online, mark_online,
                                    forum_is_unread, topic_is_unread,
                                    forum_is_unread, topic_is_unread,
                                    render_template, render_markup,
                                    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
 from flaskbb.utils.translations import FlaskBBDomain
 # permission checks (here they are used for the jinja filters)
 # permission checks (here they are used for the jinja filters)
 from flaskbb.utils.requirements import (IsAdmin, IsAtleastModerator,
 from flaskbb.utils.requirements import (IsAdmin, IsAtleastModerator,
@@ -63,16 +64,31 @@ from flaskbb.plugins import spec
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def create_app(config=None):
+def create_app(config=None, instance_path=None):
     """Creates the app.
     """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.
     :param config: The configuration file or object.
                    The environment variable is weightet as the heaviest.
                    The environment variable is weightet as the heaviest.
                    For example, if the config is specified via an file
                    For example, if the config is specified via an file
                    and a ENVVAR, it will load the config via the file and
                    and a ENVVAR, it will load the config via the file and
                    later overwrite it from the ENVVAR.
                    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_app(app, config)
     configure_celery_app(app, celery)
     configure_celery_app(app, celery)
     configure_extensions(app)
     configure_extensions(app)
@@ -94,18 +110,20 @@ def configure_app(app, config):
     """Configures FlaskBB."""
     """Configures FlaskBB."""
     # Use the default config and override it afterwards
     # Use the default config and override it afterwards
     app.config.from_object('flaskbb.configs.default.DefaultConfig')
     app.config.from_object('flaskbb.configs.default.DefaultConfig')
-
+    config = get_flaskbb_config(app, config)
-    if isinstance(config, string_types) and \
+    # Path
-            os.path.exists(os.path.abspath(config)):
+    if isinstance(config, string_types):
-        config = os.path.abspath(config)
         app.config.from_pyfile(config)
         app.config.from_pyfile(config)
+    # Module
     else:
     else:
         # try to update the config from the object
         # try to update the config from the object
         app.config.from_object(config)
         app.config.from_object(config)
+
     # Add the location of the config to the config
     # Add the location of the config to the config
     app.config["CONFIG_PATH"] = 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)
     app.config.from_envvar("FLASKBB_SETTINGS", silent=True)
 
 
     # Parse the env for FLASKBB_ prefixed env variables and set
     # 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
     # Setting up logging as early as possible
     configure_logging(app)
     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_')
     app.pluggy = FlaskBBPluginManager('flaskbb', implprefix='flaskbb_')
 
 
 
 
@@ -259,7 +284,6 @@ def configure_context_processors(app):
         """Injects the ``flaskbb_config`` config variable into the
         """Injects the ``flaskbb_config`` config variable into the
         templates.
         templates.
         """
         """
-
         return dict(flaskbb_config=flaskbb_config, format_date=format_date)
         return dict(flaskbb_config=flaskbb_config, format_date=format_date)
 
 
 
 
@@ -270,7 +294,6 @@ def configure_before_handlers(app):
     def update_lastseen():
     def update_lastseen():
         """Updates `lastseen` before every reguest if the user is
         """Updates `lastseen` before every reguest if the user is
         authenticated."""
         authenticated."""
-
         if current_user.is_authenticated:
         if current_user.is_authenticated:
             current_user.lastseen = time_utcnow()
             current_user.lastseen = time_utcnow()
             db.session.add(current_user)
             db.session.add(current_user)

+ 53 - 5
flaskbb/utils/helpers.py

@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 import ast
 import ast
-import glob
 import itertools
 import itertools
 import logging
 import logging
 import operator
 import operator
@@ -26,13 +25,14 @@ import requests
 import unidecode
 import unidecode
 from babel.core import get_locale_identifier
 from babel.core import get_locale_identifier
 from babel.dates import format_timedelta as babel_format_timedelta
 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 import flash, g, redirect, request, session, url_for
 from flask_allows import Permission
 from flask_allows import Permission
 from flask_babelplus import lazy_gettext as _
 from flask_babelplus import lazy_gettext as _
 from flask_login import current_user
 from flask_login import current_user
 from flask_themes2 import get_themes_list, render_theme_template
 from flask_themes2 import get_themes_list, render_theme_template
-from flaskbb._compat import (iteritems, range_method, text_type, to_bytes,
+from flaskbb._compat import (iteritems, range_method, text_type, string_types,
-                             to_unicode)
+                             to_bytes, to_unicode)
 from flaskbb.extensions import babel, redis_store
 from flaskbb.extensions import babel, redis_store
 from flaskbb.utils.markup import markdown
 from flaskbb.utils.markup import markdown
 from flaskbb.utils.settings import flaskbb_config
 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'])
     minutes = range_method(flaskbb_config['ONLINE_LAST_MINUTES'])
     if guest:
     if guest:
         users = redis_store.sunion(['online-guests/%d' % (current - x)
         users = redis_store.sunion(['online-guests/%d' % (current - x)
-                                   for x in minutes])
+                                    for x in minutes])
     else:
     else:
         users = redis_store.sunion(['online-users/%d' % (current - x)
         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]
     return [to_unicode(u) for u in users]
 
 
@@ -589,6 +589,54 @@ def app_config_from_env(app, prefix="FLASKBB_"):
     return app
     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):
 class ReverseProxyPathFix(object):
     """Wrap the application in this middleware and configure the
     """Wrap the application in this middleware and configure the
     front-end server to add these headers, to let you quietly bind
     front-end server to add these headers, to let you quietly bind