From 3f6723c0d2842c1dac321f5d1ee1280b1e24ec55 Mon Sep 17 00:00:00 2001 From: Serghei Iakovlev Date: Fri, 28 May 2021 12:09:17 +0300 Subject: [PATCH 1/3] Override environment vars that already have value with in a env-file or the OS --- CHANGELOG.rst | 1 + docs/essentials.rst | 17 +++++++++++++---- environ/environ.py | 9 ++++++--- tests/conftest.py | 8 +++++++- tests/test_env.py | 5 +---- tests/test_read_env.py | 18 +++++++++--------- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 99e87c9..d03cc67 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ Breaking Changes module. If an ImportError is encountered while it attempts to do this, ``Env.read_env()`` will assume there's no ``.env`` file to be found, log a WARN-level log message to that effect, and continue on. +* Make ``Env.read_env()``'s ``overrides`` argument actually override variables. Features diff --git a/docs/essentials.rst b/docs/essentials.rst index 6b28d18..652e846 100644 --- a/docs/essentials.rst +++ b/docs/essentials.rst @@ -10,7 +10,7 @@ This method is responsible for reading key-value pairs from the ``.env`` file and adding them to environment variables. ``read_env()`` expects a path to the ``.env`` file. If one is not provided, it -will attempt to use the ``django.BASE_DIR`` constant from the Django settings +will attempt to use the ``django.BASE_DIR`` constant from the Django ``settings`` module. If an ``ImportError`` or ``NameError`` is encountered while it attempts to do this, ``read_env()`` will assume there's no ``.env`` file to be found, log a WARN-level log message to that effect, and continue on. @@ -44,7 +44,7 @@ The following things should also be mentioned: * The values read in from the ``.env`` file are overridden by any that already existed in the environment. This means that environment variables obtained from the ``os.environ`` will have a higher priority. -* ``read_env()`` also takes an additional key/value **kwargs. Any additional keyword +* ``read_env()`` also takes an additional overrides list. Any additional keyword arguments provided directly to ``read_env`` will be added to the environment. If the key matches an existing environment variable, the value will be overridden. * ``read_env()`` updates ``os.environ`` directly, rather than just that particular @@ -57,6 +57,7 @@ The following example demonstrates the above: .. code-block:: shell # .env file contents DJANGO_SETTINGS_MODULE=settings.prod + EMAIL=sales@acme.com DEBUG=on @@ -71,10 +72,18 @@ The following example demonstrates the above: assert 'SECRET_KEY' not in os.environ assert 'DEBUG' not in os.environ - # Take environment variables from .env file - overrides = {'SECRET_KEY': 'Enigma'} + + overrides = { + 'SECRET_KEY': 'Enigma', + 'EMAIL': 'dev@acme.localhost', + } + + # Take environment variables from .env file using + # os.path.join(BASE_DIR, '.env'). Also take the overrides list. environ.Env.read_env(**overrides) assert os.environ['SECRET_KEY'] == 'Enigma' assert os.environ['DJANGO_SETTINGS_MODULE'] == 'settings.dev' + assert os.environ['EMAIL'] == 'dev@acme.localhost' + assert 'DEBUG' in os.environ diff --git a/environ/environ.py b/environ/environ.py index 2821589..777e3af 100644 --- a/environ/environ.py +++ b/environ/environ.py @@ -719,7 +719,10 @@ def read_env(cls, env_file=None, **overrides): """Read a .env file into ENVIRON. Existing environment variables take precedent and are NOT overwritten - by the file content or key/value pairs given as `overrides`. + by the file content. + + Key/value pairs given as `overrides` DO overwrite existing variables. + Keep in mind, that variable names are case sensitive, when overriding. :param env_file: The path to the `.env` file your application should use. If a path is not provided, `read_env` will attempt to import @@ -770,9 +773,9 @@ def read_env(cls, env_file=None, **overrides): val = re.sub(r'\\(.)', r'\1', m3.group(1)) cls.ENVIRON.setdefault(key, str(val)) - # set defaults + # set overrides for key, value in overrides.items(): - cls.ENVIRON.setdefault(key, value) + cls.ENVIRON[key] = value class Path: diff --git a/tests/conftest.py b/tests/conftest.py index 641ec63..055383b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -65,5 +65,11 @@ def search_url(request): @pytest.fixture def env_file(): - """Return env file for tests..""" + """Return test_env.txt file path for the testing purposes.""" return os.path.join(os.path.dirname(__file__), 'test_env.txt') + + +@pytest.fixture +def simple_env_file(): + """Return simple_env.txt file path for the testing purposes.""" + return os.path.join(os.path.dirname(__file__), 'simple_env.txt') diff --git a/tests/test_env.py b/tests/test_env.py index 01a8dde..806bf40 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -263,10 +263,7 @@ def setup_method(self, method): super().setup_method(method) Env.ENVIRON = {} - self.env.read_env( - Path(__file__, is_file=True)('test_env.txt'), - PATH_VAR=Path(__file__, is_file=True).__root__ - ) + self.env.read_env(Path(__file__, is_file=True)('test_env.txt')) class TestSubClass(TestEnv): diff --git a/tests/test_read_env.py b/tests/test_read_env.py index 6d91363..df6c6ed 100644 --- a/tests/test_read_env.py +++ b/tests/test_read_env.py @@ -15,15 +15,15 @@ from environ import Env, Path -def test_read_env_priority(env_file, monkeypatch): +def test_read_env_priority(simple_env_file, monkeypatch): """Values obtained from the os.environ should have a higher priority.""" - monkeypatch.setenv('PATH_VAR', '/tmp') + monkeypatch.setenv('EMAIL', 'dev@acme.localhost') env = Env() - env.read_env(env_file, PATH_VAR='/var') + env.read_env(simple_env_file) - # In env_file: PATH_VAR=/home/dev - assert os.environ['PATH_VAR'] == '/tmp' + # In env_file: EMAIL=sales@acme.com + assert os.environ['EMAIL'] == 'dev@acme.localhost' def test_read_env_overrides(env_file): @@ -39,15 +39,15 @@ def test_read_env_overrides(env_file): assert os.environ['SECRET'] == 'top_secret' -def test_read_env_overrides_os(env_file, monkeypatch): - """Additional keywords should not override vars from the os.environ.""" +def test_read_env_override_os(env_file, monkeypatch): + """Additional keywords should override vars from the os.environ.""" monkeypatch.setenv('SECRET_KEY', 'enigma') env = Env() env.read_env(env_file=env_file, SECRET_KEY='top_secret') - assert env.ENVIRON['SECRET_KEY'] == 'enigma' - assert os.environ['SECRET_KEY'] == 'enigma' + assert env.ENVIRON['SECRET_KEY'] == 'top_secret' + assert os.environ['SECRET_KEY'] == 'top_secret' @pytest.mark.parametrize( From 0c10c943a1df49723af26f2bcf1aaf6ebbd43e3f Mon Sep 17 00:00:00 2001 From: Serghei Iakovlev Date: Fri, 28 May 2021 12:13:12 +0300 Subject: [PATCH 2/3] Add missed simple_env.txt --- tests/simple_env.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/simple_env.txt diff --git a/tests/simple_env.txt b/tests/simple_env.txt new file mode 100644 index 0000000..b4050bf --- /dev/null +++ b/tests/simple_env.txt @@ -0,0 +1 @@ +EMAIL=sales@acme.com From f3d2ddd02842441d88fba7d5b3c8372b3623dd11 Mon Sep 17 00:00:00 2001 From: Serghei Iakovlev Date: Fri, 28 May 2021 12:15:22 +0300 Subject: [PATCH 3/3] Fix code blocks --- docs/essentials.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/essentials.rst b/docs/essentials.rst index 652e846..3ce3edc 100644 --- a/docs/essentials.rst +++ b/docs/essentials.rst @@ -55,6 +55,7 @@ The following example demonstrates the above: **.env file**: .. code-block:: shell + # .env file contents DJANGO_SETTINGS_MODULE=settings.prod EMAIL=sales@acme.com @@ -64,6 +65,7 @@ The following example demonstrates the above: **settings.py file**: .. code-block:: python + # settings.py file contents import environ