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 @@ -10,6 +10,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixed

- An unquoted empty value followed by an inline comment (e.g. `KEY= # comment`) is now parsed as an empty string instead of the comment text by [@Noethix55555] in [#663]
- `set_key` now writes values containing `'` double-quoted, so the resulting line is valid shell and can be `source`d by [@SAY-5] in [#647]

## [1.2.3] - 2026-08-16

Expand Down Expand Up @@ -446,6 +447,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
[#606]: https://github.com/theskumar/python-dotenv/pull/606
[#638]: https://github.com/theskumar/python-dotenv/pull/638
[#640]: https://github.com/theskumar/python-dotenv/pull/640
[#647]: https://github.com/theskumar/python-dotenv/pull/647
[#663]: https://github.com/theskumar/python-dotenv/pull/663
[#680]: https://github.com/theskumar/python-dotenv/pull/680
[790c5c0]: https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311
Expand Down Expand Up @@ -489,6 +491,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
[@mgorny]: https://github.com/mgorny
[@naorlivne]: https://github.com/naorlivne
[@Noethix55555]: https://github.com/Noethix55555
[@SAY-5]: https://github.com/SAY-5
[@qnighy]: https://github.com/qnighy
[@rabinadk1]: https://github.com/rabinadk1
[@randomseed42]: https://github.com/randomseed42
Expand Down
18 changes: 12 additions & 6 deletions src/dotenv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,18 @@ def set_key(
)

if quote:
# The single-quoted-value parser decodes `\\` and `\'`, so both have to
# be escaped here for the value to survive a write/read round-trip.
# Backslashes first, otherwise the backslash added by the quote
# escaping would be escaped in turn.
escaped = value_to_set.replace("\\", "\\\\").replace("'", "\\'")
value_out = f"'{escaped}'"
if "'" in value_to_set:
# A single quote cannot be escaped inside a single-quoted shell
# value, so values containing one are written double-quoted to
# keep the file source-able.
escaped = value_to_set.replace("\\", "\\\\").replace('"', '\\"')
value_out = f'"{escaped}"'
else:
# The single-quoted-value parser decodes `\\`, so backslashes have
# to be escaped here for the value to survive a write/read
# round-trip.
escaped = value_to_set.replace("\\", "\\\\")
value_out = f"'{escaped}'"
else:
value_out = value_to_set
if export:
Expand Down
7 changes: 4 additions & 3 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ def test_set_key_no_file(tmp_path):
[
("", "a", "", (True, "a", ""), "a=''\n"),
("", "a", "b", (True, "a", "b"), "a='b'\n"),
("", "a", "'b'", (True, "a", "'b'"), "a='\\'b\\''\n"),
("", "a", "'b'", (True, "a", "'b'"), "a=\"'b'\"\n"),
("", "a", '"b"', (True, "a", '"b"'), "a='\"b\"'\n"),
("", "a", "b'c", (True, "a", "b'c"), "a='b\\'c'\n"),
("", "a", "b'c", (True, "a", "b'c"), 'a="b\'c"\n'),
("", "a", 'b"c', (True, "a", 'b"c'), "a='b\"c'\n"),
("", "a", 'I\'m "in"', (True, "a", 'I\'m "in"'), 'a="I\'m \\"in\\""\n'),
("a=b", "a", "c", (True, "a", "c"), "a='c'\n"),
("a=b\n", "a", "c", (True, "a", "c"), "a='c'\n"),
("a=b\n\n", "a", "c", (True, "a", "c"), "a='c'\n\n"),
Expand All @@ -42,7 +43,7 @@ def test_set_key_no_file(tmp_path):
("a=b", "c", "d", (True, "c", "d"), "a=b\nc='d'\n"),
("", "a", "b\\c", (True, "a", "b\\c"), "a='b\\\\c'\n"),
("", "a", "b\\", (True, "a", "b\\"), "a='b\\\\'\n"),
("", "a", "b\\'c", (True, "a", "b\\'c"), "a='b\\\\\\'c'\n"),
("", "a", "b\\'c", (True, "a", "b\\'c"), 'a="b\\\\\'c"\n'),
],
)
def test_set_key(dotenv_path, before, key, value, expected, after):
Expand Down