0

(Maybe this question is more about Python but Django is the context, so here it goes)

Suppose you need a setting FOO whose value depends on the value of setting BAR (simplest case is make CELERY_RESULT_BACKEND equal to BROKER_URL).

If you have only one settings file, that's simple to achieve:

BAR = some_value
FOO = some_function(BAR)

However, it's quite popular to have many settings files, one for each environment (e.g. production, development, test, stage, etc), as proposed in the project layout from the book, "Two Scoops of Django: Best Practices for Django 1.5".

In that case there is a settings.base module, which is imported with all its contents by settings.dev, settings.prod, etc, which add their own specific values or override those defined in settings.base.

The problem happens when I want to override BAR in some of those modules, but I have to remember to recalculate FOO every time after that override. That's error prone and not DRY.

A lambda function won't work because that setting would be a callable, not the resulting value. Built-in function/decorator property would be ideal but it can only be used within classes (new-style). I don't know anything else like that.

Ideas?

glarrain
  • 8,031
  • 7
  • 31
  • 44

2 Answers2

0

Yes, it is, the settings file is just a python script as well.

Most people do it already to build the Template Path setting.

Django template Path

Now let's imagine this:

ref = {
    'dev':  'FOO',
    'qa':   'BAR'
    'prod': 'BAZ'
}

ENV = 'PROD'  # Can also be DEV or QA

DYNAMIC_SETTING = ref.get(ENV.lower(), None)  # => 'BAZ'
Community
  • 1
  • 1
DevLounge
  • 8,313
  • 3
  • 31
  • 44
  • I think you misunderstood what I needed. What you post as `DYNAMIC_SETTING` isn't so: if I change `ENV` after your snippet, and then access `DYNAMIC_SETTING` again, its value will not change. Why? Because that line (`DYNAMIC_SETTING = ref ...`) was already executed and won't again. – glarrain Aug 02 '13 at 22:35
0

Slightly hackish:

class DynamicWrapper(object):
    _initialized = False

    def __init__(self, wrapped_name)
        self._wrapped = wrapped_name

    def __get__(self):
        if not self._initialized:
            self._initialized = True
            return self
        if not hasattr(self, '_computed_val'):
            from django.conf import settings
            val = getattr(settings, self._wrapped)
            self._computed_val = some_func(val)
        return self._computed_val

BAR = 'some_value'
FOO = DynamicWrapper('BAR')

The idea is as follows:

  • Django's settings object uses getattr(settings_module, setting) once when the settings object is initialized (note that when you do from django.conf import settings you actually import an object, not a module). The result is set as an attribute on the settings object. __get__ is called, returning self, thus settings.FOO is the DynamicWrapper instance.
  • When settings.FOO is accessed, this will call __get__ a second time on the DynamicWrapper object, this is when you want it to return the actual value. The value is computed according to some_func and the value is returned instead of the DynamicWrapper object.
  • Any subsequent attempt to get the value of settings.FOO will also call the __get__ method, but this time the computed value is cached as self._computed_val and immediately returned.

This doesn't handle the (edge) cases when there are multiple Settings objects, when FOO is accessed in another way than through django.conf.settings or when BAR is not defined.

knbk
  • 52,111
  • 9
  • 124
  • 122
  • Good idea although it certainly is hackish way to achieve what I wanted. It never crossed my mind to approach the problem this way. Congrats – glarrain Aug 04 '13 at 22:42