Repository: joke2k/django-environ
Pull request #242 (2019-11-14 22:00:07)

added env variable to point to .env file
This allows to set an environment variable 'ENV_FILE' to point to the .env file location, especially when production has different .env file location.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/27000095/badge)](https://coveralls.io/builds/27000095)

Coverage decreased (-0.3%) to 89.061% when pulling **33181f62e18bd204eb7b40fbd2a80edf08296537 on variable:feature/env_file_path_environment_var** into **f0af058f9efcbed6eb06daf98a48c3286a303cdc on joke2k:develop**.

sergeyklay: PR was closed by mistake. Reopened it.
variable: I have updated my branch

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0e4bd83..335a267 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,12 +17,13 @@ Added
   `#314 <https://github.com/joke2k/django-environ/pull/314>`_.
 - Provided ability to use ``bytes`` or ``str`` as a default value for ``Env.bytes()``.
 
-
 Fixed
 +++++
-- Fixed links in the documentation
+- Fixed links in the documentation.
 - Use default option in ``Env.bytes()``
   `#206 <https://github.com/joke2k/django-environ/pull/206>`_.
+- Safely evaluate a string containing an invalid Python literal
+  `#200 <https://github.com/joke2k/django-environ/issues/200>`_.
 
 Changed
 +++++++
diff --git a/environ/environ.py b/environ/environ.py
index 0b97c5e..2cac868 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -44,7 +44,7 @@ def _cast(value):
     # https://docs.python.org/3/library/ast.html#ast.literal_eval
     try:
         return ast.literal_eval(value)
-    except ValueError:
+    except (ValueError, SyntaxError):
         return value
 
 
@@ -748,6 +748,7 @@ class Env:
                 os.path.dirname(frame.f_back.f_code.co_filename),
                 '.env'
             )
+            env_file = os.environ.get('ENV_FILE') or env_file
             if not os.path.exists(env_file):
                 logger.info(
                     "%s doesn't exist - if you're not configuring your "


Test patch:
diff --git a/tests/test_cache.py b/tests/test_cache.py
index 13077a5..42c72bb 100644
--- a/tests/test_cache.py
+++ b/tests/test_cache.py
@@ -149,3 +149,80 @@ def test_unknown_backend():
 def test_empty_url_is_mapped_to_empty_config():
     assert Env.cache_url_config('') == {}
     assert Env.cache_url_config(None) == {}
+
+
+@pytest.mark.parametrize(
+    'chars',
+    ['!', '$', '&', "'", '(', ')', '*', '+', ';', '=', '-', '.', '-v1.2']
+)
+def test_cache_url_password_using_sub_delims(monkeypatch, chars):
+    """Ensure CACHE_URL passwords may contains some unsafe characters.
+
+    See: https://github.com/joke2k/django-environ/issues/200 for details."""
+    url = 'rediss://enigma:secret{}@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    result = env.cache_url_config(url)
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    url = 'rediss://enigma:sec{}ret@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    result = env.cache_url_config(url)
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    url = 'rediss://enigma:{}secret@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    result = env.cache_url_config(url)
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+
+@pytest.mark.parametrize(
+    'chars', ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%2C']
+)
+def test_cache_url_password_using_gen_delims(monkeypatch, chars):
+    """Ensure CACHE_URL passwords may contains %-encoded characters.
+
+    See: https://github.com/joke2k/django-environ/issues/200 for details."""
+    url = 'rediss://enigma:secret{}@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    url = 'rediss://enigma:sec{}ret@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
+
+    url = 'rediss://enigma:{}secret@ondigitalocean.com:25061/2'.format(chars)
+    monkeypatch.setenv('CACHE_URL', url)
+    env = Env()
+
+    result = env.cache()
+    assert result['BACKEND'] == 'django_redis.cache.RedisCache'
+    assert result['LOCATION'] == url
diff --git a/tests/test_env.py b/tests/test_env.py
index efb09fb..8eacd35 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -351,3 +351,18 @@ class TestSubClass(TestEnv):
 
     def test_singleton_environ(self):
         assert self.CONFIG is self.env.ENVIRON
+
+
+class FileEnvByEnvVarTests(TestEnv):
+
+    def setup_method(self, method):
+        super(FileEnvByEnvVarTests, self).setUp()
+        with open('./tmp/test_env.txt') as env_file:
+            env_file.writeline('VAR=TEST')
+        Env.ENVIRON = {}
+        os.environ['ENV_FILE'] = './tmp/test_env.txt'
+        self.env = Env()
+
+    def test_read_env(self):
+        assert_type_and_value(str, 'TEST', self.env('VAR'))
+
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..523a72d
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,22 @@
+# This file is part of the django-environ.
+#
+# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
+#
+# For the full copyright and license information, please view
+# the LICENSE.txt file that was distributed with this source code.
+
+import pytest
+from environ.environ import _cast
+
+
+@pytest.mark.parametrize(
+    'literal',
+    ['anything-', 'anything*', '*anything', 'anything.',
+     'anything.1', '(anything', 'anything-v1.2', 'anything-1.2', 'anything=']
+)
+def test_cast(literal):
+    """Safely evaluate a string containing an invalid Python literal.
+
+    See https://github.com/joke2k/django-environ/issues/200 for details."""
+    assert _cast(literal) == literal