Tips¶
Handling Inline Comments in .env Files¶
django-environ provides an optional feature to parse inline comments in .env
files. This is controlled by the parse_comments parameter in the read_env
method.
Modes¶
Enabled (``parse_comments=True``): Inline comments starting with
#will be ignored.Disabled (``parse_comments=False``): The entire line, including comments, will be read as the value.
Default: The behavior is the same as when
parse_comments=False.
Side Effects¶
While this feature can be useful for adding context to your .env files,
it can introduce unexpected behavior. For example, if your value includes
a # symbol, it will be truncated when parse_comments=True.
Why Disabled by Default?¶
In line with the project’s philosophy of being explicit and avoiding unexpected behavior, this feature is disabled by default. If you understand the implications and find the feature useful, you can enable it explicitly.
Example¶
Here is an example demonstrating the different modes of handling inline comments.
.env file contents:
# .env file contents
BOOL_TRUE_WITH_COMMENT=True # This is a comment
STR_WITH_HASH=foo#bar # This is also a comment
Python code:
import environ
# Using parse_comments=True
env = environ.Env()
env.read_env(parse_comments=True)
print(env('BOOL_TRUE_WITH_COMMENT')) # Output: True
print(env('STR_WITH_HASH')) # Output: foo
# Using parse_comments=False
env = environ.Env()
env.read_env(parse_comments=False)
print(env('BOOL_TRUE_WITH_COMMENT')) # Output: True # This is a comment
print(env('STR_WITH_HASH')) # Output: foo#bar # This is also a comment
# Using default behavior
env = environ.Env()
env.read_env()
print(env('BOOL_TRUE_WITH_COMMENT')) # Output: True # This is a comment
print(env('STR_WITH_HASH')) # Output: foo#bar # This is also a comment
Docker-style file based variables¶
Docker (swarm) and Kubernetes are two widely used platforms that store their secrets in tmpfs inside containers as individual files, providing a secure way to be able to share configuration data between containers.
Use environ.FileAwareEnv rather than environ.Env to first look for
environment variables with _FILE appended. If found, their contents will be
read from the file system and used instead.
For example, given an app with the following in its settings module:
import environ
env = environ.FileAwareEnv()
SECRET_KEY = env("SECRET_KEY")
the example docker-compose.yml for would contain:
secrets:
secret_key:
external: true
services:
app:
secrets:
- secret_key
environment:
- SECRET_KEY_FILE=/run/secrets/secret_key
Using unsafe characters in URLs¶
In order to use unsafe characters you have to encode with urllib.parse.quote()
before you set into .env file. Encode only the value (i.e. the password) not the whole url.
DATABASE_URL=mysql://user:%23password@127.0.0.1:3306/dbname
See https://perishablepress.com/stop-using-unsafe-characters-in-urls/ for reference.
Smart Casting¶
django-environ has a “Smart-casting” enabled by default, if you don’t provide a cast type, it will be detected from default type.
This could raise side effects (see #192).
To disable it use env.smart_cast = False.
Note
The next major release will disable it by default.
Callable defaults (lazy evaluation)¶
The default argument accepts any callable (a function, lambda, or class
that implements __call__). When the environment variable is absent the
callable is invoked with no arguments and its return value is used as the
default. The callable is not invoked when the variable is present in the
environment.
This is useful when generating a default value has side-effects (e.g. creating a temporary directory) and you only want that to happen when the variable is truly missing.
import tempfile
import environ
env = environ.Env()
# tempfile.mkdtemp() is called only when MEDIA_ROOT is not set
MEDIA_ROOT = env('MEDIA_ROOT', default=tempfile.mkdtemp)
Note
When smart_cast is enabled (the default), the cast type is not
inferred from a callable default. Provide an explicit cast or a type
in the scheme tuple if you need type coercion.
Warn when defaults are used¶
If you want visibility when a missing environment variable falls back to a
default value, enable warnings on the Env instance:
import environ
env = environ.Env()
env.warn_on_default = True
value = env("MISSING_VAR", default="fallback")
When enabled, django-environ emits DefaultValueWarning for missing
variables that return an explicit default.
Multiple redis cache locations¶
For redis cache, multiple master/slave or shard locations can be configured as follows:
CACHE_URL='rediscache://master:6379,slave1:6379,slave2:6379/1'
Email settings¶
In order to set email configuration for Django you can use this code:
# The email() method is an alias for email_url().
EMAIL_CONFIG = env.email(
'EMAIL_URL',
default='smtp://user:password@localhost:25'
)
vars().update(EMAIL_CONFIG)
SQLite urls¶
SQLite connects to file based databases. The same URL format is used, omitting the hostname, and using the “file” portion as the filename of the database. This has the effect of four slashes being present for an absolute
file path: sqlite:////full/path/to/your/database/file.sqlite.
Nested lists¶
Some settings such as Django’s ADMINS make use of nested lists.
You can use something like this to handle similar cases.
# DJANGO_ADMINS=Blake:blake@cyb.org,Alice:alice@cyb.org
ADMINS = [x.split(':') for x in env.list('DJANGO_ADMINS')]
# or use more specific function
from email.utils import getaddresses
# DJANGO_ADMINS=Alice Judge <alice@cyb.org>,blake@cyb.org
ADMINS = getaddresses([env('DJANGO_ADMINS')])
# another option is to use parseaddr from email.utils
# DJANGO_ADMINS="Blake <blake@cyb.org>, Alice Judge <alice@cyb.org>"
from email.utils import parseaddr
ADMINS = tuple(parseaddr(email) for email in env.list('DJANGO_ADMINS'))
Complex dict format¶
Sometimes we need to get a bit more complex dict type than usual. For example,
consider Djangosaml2’s SAML_ATTRIBUTE_MAPPING:
SAML_ATTRIBUTE_MAPPING = {
'uid': ('username', ),
'mail': ('email', ),
'cn': ('first_name', ),
'sn': ('last_name', ),
}
A dict of this format can be obtained as shown below:
.env file:
# .env file contents
SAML_ATTRIBUTE_MAPPING="uid=username;mail=email;cn=first_name;sn=last_name;"
settings.py file:
# settings.py file contents
import environ
env = environ.Env()
# {'uid': ('username',), 'mail': ('email',), 'cn': ('first_name',), 'sn': ('last_name',)}
SAML_ATTRIBUTE_MAPPING = env.dict(
'SAML_ATTRIBUTE_MAPPING',
cast={'value': tuple},
default={}
)
Multiline value¶
To get multiline value pass multiline=True to `str()`.
Note
You shouldn’t escape newline/tab characters yourself if you want to preserve the formatting.
The following example demonstrates the above:
.env file:
# .env file contents
UNQUOTED_CERT=---BEGIN---\r\n---END---
QUOTED_CERT="---BEGIN---\r\n---END---"
ESCAPED_CERT=---BEGIN---\\n---END---
settings.py file:
# settings.py file contents
import environ
env = environ.Env()
print(env.str('UNQUOTED_CERT', multiline=True))
# ---BEGIN---
# ---END---
print(env.str('UNQUOTED_CERT', multiline=False))
# ---BEGIN---\r\n---END---
print(env.str('QUOTED_CERT', multiline=True))
# ---BEGIN---
# ---END---
print(env.str('QUOTED_CERT', multiline=False))
# ---BEGIN---\r\n---END---
print(env.str('ESCAPED_CERT', multiline=True))
# ---BEGIN---\
# ---END---
print(env.str('ESCAPED_CERT', multiline=False))
# ---BEGIN---\\n---END---
Restrict string values with choices¶
You can restrict env.str() to an allowed list of values using
choices. If the value is not in the provided list,
django.core.exceptions.ImproperlyConfigured is raised.
import environ
from django.core.exceptions import ImproperlyConfigured
env = environ.Env()
# APP_ENV=prod
env.str("APP_ENV", choices=("dev", "prod", "staging")) # "prod"
# APP_ENV=unknown
try:
env.str("APP_ENV", choices=("dev", "prod", "staging"))
except ImproperlyConfigured:
...
Proxy value¶
Values that begin with a $ may be interpolated. This feature is enabled by
default:
import environ
env = environ.Env()
# BAR=FOO
# PROXY=$BAR
assert env.str('PROXY') == 'FOO'
To disable proxy interpolation and read the raw value instead, set
env.interpolate = False:
env = environ.Env()
env.interpolate = False
# PROXY=$BAR
assert env.str('PROXY') == '$BAR'
Escape Proxy¶
If you’re having trouble with values starting with dollar sign ($) without the intention of proxying the value to
another, You should enable the escape_proxy and prepend a backslash to it.
import environ
env = environ.Env()
env.escape_proxy = True
# ESCAPED_VAR=\$baz
env.str('ESCAPED_VAR') # $baz
Reading env files¶
Multiple env files¶
There is an ability point to the .env file location using an environment variable. This feature may be convenient in a production systems with a different .env file location.
The following example demonstrates the above:
# /etc/environment file contents
DEBUG=False
# .env file contents
DEBUG=True
env = environ.Env()
env.read_env(env.str('ENV_PATH', '.env'))
Now ENV_PATH=/etc/environment ./manage.py runserver uses /etc/environment
while ./manage.py runserver uses .env.
Using Path objects when reading env¶
It is possible to use of pathlib.Path objects when reading environment
file from the filesystem:
import os
import pathlib
import environ
# Build paths inside the project like this: BASE_DIR('subdir').
BASE_DIR = environ.Path(__file__) - 3
env = environ.Env()
# The four lines below do the same:
env.read_env(BASE_DIR('.env'))
env.read_env(os.path.join(BASE_DIR, '.env'))
env.read_env(pathlib.Path(str(BASE_DIR)).joinpath('.env'))
env.read_env(pathlib.Path(str(BASE_DIR)) / '.env')
Overwriting existing environment values from env files¶
If you want variables set within your env files to take higher precedence than
an existing set environment variable, use the overwrite=True argument of
environ.Env.read_env(). For example:
env = environ.Env()
env.read_env(BASE_DIR('.env'), overwrite=True)
Handling prefixes¶
Sometimes it is desirable to be able to prefix all environment variables. For
example, if you are using Django, you may want to prefix all environment
variables with DJANGO_. This can be done by setting the prefix
to desired prefix. For example:
.env file:
# .env file contents
DJANGO_TEST="foo"
settings.py file:
# settings.py file contents
import environ
env = environ.Env()
env.prefix = 'DJANGO_'
env.str('TEST') # foo