This blog post is a short explanation how to give and consume environment variables in your (Python) code. It’s a nice little trick, but if you are not well-versed in UNIX systems you might not know it.
Table Of Content
Please see the original blog post for syntax color examples.
1. Configuring your application
An application can be configured usually in three ways. Through
- command-line arguments (I prefer simplistic plac library with Python)
- configuration files (I prefer YAML or Python standard library ConfigParser for INI style files)
- environment variables
The two first ones are the most well-known methods. However, the last option is the easiest for quick and dirty hacks.
2. Using environment variables in UNIX
Note: Environment variables work alike in UNIX and Windows. However, I have no longer valid experience about Windows and how to set environment variables in the latest cmd.exe incarnations. (I used to write AUTOEXEC.bats for DOS, but that’s like long time ago). So if there are Windows gurus around please leave a comment.
- Easy way to make deployment specific changes in your application: run application differently on different computers by starting the app with a different command
- Pass parameters for your application when you cannot parse command line yourself. E.g. your application is using Django or Zope launcher script and poking the launch script arguments is not easy.
- Can be consumed everywhere inside your application, whether it is module level code (run on import), inside function, inside class and so on…
- Needs only two lines to set up (import os ; os.environ.get())
- Environment variables are especially useful if you want to have, in your scripts, some secret variables (username, password) which must not be committed on a public code repositories like Github (example). Note: the command lines are public for the all users of the UNIX system, so don’t do this on a shared server if you cannot expose the information to other server users.
Whether and how using environment variables is a good practice is debatable. But it definitely saves your butt when you need to make something quick and dirty.
You can give environment variables to your application on UNIX command line by simply prefixing your command with them (bash, sh, etc. shells):
DEMO_MODE=true python myapp.py
Or if you want to make the effect persistent for the current shell session use export (bash style)
export DEMO_MODE=true python myapp.py # Again python myapp.py
Then you can read out for this environment variable easily using os.environ dictionary (pseudo code)
import os # Just to check for the existing of DEMO_MODE environment variable, # but you could also compare its value, pass it forward and so on DEMO_MODE = os.environ.get("DEMO_MODE", None) # We make some of the class members conditional # by given environment vaiables class MyForm(object): name = StringField() if not DEMO_MODE: secret = PasswordField()
If you are using Python as the configuration language of your application (Django’s settings.py, Pyramid’s Configurator) you can also use this trick there to make some settings conditional which you normally hardcode in Python. Example settings.py:
import os # Set Django debug mode based on environment variable DEBUG = "DEBUG_MODE" in os.environ
Then, on the staging server, you would launch your Django application as
DEBUG_MODE=xxx python manage.py runserver
You can also give several environment variables once:
DEBUG_MODE=xxx API_SECRET=yyy python manage.py runserver
3. Another trick: socket.gethostname()
This is a way to bind certain settings to certain computers in your Python code by checking the name of the computer as returned by gethostname() call. This is useful if you don’t want to forget giving a specific launcher command on a specific server.
import socket # Temporary hack to run hidden fields on a demo server if socket.gethostname() in ['mikko-laptop', 'demoserver.example.com']: import special_config as config else: import normal_config as config
The only downside is that sometimes the hostname is not stable: I have noticed this behavior on OSX when connecting different networks on my laptop and apparently the name is given by the network.