Skip to content

Commit c715d19

Browse files
feat: add PYTHON_DOTENV_DISABLED flag to disable load_dotenv (fixes #510) (#569)
Co-authored-by: Saurabh Kumar <theskumar@users.noreply.github.com>
1 parent 666984d commit c715d19

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ Optional flags:
134134
- `-o` to override existing variables.
135135
- `-v` for increased verbosity.
136136

137+
### Disable load_dotenv
138+
139+
Set `PYTHON_DOTENV_DISABLED=1` to disable `load_dotenv()` from loading .env files or streams. Useful when you can't modify third-party package calls or in production.
140+
137141
## Command-line Interface
138142

139143
A CLI interface `dotenv` is also included, which helps you manipulate the `.env` file

src/dotenv/main.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ def load_dotenv(
350350
of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result
351351
to this function as `dotenv_path`.
352352
"""
353+
if _load_dotenv_disabled():
354+
logger.debug(
355+
"python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
356+
)
357+
return False
358+
353359
if dotenv_path is None and stream is None:
354360
dotenv_path = find_dotenv()
355361

@@ -398,3 +404,12 @@ def dotenv_values(
398404
override=True,
399405
encoding=encoding,
400406
).dict()
407+
408+
def _load_dotenv_disabled() -> bool:
409+
"""
410+
Determine if dotenv loading has been disabled.
411+
"""
412+
if "PYTHON_DOTENV_DISABLED" not in os.environ:
413+
return False
414+
value = os.environ["PYTHON_DOTENV_DISABLED"].casefold()
415+
return value in {"1", "true", "t", "yes", "y"}

tests/test_main.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,130 @@ def test_load_dotenv_existing_file(dotenv_path):
245245
assert os.environ == {"a": "b"}
246246

247247

248+
@pytest.mark.parametrize(
249+
"flag_value",
250+
[
251+
"true",
252+
"yes",
253+
"1",
254+
"t",
255+
"y",
256+
"True",
257+
"Yes",
258+
"TRUE",
259+
"YES",
260+
"T",
261+
"Y",
262+
],
263+
)
264+
def test_load_dotenv_disabled(dotenv_path, flag_value):
265+
expected_environ = {"PYTHON_DOTENV_DISABLED": flag_value}
266+
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
267+
dotenv_path.write_text("a=b")
268+
269+
result = dotenv.load_dotenv(dotenv_path)
270+
271+
assert result is False
272+
assert os.environ == expected_environ
273+
274+
275+
@pytest.mark.parametrize(
276+
"flag_value",
277+
[
278+
"true",
279+
"yes",
280+
"1",
281+
"t",
282+
"y",
283+
"True",
284+
"Yes",
285+
"TRUE",
286+
"YES",
287+
"T",
288+
"Y",
289+
],
290+
)
291+
def test_load_dotenv_disabled_notification(dotenv_path, flag_value):
292+
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
293+
dotenv_path.write_text("a=b")
294+
295+
logger = logging.getLogger("dotenv.main")
296+
with mock.patch.object(logger, "debug") as mock_debug:
297+
result = dotenv.load_dotenv(dotenv_path)
298+
299+
assert result is False
300+
mock_debug.assert_called_once_with(
301+
"python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
302+
)
303+
304+
305+
@pytest.mark.parametrize(
306+
"flag_value",
307+
[
308+
"",
309+
"false",
310+
"no",
311+
"0",
312+
"f",
313+
"n",
314+
"False",
315+
"No",
316+
"FALSE",
317+
"NO",
318+
"F",
319+
"N",
320+
],
321+
)
322+
def test_load_dotenv_enabled(dotenv_path, flag_value):
323+
expected_environ = {"PYTHON_DOTENV_DISABLED": flag_value, "a": "b"}
324+
with mock.patch.dict(os.environ, {"PYTHON_DOTENV_DISABLED": flag_value}, clear=True):
325+
dotenv_path.write_text("a=b")
326+
327+
result = dotenv.load_dotenv(dotenv_path)
328+
329+
assert result is True
330+
assert os.environ == expected_environ
331+
332+
333+
@pytest.mark.parametrize(
334+
"flag_value",
335+
[
336+
"",
337+
"false",
338+
"no",
339+
"0",
340+
"f",
341+
"n",
342+
"False",
343+
"No",
344+
"FALSE",
345+
"NO",
346+
"F",
347+
"N",
348+
],
349+
)
350+
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):
352+
dotenv_path.write_text("a=b")
353+
354+
logger = logging.getLogger("dotenv.main")
355+
with mock.patch.object(logger, "debug") as mock_debug:
356+
result = dotenv.load_dotenv(dotenv_path)
357+
358+
assert result is True
359+
mock_debug.assert_not_called()
360+
361+
362+
@mock.patch.dict(os.environ, {}, clear=True)
363+
def test_load_dotenv_doesnt_disable_itself(dotenv_path):
364+
dotenv_path.write_text("PYTHON_DOTENV_DISABLED=true")
365+
366+
result = dotenv.load_dotenv(dotenv_path)
367+
368+
assert result is True
369+
assert os.environ == {"PYTHON_DOTENV_DISABLED": "true"}
370+
371+
248372
def test_load_dotenv_no_file_verbose():
249373
logger = logging.getLogger("dotenv.main")
250374

0 commit comments

Comments
 (0)