From 8119529dcd8b6e3847156f4ba307f64c620b1bdc Mon Sep 17 00:00:00 2001 From: Mateeh Ullah Date: Sat, 31 Oct 2026 19:55:20 +0500 Subject: [PATCH 1/2] fix(parser): preserve unquoted hash characters in CLI arguments --- fire/parser.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fire/parser.py b/fire/parser.py index a335cc2c..572a81e9 100644 --- a/fire/parser.py +++ b/fire/parser.py @@ -16,7 +16,9 @@ import argparse import ast +import io import sys +import tokenize if sys.version_info[0:2] < (3, 8): _StrNode = ast.Str # type: ignore # pylint: disable=no-member # deprecated but needed for Python < 3.8 @@ -68,7 +70,17 @@ def DefaultParseValue(value): Returns: The parsed value, of the type determined most appropriate. """ - # Note: _LiteralEval will treat '#' as the start of a comment. + # Keep unquoted hash characters intact rather than letting Python's AST + # parser silently drop a trailing comment from command-line arguments. + # Quoted hashes remain valid parts of Python literals. + if '#' in value: + try: + tokens = tokenize.generate_tokens(io.StringIO(value).readline) + if any(token.type == tokenize.COMMENT for token in tokens): + return value + except tokenize.TokenError: + # Let the existing literal parser handle incomplete expressions. + pass try: return _LiteralEval(value) except (SyntaxError, ValueError): From 8baeaff8dc7af2211d1d0d496ffe6b9ee982eaab Mon Sep 17 00:00:00 2001 From: Mateeh Ullah Date: Sat, 31 Oct 2026 19:55:22 +0500 Subject: [PATCH 2/2] fix(parser): preserve unquoted hash characters in CLI arguments --- fire/parser_test.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fire/parser_test.py b/fire/parser_test.py index a404eea2..5b1a4589 100644 --- a/fire/parser_test.py +++ b/fire/parser_test.py @@ -119,8 +119,15 @@ def testDefaultParseValueNestedContainers(self): def testDefaultParseValueComments(self): self.assertEqual(parser.DefaultParseValue('"0#comments"'), '0#comments') - # Comments are stripped. This behavior may change in the future. - self.assertEqual(parser.DefaultParseValue('0#comments'), 0) + self.assertEqual(parser.DefaultParseValue('0#comments'), '0#comments') + self.assertEqual(parser.DefaultParseValue('qgm#123'), 'qgm#123') + self.assertEqual(parser.DefaultParseValue('#literal'), '#literal') + self.assertEqual(parser.DefaultParseValue('a#b#c'), 'a#b#c') + + def testDefaultParseValueQuotedHashesInContainers(self): + self.assertEqual(parser.DefaultParseValue('["a#b", 2]'), ['a#b', 2]) + self.assertEqual(parser.DefaultParseValue('{"key": "a#b"}'), + {'key': 'a#b'}) def testDefaultParseValueBadLiteral(self): # If it can't be parsed, we treat it as a string. This behavior may change.