Django-Environ




Startup


manage.py


#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)



STARTUP


wsgi.py


import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()


DJANGO_SETTINGS_MODULE



Who exports?
Where?
When?

LOCAL settings

project/settings.py
DEBUG = False

SECRET_KEY = 'change-me'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'mydatabase',
    }
}

try:
    from local_settings import *
except ImportError:
    pass


LOCAL SETTINGS

project/local_settings.py
DEBUG = True

SECRET_KEY = 'my-secret-key'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'mydatabase',
    }
}


LOCAL SETTINGS


out of source control (.gitignore)

managed manually for environment-specific settings

hard to extend the base settings

project/local_settings.py
INSTALLED_APPS += ('debug_toolbar.apps.DebugToolbarConfig', ) # error

MIDDLEWARE_CLASSES += (
    'django.contrib.sessions.middleware.SessionMiddleware',
) # error

LOCAL SETTINGS



ANTI-PATTERN!


Jacob Kaplan-Moss



THE twelve-factor APP


Apps sometimes store config as constants in the code.
This is a violation of twelve-factor, which requires strict separation of config from code.
Config varies substantially across deploys, code does not.

  • declarative formats for setup automation
  • maximum portability between execution environments
  • suitable for deployment on modern cloud platforms
  • minimize divergence between development and production
  • scale up without significant changes to tooling

THE TWELVE-FACTOR APP


Configuration

  • config is everything that is likely to vary between deploys
  • strict separation of config from code
  • stores config in environment variables

Django settings module is a weird mix of:

application configuration
environment-specific configuration

THE TWELVE-FACTOR APP

Backing Services

no distinction between local and third party services

THE TWELVE-FACTOR APP

Build, release, run

strict separation between the build, release, and run stages


The One True Way

Coined by Jacob Kaplan-Moss

reversing the import flow

 import the base settings 
from the environment-specific settings

project/settings/local.py
from .base import *

INSTALLED_APPS += ('debug_toolbar.apps.DebugToolbarConfig', )

MIDDLEWARE_CLASSES += (
    'debug_toolbar.middleware.DebugToolbarMiddleware',
) 

THE TWO-SCOOPS WAY

book written by Daniel Greenfeld and Audrey Roy

Best Practices for Django projects


  • using Virtualenvs
  • environment-related requirements and settings
  • ...


In practice

Multiple settings modules and
Multiple requirements files
for each environment.

manage.py
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.development)
wsgi.py
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings.production")
run tests
python manage.py tests --settings=project.settings.tests

Read environment


The raw way


# project/settings/base.py

import os

DEBUG = bool(os.environ.get('DEBUG', False)) 

SECRET_KEY = os.environ['SECRET_KEY']

ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split()


Read environment


The semi-raw way

# project/settings/base.py
import os
from django.core.exceptions import ImproperlyConfigured

NOTSET = object()
def get_env_setting(setting, default=NOTSET):
  try:
    return os.environ[setting]
  except KeyError:
    if default is NOTSET:
      raise ImproperlyConfigured("Set the %s env variable" % setting)
    return default

DEBUG = bool(get_env_setting('DEBUG', Fals)) 
SECRET_KEY = get_env_setting('SECRET_KEY')
ALLOWED_HOSTS = get_env_setting('ALLOWED_HOSTS').split()

Read environment



Defaults
Casting
Validations
Errors Handling
Overrides


DJANGO-ENVIRON


defaults

import environ

env = environ.Env() 

DEBUG = env.bool('DEBUG', default=False)

# or

env = environ.Env(DEBUG=(bool, False),) 

DEBUG = env('DEBUG')

DJANGO-ENVIRON


VALIDATIONS & ERRORS HANDLING

import environ

env = environ.Env() 

# Raises ImproperlyConfigured exception if SITE_ID is not defined in os.environ or if os.environ['SITE_ID'] contains not valid int.
SITE_ID = env.int('SITE_ID')

# Raises ImproperlyConfigured exception if SECRET_KEY not in os.environ
SECRET_KEY = env('SECRET_KEY')


DJANGO-ENVIRON


Casting

import environ

env = environ.Env() 

DATABASES = {
    # Raises ImproperlyConfigured if DATABASE_URL not in os.environ
    'default': env.db(default='sqlite:////tmp/my-tmp-sqlite.db'),
}
dj-database-url
dj-search-url
django-cache-url
....

LOAD ENVIRONMENT


virtualenv  postactivate/postdeactivate  
export DEBUG=1
export SECRET_KEY=secure 
supervisord
[program:my-project]
environment=DEBUG="1",SECRET_KEY="secure" 
uwsgi
[uwsgi]
env=DEBUG=1
env=SECRET_KEY=secure 

LOAD ENVIRONMENT

LOAD ENVIRONMENT





where?
manage.py / wsgi.py
or
settings/base.py

LINKS







:D

Django Environment

By Daniele Faraglia

Django Environment

  • 1,951