diff --git a/pydantic_settings/sources/providers/cli.py b/pydantic_settings/sources/providers/cli.py index a2d6f839..c0a43983 100644 --- a/pydantic_settings/sources/providers/cli.py +++ b/pydantic_settings/sources/providers/cli.py @@ -450,8 +450,13 @@ def _consume_comma(self, item: str, merged_list: list[str], is_last_consumed_a_v def _consume_object_or_array(self, item: str, merged_list: list[str]) -> str: count = 1 close_delim = '}' if item.startswith('{') else ']' + in_str = False for consumed in range(1, len(item)): - if item[consumed] in ('{', '['): + if item[consumed] == '"' and item[consumed - 1] != '\\': + in_str = not in_str + elif in_str: + continue + elif item[consumed] in ('{', '['): count += 1 elif item[consumed] in ('}', ']'): count -= 1 diff --git a/tests/test_source_cli.py b/tests/test_source_cli.py index 98c4b0a6..5d711fe1 100644 --- a/tests/test_source_cli.py +++ b/tests/test_source_cli.py @@ -2452,6 +2452,40 @@ class Root(BaseModel): ) +def test_cli_with_unbalanced_brackets_in_json_string(): + class StrToStrDictOptions(BaseSettings): + nested: dict[str, str] + + assert CliApp.run(StrToStrDictOptions, cli_args=['--nested={"test": "{"}']).model_dump() == { + 'nested': {'test': '{'} + } + assert CliApp.run(StrToStrDictOptions, cli_args=['--nested={"test": "}"}']).model_dump() == { + 'nested': {'test': '}'} + } + assert CliApp.run(StrToStrDictOptions, cli_args=['--nested={"test": "["}']).model_dump() == { + 'nested': {'test': '['} + } + assert CliApp.run(StrToStrDictOptions, cli_args=['--nested={"test": "]"}']).model_dump() == { + 'nested': {'test': ']'} + } + + class StrToListDictOptions(BaseSettings): + nested: dict[str, list[str]] + + assert CliApp.run(StrToListDictOptions, cli_args=['--nested={"test": ["{"]}']).model_dump() == { + 'nested': {'test': ['{']} + } + assert CliApp.run(StrToListDictOptions, cli_args=['--nested={"test": ["}"]}']).model_dump() == { + 'nested': {'test': ['}']} + } + assert CliApp.run(StrToListDictOptions, cli_args=['--nested={"test": ["["]}']).model_dump() == { + 'nested': {'test': ['[']} + } + assert CliApp.run(StrToListDictOptions, cli_args=['--nested={"test": ["]"]}']).model_dump() == { + 'nested': {'test': [']']} + } + + def test_cli_json_optional_default(): class Nested(BaseModel): foo: int = 1