Repository: theskumar/python-dotenv
Pull request #63 (2017-08-21 19:44:36)

Support override variables
This is a PR for #5 .
The current open item is that I didn't add the functionality into the ipython support

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

Coverage increased (+0.2%) to 82.609% when pulling **8d94c30d4896e99adbc40a55e902ba2e18d072c0 on milonimrod:support_override_variables** into **5e839d4f545d8d8c078e7aa8b3e3369c127251c4 on theskumar:master**.

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

Coverage increased (+0.2%) to 82.609% when pulling **ee6bde5a5096a5956df72cb9886e660f18db53eb on milonimrod:support_override_variables** into **5e839d4f545d8d8c078e7aa8b3e3369c127251c4 on theskumar:master**.

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

Coverage increased (+0.4%) to 82.813% when pulling **d4f32431cb9e2a6404e3f2184794b69e66645fa1 on milonimrod:support_override_variables** into **5e839d4f545d8d8c078e7aa8b3e3369c127251c4 on theskumar:master**.

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

Coverage increased (+3.5%) to 85.938% when pulling **249cbdd80a5e5b2f04d2cacd91d917365f75f5d5 on milonimrod:support_override_variables** into **5e839d4f545d8d8c078e7aa8b3e3369c127251c4 on theskumar:master**.

