Skip to content

Commit

Permalink
CLI arg list whitespaces fix. (#369)
Browse files Browse the repository at this point in the history
  • Loading branch information
kschwab authored Aug 21, 2024
1 parent 136c2c7 commit 0145d7e
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 32 deletions.
4 changes: 3 additions & 1 deletion pydantic_settings/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,9 +1171,11 @@ def _merge_parsed_list(self, parsed_list: list[str], field_name: str) -> str:
list if parsed_list and (len(parsed_list) > 1 or parsed_list[0].startswith('[')) else str
)
for val in parsed_list:
val = val.strip()
if val.startswith('[') and val.endswith(']'):
val = val[1:-1]
val = val[1:-1].strip()
while val:
val = val.strip()
if val.startswith(','):
val = self._consume_comma(val, merged_list, is_last_consumed_a_value)
is_last_consumed_a_value = False
Expand Down
102 changes: 71 additions & 31 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2625,8 +2625,26 @@ class Settings(BaseSettings):
assert s.n.bar == 'bar value'


def no_add_cli_arg_spaces(arg_str: str, has_quote_comma: bool = False) -> str:
return arg_str


def add_cli_arg_spaces(arg_str: str, has_quote_comma: bool = False) -> str:
arg_str = arg_str.replace('[', ' [ ')
arg_str = arg_str.replace(']', ' ] ')
arg_str = arg_str.replace('{', ' { ')
arg_str = arg_str.replace('}', ' } ')
arg_str = arg_str.replace(':', ' : ')
if not has_quote_comma:
arg_str = arg_str.replace(',', ' , ')
else:
arg_str = arg_str.replace('",', '" , ')
return f' {arg_str} '


@pytest.mark.parametrize('arg_spaces', [no_add_cli_arg_spaces, add_cli_arg_spaces])
@pytest.mark.parametrize('prefix', ['', 'child.'])
def test_cli_list_arg(prefix):
def test_cli_list_arg(prefix, arg_spaces):
class Obj(BaseModel):
val: int

Expand Down Expand Up @@ -2657,8 +2675,8 @@ def check_answer(cfg, prefix, expected):
assert cfg.model_dump() == expected

args: List[str] = []
args = [f'--{prefix}num_list', '[1,2]']
args += [f'--{prefix}num_list', '3,4']
args = [f'--{prefix}num_list', arg_spaces('[1,2]')]
args += [f'--{prefix}num_list', arg_spaces('3,4')]
args += [f'--{prefix}num_list', '5', f'--{prefix}num_list', '6']
cfg = Cfg(_cli_parse_args=args)
expected = {
Expand All @@ -2669,9 +2687,9 @@ def check_answer(cfg, prefix, expected):
}
check_answer(cfg, prefix, expected)

args = [f'--{prefix}obj_list', '[{"val":1},{"val":2}]']
args += [f'--{prefix}obj_list', '{"val":3},{"val":4}']
args += [f'--{prefix}obj_list', '{"val":5}', f'--{prefix}obj_list', '{"val":6}']
args = [f'--{prefix}obj_list', arg_spaces('[{"val":1},{"val":2}]')]
args += [f'--{prefix}obj_list', arg_spaces('{"val":3},{"val":4}')]
args += [f'--{prefix}obj_list', arg_spaces('{"val":5}'), f'--{prefix}obj_list', arg_spaces('{"val":6}')]
cfg = Cfg(_cli_parse_args=args)
expected = {
'num_list': None,
Expand All @@ -2681,9 +2699,9 @@ def check_answer(cfg, prefix, expected):
}
check_answer(cfg, prefix, expected)

args = [f'--{prefix}union_list', '[{"val":1},2]', f'--{prefix}union_list', '[3,{"val":4}]']
args += [f'--{prefix}union_list', '{"val":5},6', f'--{prefix}union_list', '7,{"val":8}']
args += [f'--{prefix}union_list', '{"val":9}', f'--{prefix}union_list', '10']
args = [f'--{prefix}union_list', arg_spaces('[{"val":1},2]'), f'--{prefix}union_list', arg_spaces('[3,{"val":4}]')]
args += [f'--{prefix}union_list', arg_spaces('{"val":5},6'), f'--{prefix}union_list', arg_spaces('7,{"val":8}')]
args += [f'--{prefix}union_list', arg_spaces('{"val":9}'), f'--{prefix}union_list', '10']
cfg = Cfg(_cli_parse_args=args)
expected = {
'num_list': None,
Expand All @@ -2693,9 +2711,14 @@ def check_answer(cfg, prefix, expected):
}
check_answer(cfg, prefix, expected)

