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]
- Reading a `.env` file no longer rewrites a carriage return inside a value to `\n`, so a value written by `set_key` round-trips byte-for-byte and reading a path agrees with reading the same bytes from a stream by [@Sreekant13] in [#724]

## [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
[#724]: https://github.com/theskumar/python-dotenv/pull/724
[790c5c0]: https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311

<!-- contributors -->
Expand Down Expand Up @@ -518,6 +520,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
[@sammck]: https://github.com/sammck
[@samwyma]: https://github.com/samwyma
[@ShamikOfficial]: https://github.com/ShamikOfficial
[@Sreekant13]: https://github.com/Sreekant13
[@sidharth-sudhir]: https://github.com/sidharth-sudhir
[@snobu]: https://github.com/snobu
[@techalchemy]: https://github.com/techalchemy
Expand Down
13 changes: 11 additions & 2 deletions src/dotenv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ def __init__(
@contextmanager
def _get_stream(self) -> Iterator[IO[str]]:
if self.dotenv_path and _is_file_or_fifo(self.dotenv_path):
with open(self.dotenv_path, encoding=self.encoding) as stream:
# newline="" disables universal newline translation so that a
# carriage return inside a (quoted) value is not rewritten to "\n"
# before parsing. The parser handles CRLF, CR and LF line endings
# itself, so reading a path now matches reading the same bytes from
# a stream.
with open(self.dotenv_path, encoding=self.encoding, newline="") as stream:
yield stream
elif self.stream is not None:
yield self.stream
Expand Down Expand Up @@ -168,7 +173,10 @@ def rewrite(
path = os.path.realpath(path)

try:
source: IO[str] = open(path, encoding=encoding)
# newline="" preserves the exact bytes of untouched lines (and of any
# carriage return inside a value) instead of letting universal-newline
# translation rewrite them before they are echoed back out.
source: IO[str] = open(path, encoding=encoding, newline="")
try:
path_stat = os.lstat(path)
original_mode: Optional[int] = (
Expand All @@ -187,6 +195,7 @@ def rewrite(
temp_file = tempfile.NamedTemporaryFile(
mode="w",
encoding=encoding,
newline="",
delete=False,
prefix=".tmp_",
dir=os.path.dirname(os.path.abspath(path)),
Expand Down
6 changes: 6 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def test_set_key(dotenv_path, before, key, value, expected, after):
"a\\nb",
"plain",
"",
# A real newline or carriage return in the value must survive the
# write/read file round-trip and not be rewritten by universal-newline
# translation (a lone "\r" used to be read back as "\n").
"a\nb",
"a\rb",
"a\r\nb",
],
)
def test_set_key_round_trips(dotenv_path, value):
Expand Down
Loading