From ec5b182f15d711aa92fe16480011fbe0fb9d3a63 Mon Sep 17 00:00:00 2001 From: Karol Kuczmarski Date: Sat, 22 Jun 2013 22:09:30 +0200 Subject: [PATCH 01/15] Add Flask.config_class feature --- flask/app.py | 11 ++++++++++- flask/testsuite/__init__.py | 3 +++ flask/testsuite/config.py | 10 ++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index addc40b4..216afd8b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -175,6 +175,15 @@ class Flask(_PackageBoundObject): _set_request_globals_class) del _get_request_globals_class, _set_request_globals_class + #: The class that is used for the ``config`` attribute of this app. + #: Defaults to :class:`~flask.Config`. + #: + #: Example use cases for a custom class: + #: + #: 1. Default values for certain config options. + #: 2. Access to config values through attributes in addition to keys. + config_class = Config + #: The debug flag. Set this to `True` to enable debugging of the #: application. In debug mode the debugger will kick in when an unhandled #: exception occurs and the integrated server will automatically reload @@ -609,7 +618,7 @@ class Flask(_PackageBoundObject): root_path = self.root_path if instance_relative: root_path = self.instance_path - return Config(root_path, self.default_config) + return self.config_class(root_path, self.default_config) def auto_find_instance_path(self): """Tries to locate the instance path if it was not provided to the diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 7fe61484..d6c4604e 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -157,6 +157,9 @@ class FlaskTestCase(unittest.TestCase): def assert_not_in(self, x, y): self.assertNotIn(x, y) + def assert_isinstance(self, obj, cls): + self.assertIsInstance(obj, cls) + if sys.version_info[:2] == (2, 6): def assertIn(self, x, y): assert x in y, "%r unexpectedly not in %r" % (x, y) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 477c6db9..7d074c01 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -99,6 +99,16 @@ class ConfigTestCase(FlaskTestCase): self.assert_true(0, 'expected config') self.assert_false(app.config.from_pyfile('missing.cfg', silent=True)) + def test_custom_config_class(self): + class Config(flask.Config): + pass + class Flask(flask.Flask): + config_class = Config + app = Flask(__name__) + self.assert_isinstance(app.config, Config) + app.config.from_object(__name__) + self.common_object_test(app) + def test_session_lifetime(self): app = flask.Flask(__name__) app.config['PERMANENT_SESSION_LIFETIME'] = 42 From b290bf4079bc325d6b13f9043afb17c4aee48df0 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Wed, 7 Aug 2013 18:03:37 -0400 Subject: [PATCH 02/15] Add ability to config from a JSON file --- flask/config.py | 27 +++++++++++++++++++++++++++ flask/testsuite/config.py | 19 +++++++++++++++++++ flask/testsuite/static/config.json | 4 ++++ 3 files changed, 50 insertions(+) create mode 100644 flask/testsuite/static/config.json diff --git a/flask/config.py b/flask/config.py index 155afa2f..0d9f822f 100644 --- a/flask/config.py +++ b/flask/config.py @@ -15,6 +15,7 @@ import errno from werkzeug.utils import import_string from ._compat import string_types +from . import json class ConfigAttribute(object): @@ -164,5 +165,31 @@ class Config(dict): if key.isupper(): self[key] = getattr(obj, key) + def from_json(self, filename, silent=False): + """Updates the values in the config from a JSON file. This function + behaves as if the JSON object was a dictionary and passed ot the + :meth:`from_object` function. + + :param filename: the filename of the JSON file. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to `True` if you want silent failure for missing + files. + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename) as json_file: + obj = json.loads(json_file.read()) + except IOError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + e.strerror = 'Unable to load configuration file (%s)' % e.strerror + raise + for key in obj.keys(): + if key.isupper(): + self[key] = obj[key] + return True + def __repr__(self): return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 477c6db9..7a9f574c 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -40,6 +40,12 @@ class ConfigTestCase(FlaskTestCase): app.config.from_object(__name__) self.common_object_test(app) + def test_config_from_json(self): + app = flask.Flask(__name__) + current_dir = os.path.dirname(os.path.abspath(__file__)) + app.config.from_json(os.path.join(current_dir, 'static', 'config.json')) + self.common_object_test(app) + def test_config_from_class(self): class Base(object): TEST_KEY = 'foo' @@ -99,6 +105,19 @@ class ConfigTestCase(FlaskTestCase): self.assert_true(0, 'expected config') self.assert_false(app.config.from_pyfile('missing.cfg', silent=True)) + def test_config_missing_json(self): + app = flask.Flask(__name__) + try: + app.config.from_json('missing.json') + except IOError as e: + msg = str(e) + self.assert_true(msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):')) + self.assert_true(msg.endswith("missing.json'")) + else: + self.assert_true(0, 'expected config') + self.assert_false(app.config.from_json('missing.json', silent=True)) + def test_session_lifetime(self): app = flask.Flask(__name__) app.config['PERMANENT_SESSION_LIFETIME'] = 42 diff --git a/flask/testsuite/static/config.json b/flask/testsuite/static/config.json new file mode 100644 index 00000000..4a9722ec --- /dev/null +++ b/flask/testsuite/static/config.json @@ -0,0 +1,4 @@ +{ + "TEST_KEY": "foo", + "SECRET_KEY": "devkey" +} From 231f45b432189500e760a63b41664f5f406793be Mon Sep 17 00:00:00 2001 From: dmackinnon Date: Wed, 25 Sep 2013 11:50:02 -0400 Subject: [PATCH 03/15] Fix typo --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 9e26196d..4db7a209 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -269,7 +269,7 @@ of the box (see :ref:`debug-mode`). If you would like to use another Python debugger, note that debuggers interfere with each other. You have to set some options in order to use your favorite debugger: -* ``debug`` - whether to enable debug mode and catch exceptinos +* ``debug`` - whether to enable debug mode and catch exceptions * ``use_debugger`` - whether to use the internal Flask debugger * ``use_reloader`` - whether to reload and fork the process on exception From 475b0c1cd94716efc1bb42ed0a2fd52dd7cf71c1 Mon Sep 17 00:00:00 2001 From: defuz Date: Thu, 26 Sep 2013 18:46:30 +0300 Subject: [PATCH 04/15] fix typo (jsonfiy) --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 1bc46afa..4cf7311a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -155,7 +155,7 @@ The following configuration values are used internally by Flask: ascii-encoded JSON. If this is set to ``False`` Flask will not encode to ASCII and output strings as-is and return - unicode strings. ``jsonfiy`` will + unicode strings. ``jsonify`` will automatically encode it in ``utf-8`` then for transport for instance. ``JSON_SORT_KEYS`` By default Flask will serialize JSON From 5ecca4c0dae52c7645f9dbda6cbfc32ec76a3498 Mon Sep 17 00:00:00 2001 From: Hyunjun Kim Date: Wed, 2 Oct 2013 15:10:27 +0900 Subject: [PATCH 05/15] Fix a typo on blueprints module name. --- flask/blueprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index d45fd062..2c32502b 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -79,7 +79,7 @@ class BlueprintSetupState(object): class Blueprint(_PackageBoundObject): """Represents a blueprint. A blueprint is an object that records functions that will be called with the - :class:`~flask.blueprint.BlueprintSetupState` later to register functions + :class:`~flask.blueprints.BlueprintSetupState` later to register functions or other things on the main application. See :ref:`blueprints` for more information. From f161a71c190da7320d4a6be177c14928961fdf09 Mon Sep 17 00:00:00 2001 From: Alexey Shamrin Date: Thu, 3 Oct 2013 05:15:27 +0400 Subject: [PATCH 06/15] quickstart: import `request` in HTTP methods example --- docs/quickstart.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 3cb9b2f7..83ed2af8 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -257,6 +257,8 @@ accessing URLs. By default, a route only answers to `GET` requests, but that can be changed by providing the `methods` argument to the :meth:`~flask.Flask.route` decorator. Here are some examples:: + from flask import request + @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': From 90a50f8b51bdd71d9f2b1fd82352628cfd681898 Mon Sep 17 00:00:00 2001 From: Matt Wright Date: Thu, 23 Jan 2014 15:05:37 -0500 Subject: [PATCH 07/15] Add `get_namespace` method on `Config` object for convenient access of namespaced config options. --- flask/config.py | 34 ++++++++++++++++++++++++++++++++++ flask/testsuite/config.py | 15 +++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/flask/config.py b/flask/config.py index 07d6fbc8..66bf7eb4 100644 --- a/flask/config.py +++ b/flask/config.py @@ -164,5 +164,39 @@ class Config(dict): if key.isupper(): self[key] = getattr(obj, key) + def get_namespace(self, namespace, lowercase=True): + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store` would look like:: + + { + 'type': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + """ + rv = {} + for k, v in self.iteritems(): + if not k.startswith(namespace): + continue + key = k[len(namespace):] + if lowercase: + key = key.lower() + rv[key] = v + return rv + def __repr__(self): return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index cdc6273f..c2c14cb1 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -105,6 +105,21 @@ class ConfigTestCase(FlaskTestCase): app.config['PERMANENT_SESSION_LIFETIME'] = 42 self.assert_equal(app.permanent_session_lifetime.seconds, 42) + def test_get_namespace(self): + app = flask.Flask(__name__) + app.config['FOO_OPTION_1'] = 'foo option 1' + app.config['FOO_OPTION_2'] = 'foo option 2' + app.config['BAR_STUFF_1'] = 'bar stuff 1' + app.config['BAR_STUFF_2'] = 'bar stuff 2' + foo_options = app.config.get_namespace('FOO_') + self.assert_equal(2, len(foo_options)) + self.assert_equal('foo option 1', foo_options['option_1']) + self.assert_equal('foo option 2', foo_options['option_2']) + bar_options = app.config.get_namespace('BAR_', lowercase=False) + self.assert_equal(2, len(bar_options)) + self.assert_equal('bar stuff 1', bar_options['STUFF_1']) + self.assert_equal('bar stuff 2', bar_options['STUFF_2']) + class LimitedLoaderMockWrapper(object): def __init__(self, loader): From 97411295e3b46080f8976f14e73a6493ca17a2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 12 Feb 2014 23:53:51 +0100 Subject: [PATCH 08/15] Add Config.from_json to changelog --- CHANGES | 1 + flask/config.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index d646383e..69648f23 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Version 1.0 - Made Flask support custom JSON mimetypes for incoming data. - Added support for returning tuples in the form ``(response, headers)`` from a view function. +- Added :meth:`flask.Config.from_json`. Version 0.10.2 -------------- diff --git a/flask/config.py b/flask/config.py index 41909857..2ed5c180 100644 --- a/flask/config.py +++ b/flask/config.py @@ -175,6 +175,8 @@ class Config(dict): root path. :param silent: set to `True` if you want silent failure for missing files. + + .. versionadded:: 1.0 """ filename = os.path.join(self.root_path, filename) From 19baae3d3445344679b68436322de08b5babf9a1 Mon Sep 17 00:00:00 2001 From: Joe Friedl Date: Thu, 13 Feb 2014 10:28:21 -0500 Subject: [PATCH 09/15] Update docs copyright date Happy belated New Year! --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index feed359f..16c841f4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,7 +43,7 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2013, Armin Ronacher' +copyright = u'2014, Armin Ronacher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 3f8e29b12cd2b909d4dbbc3964339fd3f56c3203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Thu, 20 Feb 2014 19:15:42 +0100 Subject: [PATCH 10/15] Add Flask.config_class to changelog --- CHANGES | 1 + flask/app.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 69648f23..c6d252af 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Version 1.0 - Added support for returning tuples in the form ``(response, headers)`` from a view function. - Added :meth:`flask.Config.from_json`. +- Added :attr:`flask.Flask.config_class`. Version 0.10.2 -------------- diff --git a/flask/app.py b/flask/app.py index 9222540a..257c5e93 100644 --- a/flask/app.py +++ b/flask/app.py @@ -182,6 +182,8 @@ class Flask(_PackageBoundObject): #: #: 1. Default values for certain config options. #: 2. Access to config values through attributes in addition to keys. + #: + #: .. versionadded:: 1.0 config_class = Config #: The debug flag. Set this to `True` to enable debugging of the From d43bfb261a0cb562fdfec6d94768c80d95844578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Thu, 20 Feb 2014 19:17:59 +0100 Subject: [PATCH 11/15] Add assertIsInstance to FlaskTestCase on 2.6 This fixes an error in ConfigTestCase.test_custom_config_class --- flask/testsuite/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 8a6d1477..d0d62f29 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -167,6 +167,9 @@ class FlaskTestCase(unittest.TestCase): def assertNotIn(self, x, y): assert x not in y, "%r unexpectedly in %r" % (x, y) + def assertIsInstance(self, x, y): + assert isinstance(x, y), "not isinstance(%r, %r)" % (x, y) + class _ExceptionCatcher(object): From ffff509cf07b4791201915f98116aec51eb4a651 Mon Sep 17 00:00:00 2001 From: Eliseo Ocampos Date: Tue, 4 Mar 2014 14:57:12 -0800 Subject: [PATCH 12/15] Added 'import os' statement Added 'import os' statement so you can use os.path.join() when defining DATABASE location --- docs/tutorial/setup.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index ed8a744c..65d4b3f2 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -14,6 +14,7 @@ load that or import the values from there. First we add the imports in `flaskr.py`:: # all the imports + import os import sqlite3 from flask import Flask, request, session, g, redirect, url_for, abort, \ render_template, flash From dfae2679a6be1d3e61eaa9f83e85e0daf655187c Mon Sep 17 00:00:00 2001 From: Charles-Axel Dein Date: Wed, 5 Mar 2014 15:22:59 -0800 Subject: [PATCH 13/15] Clarify the after_request argument Make it a bit clearer that it's an instance of response_class that is expected, not the actual response_class class. --- flask/app.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 257c5e93..2bff6af0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1219,9 +1219,11 @@ class Flask(_PackageBoundObject): @setupmethod def after_request(self, f): - """Register a function to be run after each request. Your function - must take one parameter, a :attr:`response_class` object and return - a new response object or the same (see :meth:`process_response`). + """Register a function to be run after each request. + + Your function must take one parameter, an instance of + :attr:`response_class` and return a new response object or the + same (see :meth:`process_response`). As of Flask 0.7 this function might not be executed at the end of the request in case an unhandled exception occurred. From ba80e1e33bd93fd895d1b88853f47fb30cd0b670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Thu, 13 Mar 2014 20:22:18 +0100 Subject: [PATCH 14/15] Fix Python 3 compat issue in Config.get_namespace --- flask/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/config.py b/flask/config.py index cb1c9bb8..164a04ba 100644 --- a/flask/config.py +++ b/flask/config.py @@ -14,7 +14,7 @@ import os import errno from werkzeug.utils import import_string -from ._compat import string_types +from ._compat import string_types, iteritems from . import json @@ -218,7 +218,7 @@ class Config(dict): dictionary should be lowercase """ rv = {} - for k, v in self.iteritems(): + for k, v in iteritems(self): if not k.startswith(namespace): continue key = k[len(namespace):] From 06857c9ba58fb3b93daa3604538ccd3fd2f1d07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Thu, 13 Mar 2014 20:23:43 +0100 Subject: [PATCH 15/15] Add Config.get_namespace to CHANGES --- CHANGES | 1 + flask/config.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index c6d252af..a6fa7ec6 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Version 1.0 from a view function. - Added :meth:`flask.Config.from_json`. - Added :attr:`flask.Flask.config_class`. +- Added :meth:`flask.config.Config.get_namespace`. Version 0.10.2 -------------- diff --git a/flask/config.py b/flask/config.py index 164a04ba..dfc2f6b3 100644 --- a/flask/config.py +++ b/flask/config.py @@ -216,6 +216,8 @@ class Config(dict): :param namespace: a configuration namespace :param lowercase: a flag indicating if the keys of the resulting dictionary should be lowercase + + .. versionadded:: 1.0 """ rv = {} for k, v in iteritems(self):