Login

Decorator and context manager to override settings

Author:
jezdez
Posted:
May 17, 2011
Language:
Python
Version:
1.3
Tags:
settings decorator contextmanager
Score:
2 (after 2 ratings)

Overriding settings

For testing purposes it's often useful to change a setting temporarily and revert to the original value after running the testing code. The following code doubles as a context manager and decorator. It's used like this:

from django.test import TestCase
from whatever import override_settings

class LoginTestCase(TestCase):

    @override_settings(LOGIN_URL='/other/login/')
    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')

The decorator can also be applied to test case classes:

from django.test import TestCase
from whatever import override_settings

class LoginTestCase(TestCase):

    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')

LoginTestCase = override_settings(LOGIN_URL='/other/login/')(LoginTestCase)

On Python 2.6 and higher you can also use the well known decorator syntax to decorate the class:

from django.test import TestCase
from whatever import override_settings

@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):

    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')

Alternatively it can be used as a context manager:

from django.test import TestCase
from whatever import override_settings

class LoginTestCase(TestCase):

    def test_login(self):
        with override_settings(LOGIN_URL='/other/login/'):
            response = self.client.get('/sekrit/')
            self.assertRedirects(response, '/other/login/?next=/sekrit/')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from __future__ import with_statement
from django.conf import settings, UserSettingsHolder
from django.utils.functional import wraps

class override_settings(object):
    """
    Acts as either a decorator, or a context manager.  If it's a decorator it
    takes a function and returns a wrapped function.  If it's a contextmanager
    it's used with the ``with`` statement.  In either event entering/exiting
    are called before and after, respectively, the function/block is executed.
    """
    def __init__(self, **kwargs):
        self.options = kwargs
        self.wrapped = settings._wrapped

    def __enter__(self):
        self.enable()

    def __exit__(self, exc_type, exc_value, traceback):
        self.disable()

    def __call__(self, func):
        @wraps(func)
        def inner(*args, **kwargs):
            with self:
                return func(*args, **kwargs)
        return inner

    def enable(self):
        override = UserSettingsHolder(settings._wrapped)
        for key, new_value in self.options.items():
            setattr(override, key, new_value)
        settings._wrapped = override

    def disable(self):
        settings._wrapped = self.wrapped

More like this

  1. Modify requests in your unit tests (improvement on RequestFactory) by vaughnkoch 4 years, 11 months ago
  2. Automatic testing of add and changelist admin views by Taifu 4 years ago
  3. EasyFeed class by limodou 8 years, 6 months ago
  4. Format transition middleware by limodou 8 years, 6 months ago
  5. Testrunner with testmodels by nfg 5 years, 7 months ago

Comments

tartley (on August 19, 2011):

Hey. This is great, thanks.

Just to check I'm understanding correctly: The Django 1.2 docs specifically forbid changing settings anywhere other than in your settings files: https://docs.djangoproject.com/en/dev/topics/settings/#altering-settings-at-runtime

Is this because of the hazards of not undoing the changes that are wrought, thus leaving the application in an inconsistent state? (early-imported modules thing the settings say one thing, imports after your modification think the settings say another thing)

Does a snippet like this run the risk that any global state which depends on the modified settings, and is set within the scope of the context manager, will permanently be set to a value which is inconsistent with the rest of the application?

If so, presumably this can be avoided by not ever setting global state that depends on the values of settings - the settings should be referred to directly every time their value is needed.

Am I vaguely understanding correctly?

#

Please login first before commenting.