Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `set_key` and `unset_key` no longer leave a `.tmp_*` file behind on Windows when writing a read-only `.env` fails, and the error raised is the one from the failed write rather than from cleaning up the temporary file by [@MohammedAlkindi] in [#686]
- `load_dotenv`, `dotenv_values`, `get_key`, `set_key`, `unset_key` and the CLI `--file` option now expand a leading `~` to the user's home directory by [@veeceey] in [#615]
- `find_dotenv` and the IPython `%dotenv` magic now expand a leading `~` in the file name by [@theskumar] in [#714]
- `load_dotenv`, `dotenv_values` and `get_key` now warn when the given `.env` path is a directory instead of silently loading nothing by [@boriskozak] in [#427]

## [1.2.4] - 2026-10-01

Expand Down Expand Up @@ -467,6 +468,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
[#700]: https://github.com/theskumar/python-dotenv/pull/700
[#711]: https://github.com/theskumar/python-dotenv/pull/711
[#714]: https://github.com/theskumar/python-dotenv/pull/714
[#427]: https://github.com/theskumar/python-dotenv/issues/427
[790c5c0]: https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311

<!-- contributors -->
Expand Down Expand Up @@ -532,6 +534,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
[@zueve]: https://github.com/zueve
[@JYOuyang]: https://github.com/JYOuyang
[@burnout-projects]: https://github.com/burnout-projects
[@boriskozak]: https://github.com/boriskozak
[@cpackham-atlnz]: https://github.com/cpackham-atlnz
[Unreleased]: https://github.com/theskumar/python-dotenv/compare/v1.2.4...HEAD
[1.2.4]: https://github.com/theskumar/python-dotenv/compare/v1.2.3...v1.2.4
Expand Down
8 changes: 7 additions & 1 deletion src/dotenv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ def _get_stream(self) -> Iterator[IO[str]]:
elif self.stream is not None:
yield self.stream
else:
if self.verbose:
if self.dotenv_path and os.path.exists(self.dotenv_path):
logger.warning(
"python-dotenv could not load configuration file %s: "
"it is not a file.",
self.dotenv_path,
)
elif self.verbose:
logger.info(
"python-dotenv could not find configuration file %s.",
self.dotenv_path or ".env",
Expand Down
47 changes: 47 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,53 @@ def test_load_dotenv_no_file_verbose():
)


@pytest.mark.parametrize("verbose", [False, True])
@mock.patch.dict(os.environ, {}, clear=True)
def test_load_dotenv_directory_warns(tmp_path, verbose):
logger = logging.getLogger("dotenv.main")

with (
mock.patch.object(logger, "warning") as mock_warning,
mock.patch.object(logger, "info") as mock_info,
):
result = dotenv.load_dotenv(tmp_path, verbose=verbose)

assert result is False
assert os.environ == {}
mock_warning.assert_called_once_with(
"python-dotenv could not load configuration file %s: it is not a file.",
tmp_path,
)
mock_info.assert_not_called()


def test_dotenv_values_directory_warns(tmp_path):
logger = logging.getLogger("dotenv.main")

with mock.patch.object(logger, "warning") as mock_warning:
result = dotenv.dotenv_values(tmp_path)

assert result == {}
mock_warning.assert_called_once_with(
"python-dotenv could not load configuration file %s: it is not a file.",
tmp_path,
)


@mock.patch.dict(os.environ, {}, clear=True)
def test_load_dotenv_directory_with_stream_loads(tmp_path):
logger = logging.getLogger("dotenv.main")

with mock.patch.object(logger, "warning") as mock_warning:
result = dotenv.load_dotenv(
tmp_path, stream=io.StringIO("FOO=bar\n"), verbose=True
)

assert result is True
assert os.environ["FOO"] == "bar"
mock_warning.assert_not_called()


@pytest.mark.skipif(
sys.platform == "win32", reason="This test assumes case-sensitive variable names"
)
Expand Down
Loading