Repository: theskumar/python-dotenv

Issue:
Python-dotenv could not parse statement
Hi Guys,

I use dotenv in my Django app and ran into one issue when i runserver:

`> Python-dotenv could not parse statement starting at line 10`

although the django seem working fine, but I couldn't feel right about this.

Here is my .env file:
```
DJANGO_SECRET_KEY="xxxxxxxxxxxxxxxxx"
DEBUG=True
DATABASE_DEFAULT_ENGINE="django.db.backends.postgresql"
DATABASE_DEFAULT_NAME="postgres"
DATABASE_DEFAULT_USER="postgres"
DATABASE_DEFAULT_PASSWORD="abc123"
DATABASE_DEFAULT_HOST="localhost"
DATABASE_DEFAULT_PORT="5432"
SENDGRID_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxx"
```
And i have load the dotenv in wsgi.py:
```
import os

from dotenv import load_dotenv

from django.core.wsgi import get_wsgi_application

load_dotenv()

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webapp.settings')

application = get_wsgi_application()
```

and i even done it in manage.py:
```
import os
import sys

from pathlib import Path

from dotenv import load_dotenv

env_path = Path('webapp') / '.env'
load_dotenv(dotenv_path=env_path)
... the rest as usual
```

is this a bug or my own mistakes?



Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47163f3..8566a50 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 - Fix escaping of quoted values written by `set_key` (#236 by [@bbc2]).
 - Fix `dotenv run` crashing on environment variables without values (#237 by [@yannham]).
+- Remove warning when last line is empty (#238 by [@bbc2]).
 
 ## [0.11.0] - 2020-02-07
 
diff --git a/src/dotenv/parser.py b/src/dotenv/parser.py
index 2904af8..2c93cbd 100644
--- a/src/dotenv/parser.py
+++ b/src/dotenv/parser.py
@@ -197,6 +197,13 @@ def parse_binding(reader):
     reader.set_mark()
     try:
         reader.read_regex(_multiline_whitespace)
+        if not reader.has_next():
+            return Binding(
+                key=None,
+                value=None,
+                original=reader.get_marked(),
+                error=False,
+            )
         reader.read_regex(_export)
         key = parse_key(reader)
         reader.read_regex(_whitespace)


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index a4fb5b4..d867858 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -30,63 +30,32 @@ def test_set_key_no_file(tmp_path):
 
 
 @pytest.mark.parametrize(
-    "key,value,expected,content",
+    "before,key,value,expected,after",
     [
-        ("a", "", (True, "a", ""), 'a=""\n'),
-        ("a", "b", (True, "a", "b"), 'a="b"\n'),
-        ("a", "'b'", (True, "a", "b"), 'a="b"\n'),
-        ("a", "\"b\"", (True, "a", "b"), 'a="b"\n'),
-        ("a", "b'c", (True, "a", "b'c"), 'a="b\'c"\n'),
-        ("a", "b\"c", (True, "a", "b\"c"), 'a="b\\\"c"\n'),
+        ("", "a", "", (True, "a", ""), 'a=""\n'),
+        ("", "a", "b", (True, "a", "b"), 'a="b"\n'),
+        ("", "a", "'b'", (True, "a", "b"), 'a="b"\n'),
+        ("", "a", "\"b\"", (True, "a", "b"), 'a="b"\n'),
+        ("", "a", "b'c", (True, "a", "b'c"), 'a="b\'c"\n'),
+        ("", "a", "b\"c", (True, "a", "b\"c"), 'a="b\\\"c"\n'),
+        ("a=b", "a", "c", (True, "a", "c"), 'a="c"\n'),
+        ("a=b\n", "a", "c", (True, "a", "c"), 'a="c"\n'),
+        ("a=b\n\n", "a", "c", (True, "a", "c"), 'a="c"\n\n'),
+        ("a=b\nc=d", "a", "e", (True, "a", "e"), 'a="e"\nc=d'),
+        ("a=b\nc=d\ne=f", "c", "g", (True, "c", "g"), 'a=b\nc="g"\ne=f'),
+        ("a=b\n", "c", "d", (True, "c", "d"), 'a=b\nc="d"\n'),
     ],
 )
-def test_set_key_new(dotenv_file, key, value, expected, content):
+def test_set_key(dotenv_file, before, key, value, expected, after):
     logger = logging.getLogger("dotenv.main")
+    with open(dotenv_file, "w") as f:
+        f.write(before)
 
     with mock.patch.object(logger, "warning") as mock_warning:
         result = dotenv.set_key(dotenv_file, key, value)
 
     assert result == expected
-    assert open(dotenv_file, "r").read() == content
-    mock_warning.assert_not_called()
-
-
-def test_set_key_new_with_other_values(dotenv_file):
-    logger = logging.getLogger("dotenv.main")
-    with open(dotenv_file, "w") as f:
-        f.write("a=b\n")
-
-    with mock.patch.object(logger, "warning") as mock_warning:
-        result = dotenv.set_key(dotenv_file, "foo", "bar")
-
-    assert result == (True, "foo", "bar")
-    assert open(dotenv_file, "r").read() == 'a=b\nfoo="bar"\n'
-    mock_warning.assert_not_called()
-
-
-def test_set_key_existing(dotenv_file):
-    logger = logging.getLogger("dotenv.main")
-    with open(dotenv_file, "w") as f:
-        f.write("foo=bar")
-
-    with mock.patch.object(logger, "warning") as mock_warning:
-        result = dotenv.set_key(dotenv_file, "foo", "baz")
-
-    assert result == (True, "foo", "baz")
-    assert open(dotenv_file, "r").read() == 'foo="baz"\n'
-    mock_warning.assert_not_called()
-
-
-def test_set_key_existing_with_other_values(dotenv_file):
-    logger = logging.getLogger("dotenv.main")
-    with open(dotenv_file, "w") as f:
-        f.write("a=b\nfoo=bar\nc=d")
-
-    with mock.patch.object(logger, "warning") as mock_warning:
-        result = dotenv.set_key(dotenv_file, "foo", "baz")
-
-    assert result == (True, "foo", "baz")
-    assert open(dotenv_file, "r").read() == 'a=b\nfoo="baz"\nc=d'
+    assert open(dotenv_file, "r").read() == after
     mock_warning.assert_not_called()
 
 
diff --git a/tests/test_parser.py b/tests/test_parser.py
index dae51d3..f807513 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -86,6 +86,19 @@ from dotenv.parser import Binding, Original, parse_stream
             Binding(key=u"b", value=u'c', original=Original(string=u"b=c", line=2), error=False),
         ]
     ),
+    (
+        u"\n\n",
+        [
+            Binding(key=None, value=None, original=Original(string=u"\n\n", line=1), error=False),
+        ]
+    ),
+    (
+        u"a=b\n\n",
+        [
+            Binding(key=u"a", value=u"b", original=Original(string=u"a=b\n", line=1), error=False),
+            Binding(key=None, value=None, original=Original(string=u"\n", line=2), error=False),
+        ]
+    ),
     (
         u'a=b\n\nc=d',
         [