Browse Source

Merge branch 'master' of github.com:mitsuhiko/flask

pull/888/merge
Armin Ronacher 11 years ago
parent
commit
36e86be06d
  1. 3
      CHANGES
  2. 2
      docs/conf.py
  3. 2
      docs/config.rst
  4. 2
      docs/errorhandling.rst
  5. 2
      docs/quickstart.rst
  6. 21
      flask/app.py
  7. 2
      flask/blueprints.py
  8. 67
      flask/config.py
  9. 6
      flask/testsuite/__init__.py
  10. 44
      flask/testsuite/config.py
  11. 4
      flask/testsuite/static/config.json

3
CHANGES

@ -17,6 +17,9 @@ Version 1.0
- Made Flask support custom JSON mimetypes for incoming data. - Made Flask support custom JSON mimetypes for incoming data.
- Added support for returning tuples in the form ``(response, headers)`` - Added support for returning tuples in the form ``(response, headers)``
from a view function. 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 Version 0.10.2
-------------- --------------

2
docs/conf.py

@ -43,7 +43,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Flask' 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 # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the

2
docs/config.rst

@ -155,7 +155,7 @@ The following configuration values are used internally by Flask:
ascii-encoded JSON. If this is set to ascii-encoded JSON. If this is set to
``False`` Flask will not encode to ASCII ``False`` Flask will not encode to ASCII
and output strings as-is and return and output strings as-is and return
unicode strings. ``jsonfiy`` will unicode strings. ``jsonify`` will
automatically encode it in ``utf-8`` automatically encode it in ``utf-8``
then for transport for instance. then for transport for instance.
``JSON_SORT_KEYS`` By default Flask will serialize JSON ``JSON_SORT_KEYS`` By default Flask will serialize JSON

2
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 debugger, note that debuggers interfere with each other. You have to set some
options in order to use your favorite debugger: 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_debugger`` - whether to use the internal Flask debugger
* ``use_reloader`` - whether to reload and fork the process on exception * ``use_reloader`` - whether to reload and fork the process on exception

2
docs/quickstart.rst

@ -258,6 +258,8 @@ accessing URLs. By default, a route only answers to `GET` requests, but that
can be changed by providing the `methods` argument to the can be changed by providing the `methods` argument to the
:meth:`~flask.Flask.route` decorator. Here are some examples:: :meth:`~flask.Flask.route` decorator. Here are some examples::
from flask import request
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if request.method == 'POST': if request.method == 'POST':

21
flask/app.py

@ -175,6 +175,17 @@ class Flask(_PackageBoundObject):
_set_request_globals_class) _set_request_globals_class)
del _get_request_globals_class, _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.
#:
#: .. versionadded:: 1.0
config_class = Config
#: The debug flag. Set this to `True` to enable debugging of the #: The debug flag. Set this to `True` to enable debugging of the
#: application. In debug mode the debugger will kick in when an unhandled #: application. In debug mode the debugger will kick in when an unhandled
#: exception occurs and the integrated server will automatically reload #: exception occurs and the integrated server will automatically reload
@ -610,7 +621,7 @@ class Flask(_PackageBoundObject):
root_path = self.root_path root_path = self.root_path
if instance_relative: if instance_relative:
root_path = self.instance_path 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): def auto_find_instance_path(self):
"""Tries to locate the instance path if it was not provided to the """Tries to locate the instance path if it was not provided to the
@ -1208,9 +1219,11 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def after_request(self, f): def after_request(self, f):
"""Register a function to be run after each request. Your function """Register a function to be run after each request.
must take one parameter, a :attr:`response_class` object and return
a new response object or the same (see :meth:`process_response`). 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 As of Flask 0.7 this function might not be executed at the end of the
request in case an unhandled exception occurred. request in case an unhandled exception occurred.

2
flask/blueprints.py

@ -79,7 +79,7 @@ class BlueprintSetupState(object):
class Blueprint(_PackageBoundObject): class Blueprint(_PackageBoundObject):
"""Represents a blueprint. A blueprint is an object that records """Represents a blueprint. A blueprint is an object that records
functions that will be called with the 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 or other things on the main application. See :ref:`blueprints` for more
information. information.

67
flask/config.py

@ -14,7 +14,8 @@ import os
import errno import errno
from werkzeug.utils import import_string from werkzeug.utils import import_string
from ._compat import string_types from ._compat import string_types, iteritems
from . import json
class ConfigAttribute(object): class ConfigAttribute(object):
@ -164,5 +165,69 @@ class Config(dict):
if key.isupper(): if key.isupper():
self[key] = getattr(obj, key) 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.
.. versionadded:: 1.0
"""
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 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
.. versionadded:: 1.0
"""
rv = {}
for k, v in iteritems(self):
if not k.startswith(namespace):
continue
key = k[len(namespace):]
if lowercase:
key = key.lower()
rv[key] = v
return rv
def __repr__(self): def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))

6
flask/testsuite/__init__.py

@ -157,6 +157,9 @@ class FlaskTestCase(unittest.TestCase):
def assert_not_in(self, x, y): def assert_not_in(self, x, y):
self.assertNotIn(x, y) self.assertNotIn(x, y)
def assert_isinstance(self, obj, cls):
self.assertIsInstance(obj, cls)
if sys.version_info[:2] == (2, 6): if sys.version_info[:2] == (2, 6):
def assertIn(self, x, y): def assertIn(self, x, y):
assert x in y, "%r unexpectedly not in %r" % (x, y) assert x in y, "%r unexpectedly not in %r" % (x, y)
@ -164,6 +167,9 @@ class FlaskTestCase(unittest.TestCase):
def assertNotIn(self, x, y): def assertNotIn(self, x, y):
assert x not in y, "%r unexpectedly in %r" % (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): class _ExceptionCatcher(object):

44
flask/testsuite/config.py

@ -41,6 +41,12 @@ class ConfigTestCase(FlaskTestCase):
app.config.from_object(__name__) app.config.from_object(__name__)
self.common_object_test(app) 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): def test_config_from_class(self):
class Base(object): class Base(object):
TEST_KEY = 'foo' TEST_KEY = 'foo'
@ -100,11 +106,49 @@ class ConfigTestCase(FlaskTestCase):
self.assert_true(0, 'expected config') self.assert_true(0, 'expected config')
self.assert_false(app.config.from_pyfile('missing.cfg', silent=True)) 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_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): def test_session_lifetime(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config['PERMANENT_SESSION_LIFETIME'] = 42 app.config['PERMANENT_SESSION_LIFETIME'] = 42
self.assert_equal(app.permanent_session_lifetime.seconds, 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): class LimitedLoaderMockWrapper(object):
def __init__(self, loader): def __init__(self, loader):

4
flask/testsuite/static/config.json

@ -0,0 +1,4 @@
{
"TEST_KEY": "foo",
"SECRET_KEY": "devkey"
}
Loading…
Cancel
Save