Skip to content

Commit 9a32042

Browse files
author
Sidharth Sudhir
committed
handle FileNotFoundError in FIFO checks and add FIFO load test
1 parent fe9ddfc commit 9a32042

File tree

3 files changed

+63
-7
lines changed

3 files changed

+63
-7
lines changed

src/dotenv/main.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262

6363
@contextmanager
6464
def _get_stream(self) -> Iterator[IO[str]]:
65-
if self.dotenv_path and os.path.isfile(self.dotenv_path) or stat.S_ISFIFO(os.stat(self.dotenv_path).st_mode):
65+
if self.dotenv_path and _is_file_or_fifo(self.dotenv_path):
6666
with open(self.dotenv_path, encoding=self.encoding) as stream:
6767
yield stream
6868
elif self.stream is not None:
@@ -326,7 +326,7 @@ def _is_debugger():
326326

327327
for dirname in _walk_to_root(path):
328328
check_path = os.path.join(dirname, filename)
329-
if os.path.isfile(check_path) or stat.S_ISFIFO(os.stat(check_path).st_mode):
329+
if _is_file_or_fifo(check_path):
330330
return check_path
331331

332332
if raise_error_if_not_found:
@@ -418,3 +418,18 @@ def dotenv_values(
418418
override=True,
419419
encoding=encoding,
420420
).dict()
421+
422+
423+
def _is_file_or_fifo(path: StrPath) -> bool:
424+
"""
425+
Return True if `path` exists and is either a regular file or a FIFO.
426+
"""
427+
if os.path.isfile(path):
428+
return True
429+
430+
try:
431+
st = os.stat(path)
432+
except (FileNotFoundError, OSError):
433+
return False
434+
435+
return stat.S_ISFIFO(st.st_mode)

tests/test_fifo_dotenv.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
import pathlib
3+
import sys
4+
import threading
5+
6+
import pytest
7+
8+
from dotenv import load_dotenv
9+
10+
pytestmark = pytest.mark.skipif(
11+
sys.platform.startswith("win"), reason="FIFOs are Unix-only"
12+
)
13+
14+
15+
def test_load_dotenv_from_fifo(tmp_path: pathlib.Path, monkeypatch):
16+
fifo = tmp_path / ".env"
17+
os.mkfifo(fifo) # create named pipe
18+
19+
def writer():
20+
with open(fifo, "w", encoding="utf-8") as w:
21+
w.write("MY_PASSWORD=pipe-secret\n")
22+
23+
t = threading.Thread(target=writer)
24+
t.start()
25+
26+
# Ensure env is clean
27+
monkeypatch.delenv("MY_PASSWORD", raising=False)
28+
29+
ok = load_dotenv(dotenv_path=str(fifo), override=True)
30+
t.join(timeout=2)
31+
32+
assert ok is True
33+
assert os.getenv("MY_PASSWORD") == "pipe-secret"

tests/test_main.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,9 @@ def test_load_dotenv_existing_file(dotenv_path):
263263
)
264264
def test_load_dotenv_disabled(dotenv_path, flag_value):
265265
expected_environ = {"PYTHON_DOTENV_DISABLED": flag_value}
266-
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
266+
with mock.patch.dict(
267+
os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True
268+
):
267269
dotenv_path.write_text("a=b")
268270

269271
result = dotenv.load_dotenv(dotenv_path)
@@ -289,7 +291,9 @@ def test_load_dotenv_disabled(dotenv_path, flag_value):
289291
],
290292
)
291293
def test_load_dotenv_disabled_notification(dotenv_path, flag_value):
292-
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
294+
with mock.patch.dict(
295+
os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True
296+
):
293297
dotenv_path.write_text("a=b")
294298

295299
logger = logging.getLogger("dotenv.main")
@@ -298,7 +302,7 @@ def test_load_dotenv_disabled_notification(dotenv_path, flag_value):
298302

299303
assert result is False
300304
mock_debug.assert_called_once_with(
301-
"python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
305+
"python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
302306
)
303307

304308

@@ -321,7 +325,9 @@ def test_load_dotenv_disabled_notification(dotenv_path, flag_value):
321325
)
322326
def test_load_dotenv_enabled(dotenv_path, flag_value):
323327
expected_environ = {"PYTHON_DOTENV_DISABLED": flag_value, "a": "b"}
324-
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
328+
with mock.patch.dict(
329+
os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True
330+
):
325331
dotenv_path.write_text("a=b")
326332

327333
result = dotenv.load_dotenv(dotenv_path)
@@ -348,7 +354,9 @@ def test_load_dotenv_enabled(dotenv_path, flag_value):
348354
],
349355
)
350356
def test_load_dotenv_enabled_no_notification(dotenv_path, flag_value):
351-
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
357+
with mock.patch.dict(
358+
os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True
359+
):
352360
dotenv_path.write_text("a=b")
353361

354362
logger = logging.getLogger("dotenv.main")

0 commit comments

Comments
 (0)