diff --git a/CHANGES b/CHANGES index c1d33869..817c21f4 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,11 @@ Relase date to be decided, codename to be chosen. - Malformed JSON data will now trigger a bad request HTTP exception instead of a value error which usually would result in a 500 internal server error if not handled. This is a backwards incompatible change. +- Applications now not only have a root path where the resources and modules + are located but also an instane path which is the designated place to + drop files that are modified at runtime (uploads etc.). Also this is + conceptionally only instance depending and outside version control so it's + the perfect place to put configuration files etc. Version 0.7.3 ------------- diff --git a/flask/app.py b/flask/app.py index 00c36a38..4c969852 100644 --- a/flask/app.py +++ b/flask/app.py @@ -11,6 +11,7 @@ from __future__ import with_statement +import os import sys from threading import Lock from datetime import timedelta @@ -103,14 +104,33 @@ class Flask(_PackageBoundObject): pick up SQL queries in `yourapplication.app` and not `yourapplication.views.frontend`) - .. versionadded:: 0.5 - The `static_path` parameter was added. + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. :param import_name: the name of the application package - :param static_path: can be used to specify a different path for the - static files on the web. Defaults to ``/static``. - This does not affect the folder the files are served - *from*. + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: the folder with static files that should be served + at `static_url_path`. Defaults to the ``'static'`` + folder in the root path of the application. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :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. + :param instance_relative_config: if set to `True` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. """ #: The class that is used for request objects. See :class:`~flask.Request` @@ -238,7 +258,8 @@ class Flask(_PackageBoundObject): session_interface = SecureCookieSessionInterface() def __init__(self, import_name, static_path=None, static_url_path=None, - static_folder='static', template_folder='templates'): + static_folder='static', template_folder='templates', + instance_path=None, instance_relative_config=False): _PackageBoundObject.__init__(self, import_name, template_folder=template_folder) if static_path is not None: @@ -251,11 +272,21 @@ class Flask(_PackageBoundObject): self.static_url_path = static_url_path if static_folder is not None: self.static_folder = static_folder + if instance_path is None: + instance_path = self.auto_find_instance_path() + elif not os.path.isabs(instance_path): + raise ValueError('If an instance path is provided it must be ' + 'absolute. A relative path was given instead.') + + #: Holds the path to the instance folder. + #: + #: .. versionadded:: 0.8 + self.instance_path = instance_path #: The configuration dictionary as :class:`Config`. This behaves #: exactly like a regular dictionary but supports additional methods #: to load a config from files. - self.config = Config(self.root_path, self.default_config) + self.config = self.make_config(instance_relative_config) # Prepare the deferred setup of the logger. self._logger = None @@ -491,6 +522,38 @@ class Flask(_PackageBoundObject): """ return self._got_first_request + def make_config(self, instance_relative=False): + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + return Config(root_path, self.default_config) + + def auto_find_instance_path(self): + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + root_mod = sys.modules[self.import_name.split('.')[0]] + instance_path = None + if hasattr(root_mod, '__path__'): + package_dir = os.path.dirname(root_mod.__file__) + instance_path = os.path.join(package_dir, os.path.pardir) + else: + instance_path = os.path.dirname(root_mod.__file__) + basedir = os.path.normpath(os.path.abspath(instance_path)) + return os.path.join(basedir, 'instance') + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` and :meth:`select_jinja_autoescape`. Since 0.7 this also adds diff --git a/tests/flask_tests.py b/tests/flask_tests.py index e5fbf20f..6e9f4555 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1005,6 +1005,24 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = c.post('/foo', data={}, follow_redirects=True) self.assertEqual(rv.data, 'success') + def test_basic_instance_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + app = flask.Flask(__name__) + self.assertEqual(app.instance_path, os.path.join(here, 'instance')) + + app = flask.Flask(__name__, instance_path=here) + self.assertEqual(app.instance_path, here) + + try: + flask.Flask(__name__, instance_path='instance') + except ValueError, e: + self.assert_('must be absolute' in str(e)) + else: + self.fail('Expected value error') + + from blueprintapp import app + self.assertEqual(app.instance_path, os.path.join(here, 'instance')) + class JSONTestCase(unittest.TestCase):