milonimrod: Please notice that on commit 249cbdd80a5e5b2f04d2cacd91d917365f75f5d5 I also added a general case test for the IPython magic (regardless of the override option)
theskumar: Thanks for pull request. I'll take a look. 
coveralls: 
[![Coverage Status](https://coveralls.io/builds/12948849/badge)](https://coveralls.io/builds/12948849)

Coverage increased (+3.5%) to 86.01% when pulling **41bbd1a61c4b4fa65f3eea1c0c0dba0a8af1abf1 on milonimrod:support_override_variables** into **3aa526c0e2d5223ddbd738810f57eac823914125 on theskumar:master**.

theskumar: This looks super good feature to go in. IPython support with override `-o` argument needs to be documented. Happy to merge and release a new build with updated docs. 👍 
theskumar: I've also resolved a merge conflict with web UI for this branch/PR, but not sure how Github would reflect that on your side. If you face any problem let me know, i'll update the docs on my end.
milonimrod: No problem. I'll pull the new master, merge them and add the documentation (also, I'll add the support for the verbose on the IPython side)
coveralls: 
[![Coverage Status](https://coveralls.io/builds/12950233/badge)](https://coveralls.io/builds/12950233)

Coverage increased (+3.6%) to 86.082% when pulling **edc41313a31fbbbfc394a3efa161d36c1c639219 on milonimrod:support_override_variables** into **3aa526c0e2d5223ddbd738810f57eac823914125 on theskumar:master**.

milonimrod: @theskumar - all done
coveralls: 
[![Coverage Status](https://coveralls.io/builds/12951363/badge)](https://coveralls.io/builds/12951363)

Coverage increased (+3.6%) to 86.082% when pulling **e2131be8ec92ef527372f3037130997b4ac86ca4 on milonimrod:support_override_variables** into **3aa526c0e2d5223ddbd738810f57eac823914125 on theskumar:master**.


Patch:
diff --git a/.gitignore b/.gitignore
index d493953..d2a4839 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ __pycache__
 .DS_Store
 htmlcov/
 .cache/
+.idea
diff --git a/Makefile b/Makefile
index bad2c7d..50b80b4 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ test:
 	py.test tests/
 
 coverage:
-	coverage run --source=dotenv.py --omit='*tests*' -m py.test tests/ -v --tb=native
+	coverage run --source=dotenv --omit='*tests*' -m py.test tests/ -v --tb=native
 	coverage report
 
 coverage-html: coverage
diff --git a/README.rst b/README.rst
index 0c9e862..08c3f15 100644
--- a/README.rst
+++ b/README.rst
@@ -63,6 +63,12 @@ specified file -- called ``.env`` by default.
     from dotenv import load_dotenv, find_dotenv
     load_dotenv(find_dotenv())
 
+You can also set _load_dotenv_ to override existing variables:
+.. code:: python
+
+    from dotenv import load_dotenv, find_dotenv
+    load_dotenv(find_dotenv(), override=True)
+
 Now, you can access the variables either from system environment
 variable or loaded from ``.env`` file. **System environment variables
 gets higher precedence** and it's advised not to include it in version control.
@@ -154,6 +160,12 @@ You can use dotenv with iPython. You can either let the dotenv search for .env w
     # Specify a particular file
     %dotenv relative/or/absolute/path/to/.env
 
+    # Use _-o_ to indicate override of existing variables
+    %dotenv -o
+
+    # Use _-v_ to turn verbose mode on
+    %dotenv -v
+
 
 Setting config on remote servers
 --------------------------------
@@ -220,7 +232,7 @@ commands like so ``fab config:set,<key1>,<value1> config:set,<key2>,<value2>``
     $ fab config:set,hello,world config:set,foo,bar config:set,fizz=buzz
 
 
-Releated Projects
+Related Projects
 =================
 
 -  `Honcho <https://github.com/nickstenning/honcho>`__ - For managing
diff --git a/dotenv/ipython.py b/dotenv/ipython.py
index ffb74f0..7d84ac2 100644
--- a/dotenv/ipython.py
+++ b/dotenv/ipython.py
@@ -1,25 +1,40 @@
 from __future__ import print_function
 from .main import load_dotenv, find_dotenv
 
+from IPython.core.magic import Magics, magics_class, line_magic
+from IPython.core.magic_arguments import (argument, magic_arguments,
+                                          parse_argstring)
 
-def _magic(dotenv_path):
-    """
-    dotenv [dotenv_path]
 
-    Search in increasingly higher folders for the `dotenv_path`
-    """
-    # Locate the .env file
-    dotenv_path = dotenv_path or '.env'
-    try:
-        dotenv_path = find_dotenv(dotenv_path, True, True)
-    except IOError:
-        print("cannot find .env file")
-        return
+@magics_class
+class IPythonDotEnv(Magics):
 
-    # Load the .env file
-    load_dotenv(dotenv_path)
+    @magic_arguments()
+    @argument(
+        '-o', '--override', action='store_true',
+        help="Indicate to override existing variables"
+    )
+    @argument(
+        '-v', '--verbose', action='store_true',
+        help="Indicate function calls to be verbose"
+    )
+    @argument('dotenv_path', nargs='?', type=str, default='.env',
+              help='Search in increasingly higher folders for the `dotenv_path`')
+    @line_magic
+    def dotenv(self, line):
+        args = parse_argstring(self.dotenv, line)
+        # Locate the .env file
+        dotenv_path = args.dotenv_path
+        try:
+            dotenv_path = find_dotenv(dotenv_path, True, True)
+        except IOError:
+            print("cannot find .env file")
+            return
+
+        # Load the .env file
+        load_dotenv(dotenv_path, verbose=args.verbose, override=args.override)
 
 
 def load_ipython_extension(ipython):
     """Register the %dotenv magic."""
-    ipython.register_magic_function(_magic, magic_name='dotenv')
+    ipython.register_magics(IPythonDotEnv)
diff --git a/dotenv/main.py b/dotenv/main.py
index 45f9905..ba74cb6 100644
--- a/dotenv/main.py
+++ b/dotenv/main.py
@@ -16,7 +16,7 @@ def decode_escaped(escaped):
     return __escape_decoder(escaped)[0]
 
 
-def load_dotenv(dotenv_path, verbose=False):
+def load_dotenv(dotenv_path, verbose=False, override=False):
     """
     Read a .env file and load into os.environ.
     """
@@ -25,7 +25,10 @@ def load_dotenv(dotenv_path, verbose=False):
             warnings.warn("Not loading %s - it doesn't exist." % dotenv_path)
         return None
     for k, v in dotenv_values(dotenv_path).items():
-        os.environ.setdefault(k, v)
+        if override:
+            os.environ[k] = v
+        else:
+            os.environ.setdefault(k, v)
     return True
 
 
diff --git a/requirements.txt b/requirements.txt
index 55a766b..673cb10 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,4 @@ wheel
 pytest-cov
 pytest-flake8
 click
+ipython


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 8e9d3f0..4e9adda 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -6,6 +6,7 @@ import warnings
 import sh
 
 from dotenv import load_dotenv, find_dotenv, set_key
+from IPython.terminal.embed import InteractiveShellEmbed
 
 
 def test_warns_if_file_does_not_exist():
@@ -69,3 +70,43 @@ def test_load_dotenv(cli):
         assert 'DOTENV' in os.environ
         assert os.environ['DOTENV'] == 'WORKS'
         sh.rm(dotenv_path)
+
+
+def test_load_dotenv_override(cli):
+    dotenv_path = '.test_load_dotenv_override'
+    key_name = "DOTENV_OVER"
+
+    with cli.isolated_filesystem():
+        sh.touch(dotenv_path)
+        os.environ[key_name] = "OVERRIDE"
+        set_key(dotenv_path, key_name, 'WORKS')
+        success = load_dotenv(dotenv_path, override=True)
+        assert success
+        assert key_name in os.environ
+        assert os.environ[key_name] == 'WORKS'
+        sh.rm(dotenv_path)
+
+
+def test_ipython():
+    tmpdir = os.path.realpath(tempfile.mkdtemp())
+    os.chdir(tmpdir)
+    filename = os.path.join(tmpdir, '.env')
+    with open(filename, 'w') as f:
+        f.write("MYNEWVALUE=q1w2e3\n")
+    ipshell = InteractiveShellEmbed()
+    ipshell.magic("load_ext dotenv")
+    ipshell.magic("dotenv")
+    assert os.environ["MYNEWVALUE"] == 'q1w2e3'
+
+
+def test_ipython_override():
+    tmpdir = os.path.realpath(tempfile.mkdtemp())
+    os.chdir(tmpdir)
+    filename = os.path.join(tmpdir, '.env')
+    os.environ["MYNEWVALUE"] = "OVERRIDE"
+    with open(filename, 'w') as f:
+        f.write("MYNEWVALUE=q1w2e3\n")
+    ipshell = InteractiveShellEmbed()
+    ipshell.magic("load_ext dotenv")
+    ipshell.magic("dotenv -o")
+    assert os.environ["MYNEWVALUE"] == 'q1w2e3'