0
# Type System and Validation
1
2
Comprehensive type conversion system with pre-built validated types and custom validation support for robust CLI input handling.
3
4
## Capabilities
5
6
### Core Conversion Function
7
8
Central function for converting command-line tokens to Python types.
9
10
```python { .api }
11
def convert(type_: type, tokens: list[Token]) -> Any:
12
"""
13
Convert command-line tokens to target Python type.
14
15
Parameters
16
----------
17
type_
18
Target Python type for conversion
19
tokens
20
List of Token objects containing user input
21
22
Returns
23
-------
24
Any
25
Converted value of the target type
26
27
Raises
28
------
29
CoercionError
30
If conversion fails or tokens are invalid for the type
31
"""
32
```
33
34
### Environment Variable Utilities
35
36
Parse environment variables into appropriate types.
37
38
```python { .api }
39
def env_var_split(value: str, type_: type) -> list[str]:
40
"""
41
Split environment variable value based on target type.
42
43
Parameters
44
----------
45
value
46
Environment variable string value
47
type_
48
Target type that determines splitting behavior
49
50
Returns
51
-------
52
list[str]
53
List of string tokens for conversion
54
"""
55
```
56
57
### Pre-built Path Types
58
59
Validated Path types with existence and type checking.
60
61
```python { .api }
62
# Basic path types
63
ExistingPath = Annotated[Path, ...]
64
"""Path that must exist."""
65
66
NonExistentPath = Annotated[Path, ...]
67
"""Path that must not exist."""
68
69
ExistingFile = Annotated[Path, ...]
70
"""File path that must exist."""
71
72
NonExistentFile = Annotated[Path, ...]
73
"""File path that must not exist."""
74
75
ExistingDirectory = Annotated[Path, ...]
76
"""Directory path that must exist."""
77
78
NonExistentDirectory = Annotated[Path, ...]
79
"""Directory path that must not exist."""
80
81
File = Annotated[Path, ...]
82
"""Generic file path type."""
83
84
Directory = Annotated[Path, ...]
85
"""Generic directory path type."""
86
87
# Resolved path types
88
ResolvedPath = Annotated[Path, ...]
89
"""Path resolved to absolute form."""
90
91
ResolvedExistingPath = Annotated[Path, ...]
92
"""Existing path resolved to absolute form."""
93
94
ResolvedExistingFile = Annotated[Path, ...]
95
"""Existing file path resolved to absolute form."""
96
97
ResolvedExistingDirectory = Annotated[Path, ...]
98
"""Existing directory path resolved to absolute form."""
99
100
ResolvedDirectory = Annotated[Path, ...]
101
"""Directory path resolved to absolute form."""
102
103
ResolvedFile = Annotated[Path, ...]
104
"""File path resolved to absolute form."""
105
106
# Extension-specific path types
107
BinPath = Annotated[Path, ...]
108
"""Binary file path."""
109
110
CsvPath = Annotated[Path, ...]
111
"""CSV file path."""
112
113
ImagePath = Annotated[Path, ...]
114
"""Image file path."""
115
116
JsonPath = Annotated[Path, ...]
117
"""JSON file path."""
118
119
Mp4Path = Annotated[Path, ...]
120
"""MP4 video file path."""
121
122
TomlPath = Annotated[Path, ...]
123
"""TOML configuration file path."""
124
125
TxtPath = Annotated[Path, ...]
126
"""Text file path."""
127
128
YamlPath = Annotated[Path, ...]
129
"""YAML configuration file path."""
130
131
# Extension-specific path types with existence constraints
132
ExistingBinPath = Annotated[Path, ...]
133
"""Binary file path that must exist."""
134
135
NonExistentBinPath = Annotated[Path, ...]
136
"""Binary file path that must not exist."""
137
138
ExistingCsvPath = Annotated[Path, ...]
139
"""CSV file path that must exist."""
140
141
NonExistentCsvPath = Annotated[Path, ...]
142
"""CSV file path that must not exist."""
143
144
ExistingImagePath = Annotated[Path, ...]
145
"""Image file path that must exist."""
146
147
NonExistentImagePath = Annotated[Path, ...]
148
"""Image file path that must not exist."""
149
150
ExistingJsonPath = Annotated[Path, ...]
151
"""JSON file path that must exist."""
152
153
NonExistentJsonPath = Annotated[Path, ...]
154
"""JSON file path that must not exist."""
155
156
ExistingMp4Path = Annotated[Path, ...]
157
"""MP4 file path that must exist."""
158
159
NonExistentMp4Path = Annotated[Path, ...]
160
"""MP4 file path that must not exist."""
161
162
ExistingTomlPath = Annotated[Path, ...]
163
"""TOML file path that must exist."""
164
165
NonExistentTomlPath = Annotated[Path, ...]
166
"""TOML file path that must not exist."""
167
168
ExistingTxtPath = Annotated[Path, ...]
169
"""Text file path that must exist."""
170
171
NonExistentTxtPath = Annotated[Path, ...]
172
"""Text file path that must not exist."""
173
174
ExistingYamlPath = Annotated[Path, ...]
175
"""YAML file path that must exist."""
176
177
NonExistentYamlPath = Annotated[Path, ...]
178
"""YAML file path that must not exist."""
179
```
180
181
### Pre-built Numeric Types
182
183
Validated numeric types with range constraints.
184
185
```python { .api }
186
# Float range types
187
PositiveFloat = Annotated[float, ...]
188
"""Float greater than 0."""
189
190
NonNegativeFloat = Annotated[float, ...]
191
"""Float greater than or equal to 0."""
192
193
NegativeFloat = Annotated[float, ...]
194
"""Float less than 0."""
195
196
NonPositiveFloat = Annotated[float, ...]
197
"""Float less than or equal to 0."""
198
199
# Integer range types
200
PositiveInt = Annotated[int, ...]
201
"""Integer greater than 0."""
202
203
NonNegativeInt = Annotated[int, ...]
204
"""Integer greater than or equal to 0."""
205
206
NegativeInt = Annotated[int, ...]
207
"""Integer less than 0."""
208
209
NonPositiveInt = Annotated[int, ...]
210
"""Integer less than or equal to 0."""
211
212
# Fixed-width integer types
213
UInt8 = Annotated[int, ...]
214
"""8-bit unsigned integer (0-255)."""
215
216
Int8 = Annotated[int, ...]
217
"""8-bit signed integer (-128 to 127)."""
218
219
UInt16 = Annotated[int, ...]
220
"""16-bit unsigned integer (0-65535)."""
221
222
Int16 = Annotated[int, ...]
223
"""16-bit signed integer (-32768 to 32767)."""
224
225
UInt32 = Annotated[int, ...]
226
"""32-bit unsigned integer."""
227
228
Int32 = Annotated[int, ...]
229
"""32-bit signed integer."""
230
231
UInt64 = Annotated[int, ...]
232
"""64-bit unsigned integer."""
233
234
Int64 = Annotated[int, ...]
235
"""64-bit signed integer."""
236
237
# Hexadecimal integer types
238
HexUInt = Annotated[int, ...]
239
"""Unsigned integer from hexadecimal string."""
240
241
HexUInt8 = Annotated[int, ...]
242
"""8-bit unsigned integer from hexadecimal string."""
243
244
HexUInt16 = Annotated[int, ...]
245
"""16-bit unsigned integer from hexadecimal string."""
246
247
HexUInt32 = Annotated[int, ...]
248
"""32-bit unsigned integer from hexadecimal string."""
249
250
HexUInt64 = Annotated[int, ...]
251
"""64-bit unsigned integer from hexadecimal string."""
252
```
253
254
### Other Validated Types
255
256
Additional pre-built types for common use cases.
257
258
```python { .api }
259
Json = Annotated[Any, ...]
260
"""JSON string parsed to Python object."""
261
262
Email = Annotated[str, ...]
263
"""Email address with validation."""
264
265
Port = Annotated[int, ...]
266
"""Network port number (1-65535)."""
267
268
URL = Annotated[str, ...]
269
"""URL with validation."""
270
```
271
272
### Validator Classes
273
274
Built-in validators for custom validation logic.
275
276
```python { .api }
277
class Number:
278
def __init__(
279
self,
280
min: float | None = None,
281
max: float | None = None,
282
min_inclusive: bool = True,
283
max_inclusive: bool = True
284
):
285
"""
286
Numeric range validator.
287
288
Parameters
289
----------
290
min
291
Minimum allowed value
292
max
293
Maximum allowed value
294
min_inclusive
295
Whether minimum is inclusive
296
max_inclusive
297
Whether maximum is inclusive
298
"""
299
300
def __call__(self, value: float) -> float:
301
"""Validate numeric value against range constraints."""
302
303
class Path:
304
def __init__(
305
self,
306
exists: bool | None = None,
307
file_okay: bool = True,
308
dir_okay: bool = True,
309
resolve: bool = False
310
):
311
"""
312
Path validator with existence and type checking.
313
314
Parameters
315
----------
316
exists
317
Whether path must exist (True), not exist (False), or either (None)
318
file_okay
319
Whether files are allowed
320
dir_okay
321
Whether directories are allowed
322
resolve
323
Whether to resolve path to absolute form
324
"""
325
326
def __call__(self, value: Path) -> Path:
327
"""Validate path against constraints."""
328
329
class LimitedChoice:
330
def __init__(self, *choices: Any, case_sensitive: bool = True):
331
"""
332
Limit value to specific choices.
333
334
Parameters
335
----------
336
choices
337
Valid choice values
338
case_sensitive
339
Whether string comparison is case sensitive
340
"""
341
342
def __call__(self, value: Any) -> Any:
343
"""Validate value is in allowed choices."""
344
345
class MutuallyExclusive:
346
def __init__(self, *groups: str):
347
"""
348
Ensure mutual exclusivity between parameter groups.
349
350
Parameters
351
----------
352
groups
353
Parameter group names that are mutually exclusive
354
"""
355
356
def __call__(self, namespace: dict) -> dict:
357
"""Validate mutual exclusivity constraints."""
358
```
359
360
### Validator Functions
361
362
Standalone validator functions for common patterns.
363
364
```python { .api }
365
def mutually_exclusive(*groups: str) -> Callable:
366
"""
367
Create validator for mutually exclusive parameter groups.
368
369
Parameters
370
----------
371
groups
372
Parameter group names that are mutually exclusive
373
374
Returns
375
-------
376
Callable
377
Validator function
378
"""
379
380
def all_or_none(*parameters: str) -> Callable:
381
"""
382
Create validator requiring all or none of the specified parameters.
383
384
Parameters
385
----------
386
parameters
387
Parameter names with all-or-none constraint
388
389
Returns
390
-------
391
Callable
392
Validator function
393
"""
394
```
395
396
## Usage Examples
397
398
### Custom Type with Validation
399
400
```python
401
from cyclopts import App, Parameter
402
from cyclopts.validators import Number
403
from typing import Annotated
404
405
# Custom type with range validation
406
Percentage = Annotated[float, Number(min=0.0, max=100.0)]
407
408
app = App()
409
410
@app.command
411
def set_threshold(value: Percentage):
412
"""Set threshold as a percentage."""
413
print(f"Threshold set to {value}%")
414
```
415
416
### Using Pre-built Types
417
418
```python
419
from cyclopts import App
420
from cyclopts.types import ExistingFile, PositiveInt, Email
421
422
app = App()
423
424
@app.command
425
def process_user_data(
426
input_file: ExistingFile,
427
max_records: PositiveInt,
428
notify_email: Email
429
):
430
"""Process user data file."""
431
print(f"Processing {input_file} (max {max_records} records)")
432
print(f"Will notify {notify_email}")
433
```
434
435
### Custom Converter Example
436
437
```python
438
from cyclopts import App, Parameter
439
from datetime import datetime
440
441
def parse_date(value: str) -> datetime:
442
"""Custom date converter."""
443
return datetime.strptime(value, "%Y-%m-%d")
444
445
app = App()
446
447
@app.command
448
def schedule_task(
449
name: str,
450
due_date: datetime = Parameter(converter=parse_date, help="Date in YYYY-MM-DD format")
451
):
452
"""Schedule a task with due date."""
453
print(f"Task '{name}' scheduled for {due_date.strftime('%Y-%m-%d')}")
454
```
455
456
### Multiple Validators
457
458
```python
459
from cyclopts import App, Parameter
460
from cyclopts.validators import Number, LimitedChoice
461
462
app = App()
463
464
@app.command
465
def configure_server(
466
port: int = Parameter(
467
validator=[
468
Number(min=1024, max=65535),
469
LimitedChoice(8080, 8443, 9000, 9443)
470
],
471
help="Server port (must be 1024-65535 and one of: 8080, 8443, 9000, 9443)"
472
)
473
):
474
"""Configure server with validated port."""
475
print(f"Server configured on port {port}")
476
```