Skip to content

Commit b0134f6

Browse files
authored
Merge 4283f12 into 6eb1698
2 parents 6eb1698 + 4283f12 commit b0134f6

3 files changed

Lines changed: 63 additions & 14 deletions

File tree

‎README.rst‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,34 @@ Django
110110
If you are using django you should add the above loader script at the
111111
top of ``wsgi.py`` and ``manage.py``.
112112

113+
114+
In-memory filelikes
115+
-------------------
116+
117+
Is possible to not rely on the filesystem to parse filelikes from other sources
118+
(e.g. from a network storage). ``parse_dotenv`` accepts a filelike `stream`.
119+
Just be sure to rewind it before passing.
120+
121+
.. code:: python
122+
123+
from io import StringIO # Python2: from StringIO import StringIO
124+
from dotenv.main import parse_dotenv
125+
filelike = StringIO('SPAM=EGSS\n')
126+
filelike.seek(0)
127+
parsed = parse_dotenv(stream=filelike)
128+
129+
The returned lazy generator yields ``(key,value)`` tuples.
130+
To ease the consumption, is suggested to unpack it into a dictionary.
131+
132+
.. code:: python
133+
134+
os_env_like = dict(iter(parsed))
135+
assert os_env_like['SPAM'] == 'EGGS'
136+
137+
This could be useful if you need to *consume* the envfile but not *apply* it
138+
directly into the system environment.
139+
140+
113141
Installation
114142
============
115143

‎dotenv/main.py‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,24 +95,31 @@ def dotenv_values(dotenv_path):
9595
return values
9696

9797

98-
def parse_dotenv(dotenv_path):
99-
with open(dotenv_path) as f:
100-
for line in f:
101-
line = line.strip()
102-
if not line or line.startswith('#') or '=' not in line:
103-
continue
104-
k, v = line.split('=', 1)
98+
def parse_dotenv(dotenv_path=None, stream=None):
99+
if dotenv_path:
100+
f = open(dotenv_path)
101+
elif stream:
102+
f = stream
105103

106-
# Remove any leading and trailing spaces in key, value
107-
k, v = k.strip(), v.strip().encode('unicode-escape').decode('ascii')
104+
for line in f:
105+
line = line.strip()
106+
if not line or line.startswith('#') or '=' not in line:
107+
continue
108+
k, v = line.split('=', 1)
108109

109-
if len(v) > 0:
110-
quoted = v[0] == v[-1] in ['"', "'"]
110+
# Remove any leading and trailing spaces in key, value
111+
k, v = k.strip(), v.strip().encode('unicode-escape').decode('ascii')
111112

112-
if quoted:
113-
v = decode_escaped(v[1:-1])
113+
if len(v) > 0:
114+
quoted = v[0] == v[-1] in ['"', "'"]
114115

115-
yield k, v
116+
if quoted:
117+
v = decode_escaped(v[1:-1])
118+
119+
yield k, v
120+
121+
if dotenv_path:
122+
f.close()
116123

117124

118125
def resolve_nested_variables(values):

‎tests/test_core.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
import tempfile
55
import warnings
66
import sh
7+
try:
8+
from StringIO import StringIO
9+
except ImportError:
10+
from io import StringIO
711

812
from dotenv import load_dotenv, find_dotenv, set_key
13+
from dotenv.main import parse_dotenv
914
from IPython.terminal.embed import InteractiveShellEmbed
1015

1116

@@ -110,3 +115,12 @@ def test_ipython_override():
110115
ipshell.magic("load_ext dotenv")
111116
ipshell.magic("dotenv -o")
112117
assert os.environ["MYNEWVALUE"] == 'q1w2e3'
118+
119+
120+
def test_parse_dotenv_stream():
121+
stream = StringIO('DOTENV=WORKS\n')
122+
stream.seek(0)
123+
parsed_generator = parse_dotenv(stream=stream)
124+
parsed_dict = dict(iter(parsed_generator))
125+
assert 'DOTENV' in parsed_dict
126+
assert parsed_dict['DOTENV'] == 'WORKS'

0 commit comments

Comments
 (0)