args = [f'--{prefix}str_list', '["0,0","1,1"]']
args += [f'--{prefix}str_list', '"2,2","3,3"']
args += [f'--{prefix}str_list', '"4,4"', f'--{prefix}str_list', '"5,5"']
args = [f'--{prefix}str_list', arg_spaces('["0,0","1,1"]', has_quote_comma=True)]
args += [f'--{prefix}str_list', arg_spaces('"2,2","3,3"', has_quote_comma=True)]
args += [
f'--{prefix}str_list',
arg_spaces('"4,4"', has_quote_comma=True),
f'--{prefix}str_list',
arg_spaces('"5,5"', has_quote_comma=True),
]
cfg = Cfg(_cli_parse_args=args)
expected = {
'num_list': None,
Expand All @@ -2706,29 +2729,31 @@ def check_answer(cfg, prefix, expected):
check_answer(cfg, prefix, expected)


def test_cli_list_json_value_parsing():
@pytest.mark.parametrize('arg_spaces', [no_add_cli_arg_spaces, add_cli_arg_spaces])
def test_cli_list_json_value_parsing(arg_spaces):
class Cfg(BaseSettings):
json_list: List[Union[str, bool, None]]

assert Cfg(
_cli_parse_args=[
'--json_list',
'true,"true"',
arg_spaces('true,"true"'),
'--json_list',
'false,"false"',
arg_spaces('false,"false"'),
'--json_list',
'null,"null"',
arg_spaces('null,"null"'),
'--json_list',
'hi,"bye"',
arg_spaces('hi,"bye"'),
]
).model_dump() == {'json_list': [True, 'true', False, 'false', None, 'null', 'hi', 'bye']}

assert Cfg(_cli_parse_args=['--json_list', '"","","",""']).model_dump() == {'json_list': ['', '', '', '']}
assert Cfg(_cli_parse_args=['--json_list', ',,,']).model_dump() == {'json_list': ['', '', '', '']}


@pytest.mark.parametrize('arg_spaces', [no_add_cli_arg_spaces, add_cli_arg_spaces])
@pytest.mark.parametrize('prefix', ['', 'child.'])
def test_cli_dict_arg(prefix):
def test_cli_dict_arg(prefix, arg_spaces):
class Child(BaseModel):
check_dict: Dict[str, str]

Expand All @@ -2737,19 +2762,34 @@ class Cfg(BaseSettings):
child: Optional[Child] = None

args: List[str] = []
args = [f'--{prefix}check_dict', '{"k1":"a","k2":"b"}']
args += [f'--{prefix}check_dict', '{"k3":"c"},{"k4":"d"}']
args += [f'--{prefix}check_dict', '{"k5":"e"}', f'--{prefix}check_dict', '{"k6":"f"}']
args += [f'--{prefix}check_dict', '[k7=g,k8=h]']
args += [f'--{prefix}check_dict', 'k9=i,k10=j']
args += [f'--{prefix}check_dict', 'k11=k', f'--{prefix}check_dict', 'k12=l']
args += [f'--{prefix}check_dict', '[{"k13":"m"},k14=n]', f'--{prefix}check_dict', '[k15=o,{"k16":"p"}]']
args += [f'--{prefix}check_dict', '{"k17":"q"},k18=r', f'--{prefix}check_dict', 'k19=s,{"k20":"t"}']
args += [f'--{prefix}check_dict', '{"k21":"u"},k22=v,{"k23":"w"}']
args += [f'--{prefix}check_dict', 'k24=x,{"k25":"y"},k26=z']
args += [f'--{prefix}check_dict', '[k27="x,y",k28="x,y"]']
args += [f'--{prefix}check_dict', 'k29="x,y",k30="x,y"']
args += [f'--{prefix}check_dict', 'k31="x,y"', f'--{prefix}check_dict', 'k32="x,y"']
args = [f'--{prefix}check_dict', arg_spaces('{"k1":"a","k2":"b"}')]
args += [f'--{prefix}check_dict', arg_spaces('{"k3":"c"},{"k4":"d"}')]
args += [f'--{prefix}check_dict', arg_spaces('{"k5":"e"}'), f'--{prefix}check_dict', arg_spaces('{"k6":"f"}')]
args += [f'--{prefix}check_dict', arg_spaces('[k7=g,k8=h]')]
args += [f'--{prefix}check_dict', arg_spaces('k9=i,k10=j')]
args += [f'--{prefix}check_dict', arg_spaces('k11=k'), f'--{prefix}check_dict', arg_spaces('k12=l')]
args += [
f'--{prefix}check_dict',
arg_spaces('[{"k13":"m"},k14=n]'),
f'--{prefix}check_dict',
arg_spaces('[k15=o,{"k16":"p"}]'),
]
args += [
f'--{prefix}check_dict',
arg_spaces('{"k17":"q"},k18=r'),
f'--{prefix}check_dict',
arg_spaces('k19=s,{"k20":"t"}'),
]
args += [f'--{prefix}check_dict', arg_spaces('{"k21":"u"},k22=v,{"k23":"w"}')]
args += [f'--{prefix}check_dict', arg_spaces('k24=x,{"k25":"y"},k26=z')]
args += [f'--{prefix}check_dict', arg_spaces('[k27="x,y",k28="x,y"]', has_quote_comma=True)]
args += [f'--{prefix}check_dict', arg_spaces('k29="x,y",k30="x,y"', has_quote_comma=True)]
args += [
f'--{prefix}check_dict',
arg_spaces('k31="x,y"', has_quote_comma=True),
f'--{prefix}check_dict',
arg_spaces('k32="x,y"', has_quote_comma=True),
]
cfg = Cfg(_cli_parse_args=args)
expected: Dict[str, Any] = {
'check_dict': {
Expand Down

0 comments on commit 0145d7e

Please sign in to comment.