0
# Command Line Interface
1
2
CLI argument parsing and settings generation with support for subcommands, flags, positional arguments, and running Pydantic models as CLI applications. The CLI system provides comprehensive integration with argparse and supports complex argument structures.
3
4
## Capabilities
5
6
### CLI Settings Source
7
8
Parse command-line arguments and convert them to settings values with full integration with argparse functionality.
9
10
```python { .api }
11
class CliSettingsSource(EnvSettingsSource, Generic[T]):
12
"""Source class for loading settings values from CLI."""
13
14
def __init__(
15
self,
16
settings_cls: type[BaseSettings],
17
cli_prog_name: str | None = None,
18
cli_parse_args: bool | list[str] | tuple[str, ...] | None = None,
19
cli_parse_none_str: str | None = None,
20
cli_hide_none_type: bool = False,
21
cli_avoid_json: bool = False,
22
cli_enforce_required: bool = False,
23
cli_use_class_docs_for_groups: bool = False,
24
cli_exit_on_error: bool = True,
25
cli_prefix: str = "",
26
cli_flag_prefix_char: str = "-",
27
cli_implicit_flags: bool | None = None,
28
cli_ignore_unknown_args: bool | None = None,
29
cli_kebab_case: bool | None = None,
30
cli_shortcuts: Mapping[str, str | list[str]] | None = None,
31
case_sensitive: bool | None = None,
32
):
33
"""
34
Initialize CLI settings source.
35
36
Parameters:
37
- settings_cls: The settings class
38
- cli_prog_name: Program name for help text
39
- cli_parse_args: Arguments to parse (True for sys.argv[1:])
40
- cli_parse_none_str: String value to parse as None
41
- cli_hide_none_type: Hide None values in help text
42
- cli_avoid_json: Avoid complex JSON objects in help
43
- cli_enforce_required: Enforce required fields at CLI
44
- cli_use_class_docs_for_groups: Use class docs for group help
45
- cli_exit_on_error: Exit on parsing errors
46
- cli_prefix: Root parser arguments prefix
47
- cli_flag_prefix_char: Flag prefix character
48
- cli_implicit_flags: Convert bool fields to flags
49
- cli_ignore_unknown_args: Ignore unknown CLI args
50
- cli_kebab_case: Use kebab-case for CLI args
51
- cli_shortcuts: Mapping of field names to alias names
52
- case_sensitive: Whether CLI args are case-sensitive
53
"""
54
```
55
56
### CLI Application Runner
57
58
Utility class for running Pydantic models as CLI applications with support for subcommands and async methods.
59
60
```python { .api }
61
class CliApp:
62
"""Utility class for running Pydantic models as CLI applications."""
63
64
@staticmethod
65
def run(
66
model_cls: type[T],
67
cli_args: list[str] | Namespace | SimpleNamespace | dict[str, Any] | None = None,
68
cli_settings_source: CliSettingsSource[Any] | None = None,
69
cli_exit_on_error: bool | None = None,
70
cli_cmd_method_name: str = 'cli_cmd',
71
**model_init_data: Any,
72
) -> T:
73
"""
74
Run a Pydantic model as a CLI application.
75
76
Parameters:
77
- model_cls: The model class to run
78
- cli_args: CLI arguments to parse (defaults to sys.argv[1:])
79
- cli_settings_source: Custom CLI settings source
80
- cli_exit_on_error: Whether to exit on error
81
- cli_cmd_method_name: CLI command method name to run
82
- **model_init_data: Additional model initialization data
83
84
Returns:
85
The model instance after running the CLI command
86
87
Raises:
88
- SettingsError: If model_cls is not a BaseModel or dataclass
89
- SettingsError: If model_cls lacks the required CLI command method
90
"""
91
92
@staticmethod
93
def run_subcommand(
94
model: PydanticModel,
95
cli_exit_on_error: bool | None = None,
96
cli_cmd_method_name: str = 'cli_cmd'
97
) -> PydanticModel:
98
"""
99
Run a model subcommand.
100
101
Parameters:
102
- model: The model to run the subcommand from
103
- cli_exit_on_error: Whether to exit on error
104
- cli_cmd_method_name: CLI command method name to run
105
106
Returns:
107
The subcommand model after execution
108
109
Raises:
110
- SystemExit: When no subcommand found and cli_exit_on_error=True
111
- SettingsError: When no subcommand found and cli_exit_on_error=False
112
"""
113
```
114
115
### CLI Annotations
116
117
Type annotations for defining CLI-specific field behavior including subcommands, positional arguments, and flags.
118
119
```python { .api }
120
# CLI subcommand annotation
121
CliSubCommand = Annotated[Union[T, None], _CliSubCommand]
122
123
# Positional argument annotation
124
CliPositionalArg = Annotated[T, _CliPositionalArg]
125
126
# Boolean flag annotations
127
CliImplicitFlag = Annotated[bool, _CliImplicitFlag]
128
CliExplicitFlag = Annotated[bool, _CliExplicitFlag]
129
130
# Suppress CLI argument generation
131
CliSuppress = Annotated[T, CLI_SUPPRESS]
132
133
# Capture unknown CLI arguments
134
CliUnknownArgs = Annotated[list[str], Field(default=[]), _CliUnknownArgs, NoDecode]
135
```
136
137
### CLI Group Management
138
139
Model for managing mutually exclusive CLI argument groups.
140
141
```python { .api }
142
class CliMutuallyExclusiveGroup(BaseModel):
143
"""Model for mutually exclusive CLI argument groups."""
144
pass
145
```
146
147
### Constants
148
149
```python { .api }
150
# Suppress argument generation (from argparse.SUPPRESS)
151
CLI_SUPPRESS: str
152
```
153
154
## Usage Examples
155
156
### Basic CLI Integration
157
158
```python
159
from pydantic_settings import BaseSettings
160
from pydantic import Field
161
162
class AppSettings(BaseSettings):
163
name: str = Field(..., description="Application name")
164
debug: bool = Field(False, description="Enable debug mode")
165
port: int = Field(8000, description="Server port")
166
167
model_config = SettingsConfigDict(
168
cli_parse_args=True,
169
cli_prog_name="myapp"
170
)
171
172
# Command line: python app.py --name "My App" --debug --port 3000
173
settings = AppSettings()
174
print(f"Running {settings.name} on port {settings.port}, debug={settings.debug}")
175
```
176
177
### CLI with Custom Prefixes and Kebab Case
178
179
```python
180
class ServerSettings(BaseSettings):
181
server_host: str = Field("localhost", description="Server hostname")
182
server_port: int = Field(8000, description="Server port")
183
enable_ssl: bool = Field(False, description="Enable SSL")
184
185
model_config = SettingsConfigDict(
186
cli_parse_args=True,
187
cli_prefix="server-",
188
cli_kebab_case=True,
189
cli_implicit_flags=True
190
)
191
192
# Command line: python app.py --server-server-host prod.example.com --server-enable-ssl
193
settings = ServerSettings()
194
```
195
196
### CLI Application with Command Method
197
198
```python
199
from pydantic_settings import BaseSettings, CliApp
200
201
class CalculatorSettings(BaseSettings):
202
operation: str = Field(..., description="Math operation to perform")
203
x: float = Field(..., description="First number")
204
y: float = Field(..., description="Second number")
205
206
def cli_cmd(self):
207
"""Execute the calculator command."""
208
if self.operation == "add":
209
result = self.x + self.y
210
elif self.operation == "multiply":
211
result = self.x * self.y
212
else:
213
raise ValueError(f"Unknown operation: {self.operation}")
214
215
print(f"Result: {result}")
216
217
# Run as CLI app
218
if __name__ == "__main__":
219
# Command line: python calculator.py --operation add --x 10 --y 5
220
CliApp.run(CalculatorSettings)
221
```
222
223
### CLI with Subcommands
224
225
```python
226
from typing import Union
227
from pydantic import BaseModel
228
229
class DatabaseCommand(BaseModel):
230
host: str = Field("localhost", description="Database host")
231
232
def cli_cmd(self):
233
print(f"Connecting to database at {self.host}")
234
235
class ServerCommand(BaseModel):
236
port: int = Field(8000, description="Server port")
237
238
def cli_cmd(self):
239
print(f"Starting server on port {self.port}")
240
241
class AppSettings(BaseSettings):
242
verbose: bool = Field(False, description="Verbose output")
243
command: CliSubCommand[Union[DatabaseCommand, ServerCommand]] = None
244
245
model_config = SettingsConfigDict(cli_parse_args=True)
246
247
# Command line: python app.py --verbose database --host prod-db.com
248
# Command line: python app.py server --port 3000
249
settings = AppSettings()
250
if settings.verbose:
251
print("Verbose mode enabled")
252
253
if settings.command:
254
CliApp.run_subcommand(settings)
255
```
256
257
### CLI with Positional Arguments
258
259
```python
260
class FileProcessor(BaseSettings):
261
input_file: CliPositionalArg[str] = Field(..., description="Input file path")
262
output_file: CliPositionalArg[str] = Field(..., description="Output file path")
263
format: str = Field("json", description="Output format")
264
verbose: CliImplicitFlag[bool] = Field(False, description="Verbose output")
265
266
model_config = SettingsConfigDict(cli_parse_args=True)
267
268
def cli_cmd(self):
269
print(f"Processing {self.input_file} -> {self.output_file} ({self.format})")
270
271
# Command line: python processor.py input.txt output.txt --format csv --verbose
272
CliApp.run(FileProcessor)
273
```
274
275
### CLI with Explicit Flags and Shortcuts
276
277
```python
278
class BuildSettings(BaseSettings):
279
clean: CliExplicitFlag[bool] = Field(False, description="Clean before build")
280
release: CliImplicitFlag[bool] = Field(False, description="Release build")
281
jobs: int = Field(1, description="Number of parallel jobs")
282
283
model_config = SettingsConfigDict(
284
cli_parse_args=True,
285
cli_shortcuts={
286
"jobs": ["j"],
287
"clean": ["c"],
288
"release": ["r"]
289
}
290
)
291
292
# Command line: python build.py --clean --release -j 4
293
# Command line: python build.py -c -r -j 8
294
settings = BuildSettings()
295
```
296
297
### CLI with Unknown Arguments Capture
298
299
```python
300
class FlexibleApp(BaseSettings):
301
name: str = Field(..., description="App name")
302
debug: bool = Field(False, description="Debug mode")
303
extra_args: CliUnknownArgs = Field(default=[])
304
305
model_config = SettingsConfigDict(
306
cli_parse_args=True,
307
cli_ignore_unknown_args=True
308
)
309
310
def cli_cmd(self):
311
print(f"App: {self.name}, Debug: {self.debug}")
312
if self.extra_args:
313
print(f"Extra arguments: {self.extra_args}")
314
315
# Command line: python app.py --name MyApp --debug --custom-flag value --another-arg
316
# extra_args will contain: ['--custom-flag', 'value', '--another-arg']
317
CliApp.run(FlexibleApp)
318
```
319
320
### Async CLI Commands
321
322
```python
323
import asyncio
324
325
class AsyncApp(BaseSettings):
326
delay: int = Field(1, description="Delay in seconds")
327
message: str = Field("Hello", description="Message to display")
328
329
async def cli_cmd(self):
330
"""Async CLI command method."""
331
await asyncio.sleep(self.delay)
332
print(f"Async message: {self.message}")
333
334
# CliApp.run automatically handles async methods
335
CliApp.run(AsyncApp)
336
```
337
338
### CLI with Custom Help and Error Handling
339
340
```python
341
class RobustApp(BaseSettings):
342
"""
343
A robust application with custom CLI handling.
344
345
This app demonstrates error handling and help customization.
346
"""
347
config_file: str = Field(..., description="Configuration file path")
348
strict: bool = Field(False, description="Strict validation mode")
349
350
model_config = SettingsConfigDict(
351
cli_parse_args=True,
352
cli_prog_name="robust-app",
353
cli_exit_on_error=False, # Handle errors manually
354
cli_use_class_docs_for_groups=True
355
)
356
357
def cli_cmd(self):
358
try:
359
with open(self.config_file) as f:
360
config = f.read()
361
print(f"Loaded config: {len(config)} bytes")
362
except FileNotFoundError:
363
if self.strict:
364
raise
365
print(f"Config file {self.config_file} not found, using defaults")
366
367
# Will handle parsing errors gracefully
368
try:
369
CliApp.run(RobustApp)
370
except SettingsError as e:
371
print(f"CLI Error: {e}")
372
```