File: tips.rst

package info (click to toggle)
django-environ 0.12.0-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 516 kB
  • sloc: python: 2,434; makefile: 171
file content (426 lines) | stat: -rw-r--r-- 10,399 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
====
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**:

.. code-block:: shell

   # .env file contents
   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

   # 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 :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


Using unsafe characters in URLs
===============================

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:%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 <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.


Multiple redis cache locations
==============================

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'


Email settings
==============

In order to set email configuration for Django you can use this code:

.. code-block:: python

   # 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.

.. code-block:: python

   # 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:

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

   # .env file contents
   SAML_ATTRIBUTE_MAPPING="uid=username;mail=email;cn=first_name;sn=last_name;"

**settings.py file**:

.. code-block:: python

   # 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**:

.. code-block:: shell

   # .env file contents
   UNQUOTED_CERT=---BEGIN---\r\n---END---
   QUOTED_CERT="---BEGIN---\r\n---END---"
   ESCAPED_CERT=---BEGIN---\\n---END---

**settings.py file**:

.. code-block:: python

   # 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---

Proxy value
===========

Values that being 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)

   # BAR=FOO
   # PROXY=$BAR
   >>> print(env.str('PROXY'))
   FOO


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.

.. code-block:: python

    import environ

    env = environ.Env()
    env.escape_proxy = True

    # ESCAPED_VAR=\$baz
    env.str('ESCAPED_VAR')  # $baz


Reading env files
=================

.. _multiple-env-files-label:

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:

.. code-block:: shell

   # /etc/environment file contents
   DEBUG=False

.. code-block:: shell

   # .env file contents
   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``.


Using Path objects when reading 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


   # 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-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
:meth:`.environ.Env.read_env`. For example:

.. code-block:: python

   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**:

.. code-block:: shell

   # .env file contents
   DJANGO_TEST="foo"

**settings.py file**:

.. code-block:: python

   # settings.py file contents
   import environ


   env = environ.Env()
   env.prefix = 'DJANGO_'

   env.str('TEST')  # foo