forked from orbit-oss/flask
Merge branch 'master' of github.com:mitsuhiko/flask
This commit is contained in:
commit
36e86be06d
11 changed files with 146 additions and 9 deletions
3
CHANGES
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
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
Normal file
4
flask/testsuite/static/config.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"TEST_KEY": "foo",
|
||||||
|
"SECRET_KEY": "devkey"
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue