Skip to content

Commit

Permalink
Improved configuration support.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed May 27, 2010
1 parent 68278fd commit 4d16486
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 10 deletions.
5 changes: 5 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,8 @@ Template Rendering
.. autofunction:: render_template_string

.. autofunction:: get_template_attribute

Configuration
-------------

.. autoclass:: Config
105 changes: 95 additions & 10 deletions flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
ImmutableDict, cached_property, wrap_file, Headers
ImmutableDict, cached_property, wrap_file, Headers, \
import_string
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError
from werkzeug.contrib.securecookie import SecureCookie
Expand Down Expand Up @@ -630,6 +631,90 @@ def __set__(self, obj, value):
obj.config[self.__name__] = value


class Config(dict):
"""Works exactly like a dict but provides ways to fill it from files
or special dictionaries. There are two common patterns to populate the
config.
Either you can fill the config from a config file::
app.config.from_pyfile('yourconfig.cfg')
Or alternatively you can define the configuration options in the
module that calls :meth:`from_module` or provide an import path to
a module that should be loaded. It is also possible to tell it to
use the same module and with that provide the configuration values
just before the call::
DEBUG = True
SECRET_KEY = 'development key'
app.config.from_module(__name__)
In both cases (loading from any Python file or loading from modules),
only uppercase keys are added to the config. The actual keys in the
config are however lowercased so they are converted for you. This makes
it possible to use lowercase values in the config file for temporary
values that are not added to the config or to define the config keys in
the same file that implements the application.
:param root_path: path to which files are read relative from. When the
config object is created by the application, this is
the application's :attr:`~flask.Flask.root_path`.
:param defaults: an optional dictionary of default values
"""

def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {})
self.root_path = root_path

def from_pyfile(self, filename):
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
:meth:`from_module` function.
:param filename: the filename of the config. This can either be an
absolute filename or a filename relative to the
root path.
"""
filename = os.path.join(self.root_path, filename)
d = type(sys)('config')
d.__file__ = filename
execfile(filename, d.__dict__)
self.from_module(d)

def from_module(self, module):
"""Updates the values from the given module. A module can be of one
of the following two types:
- a string: in this case the module with that name will be imported
- an actual module reference: that module is used directly
Just the uppercase variables in that module are stored in the config
after lowercasing. Example usage::
app.config.from_module('yourapplication.default_config')
from yourapplication import default_config
app.config.from_module(default_config)
You should not use this function to load the actual configuration but
rather configuration defaults. The actual config should be loaded
with :meth;`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide.
:param module: an import name or module
"""
if isinstance(module, basestring):
d = import_string(module).__dict__
else:
d = module.__dict__
for key, value in d.iteritems():
if key.isupper():
self[key.lower()] = value

def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))


class Flask(_PackageBoundObject):
"""The flask object implements a WSGI application and acts as the central
object. It is passed the name of the module or package of the
Expand Down Expand Up @@ -675,12 +760,12 @@ class Flask(_PackageBoundObject):
secret_key = ConfigAttribute('secret_key')

#: The secure cookie uses this for the name of the session cookie
session_cookie_name = ConfigAttribute('session.cookie_name')
session_cookie_name = ConfigAttribute('session_cookie_name')

#: A :class:`~datetime.timedelta` which is used to set the expiration
#: date of a permanent session. The default is 31 days which makes a
#: permanent session survive for roughly one month.
permanent_session_lifetime = ConfigAttribute('session.permanent_lifetime')
permanent_session_lifetime = ConfigAttribute('permanent_session_lifetime')

#: Enable this if you want to use the X-Sendfile feature. Keep in
#: mind that the server has to support this. This only affects files
Expand Down Expand Up @@ -711,18 +796,18 @@ class Flask(_PackageBoundObject):
default_config = ImmutableDict({
'debug': False,
'secret_key': None,
'session.cookie_name': 'session',
'session.permanent_lifetime': timedelta(days=31),
'session_cookie_name': 'session',
'permanent_session_lifetime': timedelta(days=31),
'use_x_sendfile': False
})

def __init__(self, import_name, config=None):
def __init__(self, import_name):
_PackageBoundObject.__init__(self, import_name)

#: the configuration dictionary
self.config = self.default_config.copy()
if config:
self.config.update(config)
#: 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)

#: a dictionary of all view functions registered. The keys will
#: be function names which are also used to generate URLs and
Expand Down
24 changes: 24 additions & 0 deletions tests/flask_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
sys.path.append(os.path.join(example_path, 'minitwit'))


# config keys used for the ConfigTestCase
TEST_KEY = 'foo'
SECRET_KEY = 'devkey'


@contextmanager
def catch_stderr():
old_stderr = sys.stderr
Expand Down Expand Up @@ -686,6 +691,24 @@ def internal_server_error(e):
assert rv.data == 'Hello Server Error'


class ConfigTestCase(unittest.TestCase):

def common_module_test(self, app):
assert app.secret_key == 'devkey'
assert app.config['test_key'] == 'foo'
assert 'ConfigTestCase' not in app.config

def test_config_from_file(self):
app = flask.Flask(__name__)
app.config.from_pyfile('flask_tests.py')
self.common_module_test(app)

def test_config_from_module(self):
app = flask.Flask(__name__)
app.config.from_module(__name__)
self.common_module_test(app)


def suite():
from minitwit_tests import MiniTwitTestCase
from flaskr_tests import FlaskrTestCase
Expand All @@ -696,6 +719,7 @@ def suite():
suite.addTest(unittest.makeSuite(ModuleTestCase))
suite.addTest(unittest.makeSuite(SendfileTestCase))
suite.addTest(unittest.makeSuite(LoggingTestCase))
suite.addTest(unittest.makeSuite(ConfigTestCase))
if flask.json_available:
suite.addTest(unittest.makeSuite(JSONTestCase))
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
Expand Down

0 comments on commit 4d16486

Please sign in to comment.