Skip to content

Instantly share code, notes, and snippets.

@rduplain
Created January 10, 2012 00:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rduplain/1586037 to your computer and use it in GitHub Desktop.
Save rduplain/1586037 to your computer and use it in GitHub Desktop.
Web application config which picks values based on deployment/hostname.
"Web application config which picks values based on deployment/hostname."
# Ron DuPlain <ron.duplain@gmail.com>
# Developed by Ron DuPlain and Dan Lepage for Private Practice LLC.
#
# This listing serves as an example, extracted from a real-world config.py.
# The resulting config picks values based on hostname, which can be overriden
# by the DEPLOYMENT environment variable. Add to each .wsgi as appropriate:
#
# import os
# os.environ['DEPLOYMENT'] = 'master'
import os
import socket
DEPLOYMENT = os.environ.get('DEPLOYMENT', socket.gethostname())
PWD = os.path.abspath(os.curdir) # Do not use in production.
def pick(config_map, deployment=DEPLOYMENT, default='default'):
return config_map.get(deployment, config_map[default])
# Those few lines above are very powerful, providing for defaults on the fly.
PRODUCTION = pick({
'master': True,
'develop': True,
'topic': True,
'default': False,
})
SQLALCHEMY_DATABASE_URI = pick({
'master': 'postgresql://user:pass@localhost:5432/product',
'develop': 'postgresql://user:pass@localhost:5432/product_develop',
'topic': 'postgresql://user:pass@localhost:5432/product_topic',
'default': 'sqlite:////' + PWD + '/app.db',
'myhostname': 'sqlite:////tmp/product-app.db',
})
MAIL_SERVER = 'smtp.example.com'
MAIL_SUPPRESS_SEND = pick({
'production': False,
'development': True,
}, default='production' if PRODUCTION else 'development')
# Another pattern: group deployments, default by group.
GROUP = pick({
'master': 'group_a',
'develop': 'group_b',
'topic': 'group_b',
'default': 'default',
})
SOME_SETTING = pick({
'group_a': 'a',
'group_b': 'b',
'default': None,
}, default=GROUP)
# Also allow for local overrides of all values.
# Make sure you have local_config.py next to config.py to run the dev server.
try:
from local_config import *
import sys
print >>sys.stderr, '# Included local config.'
except ImportError:
pass
# Below local_config, apply any derived configuration.
if 'postgresql' in SQLALCHEMY_DATABASE_URI:
SQLALCHEMY_POOL_SIZE = 40
@rduplain
Copy link
Author

You can have the config print itself on python config.py to see it in action. Add to the bottom of the file:

if __name__ == '__main__':
    for name in sorted(locals().keys()):
        if name.isupper():
            print name, '=', repr(locals()[name])

In fact, this prints out a usable static config.py.

@rduplain
Copy link
Author

I now have this config scheme in production: an identical config.py on two distinct sites, from two branches of code. It readily scales to as many hosts as needed.

You can inspect a deployment's config with:

DEPLOYMENT=master python config.py

ENV=value some command is the shell convention for setting an environment variable for the duration of just one command. If you set installation variables in the config (e.g. you can symlink config.py next to setup.py), you'll likely want to use DEPLOYMENT=target when calling setup.py commands:

DEPLOYMENT=master python setup.py sdist

If you are using fabric, set os.environ['DEPLOYMENT'] based on command-line arguments passed to the function signature, then import config (or reload if you also need config at the fabfile's module level) inside the function which needs deployment configuration.

@rduplain
Copy link
Author

This config is great for runtime choice based on DEPLOYMENT, but without some work in setup.py, does not work for installed command-line tools without providing the DEPLOYMENT environment.

You can have command-line tools know their deployment without environment. The key is to use the __main__ block in the comments above which generates a usable static config.py. Write a static config when building a release, using setup.py or fabric (or a combination). Note that distutil's make_release_tree uses hard links, so updating config.py in the release tree could overwrite config.py in the working tree. In that case, simply os.unlink the release tree's config.py first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment