Showing posts with label settings. Show all posts
Showing posts with label settings. Show all posts

August 16, 2009

Django Settings Trick: Override Files

The problem we needed to solve was this: each developer wants to mess with their settings file without changing the project default settings file under version control. Additionally, the production system needs to be able to have different behavior than the default system, and it would be nice if the installer of the system could customize it with a small file rather than letting them touch the main settings file.

The solution is to have the main settings.py file import from a number of secondary files which may exist or not and override the main settings. You can do this by sticking sections of this nature at the end of your settings.py:

import sys
try:
local_settings_path = '/etc/our_django/'
if local_settings_path not in sys.path:
sys.path.append(local_settings_path)
from local_settings import *
except ImportError:
pass

In this way any settings in the file /etc/our_django/local_settings.py will import into the main settings file and replace any prior assignments. If there is no local_settings.py then that's not a problem either, the import error is suppressed. And of course anything that comes after this in the main settings file cannot be overridden by it as python chews through (swallows?) the file. The sys path munging step is only required if local_settings.py lives separately from your main code.

We ended up having a couple of separate settings override files that could be used:
  • a database settings file, because that was most often messed with by developers
  • an installation settings file for system defaults appropriate to one installation
  • a local settings file for messing around with

Our install process took the checked in settings file and then installed appropriate database and installation settings files into the installed django project directory. If someone maintaining the system needed to mess with its behavior, they were instructed to make changes to their local settings file only, which lived somewhere in /etc like a normal settings file. My own local settings file would often look like:

ENABLE_DEV_MODE_LOGGING=True
ENABLE_DANGEROUS_UTILS=True

As you can see we loved to expand beyond the basic django settings.

Some similar settings tricks are floating around the web, such as the options on the django wiki, but this optional override files version is different enough to be worth a separate mention.

We had an additional settings safeguard by also having settings unit tests which would complain if settings were in an inappropriate state - for example the main settings.py should not be checked in with our test setting FAIL_HALF_OF_ALL_REQUESTS set.

No really, we had a test setting that basically did that for testing the effect of a bad network on our XMLRPC traffic. But that's a post for another day.

August 15, 2009

Saturday I Get to Rant (XML Config Edition)

After writing a little bit about django's settings file not being in XML I'm left with half a rant ready to go and I've done enough for this week so I get to let it out.

I've been exposed to that complicated XML config file world back in Java land. I don't mind the Java language (except for generics, but that's another rant) but it has some very, very stupid friends.

A lot of the time creeping XML seems to come from a deep seated desire to create behavior that doesn't have to be programmed. Configuring somehow doesn't count as programming, even when it's thousands of lines. Which is good, because XML doesn't count as a programming language. Unfortunately the first statement there is just wishful thinking: complex system configuration is so close to programming that they're wearing the same pants. And doing a programming task in XML is like knitting with mittens on.

And of course it comes with a bunch of other problems, like separating pieces of object behavior so you can't learn both what fields it has and how it behaves without finding both the code and the magic XML section. Separating pieces of object behavior into different files drives me up two walls at once.

Finally, each XML config file format is effectively a miniature programming language designed by someone who didn't know they were designing one, with predictably wonderful results.

XML config defenders will talk about how you can validate the config file with a DTD or schema or whatever, but this is a big glass of snake oil. Sure, you can validate the format of the config file, and field values, but as soon as one field relates to another you're going to have to validate it with real code anyway. XML defenders might also talk about how XML is human readable. I'm not sure what the proper response to those people is. Probably we should make them sing their own config files - if you can read it, shouldn't you be able to sing it?

Anyway django configuration is just about my style. Generally items are just named values, or lists of values, and on the outside chance you need something more complicated, you can unleash a real programming language.

There's another related rant for another time about the whole XML web hellworld that is the WS* specifications for SOAP and associated perversions, but I should limit myself to one per week.

Django Settings Trick: Time Zone

Since django settings are just another python file (Which was one of the reasons I originally chose django - I've been to "pretend you can program in XML" land and it's a hellish blasted wasteland where angle brackets prey upon the weak.) there's no reason you have to put up with the django hardwired timezone. Here's some code that works under Debian linux to pull out the system timezone. We just tossed similar code right in settings.py:

def get_time_zone():
try:
tzfile = open('/etc/timezone')
for line in tzfile:
if line:
return line.strip()
except:
return 'GMT'
return 'GMT'
TIME_ZONE = get_time_zone()

If you're not using a linux that writes the timezone into /etc/timezone so agreeably, you'll have to do a little research to learn how to get your time zone string, but the concept stands. Remember to check that you're delivering it in the format django expects, documented in the django docs.

Don't forget to handle exceptions gracefully, and give some indication of errors (which I stripped from this code for space).