import python-django-environ_0.4.0.orig.tar.gz
Brian May
8 years ago
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 | 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 | environ |
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 | ) |