Codebase list django-environ / 9520c12
import python-django-environ_0.4.0.orig.tar.gz Brian May 8 years ago
15 changed file(s) with 2439 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Copyright (c) 2013-2015, Daniele Faraglia
1
2 Permission is hereby granted, free of charge, to any person obtaining a copy
3 of this software and associated documentation files (the "Software"), to deal
4 in the Software without restriction, including without limitation the rights
5 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6 copies of the Software, and to permit persons to whom the Software is
7 furnished to do so, subject to the following conditions:
8
9 The above copyright notice and this permission notice shall be included in
10 all copies or substantial portions of the Software.
11
12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18 THE SOFTWARE.
0 include *.rst
1 include *.txt
0 Metadata-Version: 1.1
1 Name: django-environ
2 Version: 0.4.0
3 Summary: Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.
4 Home-page: http://github.com/joke2k/django-environ
5 Author: joke2k
6 Author-email: joke2k@gmail.com
7 License: MIT License
8 Description: ==============
9 Django-environ
10 ==============
11
12 Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.
13
14 |pypi| |unix_build| |windows_build| |coverage| |downloads| |license|
15
16
17 This module is a merge of:
18
19 * `envparse`_
20 * `honcho`_
21 * `dj-database-url`_
22 * `dj-search-url`_
23 * `dj-config-url`_
24 * `django-cache-url`_
25
26 and inspired by:
27
28 * `12factor`_
29 * `12factor-django`_
30 * `Two Scoops of Django`_
31
32 This is your `settings.py` file before you have installed **django-environ**
33
34 ::
35
36 import os
37 SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
38
39 DEBUG = True
40 TEMPLATE_DEBUG = DEBUG
41
42 DATABASES = {
43 'default': {
44 'ENGINE': 'django.db.backends.postgresql_psycopg2',
45 'NAME': 'database',
46 'USER': 'user',
47 'PASSWORD': 'githubbedpassword',
48 'HOST': '127.0.0.1',
49 'PORT': '8458',
50 }
51 'extra': {
52 'ENGINE': 'django.db.backends.sqlite3',
53 'NAME': os.path.join(SITE_ROOT, 'database.sqlite')
54 }
55 }
56
57 MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
58 MEDIA_URL = 'media/'
59 STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
60 STATIC_URL = 'static/'
61
62 SECRET_KEY = '...im incredibly still here...'
63
64 CACHES = {
65 'default': {
66 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
67 'LOCATION': [
68 '127.0.0.1:11211', '127.0.0.1:11212', '127.0.0.1:11213',
69 ]
70 },
71 'redis': {
72 'BACKEND': 'django_redis.cache.RedisCache',
73 'LOCATION': '127.0.0.1:6379:1',
74 'OPTIONS': {
75 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
76 'PASSWORD': 'redis-githubbed-password',
77 }
78 }
79 }
80
81 After::
82
83 import environ
84 root = environ.Path(__file__) - 3 # three folder back (/a/b/c/ - 3 = /)
85 env = environ.Env(DEBUG=(bool, False),) # set default values and casting
86 environ.Env.read_env() # reading .env file
87
88 SITE_ROOT = root()
89
90 DEBUG = env('DEBUG') # False if not in os.environ
91 TEMPLATE_DEBUG = DEBUG
92
93 DATABASES = {
94 'default': env.db(), # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
95 'extra': env.db('SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db')
96 }
97
98 public_root = root.path('public/')
99
100 MEDIA_ROOT = public_root('media')
101 MEDIA_URL = 'media/'
102 STATIC_ROOT = public_root('static')
103 STATIC_URL = 'static/'
104
105 SECRET_KEY = env('SECRET_KEY') # Raises ImproperlyConfigured exception if SECRET_KEY not in os.environ
106
107 CACHES = {
108 'default': env.cache(),
109 'redis': env.cache('REDIS_URL')
110 }
111
112 You can also pass `read_env()` an explicit path to the .env file.
113 Create a `.env` file::
114
115 DEBUG=on
116 # DJANGO_SETTINGS_MODULE=myapp.settings.dev
117 SECRET_KEY=your-secret-key
118 DATABASE_URL=psql://urser:un-githubbedpassword@127.0.0.1:8458/database
119 # SQLITE_URL=sqlite:///my-local-sqlite.db
120 CACHE_URL=memcache://127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213
121 REDIS_URL=rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=redis-un-githubbed-password
122
123
124 How to install
125 ==============
126
127 ::
128
129 $ pip install django-environ
130
131
132 How to use
133 ==========
134
135 There are only two classes, `environ.Env` and `environ.Path`
136
137 ::
138
139 >>> import environ
140 >>> env = environ.Env(
141 DEBUG=(bool, False),
142 )
143 >>> env('DEBUG')
144 False
145 >>> env('DEBUG', default=True)
146 True
147
148 >>> open('.myenv', 'a').write('DEBUG=on')
149 >>> environ.Env.read_env('.myenv') # or env.read_env('.myenv')
150 >>> env('DEBUG')
151 True
152
153 >>> open('.myenv', 'a').write('\nINT_VAR=1010')
154 >>> env.int('INT_VAR'), env.str('INT_VAR')
155 1010, '1010'
156
157 >>> open('.myenv', 'a').write('\nDATABASE_URL=sqlite:///my-local-sqlite.db')
158 >>> env.read_env('.myenv')
159 >>> env.db()
160 {'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'my-local-sqlite.db', 'HOST': '', 'USER': '', 'PASSWORD': '', 'PORT': ''}
161
162 >>> root = env.path('/home/myproject/')
163 >>> root('static')
164 '/home/myproject/static'
165
166
167 Supported Types
168 ===============
169
170 - str
171 - bool
172 - int
173 - float
174 - json
175 - list (FOO=a,b,c)
176 - tuple (FOO=(a,b,c))
177 - dict (BAR=key=val,foo=bar)
178 - url
179 - path (environ.Path)
180 - db_url
181 - PostgreSQL: postgres://, pgsql://, psql:// or postgresql://
182 - PostGIS: postgis://
183 - MySQL: mysql:// or mysql2://
184 - MySQL for GeoDjango: mysqlgis://
185 - SQLITE: sqlite://
186 - SQLITE with SPATIALITE for GeoDjango: spatialite://
187 - LDAP: ldap://
188 - cache_url
189 - Database: dbcache://
190 - Dummy: dummycache://
191 - File: filecache://
192 - Memory: locmemcache://
193 - Memcached: memcache://
194 - Python memory: pymemcache://
195 - Redis: rediscache://
196 - search_url
197 - ElasticSearch: elasticsearch://
198 - Solr: solr://
199 - Whoosh: whoosh://
200 - Xapian: xapian://
201 - Simple cache: simple://
202 - email_url
203 - SMTP: smtp://
204 - SMTP+SSL: smtp+ssl://
205 - SMTP+TLS: smtp+tls://
206 - Console mail: consolemail://
207 - File mail: filemail://
208 - LocMem mail: memorymail://
209 - Dummy mail: dummymail://
210
211 Tests
212 =====
213
214 ::
215
216 $ git clone git@github.com:joke2k/django-environ.git
217 $ cd django-environ/
218 $ python setup.py test
219
220
221 License
222 =======
223
224 Django-environ is licensed under the MIT License - see the `LICENSE`_ file for details
225
226 Changelog
227 =========
228
229 `0.4.0 - 23-September-2015 <http://github.com/joke2k/django-environ/compare/v0.3...v0.4>`__
230 -------------------------------------------------------------------------------------------
231 - Fix non-ascii values (broken in Python 2.x)
232 - New email schemes - smtp+ssl and smtp+tls (smtps would be deprecated)
233 - redis_cache replaced by django_redis
234 - Add tuple support. Thanks to @anonymouzz
235 - Add LDAP url support for database (django-ldapdb)
236 - Fix psql/pgsql url
237
238 `0.3 - 03-June-2014 <http://github.com/joke2k/django-environ/compare/v0.2.1...v0.3>`__
239 --------------------------------------------------------------------------------------
240 - Add cache url support
241 - Add email url support
242 - Add search url support
243 - Rewriting README.rst
244
245 0.2.1 19-April-2013
246 -------------------
247 - environ/environ.py: Env.__call__ now uses Env.get_value instance method
248
249 0.2 16-April-2013
250 -----------------
251 - environ/environ.py, environ/test.py, environ/test_env.txt: add advanced
252 float parsing (comma and dot symbols to separate thousands and decimals)
253 - README.rst, docs/index.rst: fix TYPO in documentation
254
255 0.1 02-April-2013
256 -----------------
257 - initial release
258
259 Credits
260 =======
261
262 - `12factor`_
263 - `12factor-django`_
264 - `Two Scoops of Django`_
265 - `rconradharris`_ / `envparse`_
266 - `kennethreitz`_ / `dj-database-url`_
267 - `migonzalvar`_ / `dj-email-url`_
268 - `ghickman`_ / `django-cache-url`_
269 - `dstufft`_ / `dj-search-url`_
270 - `julianwachholz`_ / `dj-config-url`_
271 - `nickstenning`_ / `honcho`_
272 - `envparse`_
273 - `Distribute`_
274 - `modern-package-template`_
275
276 .. _rconradharris: https://github.com/rconradharris
277 .. _envparse: https://github.com/rconradharris/envparse
278
279 .. _kennethreitz: https://github.com/kennethreitz
280 .. _dj-database-url: https://github.com/kennethreitz/dj-database-url
281
282 .. _migonzalvar: https://github.com/migonzalvar
283 .. _dj-email-url: https://github.com/migonzalvar/dj-email-url
284
285 .. _ghickman: https://github.com/ghickman
286 .. _django-cache-url: https://github.com/ghickman/django-cache-url
287
288 .. _julianwachholz: https://github.com/julianwachholz
289 .. _dj-config-url: https://github.com/julianwachholz/dj-config-url
290
291 .. _dstufft: https://github.com/dstufft
292 .. _dj-search-url: https://github.com/dstufft/dj-search-url
293
294 .. _nickstenning: https://github.com/nickstenning
295 .. _honcho: https://github.com/nickstenning/honcho
296
297 .. _12factor: http://www.12factor.net/
298 .. _12factor-django: http://www.wellfireinteractive.com/blog/easier-12-factor-django/
299 .. _`Two Scoops of Django`: http://twoscoopspress.org/
300
301 .. _Distribute: http://pypi.python.org/pypi/distribute
302 .. _`modern-package-template`: http://pypi.python.org/pypi/modern-package-template
303
304 .. |pypi| image:: https://img.shields.io/pypi/v/django-environ.svg?style=flat-square&label=version
305 :target: https://pypi.python.org/pypi/django-environ
306 :alt: Latest version released on PyPi
307
308 .. |coverage| image:: https://img.shields.io/coveralls/joke2k/django-environ/master.svg?style=flat-square
309 :target: https://coveralls.io/r/joke2k/django-environ?branch=master
310 :alt: Test coverage
311
312 .. |unix_build| image:: https://img.shields.io/travis/joke2k/django-environ/master.svg?style=flat-square&label=unix%20build
313 :target: http://travis-ci.org/joke2k/django-environ
314 :alt: Build status of the master branch on Mac/Linux
315
316 .. |windows_build| image:: https://img.shields.io/appveyor/ci/joke2k/django-environ.svg?style=flat-square&label=windows%20build
317 :target: https://ci.appveyor.com/project/joke2k/django-environ
318 :alt: Build status of the master branch on Windows
319
320 .. |downloads| image:: https://img.shields.io/pypi/dm/django-environ.svg?style=flat-square
321 :target: https://pypi.python.org/pypi/django-environ
322 :alt: Monthly downloads
323
324 .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
325 :target: https://raw.githubusercontent.com/joke2k/django-environ/master/LICENSE.txt
326 :alt: Package license
327
328 .. _LICENSE: https://github.com/joke2k/django-environ/blob/master/LICENSE.txt
329
330 Keywords: d,j,a,n,g,o, ,e,n,v,i,r,o,n,m,e,n,t, ,v,a,r,i,a,b,l,e,s, ,1,2,f,a,c,t,o,r
331 Platform: any
332 Classifier: Development Status :: 3 - Alpha
333 Classifier: Intended Audience :: Information Technology
334 Classifier: Programming Language :: Python
335 Classifier: Programming Language :: Python :: 2
336 Classifier: Programming Language :: Python :: 3
337 Classifier: Topic :: Software Development :: Libraries :: Python Modules
338 Classifier: Topic :: Utilities
339 Classifier: License :: OSI Approved :: MIT License
340 Classifier: Framework :: Django
0 ==============
1 Django-environ
2 ==============
3
4 Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.
5
6 |pypi| |unix_build| |windows_build| |coverage| |downloads| |license|
7
8
9 This module is a merge of:
10
11 * `envparse`_
12 * `honcho`_
13 * `dj-database-url`_
14 * `dj-search-url`_
15 * `dj-config-url`_
16 * `django-cache-url`_
17
18 and inspired by:
19
20 * `12factor`_
21 * `12factor-django`_
22 * `Two Scoops of Django`_
23
24 This is your `settings.py` file before you have installed **django-environ**
25
26 ::
27
28 import os
29 SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
30
31 DEBUG = True
32 TEMPLATE_DEBUG = DEBUG
33
34 DATABASES = {
35 'default': {
36 'ENGINE': 'django.db.backends.postgresql_psycopg2',
37 'NAME': 'database',
38 'USER': 'user',
39 'PASSWORD': 'githubbedpassword',
40 'HOST': '127.0.0.1',
41 'PORT': '8458',
42 }
43 'extra': {
44 'ENGINE': 'django.db.backends.sqlite3',
45 'NAME': os.path.join(SITE_ROOT, 'database.sqlite')
46 }
47 }
48
49 MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
50 MEDIA_URL = 'media/'
51 STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
52 STATIC_URL = 'static/'
53
54 SECRET_KEY = '...im incredibly still here...'
55
56 CACHES = {
57 'default': {
58 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
59 'LOCATION': [
60 '127.0.0.1:11211', '127.0.0.1:11212', '127.0.0.1:11213',
61 ]
62 },
63 'redis': {
64 'BACKEND': 'django_redis.cache.RedisCache',
65 'LOCATION': '127.0.0.1:6379:1',
66 'OPTIONS': {
67 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
68 'PASSWORD': 'redis-githubbed-password',
69 }
70 }
71 }
72
73 After::
74
75 import environ
76 root = environ.Path(__file__) - 3 # three folder back (/a/b/c/ - 3 = /)
77 env = environ.Env(DEBUG=(bool, False),) # set default values and casting
78 environ.Env.read_env() # reading .env file
79
80 SITE_ROOT = root()
81
82 DEBUG = env('DEBUG') # False if not in os.environ
83 TEMPLATE_DEBUG = DEBUG
84
85 DATABASES = {
86 'default': env.db(), # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
87 'extra': env.db('SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db')
88 }
89
90 public_root = root.path('public/')
91
92 MEDIA_ROOT = public_root('media')
93 MEDIA_URL = 'media/'
94 STATIC_ROOT = public_root('static')
95 STATIC_URL = 'static/'
96
97 SECRET_KEY = env('SECRET_KEY') # Raises ImproperlyConfigured exception if SECRET_KEY not in os.environ
98
99 CACHES = {
100 'default': env.cache(),
101 'redis': env.cache('REDIS_URL')
102 }
103
104 You can also pass `read_env()` an explicit path to the .env file.
105 Create a `.env` file::
106
107 DEBUG=on
108 # DJANGO_SETTINGS_MODULE=myapp.settings.dev
109 SECRET_KEY=your-secret-key
110 DATABASE_URL=psql://urser:un-githubbedpassword@127.0.0.1:8458/database
111 # SQLITE_URL=sqlite:///my-local-sqlite.db
112 CACHE_URL=memcache://127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213
113 REDIS_URL=rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=redis-un-githubbed-password
114
115
116 How to install
117 ==============
118
119 ::
120
121 $ pip install django-environ
122
123
124 How to use
125 ==========
126
127 There are only two classes, `environ.Env` and `environ.Path`
128
129 ::
130
131 >>> import environ
132 >>> env = environ.Env(
133 DEBUG=(bool, False),
134 )
135 >>> env('DEBUG')
136 False
137 >>> env('DEBUG', default=True)
138 True
139
140 >>> open('.myenv', 'a').write('DEBUG=on')
141 >>> environ.Env.read_env('.myenv') # or env.read_env('.myenv')
142 >>> env('DEBUG')
143 True
144
145 >>> open('.myenv', 'a').write('\nINT_VAR=1010')
146 >>> env.int('INT_VAR'), env.str('INT_VAR')
147 1010, '1010'
148
149 >>> open('.myenv', 'a').write('\nDATABASE_URL=sqlite:///my-local-sqlite.db')
150 >>> env.read_env('.myenv')
151 >>> env.db()
152 {'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'my-local-sqlite.db', 'HOST': '', 'USER': '', 'PASSWORD': '', 'PORT': ''}
153
154 >>> root = env.path('/home/myproject/')
155 >>> root('static')
156 '/home/myproject/static'
157
158
159 Supported Types
160 ===============
161
162 - str
163 - bool
164 - int
165 - float
166 - json
167 - list (FOO=a,b,c)
168 - tuple (FOO=(a,b,c))
169 - dict (BAR=key=val,foo=bar)
170 - url
171 - path (environ.Path)
172 - db_url
173 - PostgreSQL: postgres://, pgsql://, psql:// or postgresql://
174 - PostGIS: postgis://
175 - MySQL: mysql:// or mysql2://
176 - MySQL for GeoDjango: mysqlgis://
177 - SQLITE: sqlite://
178 - SQLITE with SPATIALITE for GeoDjango: spatialite://
179 - LDAP: ldap://
180 - cache_url
181 - Database: dbcache://
182 - Dummy: dummycache://
183 - File: filecache://
184 - Memory: locmemcache://
185 - Memcached: memcache://
186 - Python memory: pymemcache://
187 - Redis: rediscache://
188 - search_url
189 - ElasticSearch: elasticsearch://
190 - Solr: solr://
191 - Whoosh: whoosh://
192 - Xapian: xapian://
193 - Simple cache: simple://
194 - email_url
195 - SMTP: smtp://
196 - SMTP+SSL: smtp+ssl://
197 - SMTP+TLS: smtp+tls://
198 - Console mail: consolemail://
199 - File mail: filemail://
200 - LocMem mail: memorymail://
201 - Dummy mail: dummymail://
202
203 Tests
204 =====
205
206 ::
207
208 $ git clone git@github.com:joke2k/django-environ.git
209 $ cd django-environ/
210 $ python setup.py test
211
212
213 License
214 =======
215
216 Django-environ is licensed under the MIT License - see the `LICENSE`_ file for details
217
218 Changelog
219 =========
220
221 `0.4.0 - 23-September-2015 <http://github.com/joke2k/django-environ/compare/v0.3...v0.4>`__
222 -------------------------------------------------------------------------------------------
223 - Fix non-ascii values (broken in Python 2.x)
224 - New email schemes - smtp+ssl and smtp+tls (smtps would be deprecated)
225 - redis_cache replaced by django_redis
226 - Add tuple support. Thanks to @anonymouzz
227 - Add LDAP url support for database (django-ldapdb)
228 - Fix psql/pgsql url
229
230 `0.3 - 03-June-2014 <http://github.com/joke2k/django-environ/compare/v0.2.1...v0.3>`__
231 --------------------------------------------------------------------------------------
232 - Add cache url support
233 - Add email url support
234 - Add search url support
235 - Rewriting README.rst
236
237 0.2.1 19-April-2013
238 -------------------
239 - environ/environ.py: Env.__call__ now uses Env.get_value instance method
240
241 0.2 16-April-2013
242 -----------------
243 - environ/environ.py, environ/test.py, environ/test_env.txt: add advanced
244 float parsing (comma and dot symbols to separate thousands and decimals)
245 - README.rst, docs/index.rst: fix TYPO in documentation
246
247 0.1 02-April-2013
248 -----------------
249 - initial release
250
251 Credits
252 =======
253
254 - `12factor`_
255 - `12factor-django`_
256 - `Two Scoops of Django`_
257 - `rconradharris`_ / `envparse`_
258 - `kennethreitz`_ / `dj-database-url`_
259 - `migonzalvar`_ / `dj-email-url`_
260 - `ghickman`_ / `django-cache-url`_
261 - `dstufft`_ / `dj-search-url`_
262 - `julianwachholz`_ / `dj-config-url`_
263 - `nickstenning`_ / `honcho`_
264 - `envparse`_
265 - `Distribute`_
266 - `modern-package-template`_
267
268 .. _rconradharris: https://github.com/rconradharris
269 .. _envparse: https://github.com/rconradharris/envparse
270
271 .. _kennethreitz: https://github.com/kennethreitz
272 .. _dj-database-url: https://github.com/kennethreitz/dj-database-url
273
274 .. _migonzalvar: https://github.com/migonzalvar
275 .. _dj-email-url: https://github.com/migonzalvar/dj-email-url
276
277 .. _ghickman: https://github.com/ghickman
278 .. _django-cache-url: https://github.com/ghickman/django-cache-url
279
280 .. _julianwachholz: https://github.com/julianwachholz
281 .. _dj-config-url: https://github.com/julianwachholz/dj-config-url
282
283 .. _dstufft: https://github.com/dstufft
284 .. _dj-search-url: https://github.com/dstufft/dj-search-url
285
286 .. _nickstenning: https://github.com/nickstenning
287 .. _honcho: https://github.com/nickstenning/honcho
288
289 .. _12factor: http://www.12factor.net/
290 .. _12factor-django: http://www.wellfireinteractive.com/blog/easier-12-factor-django/
291 .. _`Two Scoops of Django`: http://twoscoopspress.org/
292
293 .. _Distribute: http://pypi.python.org/pypi/distribute
294 .. _`modern-package-template`: http://pypi.python.org/pypi/modern-package-template
295
296 .. |pypi| image:: https://img.shields.io/pypi/v/django-environ.svg?style=flat-square&label=version
297 :target: https://pypi.python.org/pypi/django-environ
298 :alt: Latest version released on PyPi
299
300 .. |coverage| image:: https://img.shields.io/coveralls/joke2k/django-environ/master.svg?style=flat-square
301 :target: https://coveralls.io/r/joke2k/django-environ?branch=master
302 :alt: Test coverage
303
304 .. |unix_build| image:: https://img.shields.io/travis/joke2k/django-environ/master.svg?style=flat-square&label=unix%20build
305 :target: http://travis-ci.org/joke2k/django-environ
306 :alt: Build status of the master branch on Mac/Linux
307
308 .. |windows_build| image:: https://img.shields.io/appveyor/ci/joke2k/django-environ.svg?style=flat-square&label=windows%20build
309 :target: https://ci.appveyor.com/project/joke2k/django-environ
310 :alt: Build status of the master branch on Windows
311
312 .. |downloads| image:: https://img.shields.io/pypi/dm/django-environ.svg?style=flat-square
313 :target: https://pypi.python.org/pypi/django-environ
314 :alt: Monthly downloads
315
316 .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
317 :target: https://raw.githubusercontent.com/joke2k/django-environ/master/LICENSE.txt
318 :alt: Package license
319
320 .. _LICENSE: https://github.com/joke2k/django-environ/blob/master/LICENSE.txt
0 Metadata-Version: 1.1
1 Name: django-environ
2 Version: 0.4.0
3 Summary: Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.
4 Home-page: http://github.com/joke2k/django-environ
5 Author: joke2k
6 Author-email: joke2k@gmail.com
7 License: MIT License
8 Description: ==============
9 Django-environ
10 ==============
11
12 Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.
13
14 |pypi| |unix_build| |windows_build| |coverage| |downloads| |license|
15
16
17 This module is a merge of:
18
19 * `envparse`_
20 * `honcho`_
21 * `dj-database-url`_
22 * `dj-search-url`_
23 * `dj-config-url`_
24 * `django-cache-url`_
25
26 and inspired by:
27
28 * `12factor`_
29 * `12factor-django`_
30 * `Two Scoops of Django`_
31
32 This is your `settings.py` file before you have installed **django-environ**
33
34 ::
35
36 import os
37 SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
38
39 DEBUG = True
40 TEMPLATE_DEBUG = DEBUG
41
42 DATABASES = {
43 'default': {
44 'ENGINE': 'django.db.backends.postgresql_psycopg2',
45 'NAME': 'database',
46 'USER': 'user',
47 'PASSWORD': 'githubbedpassword',
48 'HOST': '127.0.0.1',
49 'PORT': '8458',
50 }
51 'extra': {
52 'ENGINE': 'django.db.backends.sqlite3',
53 'NAME': os.path.join(SITE_ROOT, 'database.sqlite')
54 }
55 }
56
57 MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
58 MEDIA_URL = 'media/'
59 STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
60 STATIC_URL = 'static/'
61
62 SECRET_KEY = '...im incredibly still here...'
63
64 CACHES = {
65 'default': {
66 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
67 'LOCATION': [
68 '127.0.0.1:11211', '127.0.0.1:11212', '127.0.0.1:11213',
69 ]
70 },
71 'redis': {
72 'BACKEND': 'django_redis.cache.RedisCache',
73 'LOCATION': '127.0.0.1:6379:1',
74 'OPTIONS': {
75 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
76 'PASSWORD': 'redis-githubbed-password',
77 }
78 }
79 }
80
81 After::
82
83 import environ
84 root = environ.Path(__file__) - 3 # three folder back (/a/b/c/ - 3 = /)
85 env = environ.Env(DEBUG=(bool, False),) # set default values and casting
86 environ.Env.read_env() # reading .env file
87
88 SITE_ROOT = root()
89
90 DEBUG = env('DEBUG') # False if not in os.environ
91 TEMPLATE_DEBUG = DEBUG
92
93 DATABASES = {
94 'default': env.db(), # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
95 'extra': env.db('SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db')
96 }
97
98 public_root = root.path('public/')
99
100 MEDIA_ROOT = public_root('media')
101 MEDIA_URL = 'media/'
102 STATIC_ROOT = public_root('static')
103 STATIC_URL = 'static/'
104
105 SECRET_KEY = env('SECRET_KEY') # Raises ImproperlyConfigured exception if SECRET_KEY not in os.environ
106
107 CACHES = {
108 'default': env.cache(),
109 'redis': env.cache('REDIS_URL')
110 }
111
112 You can also pass `read_env()` an explicit path to the .env file.
113 Create a `.env` file::
114
115 DEBUG=on
116 # DJANGO_SETTINGS_MODULE=myapp.settings.dev
117 SECRET_KEY=your-secret-key
118 DATABASE_URL=psql://urser:un-githubbedpassword@127.0.0.1:8458/database
119 # SQLITE_URL=sqlite:///my-local-sqlite.db
120 CACHE_URL=memcache://127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213
121 REDIS_URL=rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=redis-un-githubbed-password
122
123
124 How to install
125 ==============
126
127 ::
128
129 $ pip install django-environ
130
131
132 How to use
133 ==========
134
135 There are only two classes, `environ.Env` and `environ.Path`
136
137 ::
138
139 >>> import environ
140 >>> env = environ.Env(
141 DEBUG=(bool, False),
142 )
143 >>> env('DEBUG')
144 False
145 >>> env('DEBUG', default=True)
146 True
147
148 >>> open('.myenv', 'a').write('DEBUG=on')
149 >>> environ.Env.read_env('.myenv') # or env.read_env('.myenv')
150 >>> env('DEBUG')
151 True
152
153 >>> open('.myenv', 'a').write('\nINT_VAR=1010')
154 >>> env.int('INT_VAR'), env.str('INT_VAR')
155 1010, '1010'
156
157 >>> open('.myenv', 'a').write('\nDATABASE_URL=sqlite:///my-local-sqlite.db')
158 >>> env.read_env('.myenv')
159 >>> env.db()
160 {'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'my-local-sqlite.db', 'HOST': '', 'USER': '', 'PASSWORD': '', 'PORT': ''}
161
162 >>> root = env.path('/home/myproject/')
163 >>> root('static')
164 '/home/myproject/static'
165
166
167 Supported Types
168 ===============
169
170 - str
171 - bool
172 - int
173 - float
174 - json
175 - list (FOO=a,b,c)
176 - tuple (FOO=(a,b,c))
177 - dict (BAR=key=val,foo=bar)
178 - url
179 - path (environ.Path)
180 - db_url
181 - PostgreSQL: postgres://, pgsql://, psql:// or postgresql://
182 - PostGIS: postgis://
183 - MySQL: mysql:// or mysql2://
184 - MySQL for GeoDjango: mysqlgis://
185 - SQLITE: sqlite://
186 - SQLITE with SPATIALITE for GeoDjango: spatialite://
187 - LDAP: ldap://
188 - cache_url
189 - Database: dbcache://
190 - Dummy: dummycache://
191 - File: filecache://
192 - Memory: locmemcache://
193 - Memcached: memcache://
194 - Python memory: pymemcache://
195 - Redis: rediscache://
196 - search_url
197 - ElasticSearch: elasticsearch://
198 - Solr: solr://
199 - Whoosh: whoosh://
200 - Xapian: xapian://
201 - Simple cache: simple://
202 - email_url
203 - SMTP: smtp://
204 - SMTP+SSL: smtp+ssl://
205 - SMTP+TLS: smtp+tls://
206 - Console mail: consolemail://
207 - File mail: filemail://
208 - LocMem mail: memorymail://
209 - Dummy mail: dummymail://
210
211 Tests
212 =====
213
214 ::
215
216 $ git clone git@github.com:joke2k/django-environ.git
217 $ cd django-environ/
218 $ python setup.py test
219
220
221 License
222 =======
223
224 Django-environ is licensed under the MIT License - see the `LICENSE`_ file for details
225
226 Changelog
227 =========
228
229 `0.4.0 - 23-September-2015 <http://github.com/joke2k/django-environ/compare/v0.3...v0.4>`__
230 -------------------------------------------------------------------------------------------
231 - Fix non-ascii values (broken in Python 2.x)
232 - New email schemes - smtp+ssl and smtp+tls (smtps would be deprecated)
233 - redis_cache replaced by django_redis
234 - Add tuple support. Thanks to @anonymouzz
235 - Add LDAP url support for database (django-ldapdb)
236 - Fix psql/pgsql url
237
238 `0.3 - 03-June-2014 <http://github.com/joke2k/django-environ/compare/v0.2.1...v0.3>`__
239 --------------------------------------------------------------------------------------
240 - Add cache url support
241 - Add email url support
242 - Add search url support
243 - Rewriting README.rst
244
245 0.2.1 19-April-2013
246 -------------------
247 - environ/environ.py: Env.__call__ now uses Env.get_value instance method
248
249 0.2 16-April-2013
250 -----------------
251 - environ/environ.py, environ/test.py, environ/test_env.txt: add advanced
252 float parsing (comma and dot symbols to separate thousands and decimals)
253 - README.rst, docs/index.rst: fix TYPO in documentation
254
255 0.1 02-April-2013
256 -----------------
257 - initial release
258
259 Credits
260 =======
261
262 - `12factor`_
263 - `12factor-django`_
264 - `Two Scoops of Django`_
265 - `rconradharris`_ / `envparse`_
266 - `kennethreitz`_ / `dj-database-url`_
267 - `migonzalvar`_ / `dj-email-url`_
268 - `ghickman`_ / `django-cache-url`_
269 - `dstufft`_ / `dj-search-url`_
270 - `julianwachholz`_ / `dj-config-url`_
271 - `nickstenning`_ / `honcho`_
272 - `envparse`_
273 - `Distribute`_
274 - `modern-package-template`_
275
276 .. _rconradharris: https://github.com/rconradharris
277 .. _envparse: https://github.com/rconradharris/envparse
278
279 .. _kennethreitz: https://github.com/kennethreitz
280 .. _dj-database-url: https://github.com/kennethreitz/dj-database-url
281
282 .. _migonzalvar: https://github.com/migonzalvar
283 .. _dj-email-url: https://github.com/migonzalvar/dj-email-url
284
285 .. _ghickman: https://github.com/ghickman
286 .. _django-cache-url: https://github.com/ghickman/django-cache-url
287
288 .. _julianwachholz: https://github.com/julianwachholz
289 .. _dj-config-url: https://github.com/julianwachholz/dj-config-url
290
291 .. _dstufft: https://github.com/dstufft
292 .. _dj-search-url: https://github.com/dstufft/dj-search-url
293
294 .. _nickstenning: https://github.com/nickstenning
295 .. _honcho: https://github.com/nickstenning/honcho
296
297 .. _12factor: http://www.12factor.net/
298 .. _12factor-django: http://www.wellfireinteractive.com/blog/easier-12-factor-django/
299 .. _`Two Scoops of Django`: http://twoscoopspress.org/
300
301 .. _Distribute: http://pypi.python.org/pypi/distribute
302 .. _`modern-package-template`: http://pypi.python.org/pypi/modern-package-template
303
304 .. |pypi| image:: https://img.shields.io/pypi/v/django-environ.svg?style=flat-square&label=version
305 :target: https://pypi.python.org/pypi/django-environ
306 :alt: Latest version released on PyPi
307
308 .. |coverage| image:: https://img.shields.io/coveralls/joke2k/django-environ/master.svg?style=flat-square
309 :target: https://coveralls.io/r/joke2k/django-environ?branch=master
310 :alt: Test coverage
311
312 .. |unix_build| image:: https://img.shields.io/travis/joke2k/django-environ/master.svg?style=flat-square&label=unix%20build
313 :target: http://travis-ci.org/joke2k/django-environ
314 :alt: Build status of the master branch on Mac/Linux
315
316 .. |windows_build| image:: https://img.shields.io/appveyor/ci/joke2k/django-environ.svg?style=flat-square&label=windows%20build
317 :target: https://ci.appveyor.com/project/joke2k/django-environ
318 :alt: Build status of the master branch on Windows
319
320 .. |downloads| image:: https://img.shields.io/pypi/dm/django-environ.svg?style=flat-square
321 :target: https://pypi.python.org/pypi/django-environ
322 :alt: Monthly downloads
323
324 .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
325 :target: https://raw.githubusercontent.com/joke2k/django-environ/master/LICENSE.txt
326 :alt: Package license
327
328 .. _LICENSE: https://github.com/joke2k/django-environ/blob/master/LICENSE.txt
329
330 Keywords: d,j,a,n,g,o, ,e,n,v,i,r,o,n,m,e,n,t, ,v,a,r,i,a,b,l,e,s, ,1,2,f,a,c,t,o,r
331 Platform: any
332 Classifier: Development Status :: 3 - Alpha
333 Classifier: Intended Audience :: Information Technology
334 Classifier: Programming Language :: Python
335 Classifier: Programming Language :: Python :: 2
336 Classifier: Programming Language :: Python :: 3
337 Classifier: Topic :: Software Development :: Libraries :: Python Modules
338 Classifier: Topic :: Utilities
339 Classifier: License :: OSI Approved :: MIT License
340 Classifier: Framework :: Django
0 LICENSE.txt
1 MANIFEST.in
2 README.rst
3 setup.cfg
4 setup.py
5 django_environ.egg-info/PKG-INFO
6 django_environ.egg-info/SOURCES.txt
7 django_environ.egg-info/dependency_links.txt
8 django_environ.egg-info/not-zip-safe
9 django_environ.egg-info/requires.txt
10 django_environ.egg-info/top_level.txt
11 environ/__init__.py
12 environ/environ.py
13 environ/test.py
0 from .environ import *
0 """
1 Django-environ allows you to utilize 12factor inspired environment
2 variables to configure your Django application.
3 """
4 import json
5 import logging
6 import os
7 import re
8 import sys
9 import warnings
10
11 try:
12 from django.core.exceptions import ImproperlyConfigured
13 except ImportError:
14 class ImproperlyConfigured(Exception):
15 pass
16
17 from six.moves import urllib_parse as urlparse
18 from six import string_types
19
20
21 logger = logging.getLogger(__file__)
22
23
24 VERSION = '0.4.0'
25 __author__ = 'joke2k'
26 __version__ = tuple(VERSION.split('.'))
27
28
29 # return int if possible
30 def _cast_int(v):
31 return int(v) if hasattr(v, 'isdigit') and v.isdigit() else v
32
33
34 class NoValue(object):
35
36 def __repr__(self):
37 return '<{0}>'.format(self.__class__.__name__)
38
39
40 class Env(object):
41
42 """Provide scheme-based lookups of environment variables so that each
43 caller doesn't have to pass in `cast` and `default` parameters.
44
45 Usage:::
46
47 env = Env(MAIL_ENABLED=bool, SMTP_LOGIN=(str, 'DEFAULT'))
48 if env('MAIL_ENABLED'):
49 ...
50 """
51
52 NOTSET = NoValue()
53 BOOLEAN_TRUE_STRINGS = ('true', 'on', 'ok', 'y', 'yes', '1')
54 URL_CLASS = urlparse.ParseResult
55 DEFAULT_DATABASE_ENV = 'DATABASE_URL'
56 DB_SCHEMES = {
57 'postgres': 'django.db.backends.postgresql_psycopg2',
58 'postgresql': 'django.db.backends.postgresql_psycopg2',
59 'psql': 'django.db.backends.postgresql_psycopg2',
60 'pgsql': 'django.db.backends.postgresql_psycopg2',
61 'postgis': 'django.contrib.gis.db.backends.postgis',
62 'mysql': 'django.db.backends.mysql',
63 'mysql2': 'django.db.backends.mysql',
64 'mysqlgis': 'django.contrib.gis.db.backends.mysql',
65 'spatialite': 'django.contrib.gis.db.backends.spatialite',
66 'sqlite': 'django.db.backends.sqlite3',
67 'ldap': 'ldapdb.backends.ldap',
68 }
69 _DB_BASE_OPTIONS = ['CONN_MAX_AGE', 'ATOMIC_REQUESTS', 'AUTOCOMMIT']
70
71 DEFAULT_CACHE_ENV = 'CACHE_URL'
72 CACHE_SCHEMES = {
73 'dbcache': 'django.core.cache.backends.db.DatabaseCache',
74 'dummycache': 'django.core.cache.backends.dummy.DummyCache',
75 'filecache': 'django.core.cache.backends.filebased.FileBasedCache',
76 'locmemcache': 'django.core.cache.backends.locmem.LocMemCache',
77 'memcache': 'django.core.cache.backends.memcached.MemcachedCache',
78 'pymemcache': 'django.core.cache.backends.memcached.PyLibMCCache',
79 'rediscache': 'django_redis.cache.RedisCache',
80 'redis': 'django_redis.cache.RedisCache',
81 }
82 _CACHE_BASE_OPTIONS = ['TIMEOUT', 'KEY_PREFIX', 'VERSION', 'KEY_FUNCTION']
83
84 DEFAULT_EMAIL_ENV = 'EMAIL_URL'
85 EMAIL_SCHEMES = {
86 'smtp': 'django.core.mail.backends.smtp.EmailBackend',
87 'smtps': 'django.core.mail.backends.smtp.EmailBackend',
88 'smtp+tls': 'django.core.mail.backends.smtp.EmailBackend',
89 'smtp+ssl': 'django.core.mail.backends.smtp.EmailBackend',
90 'consolemail': 'django.core.mail.backends.console.EmailBackend',
91 'filemail': 'django.core.mail.backends.filebased.EmailBackend',
92 'memorymail': 'django.core.mail.backends.locmem.EmailBackend',
93 'dummymail': 'django.core.mail.backends.dummy.EmailBackend'
94 }
95 _EMAIL_BASE_OPTIONS = ['EMAIL_USE_TLS', 'EMAIL_USE_SSL']
96
97 DEFAULT_SEARCH_ENV = 'SEARCH_URL'
98 SEARCH_SCHEMES = {
99 "elasticsearch": "haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine",
100 "solr": "haystack.backends.solr_backend.SolrEngine",
101 "whoosh": "haystack.backends.whoosh_backend.WhooshEngine",
102 "xapian": "haystack.backends.xapian_backend.XapianEngine",
103 "simple": "haystack.backends.simple_backend.SimpleEngine",
104 }
105
106 def __init__(self, **scheme):
107 self.scheme = scheme
108
109 def __call__(self, var, cast=None, default=NOTSET, parse_default=False):
110 return self.get_value(var, cast=cast, default=default, parse_default=parse_default)
111
112 # Shortcuts
113
114 def str(self, var, default=NOTSET):
115 """
116 :rtype: str
117 """
118 return self.get_value(var, default=default)
119
120 def unicode(self, var, default=NOTSET):
121 """Helper for python2
122 :rtype: unicode
123 """
124 return self.get_value(var, cast=str, default=default)
125
126 def bool(self, var, default=NOTSET):
127 """
128 :rtype: bool
129 """
130 return self.get_value(var, cast=bool, default=default)
131
132 def int(self, var, default=NOTSET):
133 """
134 :rtype: int
135 """
136 return self.get_value(var, cast=int, default=default)
137
138 def float(self, var, default=NOTSET):
139 """
140 :rtype: float
141 """
142 return self.get_value(var, cast=float, default=default)
143
144 def json(self, var, default=NOTSET):
145 """
146 :returns: Json parsed
147 """
148 return self.get_value(var, cast=json.loads, default=default)
149
150 def list(self, var, cast=None, default=NOTSET):
151 """
152 :rtype: list
153 """
154 return self.get_value(var, cast=list if not cast else [cast], default=default)
155
156 def tuple(self, var, cast=None, default=NOTSET):
157 """
158 :rtype: tuple
159 """
160 return self.get_value(var, cast=tuple if not cast else (cast,), default=default)
161
162 def dict(self, var, cast=dict, default=NOTSET):
163 """
164 :rtype: dict
165 """
166 return self.get_value(var, cast=cast, default=default)
167
168 def url(self, var, default=NOTSET):
169 """
170 :rtype: urlparse.ParseResult
171 """
172 return self.get_value(var, cast=urlparse.urlparse, default=default, parse_default=True)
173
174 def db_url(self, var=DEFAULT_DATABASE_ENV, default=NOTSET, engine=None):
175 """Returns a config dictionary, defaulting to DATABASE_URL.
176
177 :rtype: dict
178 """
179 return self.db_url_config(self.get_value(var, default=default), engine=engine)
180 db = db_url
181
182 def cache_url(self, var=DEFAULT_CACHE_ENV, default=NOTSET, backend=None):
183 """Returns a config dictionary, defaulting to CACHE_URL.
184
185 :rtype: dict
186 """
187 return self.cache_url_config(self.url(var, default=default), backend=backend)
188 cache = cache_url
189
190 def email_url(self, var=DEFAULT_EMAIL_ENV, default=NOTSET, backend=None):
191 """Returns a config dictionary, defaulting to EMAIL_URL.
192
193 :rtype: dict
194 """
195 return self.email_url_config(self.url(var, default=default), backend=backend)
196 email = email_url
197
198 def search_url(self, var=DEFAULT_SEARCH_ENV, default=NOTSET, engine=None):
199 """Returns a config dictionary, defaulting to SEARCH_URL.
200
201 :rtype: dict
202 """
203 return self.search_url_config(self.url(var, default=default), engine=engine)
204
205 def path(self, var, default=NOTSET, **kwargs):
206 """
207 :rtype: Path
208 """
209 return Path(self.get_value(var, default=default), **kwargs)
210
211 def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
212 """Return value for given environment variable.
213
214 :param var: Name of variable.
215 :param cast: Type to cast return value as.
216 :param default: If var not present in environ, return this instead.
217 :param parse_default: force to parse default..
218
219 :returns: Value from environment or default (if set)
220 """
221
222 logger.debug("get '{0}' casted as '{1}' with default '{2}'".format(
223 var, cast, default
224 ))
225
226 if var in self.scheme:
227 var_info = self.scheme[var]
228
229 try:
230 has_default = len(var_info) == 2
231 except TypeError:
232 has_default = False
233
234 if has_default:
235 if not cast:
236 cast = var_info[0]
237
238 if default is self.NOTSET:
239 try:
240 default = var_info[1]
241 except IndexError:
242 pass
243 else:
244 if not cast:
245 cast = var_info
246
247 try:
248 value = os.environ[var]
249 except KeyError:
250 if default is self.NOTSET:
251 error_msg = "Set the {0} environment variable".format(var)
252 raise ImproperlyConfigured(error_msg)
253
254 value = default
255
256 # Resolve any proxied values
257 if hasattr(value, 'startswith') and value.startswith('$'):
258 value = value.lstrip('$')
259 value = self.get_value(value, cast=cast, default=default)
260
261 if value != default or (parse_default and value):
262 value = self.parse_value(value, cast)
263
264 return value
265
266 # Class and static methods
267
268 @classmethod
269 def parse_value(cls, value, cast):
270 """Parse and cast provided value
271
272 :param value: Stringed value.
273 :param cast: Type to cast return value as.
274
275 :returns: Casted value
276 """
277 if cast is None:
278 return value
279 elif cast is bool:
280 try:
281 value = int(value) != 0
282 except ValueError:
283 value = value.lower() in cls.BOOLEAN_TRUE_STRINGS
284 elif isinstance(cast, list):
285 value = list(map(cast[0], [x for x in value.split(',') if x]))
286 elif isinstance(cast, tuple):
287 val = value.strip('(').strip(')').split(',')
288 value = tuple(map(cast[0], [x for x in val if x]))
289 elif isinstance(cast, dict):
290 key_cast = cast.get('key', str)
291 value_cast = cast.get('value', str)
292 value_cast_by_key = cast.get('cast', dict())
293 value = dict(map(
294 lambda kv: (
295 key_cast(kv[0]),
296 cls.parse_value(kv[1], value_cast_by_key.get(kv[0], value_cast))
297 ),
298 [val.split('=') for val in value.split(';') if val]
299 ))
300 elif cast is dict:
301 value = dict([val.split('=') for val in value.split(',') if val])
302 elif cast is list:
303 value = [x for x in value.split(',') if x]
304 elif cast is tuple:
305 val = value.strip('(').strip(')').split(',')
306 value = tuple([x for x in val if x])
307 elif cast is float:
308 # clean string
309 float_str = re.sub(r'[^\d,\.]', '', value)
310 # split for avoid thousand separator and different locale comma/dot symbol
311 parts = re.split(r'[,\.]', float_str)
312 if len(parts) == 1:
313 float_str = parts[0]
314 else:
315 float_str = "{0}.{1}".format(''.join(parts[0:-1]), parts[-1])
316 value = float(float_str)
317 else:
318 value = cast(value)
319 return value
320
321 @classmethod
322 def db_url_config(cls, url, engine=None):
323 """Pulled from DJ-Database-URL, parse an arbitrary Database URL.
324 Support currently exists for PostgreSQL, PostGIS, MySQL and SQLite.
325
326 SQLite connects to file based databases. The same URL format is used, omitting the hostname,
327 and using the "file" portion as the filename of the database.
328 This has the effect of four slashes being present for an absolute file path:
329
330 >>> from environ import Env
331 >>> Env.db_url_config('sqlite:////full/path/to/your/file.sqlite')
332 {'ENGINE': 'django.db.backends.sqlite3', 'HOST': None, 'NAME': '/full/path/to/your/file.sqlite', 'PASSWORD': None, 'PORT': None, 'USER': None}
333 >>> Env.db_url_config('postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn')
334 {'ENGINE': 'django.db.backends.postgresql_psycopg2', 'HOST': 'ec2-107-21-253-135.compute-1.amazonaws.com', 'NAME': 'd8r82722r2kuvn', 'PASSWORD': 'wegauwhgeuioweg', 'PORT': 5431, 'USER': 'uf07k1i6d8ia0v'}
335
336 """
337 if not isinstance(url, cls.URL_CLASS):
338 if url == 'sqlite://:memory:':
339 # this is a special case, because if we pass this URL into
340 # urlparse, urlparse will choke trying to interpret "memory"
341 # as a port number
342 return {
343 'ENGINE': cls.DB_SCHEMES['sqlite'],
344 'NAME': ':memory:'
345 }
346 # note: no other settings are required for sqlite
347 url = urlparse.urlparse(url)
348
349 config = {}
350
351 # Remove query strings.
352 path = url.path[1:]
353 path = path.split('?', 2)[0]
354
355 # if we are using sqlite and we have no path, then assume we
356 # want an in-memory database (this is the behaviour of sqlalchemy)
357 if url.scheme == 'sqlite' and path == '':
358 path = ':memory:'
359 if url.scheme == 'ldap':
360 path = '{scheme}://{hostname}'.format(scheme=url.scheme, hostname=url.hostname)
361 if url.port:
362 path += ':{port}'.format(port=url.port)
363
364 # Update with environment configuration.
365 config.update({
366 'NAME': path,
367 'USER': url.username,
368 'PASSWORD': url.password,
369 'HOST': url.hostname,
370 'PORT': _cast_int(url.port),
371 })
372
373 if url.query:
374 config_options = {}
375 for k, v in urlparse.parse_qs(url.query).items():
376 if k.upper() in cls._DB_BASE_OPTIONS:
377 config.update({k.upper(): _cast_int(v[0])})
378 else:
379 config_options.update({k: _cast_int(v[0])})
380 config['OPTIONS'] = config_options
381
382 if engine:
383 config['ENGINE'] = engine
384 if url.scheme in Env.DB_SCHEMES:
385 config['ENGINE'] = Env.DB_SCHEMES[url.scheme]
386
387 if not config.get('ENGINE', False):
388 warnings.warn("Engine not recognized from url: {0}".format(config))
389 return {}
390
391 return config
392
393 @classmethod
394 def cache_url_config(cls, url, backend=None):
395 """Pulled from DJ-Cache-URL, parse an arbitrary Cache URL.
396
397 :param url:
398 :param backend:
399 :return:
400 """
401 url = urlparse.urlparse(url) if not isinstance(url, cls.URL_CLASS) else url
402
403 location = url.netloc.split(',')
404 if len(location) == 1:
405 location = location[0]
406
407 config = {
408 'BACKEND': cls.CACHE_SCHEMES[url.scheme],
409 'LOCATION': location,
410 }
411
412 if url.scheme == 'filecache':
413 config.update({
414 'LOCATION': url.netloc + url.path,
415 })
416
417 if url.path and url.scheme in ['memcache', 'pymemcache', 'rediscache']:
418 config.update({
419 'LOCATION': 'unix:' + url.path,
420 })
421
422 if url.query:
423 config_options = {}
424 for k, v in urlparse.parse_qs(url.query).items():
425 opt = {k.upper(): _cast_int(v[0])}
426 if k.upper() in cls._CACHE_BASE_OPTIONS:
427 config.update(opt)
428 else:
429 config_options.update(opt)
430 config['OPTIONS'] = config_options
431
432 if backend:
433 config['BACKEND'] = backend
434
435 return config
436
437 @classmethod
438 def email_url_config(cls, url, backend=None):
439 """Parses an email URL."""
440
441 config = {}
442
443 url = urlparse.urlparse(url) if not isinstance(url, cls.URL_CLASS) else url
444
445 # Remove query strings
446 path = url.path[1:]
447 path = path.split('?', 2)[0]
448
449 # Update with environment configuration
450 config.update({
451 'EMAIL_FILE_PATH': path,
452 'EMAIL_HOST_USER': url.username,
453 'EMAIL_HOST_PASSWORD': url.password,
454 'EMAIL_HOST': url.hostname,
455 'EMAIL_PORT': _cast_int(url.port),
456 })
457
458 if backend:
459 config['EMAIL_BACKEND'] = backend
460 elif url.scheme not in cls.EMAIL_SCHEMES:
461 raise ImproperlyConfigured('Invalid email schema %s' % url.scheme)
462 elif url.scheme in cls.EMAIL_SCHEMES:
463 config['EMAIL_BACKEND'] = cls.EMAIL_SCHEMES[url.scheme]
464
465 if url.scheme in ('smtps', 'smtp+tls'):
466 config['EMAIL_USE_TLS'] = True
467 elif url.scheme == 'smtp+ssl':
468 config['EMAIL_USE_SSL'] = True
469
470 if url.query:
471 config_options = {}
472 for k, v in urlparse.parse_qs(url.query).items():
473 opt = {k.upper(): _cast_int(v[0])}
474 if k.upper() in cls._EMAIL_BASE_OPTIONS:
475 config.update(opt)
476 else:
477 config_options.update(opt)
478 config['OPTIONS'] = config_options
479
480 return config
481
482 @classmethod
483 def search_url_config(cls, url, engine=None):
484 config = {}
485
486 url = urlparse.urlparse(url) if not isinstance(url, cls.URL_CLASS) else url
487
488 # Remove query strings.
489 path = url.path[1:]
490 path = path.split('?', 2)[0]
491
492 if url.scheme not in cls.SEARCH_SCHEMES:
493 raise ImproperlyConfigured('Invalid search schema %s' % url.scheme)
494 config["ENGINE"] = cls.SEARCH_SCHEMES[url.scheme]
495
496 # check commons params
497 params = {}
498 if url.query:
499 params = urlparse.parse_qs(url.query)
500 if 'EXCLUDED_INDEXES' in params.keys():
501 config['EXCLUDED_INDEXES'] = params['EXCLUDED_INDEXES'][0].split(',')
502 if 'INCLUDE_SPELLING' in params.keys():
503 config['INCLUDE_SPELLING'] = cls.parse_value(params['INCLUDE_SPELLING'][0], bool)
504 if 'BATCH_SIZE' in params.keys():
505 config['BATCH_SIZE'] = cls.parse_value(params['BATCH_SIZE'][0], int)
506
507 if url.scheme == 'simple':
508 return config
509 elif url.scheme in ['solr', 'elasticsearch']:
510 if 'KWARGS' in params.keys():
511 config['KWARGS'] = params['KWARGS'][0]
512
513 # remove trailing slash
514 if path.endswith("/"):
515 path = path[:-1]
516
517 if url.scheme == 'solr':
518 config['URL'] = urlparse.urlunparse(('http',) + url[1:2] + (path,) + ('', '', ''))
519 if 'TIMEOUT' in params.keys():
520 config['TIMEOUT'] = cls.parse_value(params['TIMEOUT'][0], int)
521 return config
522
523 if url.scheme == 'elasticsearch':
524
525 split = path.rsplit("/", 1)
526
527 if len(split) > 1:
528 path = "/".join(split[:-1])
529 index = split[-1]
530 else:
531 path = ""
532 index = split[0]
533
534 config['URL'] = urlparse.urlunparse(('http',) + url[1:2] + (path,) + ('', '', ''))
535 if 'TIMEOUT' in params.keys():
536 config['TIMEOUT'] = cls.parse_value(params['TIMEOUT'][0], int)
537 config['INDEX_NAME'] = index
538 return config
539
540 config['PATH'] = '/' + path
541
542 if url.scheme == 'whoosh':
543 if 'STORAGE' in params.keys():
544 config['STORAGE'] = params['STORAGE'][0]
545 if 'POST_LIMIT' in params.keys():
546 config['POST_LIMIT'] = cls.parse_value(params['POST_LIMIT'][0], int)
547 elif url.scheme == 'xapian':
548 if 'FLAGS' in params.keys():
549 config['FLAGS'] = params['FLAGS'][0]
550
551 if engine:
552 config['ENGINE'] = engine
553
554 return config
555
556 @staticmethod
557 def read_env(env_file=None, **overrides):
558 """Read a .env file into os.environ.
559
560 If not given a path to a dotenv path, does filthy magic stack backtracking
561 to find manage.py and then find the dotenv.
562
563 http://www.wellfireinteractive.com/blog/easier-12-factor-django/
564
565 https://gist.github.com/bennylope/2999704
566 """
567 if env_file is None:
568 frame = sys._getframe()
569 env_file = os.path.join(os.path.dirname(frame.f_back.f_code.co_filename), '.env')
570 if not os.path.exists(env_file):
571 warnings.warn("not reading %s - it doesn't exist." % env_file)
572 return
573
574 try:
575 with open(env_file) if isinstance(env_file, string_types) else env_file as f:
576 content = f.read()
577 except IOError:
578 warnings.warn("not reading %s - it doesn't exist." % env_file)
579 return
580
581 logger.debug('Read environment variables from: {0}'.format(env_file))
582
583 for line in content.splitlines():
584 m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
585 if m1:
586 key, val = m1.group(1), m1.group(2)
587 m2 = re.match(r"\A'(.*)'\Z", val)
588 if m2:
589 val = m2.group(1)
590 m3 = re.match(r'\A"(.*)"\Z', val)
591 if m3:
592 val = re.sub(r'\\(.)', r'\1', m3.group(1))
593 os.environ.setdefault(key, str(val))
594
595 # set defaults
596 for key, value in overrides.items():
597 os.environ.setdefault(key, value)
598
599
600 class Path(object):
601
602 """Inspired to Django Two-scoops, handling File Paths in Settings.
603
604 >>> from environ import Path
605 >>> root = Path('/home')
606 >>> root, root(), root('dev')
607 (<Path:/home>, '/home', '/home/dev')
608 >>> root == Path('/home')
609 True
610 >>> root in Path('/'), root not in Path('/other/path')
611 (True, True)
612 >>> root('dev', 'not_existing_dir', required=True)
613 Traceback (most recent call last):
614 environ.environ.ImproperlyConfigured: Create required path: /home/not_existing_dir
615 >>> public = root.path('public')
616 >>> public, public.root, public('styles')
617 (<Path:/home/public>, '/home/public', '/home/public/styles')
618 >>> assets, scripts = public.path('assets'), public.path('assets', 'scripts')
619 >>> assets.root, scripts.root
620 ('/home/public/assets', '/home/public/assets/scripts')
621 >>> assets + 'styles', str(assets + 'styles'), ~assets
622 (<Path:/home/public/assets/styles>, '/home/public/assets/styles', <Path:/home/public>)
623
624 """
625
626 def path(self, *paths, **kwargs):
627 """Create new Path based on self.root and provided paths.
628
629 :param paths: List of sub paths
630 :param kwargs: required=False
631 :rtype: Path
632 """
633 return self.__class__(self.__root__, *paths, **kwargs)
634
635 def file(self, name, *args, **kwargs):
636 """Open a file.
637
638 :param name: Filename appended to self.root
639 :param args: passed to open()
640 :param kwargs: passed to open()
641
642 :rtype: file
643 """
644 return open(self(name), *args, **kwargs)
645
646 @property
647 def root(self):
648 """Current directory for this Path"""
649 return self.__root__
650
651 def __init__(self, start='', *paths, **kwargs):
652
653 super(Path, self).__init__()
654
655 if kwargs.get('is_file', False):
656 start = os.path.dirname(start)
657
658 self.__root__ = self._absolute_join(start, *paths, **kwargs)
659
660 def __call__(self, *paths, **kwargs):
661 """Retrieve the absolute path, with appended paths
662
663 :param paths: List of sub path of self.root
664 :param kwargs: required=False
665 """
666 return self._absolute_join(self.__root__, *paths, **kwargs)
667
668 def __eq__(self, other):
669 return self.__root__ == other.__root__
670
671 def __ne__(self, other):
672 return not self.__eq__(other)
673
674 def __add__(self, other):
675 return Path(self.__root__, other if not isinstance(other, Path) else other.__root__)
676
677 def __sub__(self, other):
678 if isinstance(other, int):
679 return self.path('../' * other)
680 elif isinstance(other, string_types):
681 return Path(self.__root__.rstrip(other))
682 raise TypeError(
683 "unsupported operand type(s) for -: '{0}' and '{1}'".format(self, type(other)))
684
685 def __invert__(self):
686 return self.path('..')
687
688 def __contains__(self, item):
689 base_path = self.__root__
690 if len(base_path) > 1:
691 base_path = os.path.join(base_path, '')
692 return item.__root__.startswith(base_path)
693
694 def __repr__(self):
695 return "<Path:{0}>".format(self.__root__)
696
697 def __str__(self):
698 return self.__root__
699
700 def __unicode__(self):
701 return self.__str__()
702
703 def __getitem__(self, *args, **kwargs):
704 return self.__str__().__getitem__(*args, **kwargs)
705
706 def rfind(self, *args, **kwargs):
707 return self.__str__().rfind(*args, **kwargs)
708
709 def find(self, *args, **kwargs):
710 return self.__str__().find(*args, **kwargs)
711
712 @staticmethod
713 def _absolute_join(base, *paths, **kwargs):
714 absolute_path = os.path.abspath(os.path.join(base, *paths))
715 if kwargs.get('required', False) and not os.path.exists(absolute_path):
716 raise ImproperlyConfigured(
717 "Create required path: {0}".format(absolute_path))
718 return absolute_path
719
720
721 def register_scheme(scheme):
722 for method in dir(urlparse):
723 if method.startswith('uses_'):
724 getattr(urlparse, method).append(scheme)
725
726
727 def register_schemes(schemes):
728 for scheme in schemes:
729 register_scheme(scheme)
730
731
732 # Register database and cache schemes in URLs.
733 register_schemes(Env.DB_SCHEMES.keys())
734 register_schemes(Env.CACHE_SCHEMES.keys())
735 register_schemes(Env.SEARCH_SCHEMES.keys())
736 register_schemes(Env.EMAIL_SCHEMES.keys())
0 from __future__ import print_function
1 import json
2 import os
3 import sys
4 import unittest
5
6 from django.core.exceptions import ImproperlyConfigured
7
8 from environ import Env, Path
9
10
11 class BaseTests(unittest.TestCase):
12
13 URL = 'http://www.google.com/'
14 POSTGRES = 'postgres://uf07k1:wegauwhg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722'
15 MYSQL = 'mysql://bea6eb0:69772142@us-cdbr-east.cleardb.com/heroku_97681?reconnect=true'
16 MYSQLGIS = 'mysqlgis://user:password@127.0.0.1/some_database'
17 SQLITE = 'sqlite:////full/path/to/your/database/file.sqlite'
18 MEMCACHE = 'memcache://127.0.0.1:11211'
19 REDIS = 'rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=secret'
20 EMAIL = 'smtps://user@domain.com:password@smtp.example.com:587'
21 JSON = dict(one='bar', two=2, three=33.44)
22 DICT = dict(foo='bar', test='on')
23 PATH = '/home/dev'
24
25 @classmethod
26 def generateData(cls):
27 return dict(STR_VAR='bar',
28 INT_VAR='42',
29 FLOAT_VAR='33.3',
30 FLOAT_COMMA_VAR='33,3',
31 FLOAT_STRANGE_VAR1='123,420,333.3',
32 FLOAT_STRANGE_VAR2='123.420.333,3',
33 BOOL_TRUE_VAR='1',
34 BOOL_TRUE_VAR2='True',
35 BOOL_FALSE_VAR='0',
36 BOOL_FALSE_VAR2='False',
37 PROXIED_VAR='$STR_VAR',
38 INT_LIST='42,33',
39 INT_TUPLE='(42,33)',
40 STR_LIST_WITH_SPACES=' foo, bar',
41 EMPTY_LIST='',
42 DICT_VAR='foo=bar,test=on',
43 DATABASE_URL=cls.POSTGRES,
44 DATABASE_MYSQL_URL=cls.MYSQL,
45 DATABASE_MYSQL_GIS_URL=cls.MYSQLGIS,
46 DATABASE_SQLITE_URL=cls.SQLITE,
47 CACHE_URL=cls.MEMCACHE,
48 CACHE_REDIS=cls.REDIS,
49 EMAIL_URL=cls.EMAIL,
50 URL_VAR=cls.URL,
51 JSON_VAR=json.dumps(cls.JSON),
52 PATH_VAR=cls.PATH)
53
54 def setUp(self):
55 self.env = Env()
56 self.environ = self.generateData()
57 self._orig_environ = os.environ
58 os.environ = self.environ
59
60 def tearDown(self):
61 os.environ = self._orig_environ
62
63 def assertTypeAndValue(self, type_, expected, actual):
64 self.assertEqual(type_, type(actual))
65 self.assertEqual(expected, actual)
66
67
68 class EnvTests(BaseTests):
69
70 def test_not_present_with_default(self):
71 self.assertEqual(3, self.env('not_present', default=3))
72
73 def test_not_present_without_default(self):
74 self.assertRaises(ImproperlyConfigured, self.env, 'not_present')
75
76 def test_str(self):
77 self.assertTypeAndValue(str, 'bar', self.env('STR_VAR'))
78 self.assertTypeAndValue(str, 'bar', self.env.str('STR_VAR'))
79
80 def test_int(self):
81 self.assertTypeAndValue(int, 42, self.env('INT_VAR', cast=int))
82 self.assertTypeAndValue(int, 42, self.env.int('INT_VAR'))
83
84 def test_int_with_none_default(self):
85 self.assertTrue(self.env('NOT_PRESENT_VAR', cast=int, default=None) is None)
86
87 def test_float(self):
88 self.assertTypeAndValue(float, 33.3, self.env('FLOAT_VAR', cast=float))
89 self.assertTypeAndValue(float, 33.3, self.env.float('FLOAT_VAR'))
90
91 self.assertTypeAndValue(float, 33.3, self.env('FLOAT_COMMA_VAR', cast=float))
92 self.assertTypeAndValue(float, 123420333.3, self.env('FLOAT_STRANGE_VAR1', cast=float))
93 self.assertTypeAndValue(float, 123420333.3, self.env('FLOAT_STRANGE_VAR2', cast=float))
94
95 def test_bool_true(self):
96 self.assertTypeAndValue(bool, True, self.env('BOOL_TRUE_VAR', cast=bool))
97 self.assertTypeAndValue(bool, True, self.env('BOOL_TRUE_VAR2', cast=bool))
98 self.assertTypeAndValue(bool, True, self.env.bool('BOOL_TRUE_VAR'))
99
100 def test_bool_false(self):
101 self.assertTypeAndValue(bool, False, self.env('BOOL_FALSE_VAR', cast=bool))
102 self.assertTypeAndValue(bool, False, self.env('BOOL_FALSE_VAR2', cast=bool))
103 self.assertTypeAndValue(bool, False, self.env.bool('BOOL_FALSE_VAR'))
104
105 def test_proxied_value(self):
106 self.assertTypeAndValue(str, 'bar', self.env('PROXIED_VAR'))
107
108 def test_int_list(self):
109 self.assertTypeAndValue(list, [42, 33], self.env('INT_LIST', cast=[int]))
110 self.assertTypeAndValue(list, [42, 33], self.env.list('INT_LIST', int))
111
112 def test_int_tuple(self):
113 self.assertTypeAndValue(tuple, (42, 33), self.env('INT_LIST', cast=(int,)))
114 self.assertTypeAndValue(tuple, (42, 33), self.env.tuple('INT_LIST', int))
115 self.assertTypeAndValue(tuple, ('42', '33'), self.env.tuple('INT_LIST'))
116
117 def test_str_list_with_spaces(self):
118 self.assertTypeAndValue(list, [' foo', ' bar'],
119 self.env('STR_LIST_WITH_SPACES', cast=[str]))
120 self.assertTypeAndValue(list, [' foo', ' bar'],
121 self.env.list('STR_LIST_WITH_SPACES'))
122
123 def test_empty_list(self):
124 self.assertTypeAndValue(list, [], self.env('EMPTY_LIST', cast=[int]))
125
126 def test_dict_value(self):
127 self.assertTypeAndValue(dict, self.DICT, self.env.dict('DICT_VAR'))
128
129 def test_dict_parsing(self):
130
131 self.assertEqual({'a': '1'}, self.env.parse_value('a=1', dict))
132 self.assertEqual({'a': 1}, self.env.parse_value('a=1', dict(value=int)))
133 self.assertEqual({'a': ['1', '2', '3']}, self.env.parse_value('a=1,2,3', dict(value=[str])))
134 self.assertEqual({'a': [1, 2, 3]}, self.env.parse_value('a=1,2,3', dict(value=[int])))
135 self.assertEqual({'a': 1, 'b': [1.1, 2.2], 'c': 3},
136 self.env.parse_value('a=1;b=1.1,2.2;c=3', dict(value=int, cast=dict(b=[float]))))
137
138 def test_url_value(self):
139 url = self.env.url('URL_VAR')
140 self.assertEqual(url.__class__, self.env.URL_CLASS)
141 self.assertEqual(url.geturl(), self.URL)
142 self.assertEqual(None, self.env.url('OTHER_URL', default=None))
143
144 def test_db_url_value(self):
145 pg_config = self.env.db()
146 self.assertEqual(pg_config['ENGINE'], 'django.db.backends.postgresql_psycopg2')
147 self.assertEqual(pg_config['NAME'], 'd8r82722')
148 self.assertEqual(pg_config['HOST'], 'ec2-107-21-253-135.compute-1.amazonaws.com')
149 self.assertEqual(pg_config['USER'], 'uf07k1')
150 self.assertEqual(pg_config['PASSWORD'], 'wegauwhg')
151 self.assertEqual(pg_config['PORT'], 5431)
152
153 mysql_config = self.env.db('DATABASE_MYSQL_URL')
154 self.assertEqual(mysql_config['ENGINE'], 'django.db.backends.mysql')
155 self.assertEqual(mysql_config['NAME'], 'heroku_97681')
156 self.assertEqual(mysql_config['HOST'], 'us-cdbr-east.cleardb.com')
157 self.assertEqual(mysql_config['USER'], 'bea6eb0')
158 self.assertEqual(mysql_config['PASSWORD'], '69772142')
159 self.assertEqual(mysql_config['PORT'], None)
160
161 mysql_gis_config = self.env.db('DATABASE_MYSQL_GIS_URL')
162 self.assertEqual(mysql_gis_config['ENGINE'], 'django.contrib.gis.db.backends.mysql')
163 self.assertEqual(mysql_gis_config['NAME'], 'some_database')
164 self.assertEqual(mysql_gis_config['HOST'], '127.0.0.1')
165 self.assertEqual(mysql_gis_config['USER'], 'user')
166 self.assertEqual(mysql_gis_config['PASSWORD'], 'password')
167 self.assertEqual(mysql_gis_config['PORT'], None)
168
169 sqlite_config = self.env.db('DATABASE_SQLITE_URL')
170 self.assertEqual(sqlite_config['ENGINE'], 'django.db.backends.sqlite3')
171 self.assertEqual(sqlite_config['NAME'], '/full/path/to/your/database/file.sqlite')
172
173 def test_cache_url_value(self):
174
175 cache_config = self.env.cache_url()
176 self.assertEqual(cache_config['BACKEND'], 'django.core.cache.backends.memcached.MemcachedCache')
177 self.assertEqual(cache_config['LOCATION'], '127.0.0.1:11211')
178
179 redis_config = self.env.cache_url('CACHE_REDIS')
180 self.assertEqual(redis_config['BACKEND'], 'django_redis.cache.RedisCache')
181 self.assertEqual(redis_config['LOCATION'], '127.0.0.1:6379:1')
182 self.assertEqual(redis_config['OPTIONS'], {
183 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
184 'PASSWORD': 'secret',
185 })
186
187 def test_email_url_value(self):
188
189 email_config = self.env.email_url()
190 self.assertEqual(email_config['EMAIL_BACKEND'], 'django.core.mail.backends.smtp.EmailBackend')
191 self.assertEqual(email_config['EMAIL_HOST'], 'smtp.example.com')
192 self.assertEqual(email_config['EMAIL_HOST_PASSWORD'], 'password')
193 self.assertEqual(email_config['EMAIL_HOST_USER'], 'user@domain.com')
194 self.assertEqual(email_config['EMAIL_PORT'], 587)
195 self.assertEqual(email_config['EMAIL_USE_TLS'], True)
196
197 def test_json_value(self):
198 self.assertEqual(self.JSON, self.env.json('JSON_VAR'))
199
200 def test_path(self):
201 root = self.env.path('PATH_VAR')
202 self.assertTypeAndValue(Path, Path(self.PATH), root)
203
204
205 class FileEnvTests(EnvTests):
206
207 def setUp(self):
208 self.env = Env()
209 self._orig_environ = os.environ
210 os.environ = {}
211 file_path = Path(__file__, is_file=True)('test_env.txt')
212 self.env.read_env(file_path, PATH_VAR=Path(__file__, is_file=True).__root__)
213
214
215 class SchemaEnvTests(BaseTests):
216
217 def test_schema(self):
218 env = Env(INT_VAR=int, NOT_PRESENT_VAR=(float, 33.3), STR_VAR=str,
219 INT_LIST=[int], DEFAULT_LIST=([int], [2]))
220
221 self.assertTypeAndValue(int, 42, env('INT_VAR'))
222 self.assertTypeAndValue(float, 33.3, env('NOT_PRESENT_VAR'))
223
224 self.assertTypeAndValue(str, 'bar', env('STR_VAR'))
225 self.assertTypeAndValue(str, 'foo', env('NOT_PRESENT2', default='foo'))
226
227 self.assertTypeAndValue(list, [42, 33], env('INT_LIST'))
228 self.assertTypeAndValue(list, [2], env('DEFAULT_LIST'))
229
230 # Override schema in this one case
231 self.assertTypeAndValue(str, '42', env('INT_VAR', cast=str))
232
233
234 class DatabaseTestSuite(unittest.TestCase):
235
236 def test_postgres_parsing(self):
237 url = 'postgres://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn'
238 url = Env.db_url_config(url)
239
240 self.assertEqual(url['ENGINE'], 'django.db.backends.postgresql_psycopg2')
241 self.assertEqual(url['NAME'], 'd8r82722r2kuvn')
242 self.assertEqual(url['HOST'], 'ec2-107-21-253-135.compute-1.amazonaws.com')
243 self.assertEqual(url['USER'], 'uf07k1i6d8ia0v')
244 self.assertEqual(url['PASSWORD'], 'wegauwhgeuioweg')
245 self.assertEqual(url['PORT'], 5431)
246
247 def test_postgis_parsing(self):
248 url = 'postgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn'
249 url = Env.db_url_config(url)
250
251 self.assertEqual(url['ENGINE'], 'django.contrib.gis.db.backends.postgis')
252 self.assertEqual(url['NAME'], 'd8r82722r2kuvn')
253 self.assertEqual(url['HOST'], 'ec2-107-21-253-135.compute-1.amazonaws.com')
254 self.assertEqual(url['USER'], 'uf07k1i6d8ia0v')
255 self.assertEqual(url['PASSWORD'], 'wegauwhgeuioweg')
256 self.assertEqual(url['PORT'], 5431)
257
258 def test_mysql_gis_parsing(self):
259 url = 'mysqlgis://uf07k1i6d8ia0v:wegauwhgeuioweg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722r2kuvn'
260 url = Env.db_url_config(url)
261
262 self.assertEqual(url['ENGINE'], 'django.contrib.gis.db.backends.mysql')
263 self.assertEqual(url['NAME'], 'd8r82722r2kuvn')
264 self.assertEqual(url['HOST'], 'ec2-107-21-253-135.compute-1.amazonaws.com')
265 self.assertEqual(url['USER'], 'uf07k1i6d8ia0v')
266 self.assertEqual(url['PASSWORD'], 'wegauwhgeuioweg')
267 self.assertEqual(url['PORT'], 5431)
268
269 def test_cleardb_parsing(self):
270 url = 'mysql://bea6eb025ca0d8:69772142@us-cdbr-east.cleardb.com/heroku_97681db3eff7580?reconnect=true'
271 url = Env.db_url_config(url)
272
273 self.assertEqual(url['ENGINE'], 'django.db.backends.mysql')
274 self.assertEqual(url['NAME'], 'heroku_97681db3eff7580')
275 self.assertEqual(url['HOST'], 'us-cdbr-east.cleardb.com')
276 self.assertEqual(url['USER'], 'bea6eb025ca0d8')
277 self.assertEqual(url['PASSWORD'], '69772142')
278 self.assertEqual(url['PORT'], None)
279
280 def test_empty_sqlite_url(self):
281 url = 'sqlite://'
282 url = Env.db_url_config(url)
283
284 self.assertEqual(url['ENGINE'], 'django.db.backends.sqlite3')
285 self.assertEqual(url['NAME'], ':memory:')
286
287 def test_memory_sqlite_url(self):
288 url = 'sqlite://:memory:'
289 url = Env.db_url_config(url)
290
291 self.assertEqual(url['ENGINE'], 'django.db.backends.sqlite3')
292 self.assertEqual(url['NAME'], ':memory:')
293
294 def test_database_options_parsing(self):
295 url = 'postgres://user:pass@host:1234/dbname?conn_max_age=600'
296 url = Env.db_url_config(url)
297 self.assertEqual(url['CONN_MAX_AGE'], 600)
298
299 url = 'mysql://user:pass@host:1234/dbname?init_command=SET storage_engine=INNODB'
300 url = Env.db_url_config(url)
301 self.assertEqual(url['OPTIONS'], {
302 'init_command': 'SET storage_engine=INNODB',
303 })
304
305 def test_database_ldap_url(self):
306 url = 'ldap://cn=admin,dc=nodomain,dc=org:some_secret_password@ldap.nodomain.org/'
307 url = Env.db_url_config(url)
308
309 self.assertEqual(url['ENGINE'], 'ldapdb.backends.ldap')
310 self.assertEqual(url['HOST'], 'ldap.nodomain.org')
311 self.assertEqual(url['PORT'], None)
312 self.assertEqual(url['NAME'], 'ldap://ldap.nodomain.org')
313 self.assertEqual(url['USER'], 'cn=admin,dc=nodomain,dc=org')
314 self.assertEqual(url['PASSWORD'], 'some_secret_password')
315
316
317 class CacheTestSuite(unittest.TestCase):
318
319 def test_memcache_parsing(self):
320 url = 'memcache://127.0.0.1:11211'
321 url = Env.cache_url_config(url)
322
323 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.memcached.MemcachedCache')
324 self.assertEqual(url['LOCATION'], '127.0.0.1:11211')
325
326 def test_memcache_pylib_parsing(self):
327 url = 'pymemcache://127.0.0.1:11211'
328 url = Env.cache_url_config(url)
329
330 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.memcached.PyLibMCCache')
331 self.assertEqual(url['LOCATION'], '127.0.0.1:11211')
332
333 def test_memcache_multiple_parsing(self):
334 url = 'memcache://172.19.26.240:11211,172.19.26.242:11212'
335 url = Env.cache_url_config(url)
336
337 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.memcached.MemcachedCache')
338 self.assertEqual(url['LOCATION'], ['172.19.26.240:11211', '172.19.26.242:11212'])
339
340 def test_memcache_socket_parsing(self):
341 url = 'memcache:///tmp/memcached.sock'
342 url = Env.cache_url_config(url)
343
344 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.memcached.MemcachedCache')
345 self.assertEqual(url['LOCATION'], 'unix:/tmp/memcached.sock')
346
347 def test_dbcache_parsing(self):
348 url = 'dbcache://my_cache_table'
349 url = Env.cache_url_config(url)
350
351 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.db.DatabaseCache')
352 self.assertEqual(url['LOCATION'], 'my_cache_table')
353
354 def test_filecache_parsing(self):
355 url = 'filecache:///var/tmp/django_cache'
356 url = Env.cache_url_config(url)
357
358 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.filebased.FileBasedCache')
359 self.assertEqual(url['LOCATION'], '/var/tmp/django_cache')
360
361 def test_filecache_windows_parsing(self):
362 url = 'filecache://C:/foo/bar'
363 url = Env.cache_url_config(url)
364
365 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.filebased.FileBasedCache')
366 self.assertEqual(url['LOCATION'], 'C:/foo/bar')
367
368 def test_locmem_parsing(self):
369 url = 'locmemcache://'
370 url = Env.cache_url_config(url)
371
372 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.locmem.LocMemCache')
373 self.assertEqual(url['LOCATION'], '')
374
375 def test_locmem_named_parsing(self):
376 url = 'locmemcache://unique-snowflake'
377 url = Env.cache_url_config(url)
378
379 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.locmem.LocMemCache')
380 self.assertEqual(url['LOCATION'], 'unique-snowflake')
381
382 def test_dummycache_parsing(self):
383 url = 'dummycache://'
384 url = Env.cache_url_config(url)
385
386 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.dummy.DummyCache')
387 self.assertEqual(url['LOCATION'], '')
388
389 def test_redis_parsing(self):
390 url = 'rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=secret'
391 url = Env.cache_url_config(url)
392
393 self.assertEqual(url['BACKEND'], 'django_redis.cache.RedisCache')
394 self.assertEqual(url['LOCATION'], '127.0.0.1:6379:1')
395 self.assertEqual(url['OPTIONS'], {
396 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
397 'PASSWORD': 'secret',
398 })
399
400 def test_redis_socket_parsing(self):
401 url = 'rediscache:///path/to/socket:1'
402 url = Env.cache_url_config(url)
403 self.assertEqual(url['BACKEND'], 'django_redis.cache.RedisCache')
404 self.assertEqual(url['LOCATION'], 'unix:/path/to/socket:1')
405
406 def test_options_parsing(self):
407 url = 'filecache:///var/tmp/django_cache?timeout=60&max_entries=1000&cull_frequency=0'
408 url = Env.cache_url_config(url)
409
410 self.assertEqual(url['BACKEND'], 'django.core.cache.backends.filebased.FileBasedCache')
411 self.assertEqual(url['LOCATION'], '/var/tmp/django_cache')
412 self.assertEqual(url['TIMEOUT'], 60)
413 self.assertEqual(url['OPTIONS'], {
414 'MAX_ENTRIES': 1000,
415 'CULL_FREQUENCY': 0,
416 })
417
418 def test_custom_backend(self):
419 url = 'memcache://127.0.0.1:5400?foo=option&bars=9001'
420 backend = 'django_redis.cache.RedisCache'
421 url = Env.cache_url_config(url, backend)
422
423 self.assertEqual(url['BACKEND'], backend)
424 self.assertEqual(url['LOCATION'], '127.0.0.1:5400')
425 self.assertEqual(url['OPTIONS'], {
426 'FOO': 'option',
427 'BARS': 9001,
428 })
429
430
431 class SearchTestSuite(unittest.TestCase):
432
433 solr_url = 'solr://127.0.0.1:8983/solr'
434 elasticsearch_url = 'elasticsearch://127.0.0.1:9200/index'
435 whoosh_url = 'whoosh:///home/search/whoosh_index'
436 xapian_url = 'xapian:///home/search/xapian_index'
437 simple_url = 'simple:///'
438
439 def test_solr_parsing(self):
440 url = Env.search_url_config(self.solr_url)
441
442 self.assertEqual(url['ENGINE'], 'haystack.backends.solr_backend.SolrEngine')
443 self.assertEqual(url['URL'], 'http://127.0.0.1:8983/solr')
444
445 def test_solr_multicore_parsing(self):
446 timeout = 360
447 index = 'solr_index'
448 url = '%s/%s?TIMEOUT=%s' % (self.solr_url, index, timeout)
449 url = Env.search_url_config(url)
450
451 self.assertEqual(url['ENGINE'], 'haystack.backends.solr_backend.SolrEngine')
452 self.assertEqual(url['URL'], 'http://127.0.0.1:8983/solr/solr_index')
453 self.assertEqual(url['TIMEOUT'], timeout)
454 self.assertTrue('INDEX_NAME' not in url)
455 self.assertTrue('PATH' not in url)
456
457 def test_elasticsearch_parsing(self):
458 timeout = 360
459 url = '%s?TIMEOUT=%s' % (self.elasticsearch_url, timeout)
460 url = Env.search_url_config(url)
461
462 self.assertEqual(url['ENGINE'], 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine')
463 self.assertTrue('INDEX_NAME' in url.keys())
464 self.assertEqual(url['INDEX_NAME'], 'index')
465 self.assertTrue('TIMEOUT' in url.keys())
466 self.assertEqual(url['TIMEOUT'], timeout)
467 self.assertTrue('PATH' not in url)
468
469 def test_whoosh_parsing(self):
470 storage = 'file' # or ram
471 post_limit = 128 * 1024 * 1024
472 url = '%s?STORAGE=%s&POST_LIMIT=%s' % (self.whoosh_url, storage, post_limit)
473 url = Env.search_url_config(url)
474
475 self.assertEqual(url['ENGINE'], 'haystack.backends.whoosh_backend.WhooshEngine')
476 self.assertTrue('PATH' in url.keys())
477 self.assertEqual(url['PATH'], '/home/search/whoosh_index')
478 self.assertTrue('STORAGE' in url.keys())
479 self.assertEqual(url['STORAGE'], storage)
480 self.assertTrue('POST_LIMIT' in url.keys())
481 self.assertEqual(url['POST_LIMIT'], post_limit)
482 self.assertTrue('INDEX_NAME' not in url)
483
484 def test_xapian_parsing(self):
485 flags = 'myflags'
486 url = '%s?FLAGS=%s' % (self.xapian_url, flags)
487 url = Env.search_url_config(url)
488
489 self.assertEqual(url['ENGINE'], 'haystack.backends.xapian_backend.XapianEngine')
490 self.assertTrue('PATH' in url.keys())
491 self.assertEqual(url['PATH'], '/home/search/xapian_index')
492 self.assertTrue('FLAGS' in url.keys())
493 self.assertEqual(url['FLAGS'], flags)
494 self.assertTrue('INDEX_NAME' not in url)
495
496 def test_simple_parsing(self):
497 url = Env.search_url_config(self.simple_url)
498
499 self.assertEqual(url['ENGINE'], 'haystack.backends.simple_backend.SimpleEngine')
500 self.assertTrue('INDEX_NAME' not in url)
501 self.assertTrue('PATH' not in url)
502
503 def test_common_args_parsing(self):
504 excluded_indexes = 'myapp.indexes.A,myapp.indexes.B'
505 include_spelling = 1
506 batch_size = 100
507 params = 'EXCLUDED_INDEXES=%s&INCLUDE_SPELLING=%s&BATCH_SIZE=%s' % (
508 excluded_indexes,
509 include_spelling,
510 batch_size
511 )
512 for url in [
513 self.solr_url,
514 self.elasticsearch_url,
515 self.whoosh_url,
516 self.xapian_url,
517 self.simple_url,
518 ]:
519 url = '?'.join([url, params])
520 url = Env.search_url_config(url)
521
522 self.assertTrue('EXCLUDED_INDEXES' in url.keys())
523 self.assertTrue('myapp.indexes.A' in url['EXCLUDED_INDEXES'])
524 self.assertTrue('myapp.indexes.B' in url['EXCLUDED_INDEXES'])
525 self.assertTrue('INCLUDE_SPELLING'in url.keys())
526 self.assertTrue(url['INCLUDE_SPELLING'])
527 self.assertTrue('BATCH_SIZE' in url.keys())
528 self.assertEqual(url['BATCH_SIZE'], 100)
529
530
531 class EmailTests(unittest.TestCase):
532
533 def test_smtp_parsing(self):
534 url = 'smtps://user@domain.com:password@smtp.example.com:587'
535 url = Env.email_url_config(url)
536
537 self.assertEqual(url['EMAIL_BACKEND'], 'django.core.mail.backends.smtp.EmailBackend')
538 self.assertEqual(url['EMAIL_HOST'], 'smtp.example.com')
539 self.assertEqual(url['EMAIL_HOST_PASSWORD'], 'password')
540 self.assertEqual(url['EMAIL_HOST_USER'], 'user@domain.com')
541 self.assertEqual(url['EMAIL_PORT'], 587)
542 self.assertEqual(url['EMAIL_USE_TLS'], True)
543
544
545 class PathTests(unittest.TestCase):
546
547 def test_path_class(self):
548
549 root = Path(__file__, '..', is_file=True)
550 root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
551 self.assertEqual(root(), root_path)
552 self.assertEqual(root.__root__, root_path)
553
554 web = root.path('public')
555 self.assertEqual(web(), os.path.join(root_path, 'public'))
556 self.assertEqual(web('css'), os.path.join(root_path, 'public', 'css'))
557
558 def test_required_path(self):
559
560 self.assertRaises(ImproperlyConfigured, Path, '/not/existing/path/', required=True)
561 self.assertRaises(ImproperlyConfigured, Path(__file__), 'not_existing_path', required=True)
562
563 def test_comparison(self):
564
565 self.assertTrue(Path('/home') in Path('/'))
566 self.assertTrue(Path('/home') not in Path('/other/dir'))
567
568 self.assertTrue(Path('/home') == Path('/home'))
569 self.assertTrue(Path('/home') != Path('/home/dev'))
570
571 self.assertEqual(Path('/home/foo/').rfind('/'), str(Path('/home/foo')).rfind('/'))
572 self.assertEqual(Path('/home/foo/').find('/home'), str(Path('/home/foo/')).find('/home'))
573 self.assertEqual(Path('/home/foo/')[1], str(Path('/home/foo/'))[1])
574
575 self.assertEqual(~Path('/home'), Path('/'))
576 self.assertEqual(Path('/') + 'home', Path('/home'))
577 self.assertEqual(Path('/') + '/home/public', Path('/home/public'))
578 self.assertEqual(Path('/home/dev/public') - 2, Path('/home'))
579 self.assertEqual(Path('/home/dev/public') - 'public', Path('/home/dev'))
580
581 self.assertRaises(TypeError, lambda _: Path('/home/dev/') - 'not int')
582
583
584 def load_suite():
585
586 test_suite = unittest.TestSuite()
587 for case in [
588 EnvTests, FileEnvTests, SchemaEnvTests, PathTests, DatabaseTestSuite,
589 CacheTestSuite, EmailTests, SearchTestSuite
590 ]:
591 test_suite.addTest(unittest.makeSuite(case))
592 return test_suite
593
594 if __name__ == "__main__":
595
596 try:
597 if sys.argv[1] == '-o':
598 for key, value in BaseTests.generateData().items():
599 print("{0}={1}".format(key, value))
600 sys.exit()
601 except IndexError:
602 pass
603
604 unittest.TextTestRunner().run(load_suite())
0 [bumpversion]
1 current_version = 0.4.0
2 commit = True
3 tag = True
4 files = setup.py environ/environ.py docs/conf.py
5
6 [egg_info]
7 tag_build =
8 tag_date = 0
9 tag_svn_revision = 0
10
0 from __future__ import unicode_literals
1 from setuptools import setup, find_packages
2 import io
3 import os
4
5 here = os.path.abspath(os.path.dirname(__file__))
6 README = io.open(os.path.join(here, 'README.rst'), encoding="utf8").read()
7
8 version = '0.4.0'
9 author = 'joke2k'
10 description = "Django-environ allows you to utilize 12factor inspired environment " \
11 "variables to configure your Django application."
12 install_requires = ['django', 'six']
13
14 setup(name='django-environ',
15 version=version,
16 description=description,
17 long_description=README,
18 classifiers=[
19 # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
20 'Development Status :: 3 - Alpha',
21 'Intended Audience :: Information Technology',
22 'Programming Language :: Python',
23 'Programming Language :: Python :: 2',
24 'Programming Language :: Python :: 3',
25 'Topic :: Software Development :: Libraries :: Python Modules',
26 'Topic :: Utilities',
27 'License :: OSI Approved :: MIT License',
28 'Framework :: Django'
29 ],
30 keywords='django environment variables 12factor',
31 author=author,
32 author_email='joke2k@gmail.com',
33 url='http://github.com/joke2k/django-environ',
34 license='MIT License',
35 packages=find_packages(),
36 platforms=["any"],
37 include_package_data=True,
38 test_suite='environ.test.load_suite',
39 zip_safe=False,
40 install_requires=install_requires,
41 )