Skip to content
Merged
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
1 change: 1 addition & 0 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ def run(
if not issubclass(model_cls, BaseSettings):

class CliAppBaseSettings(BaseSettings, model_cls): # type: ignore
__doc__ = model_cls.__doc__
model_config = SettingsConfigDict(
nested_model_default_partial_update=True,
case_sensitive=True,
Expand Down
4 changes: 3 additions & 1 deletion pydantic_settings/sources/providers/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,9 @@ def _add_parser_submodels(
preferred_alias = alias_names[0]
if not self.cli_avoid_json:
added_args.append(arg_names[0])
kwargs['help'] = f'set {arg_names[0]} from JSON string'
kwargs['nargs'] = '?'
kwargs['const'] = '{}'
kwargs['help'] = f'set {arg_names[0]} from JSON string (default: {{}})'
model_group = self._add_group(parser, **model_group_kwargs)
self._add_argument(model_group, *(f'{flag_prefix}{name}' for name in arg_names), **kwargs)
for model in sub_models:
Expand Down
84 changes: 50 additions & 34 deletions tests/test_source_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,14 +499,14 @@ class Car(BaseSettings, cli_parse_args=True):
Car()
assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--driver JSON] [--driver.meow str] [--driver.bark str]
[--driver.caww str] [--driver.tweet str]
== f"""usage: example.py [-h] [--driver [JSON]] [--driver.meow str]
[--driver.bark str] [--driver.caww str] [--driver.tweet str]

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit

driver options:
--driver JSON set driver from JSON string
--driver [JSON] set driver from JSON string (default: {{}})
--driver.meow str (default: purr)
--driver.bark str (default: bark)
--driver.caww str (default: caww)
Expand Down Expand Up @@ -540,73 +540,75 @@ class Settings(BaseSettings, cli_parse_args=True):
Settings()
assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--flag bool] [--sub_model JSON]
[--sub_model.flag bool] [--sub_model.deep JSON]
== f"""usage: example.py [-h] [--flag bool] [--sub_model [JSON]]
[--sub_model.flag bool] [--sub_model.deep [JSON]]
[--sub_model.deep.flag bool]
[--sub_model.deep.deeper {{JSON,null}}]
[--sub_model.deep.deeper [{{JSON,null}}]]
[--sub_model.deep.deeper.flag bool]
[--opt_model {{JSON,null}}] [--opt_model.flag bool]
[--opt_model.deeper {{JSON,null}}]
[--opt_model.deeper.flag bool] [--fact_model JSON]
[--fact_model.flag bool] [--fact_model.deep JSON]
[--opt_model [{{JSON,null}}]] [--opt_model.flag bool]
[--opt_model.deeper [{{JSON,null}}]]
[--opt_model.deeper.flag bool] [--fact_model [JSON]]
[--fact_model.flag bool] [--fact_model.deep [JSON]]
[--fact_model.deep.flag bool]
[--fact_model.deep.deeper {{JSON,null}}]
[--fact_model.deep.deeper [{{JSON,null}}]]
[--fact_model.deep.deeper.flag bool]

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
--flag bool (default: True)

sub_model options:
--sub_model JSON set sub_model from JSON string
--sub_model [JSON] set sub_model from JSON string (default: {{}})
--sub_model.flag bool
(default: False)

sub_model.deep options:
--sub_model.deep JSON
set sub_model.deep from JSON string
--sub_model.deep [JSON]
set sub_model.deep from JSON string (default: {{}})
--sub_model.deep.flag bool
(default: True)

sub_model.deep.deeper options:
default: null (undefined)

--sub_model.deep.deeper {{JSON,null}}
set sub_model.deep.deeper from JSON string
--sub_model.deep.deeper [{{JSON,null}}]
set sub_model.deep.deeper from JSON string (default:
{{}})
--sub_model.deep.deeper.flag bool
(ifdef: required)

opt_model options:
default: null (undefined)
Group Doc

--opt_model {{JSON,null}}
set opt_model from JSON string
--opt_model [{{JSON,null}}]
set opt_model from JSON string (default: {{}})
--opt_model.flag bool
(ifdef: required)

opt_model.deeper options:
default: null (undefined)

--opt_model.deeper {{JSON,null}}
set opt_model.deeper from JSON string
--opt_model.deeper [{{JSON,null}}]
set opt_model.deeper from JSON string (default: {{}})
--opt_model.deeper.flag bool
(ifdef: required)

fact_model options:
--fact_model JSON set fact_model from JSON string
--fact_model [JSON] set fact_model from JSON string (default: {{}})
--fact_model.flag bool
(default factory: <lambda>)

fact_model.deep options:
--fact_model.deep JSON
set fact_model.deep from JSON string
--fact_model.deep [JSON]
set fact_model.deep from JSON string (default: {{}})
--fact_model.deep.flag bool
(default factory: <lambda>)

fact_model.deep.deeper options:
--fact_model.deep.deeper {{JSON,null}}
set fact_model.deep.deeper from JSON string
--fact_model.deep.deeper [{{JSON,null}}]
set fact_model.deep.deeper from JSON string (default:
{{}})
--fact_model.deep.deeper.flag bool
(default factory: <lambda>)
"""
Expand Down Expand Up @@ -1529,13 +1531,13 @@ class Settings(BaseSettings):

assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--sub_model JSON] [--sub_model.v1 int]
== f"""usage: example.py [-h] [--sub_model [JSON]] [--sub_model.v1 int]

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit

sub_model options:
--sub_model JSON set sub_model from JSON string
--sub_model [JSON] set sub_model from JSON string (default: {{}})
--sub_model.v1 int (required)
"""
)
Expand Down Expand Up @@ -1573,13 +1575,13 @@ class Settings(BaseSettings):

assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--sub_model JSON]
== f"""usage: example.py [-h] [--sub_model [JSON]]

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
-h, --help show this help message and exit

sub_model options:
--sub_model JSON set sub_model from JSON string
--sub_model [JSON] set sub_model from JSON string (default: {{}})
"""
)

Expand Down Expand Up @@ -1653,7 +1655,7 @@ class Settings(BaseSettings):

assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--sub_model JSON] [--sub_model.v1 int]
== f"""usage: example.py [-h] [--sub_model [JSON]] [--sub_model.v1 int]

My application help text.

Expand All @@ -1663,7 +1665,7 @@ class Settings(BaseSettings):
sub_model options:
The help text from the field description

--sub_model JSON set sub_model from JSON string
--sub_model [JSON] set sub_model from JSON string (default: {{}})
--sub_model.v1 int (required)
"""
)
Expand All @@ -1673,7 +1675,7 @@ class Settings(BaseSettings):

assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--sub_model JSON] [--sub_model.v1 int]
== f"""usage: example.py [-h] [--sub_model [JSON]] [--sub_model.v1 int]

My application help text.

Expand All @@ -1683,7 +1685,7 @@ class Settings(BaseSettings):
sub_model options:
The help text from the class docstring

--sub_model JSON set sub_model from JSON string
--sub_model [JSON] set sub_model from JSON string (default: {{}})
--sub_model.v1 int (required)
"""
)
Expand Down Expand Up @@ -2411,3 +2413,17 @@ class Root(BaseModel):
--deep-arg str (required)
"""
)


def test_cli_json_optional_default():
class Nested(BaseModel):
foo: int = 1
bar: int = 2

class Options(BaseSettings):
nested: Nested = Nested(foo=3, bar=4)

assert CliApp.run(Options, cli_args=[]).model_dump() == {'nested': {'foo': 3, 'bar': 4}}
assert CliApp.run(Options, cli_args=['--nested']).model_dump() == {'nested': {'foo': 1, 'bar': 2}}
assert CliApp.run(Options, cli_args=['--nested={}']).model_dump() == {'nested': {'foo': 1, 'bar': 2}}
assert CliApp.run(Options, cli_args=['--nested.foo=5']).model_dump() == {'nested': {'foo': 5, 'bar': 2}}