From e00ed30a08926b0576a528b06bf43e985108e060 Mon Sep 17 00:00:00 2001 From: dchaudhari7177 <111210939+dchaudhari7177@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:34:08 +0530 Subject: [PATCH] fix: do not treat an inline comment as an empty value `KEY= # comment` parsed to `"# comment"` rather than `""`, while `KEY=value # comment` correctly parsed to `"value"`. `_equal_sign` consumes the horizontal whitespace that follows `=`, so by the time `parse_unquoted_value` runs, its `\s+#.*` strip no longer has the whitespace it needs to recognise the comment, and the comment text becomes the value. Pass down whether that whitespace was present and, when it was, treat a value that starts with `#` as a comment. `KEY=#c` with no whitespace still parses to `"#c"`, matching the existing `a=b#c` behaviour. Fixes #600 Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 3 +++ src/dotenv/parser.py | 17 ++++++++++----- tests/test_parser.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa81cc6e..3660eaec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed - Strip a leading UTF-8 BOM from `.env` file contents so the first variable is no longer silently lost when the file is saved with BOM (e.g. by some JetBrains IDEs on Windows) by [@h1whelan] in [#640] +- Treat an inline comment that follows an empty unquoted value as a comment rather than the value, so `KEY= # comment` parses to `""` instead of `"# comment"` by [@dchaudhari7177] in [#600] ## [1.2.2] - 2026-03-01 @@ -435,6 +436,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]). [#497]: https://github.com/theskumar/python-dotenv/pull/497 [#161]: https://github.com/theskumar/python-dotenv/issues/161 [#640]: https://github.com/theskumar/python-dotenv/pull/640 +[#600]: https://github.com/theskumar/python-dotenv/issues/600 [790c5c0]: https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311 @@ -452,6 +454,7 @@ os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]). [@bbc2]: https://github.com/bbc2 [@befeleme]: https://github.com/befeleme [@cjauvin]: https://github.com/cjauvin +[@dchaudhari7177]: https://github.com/dchaudhari7177 [@eaf]: https://github.com/eaf [@earlbread]: https://github.com/earlbread [@eekstunt]: https://github.com/eekstunt diff --git a/src/dotenv/parser.py b/src/dotenv/parser.py index 66773604..6625ad7c 100644 --- a/src/dotenv/parser.py +++ b/src/dotenv/parser.py @@ -120,12 +120,17 @@ def parse_key(reader: Reader) -> Optional[str]: return key -def parse_unquoted_value(reader: Reader) -> str: +def parse_unquoted_value(reader: Reader, preceded_by_whitespace: bool = False) -> str: (part,) = reader.read_regex(_unquoted_value) + # An unquoted value only ends at a `#` that is preceded by whitespace. The + # whitespace right after the `=` is consumed by `_equal_sign`, so a value + # starting with `#` is a comment only if that whitespace was there. + if preceded_by_whitespace and part.startswith("#"): + return "" return re.sub(r"\s+#.*", "", part).rstrip() -def parse_value(reader: Reader) -> str: +def parse_value(reader: Reader, preceded_by_whitespace: bool = False) -> str: char = reader.peek(1) if char == "'": (value,) = reader.read_regex(_single_quoted_value) @@ -136,7 +141,7 @@ def parse_value(reader: Reader) -> str: elif char in ("", "\n", "\r"): return "" else: - return parse_unquoted_value(reader) + return parse_unquoted_value(reader, preceded_by_whitespace) def parse_binding(reader: Reader) -> Binding: @@ -154,8 +159,10 @@ def parse_binding(reader: Reader) -> Binding: key = parse_key(reader) reader.read_regex(_whitespace) if reader.peek(1) == "=": - reader.read_regex(_equal_sign) - value: Optional[str] = parse_value(reader) + (equal_sign,) = reader.read_regex(_equal_sign) + value: Optional[str] = parse_value( + reader, preceded_by_whitespace=len(equal_sign) > 1 + ) else: value = None reader.read_regex(_comment) diff --git a/tests/test_parser.py b/tests/test_parser.py index 4ec5a5af..d2def9df 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -119,6 +119,56 @@ ) ], ), + ( + "a= #c", + [ + Binding( + key="a", + value="", + original=Original(string="a= #c", line=1), + error=False, + ) + ], + ), + ( + "a=\t#c", + [ + Binding( + key="a", + value="", + original=Original(string="a=\t#c", line=1), + error=False, + ) + ], + ), + ( + "a=#c", + [ + Binding( + key="a", + value="#c", + original=Original(string="a=#c", line=1), + error=False, + ) + ], + ), + ( + "a= #c\nd=e", + [ + Binding( + key="a", + value="", + original=Original(string="a= #c\n", line=1), + error=False, + ), + Binding( + key="d", + value="e", + original=Original(string="d=e", line=2), + error=False, + ), + ], + ), ( "a=b c", [