or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdcloud-secrets.mdcore-settings.mdenv-file-sources.mdindex.md

cli.mddocs/

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

```