docs/tips.rst
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.
parse_comments=True): Inline comments starting with # will be ignored.parse_comments=False): The entire line, including comments, will be read as the value.parse_comments=False.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.
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.
Here is an example demonstrating the different modes of handling inline comments.
.env file contents:
.. code-block:: shell
BOOL_TRUE_WITH_COMMENT=True # This is a comment STR_WITH_HASH=foo#bar # This is also a comment
Python code:
.. code-block:: python
import environ
env = environ.Env() env.read_env(parse_comments=True) print(env('BOOL_TRUE_WITH_COMMENT')) # Output: True print(env('STR_WITH_HASH')) # Output: foo
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
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 (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 :class:.environ.FileAwareEnv rather than :class:.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:
.. code-block:: python
import environ
env = environ.FileAwareEnv() SECRET_KEY = env("SECRET_KEY")
the example docker-compose.yml for would contain:
.. code-block:: yaml
secrets: secret_key: external: true
services: app: secrets: - secret_key environment: - SECRET_KEY_FILE=/run/secrets/secret_key
In order to use unsafe characters you have to encode with :py:func:urllib.parse.quote
before you set into .env file. Encode only the value (i.e. the password) not the whole url.
.. code-block:: shell
DATABASE_URL=mysql://user:%[email protected]:3306/dbname
See https://perishablepress.com/stop-using-unsafe-characters-in-urls/ for reference.
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 <https://github.com/joke2k/django-environ/issues/192>_).
To disable it use env.smart_cast = False.
.. note::
The next major release will disable it by default.
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.
.. code-block:: python
import tempfile import environ
env = environ.Env()
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.
If you want visibility when a missing environment variable falls back to a
default value, enable warnings on the Env instance:
.. code-block:: python
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.
For redis cache, multiple master/slave or shard locations can be configured as follows:
.. code-block:: shell
CACHE_URL='rediscache://master:6379,slave1:6379,slave2:6379/1'
In order to set email configuration for Django you can use this code:
.. code-block:: python
EMAIL_CONFIG = env.email( 'EMAIL_URL', default='smtp://user:password@localhost:25' )
vars().update(EMAIL_CONFIG)
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.
Some settings such as Django's ADMINS make use of nested lists.
You can use something like this to handle similar cases.
.. code-block:: python
ADMINS = [x.split(':') for x in env.list('DJANGO_ADMINS')]
from email.utils import getaddresses
ADMINS = getaddresses([env('DJANGO_ADMINS')])
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:
.. code-block:: python
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:
.. code-block:: shell
SAML_ATTRIBUTE_MAPPING="uid=username;mail=email;cn=first_name;sn=last_name;"
settings.py file:
.. code-block:: python
import environ
env = environ.Env()
SAML_ATTRIBUTE_MAPPING = env.dict( 'SAML_ATTRIBUTE_MAPPING', cast={'value': tuple}, default={} )
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:
.. code-block:: shell
UNQUOTED_CERT=---BEGIN---\r\n---END--- QUOTED_CERT="---BEGIN---\r\n---END---" ESCAPED_CERT=---BEGIN---\n---END---
settings.py file:
.. code-block:: python
import environ
env = environ.Env()
print(env.str('UNQUOTED_CERT', multiline=True))
print(env.str('UNQUOTED_CERT', multiline=False))
print(env.str('QUOTED_CERT', multiline=True))
print(env.str('QUOTED_CERT', multiline=False))
print(env.str('ESCAPED_CERT', multiline=True))
print(env.str('ESCAPED_CERT', multiline=False))
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.
.. code-block:: python
import environ from django.core.exceptions import ImproperlyConfigured
env = environ.Env()
env.str("APP_ENV", choices=("dev", "prod", "staging")) # "prod"
try: env.str("APP_ENV", choices=("dev", "prod", "staging")) except ImproperlyConfigured: ...
Values that begin with a $ may be interpolated. Pass interpolate=True to
environ.Env() to enable this feature:
.. code-block:: python
import environ
env = environ.Env(interpolate=True)
print(env.str('PROXY')) FOO
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.
.. code-block:: python
import environ
env = environ.Env()
env.escape_proxy = True
# ESCAPED_VAR=\$baz
env.str('ESCAPED_VAR') # $baz
.. _multiple-env-files-label:
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:
.. code-block:: shell
DEBUG=False
.. code-block:: shell
DEBUG=True
.. code-block:: python
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.
It is possible to use of :py:class:pathlib.Path objects when reading environment
file from the filesystem:
.. code-block:: python
import os import pathlib
import environ
BASE_DIR = environ.Path(file) - 3
env = environ.Env()
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-env:
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
:meth:.environ.Env.read_env. For example:
.. code-block:: python
env = environ.Env() env.read_env(BASE_DIR('.env'), overwrite=True)
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:
.. code-block:: shell
DJANGO_TEST="foo"
settings.py file:
.. code-block:: python
import environ
env = environ.Env() env.prefix = 'DJANGO_'
env.str('TEST') # foo