valid8r 0.6.3__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of valid8r might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valid8r
3
- Version: 0.6.3
3
+ Version: 0.7.0
4
4
  Summary: Clean, flexible input validation for Python applications
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -78,6 +78,30 @@ A clean, flexible input validation library for Python applications.
78
78
  - **Enums**: `parse_enum` (type-safe enum parsing)
79
79
  - **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
80
80
 
81
+ ## Available Validators
82
+
83
+ ### Numeric Validators
84
+ - **`minimum(min_value)`** - Ensures value is at least the minimum (inclusive)
85
+ - **`maximum(max_value)`** - Ensures value is at most the maximum (inclusive)
86
+ - **`between(min_value, max_value)`** - Ensures value is within range (inclusive)
87
+
88
+ ### String Validators
89
+ - **`non_empty_string()`** - Rejects empty strings and whitespace-only strings
90
+ - **`matches_regex(pattern)`** - Validates string matches regex pattern (string or compiled)
91
+ - **`length(min_length, max_length)`** - Validates string length within bounds
92
+
93
+ ### Collection Validators
94
+ - **`in_set(allowed_values)`** - Ensures value is in set of allowed values
95
+ - **`unique_items()`** - Ensures all items in a list are unique
96
+ - **`subset_of(allowed_set)`** - Validates set is subset of allowed values
97
+ - **`superset_of(required_set)`** - Validates set is superset of required values
98
+ - **`is_sorted(reverse=False)`** - Ensures list is sorted (ascending or descending)
99
+
100
+ ### Custom Validators
101
+ - **`predicate(func, error_message)`** - Create custom validator from any predicate function
102
+
103
+ **Note**: All validators support custom error messages and can be combined using `&` (and), `|` (or), and `~` (not) operators.
104
+
81
105
  ## Installation
82
106
 
83
107
  **Requirements**: Python 3.11 or higher
@@ -47,6 +47,30 @@ A clean, flexible input validation library for Python applications.
47
47
  - **Enums**: `parse_enum` (type-safe enum parsing)
48
48
  - **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
49
49
 
50
+ ## Available Validators
51
+
52
+ ### Numeric Validators
53
+ - **`minimum(min_value)`** - Ensures value is at least the minimum (inclusive)
54
+ - **`maximum(max_value)`** - Ensures value is at most the maximum (inclusive)
55
+ - **`between(min_value, max_value)`** - Ensures value is within range (inclusive)
56
+
57
+ ### String Validators
58
+ - **`non_empty_string()`** - Rejects empty strings and whitespace-only strings
59
+ - **`matches_regex(pattern)`** - Validates string matches regex pattern (string or compiled)
60
+ - **`length(min_length, max_length)`** - Validates string length within bounds
61
+
62
+ ### Collection Validators
63
+ - **`in_set(allowed_values)`** - Ensures value is in set of allowed values
64
+ - **`unique_items()`** - Ensures all items in a list are unique
65
+ - **`subset_of(allowed_set)`** - Validates set is subset of allowed values
66
+ - **`superset_of(required_set)`** - Validates set is superset of required values
67
+ - **`is_sorted(reverse=False)`** - Ensures list is sorted (ascending or descending)
68
+
69
+ ### Custom Validators
70
+ - **`predicate(func, error_message)`** - Create custom validator from any predicate function
71
+
72
+ **Note**: All validators support custom error messages and can be combined using `&` (and), `|` (or), and `~` (not) operators.
73
+
50
74
  ## Installation
51
75
 
52
76
  **Requirements**: Python 3.11 or higher
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "valid8r"
3
- version = "0.6.3"
3
+ version = "0.7.0"
4
4
  description = "Clean, flexible input validation for Python applications"
5
5
  authors = ["Mike Lane <mikelane@gmail.com>"]
6
6
  license = "MIT"
@@ -0,0 +1,565 @@
1
+ """Core validators for validating values against specific criteria.
2
+
3
+ This module provides a collection of validator functions for common validation scenarios.
4
+ All validators follow the same pattern - they take a value and return a Maybe object
5
+ that either contains the validated value or an error message.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Generic,
14
+ Protocol,
15
+ TypeVar,
16
+ )
17
+
18
+ from valid8r.core.combinators import (
19
+ and_then,
20
+ not_validator,
21
+ or_else,
22
+ )
23
+ from valid8r.core.maybe import Maybe
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Callable
27
+
28
+
29
+ class SupportsComparison(Protocol): # noqa: D101
30
+ def __le__(self, other: object, /) -> bool: ... # noqa: D105
31
+ def __lt__(self, other: object, /) -> bool: ... # noqa: D105
32
+ def __ge__(self, other: object, /) -> bool: ... # noqa: D105
33
+ def __gt__(self, other: object, /) -> bool: ... # noqa: D105
34
+ def __eq__(self, other: object, /) -> bool: ... # noqa: D105
35
+ def __ne__(self, other: object, /) -> bool: ... # noqa: D105
36
+ def __hash__(self, /) -> int: ... # noqa: D105
37
+
38
+
39
+ T = TypeVar('T')
40
+ U = TypeVar('U')
41
+ N = TypeVar('N', bound=SupportsComparison)
42
+
43
+
44
+ class Validator(Generic[T]):
45
+ """A wrapper class for validator functions that supports operator overloading."""
46
+
47
+ def __init__(self, func: Callable[[T], Maybe[T]]) -> None:
48
+ """Initialize a validator with a validation function.
49
+
50
+ Args:
51
+ func: A function that takes a value and returns a Maybe
52
+
53
+ """
54
+ self.func = func
55
+
56
+ def __call__(self, value: T) -> Maybe[T]:
57
+ """Apply the validator to a value.
58
+
59
+ Args:
60
+ value: The value to validate
61
+
62
+ Returns:
63
+ A Maybe containing either the validated value or an error
64
+
65
+ """
66
+ return self.func(value)
67
+
68
+ def __and__(self, other: Validator[T]) -> Validator[T]:
69
+ """Combine with another validator using logical AND.
70
+
71
+ Args:
72
+ other: Another validator to combine with
73
+
74
+ Returns:
75
+ A new validator that passes only if both validators pass
76
+
77
+ """
78
+ return Validator(lambda value: and_then(self.func, other.func)(value))
79
+
80
+ def __or__(self, other: Validator[T]) -> Validator[T]:
81
+ """Combine with another validator using logical OR.
82
+
83
+ Args:
84
+ other: Another validator to combine with
85
+
86
+ Returns:
87
+ A new validator that passes if either validator passes
88
+
89
+ """
90
+ return Validator(lambda value: or_else(self.func, other.func)(value))
91
+
92
+ def __invert__(self) -> Validator[T]:
93
+ """Negate this validator.
94
+
95
+ Returns:
96
+ A new validator that passes if this validator fails
97
+
98
+ """
99
+ return Validator(lambda value: not_validator(self.func, 'Negated validation failed')(value))
100
+
101
+
102
+ def minimum(min_value: N, error_message: str | None = None) -> Validator[N]:
103
+ """Create a validator that ensures a value is at least the minimum.
104
+
105
+ Args:
106
+ min_value: The minimum allowed value (inclusive)
107
+ error_message: Optional custom error message
108
+
109
+ Returns:
110
+ Validator[N]: A validator function that accepts values >= min_value
111
+
112
+ Examples:
113
+ >>> from valid8r.core.validators import minimum
114
+ >>> validator = minimum(0)
115
+ >>> validator(5)
116
+ Success(5)
117
+ >>> validator(0)
118
+ Success(0)
119
+ >>> validator(-1).is_failure()
120
+ True
121
+ >>> # With custom error message
122
+ >>> validator = minimum(18, error_message="Must be an adult")
123
+ >>> validator(17).error_or("")
124
+ 'Must be an adult'
125
+
126
+ """
127
+
128
+ def validator(value: N) -> Maybe[N]:
129
+ if value >= min_value:
130
+ return Maybe.success(value)
131
+ return Maybe.failure(error_message or f'Value must be at least {min_value}')
132
+
133
+ return Validator(validator)
134
+
135
+
136
+ def maximum(max_value: N, error_message: str | None = None) -> Validator[N]:
137
+ """Create a validator that ensures a value is at most the maximum.
138
+
139
+ Args:
140
+ max_value: The maximum allowed value (inclusive)
141
+ error_message: Optional custom error message
142
+
143
+ Returns:
144
+ Validator[N]: A validator function that accepts values <= max_value
145
+
146
+ Examples:
147
+ >>> from valid8r.core.validators import maximum
148
+ >>> validator = maximum(100)
149
+ >>> validator(50)
150
+ Success(50)
151
+ >>> validator(100)
152
+ Success(100)
153
+ >>> validator(101).is_failure()
154
+ True
155
+ >>> # With custom error message
156
+ >>> validator = maximum(120, error_message="Age too high")
157
+ >>> validator(150).error_or("")
158
+ 'Age too high'
159
+
160
+ """
161
+
162
+ def validator(value: N) -> Maybe[N]:
163
+ if value <= max_value:
164
+ return Maybe.success(value)
165
+ return Maybe.failure(error_message or f'Value must be at most {max_value}')
166
+
167
+ return Validator(validator)
168
+
169
+
170
+ def between(min_value: N, max_value: N, error_message: str | None = None) -> Validator[N]:
171
+ """Create a validator that ensures a value is between minimum and maximum (inclusive).
172
+
173
+ Args:
174
+ min_value: The minimum allowed value (inclusive)
175
+ max_value: The maximum allowed value (inclusive)
176
+ error_message: Optional custom error message
177
+
178
+ Returns:
179
+ Validator[N]: A validator function that accepts values where min_value <= value <= max_value
180
+
181
+ Examples:
182
+ >>> from valid8r.core.validators import between
183
+ >>> validator = between(0, 100)
184
+ >>> validator(50)
185
+ Success(50)
186
+ >>> validator(0)
187
+ Success(0)
188
+ >>> validator(100)
189
+ Success(100)
190
+ >>> validator(-1).is_failure()
191
+ True
192
+ >>> validator(101).is_failure()
193
+ True
194
+ >>> # With custom error message
195
+ >>> validator = between(1, 10, error_message="Rating must be 1-10")
196
+ >>> validator(11).error_or("")
197
+ 'Rating must be 1-10'
198
+
199
+ """
200
+
201
+ def validator(value: N) -> Maybe[N]:
202
+ if min_value <= value <= max_value:
203
+ return Maybe.success(value)
204
+ return Maybe.failure(error_message or f'Value must be between {min_value} and {max_value}')
205
+
206
+ return Validator(validator)
207
+
208
+
209
+ def predicate(pred: Callable[[T], bool], error_message: str) -> Validator[T]:
210
+ """Create a validator using a custom predicate function.
211
+
212
+ Allows creating custom validators for any validation logic by providing
213
+ a predicate function that returns True for valid values.
214
+
215
+ Args:
216
+ pred: A function that takes a value and returns True if valid, False otherwise
217
+ error_message: Error message to return when validation fails
218
+
219
+ Returns:
220
+ Validator[T]: A validator function that applies the predicate
221
+
222
+ Examples:
223
+ >>> from valid8r.core.validators import predicate
224
+ >>> # Validate even numbers
225
+ >>> is_even = predicate(lambda x: x % 2 == 0, "Must be even")
226
+ >>> is_even(4)
227
+ Success(4)
228
+ >>> is_even(3).is_failure()
229
+ True
230
+ >>> # Validate string patterns
231
+ >>> starts_with_a = predicate(lambda s: s.startswith('a'), "Must start with 'a'")
232
+ >>> starts_with_a("apple")
233
+ Success('apple')
234
+ >>> starts_with_a("banana").error_or("")
235
+ "Must start with 'a'"
236
+
237
+ """
238
+
239
+ def validator(value: T) -> Maybe[T]:
240
+ if pred(value):
241
+ return Maybe.success(value)
242
+ return Maybe.failure(error_message)
243
+
244
+ return Validator(validator)
245
+
246
+
247
+ def length(min_length: int, max_length: int, error_message: str | None = None) -> Validator[str]:
248
+ """Create a validator that ensures a string's length is within bounds.
249
+
250
+ Args:
251
+ min_length: Minimum length of the string (inclusive)
252
+ max_length: Maximum length of the string (inclusive)
253
+ error_message: Optional custom error message
254
+
255
+ Returns:
256
+ Validator[str]: A validator function that checks string length
257
+
258
+ Examples:
259
+ >>> from valid8r.core.validators import length
260
+ >>> validator = length(3, 10)
261
+ >>> validator("hello")
262
+ Success('hello')
263
+ >>> validator("abc")
264
+ Success('abc')
265
+ >>> validator("abcdefghij")
266
+ Success('abcdefghij')
267
+ >>> validator("ab").is_failure()
268
+ True
269
+ >>> validator("abcdefghijk").is_failure()
270
+ True
271
+ >>> # With custom error message
272
+ >>> validator = length(8, 20, error_message="Password must be 8-20 characters")
273
+ >>> validator("short").error_or("")
274
+ 'Password must be 8-20 characters'
275
+
276
+ """
277
+
278
+ def validator(value: str) -> Maybe[str]:
279
+ if min_length <= len(value) <= max_length:
280
+ return Maybe.success(value)
281
+ return Maybe.failure(error_message or f'String length must be between {min_length} and {max_length}')
282
+
283
+ return Validator(validator)
284
+
285
+
286
+ def matches_regex(pattern: str | re.Pattern[str], error_message: str | None = None) -> Validator[str]:
287
+ r"""Create a validator that ensures a string matches a regular expression pattern.
288
+
289
+ Args:
290
+ pattern: Regular expression pattern (string or compiled Pattern object)
291
+ error_message: Optional custom error message
292
+
293
+ Returns:
294
+ Validator[str]: A validator function that checks pattern matching
295
+
296
+ Examples:
297
+ >>> from valid8r.core.validators import matches_regex
298
+ >>> import re
299
+ >>> # String pattern
300
+ >>> validator = matches_regex(r'^\\d{3}-\\d{2}-\\d{4}$')
301
+ >>> validator('123-45-6789')
302
+ Success('123-45-6789')
303
+ >>> validator('invalid').is_failure()
304
+ True
305
+ >>> # Compiled regex pattern
306
+ >>> pattern = re.compile(r'^[A-Z][a-z]+$')
307
+ >>> validator = matches_regex(pattern)
308
+ >>> validator('Hello')
309
+ Success('Hello')
310
+ >>> validator('hello').is_failure()
311
+ True
312
+ >>> # With custom error message
313
+ >>> validator = matches_regex(r'^\\d{5}$', error_message='Must be a 5-digit ZIP code')
314
+ >>> validator('1234').error_or('')
315
+ 'Must be a 5-digit ZIP code'
316
+
317
+ """
318
+ compiled_pattern = re.compile(pattern) if isinstance(pattern, str) else pattern
319
+
320
+ def validator(value: str) -> Maybe[str]:
321
+ if compiled_pattern.match(value):
322
+ return Maybe.success(value)
323
+ return Maybe.failure(error_message or f'Value must match pattern {compiled_pattern.pattern}')
324
+
325
+ return Validator(validator)
326
+
327
+
328
+ def in_set(allowed_values: set[T], error_message: str | None = None) -> Validator[T]:
329
+ """Create a validator that ensures a value is in a set of allowed values.
330
+
331
+ Args:
332
+ allowed_values: Set of allowed values
333
+ error_message: Optional custom error message
334
+
335
+ Returns:
336
+ Validator[T]: A validator function that checks membership
337
+
338
+ Examples:
339
+ >>> from valid8r.core.validators import in_set
340
+ >>> # String values
341
+ >>> validator = in_set({'red', 'green', 'blue'})
342
+ >>> validator('red')
343
+ Success('red')
344
+ >>> validator('yellow').is_failure()
345
+ True
346
+ >>> # Numeric values
347
+ >>> validator = in_set({1, 2, 3, 4, 5})
348
+ >>> validator(3)
349
+ Success(3)
350
+ >>> validator(10).is_failure()
351
+ True
352
+ >>> # With custom error message
353
+ >>> validator = in_set({'small', 'medium', 'large'}, error_message='Size must be S, M, or L')
354
+ >>> validator('extra-large').error_or('')
355
+ 'Size must be S, M, or L'
356
+
357
+ """
358
+
359
+ def validator(value: T) -> Maybe[T]:
360
+ if value in allowed_values:
361
+ return Maybe.success(value)
362
+ return Maybe.failure(error_message or f'Value must be one of {allowed_values}')
363
+
364
+ return Validator(validator)
365
+
366
+
367
+ def non_empty_string(error_message: str | None = None) -> Validator[str]:
368
+ """Create a validator that ensures a string is not empty.
369
+
370
+ Validates that a string contains at least one non-whitespace character.
371
+ Both empty strings and whitespace-only strings are rejected.
372
+
373
+ Args:
374
+ error_message: Optional custom error message
375
+
376
+ Returns:
377
+ Validator[str]: A validator function that checks for non-empty strings
378
+
379
+ Examples:
380
+ >>> from valid8r.core.validators import non_empty_string
381
+ >>> validator = non_empty_string()
382
+ >>> validator('hello')
383
+ Success('hello')
384
+ >>> validator(' hello ')
385
+ Success(' hello ')
386
+ >>> validator('').is_failure()
387
+ True
388
+ >>> validator(' ').is_failure()
389
+ True
390
+ >>> # With custom error message
391
+ >>> validator = non_empty_string(error_message='Name is required')
392
+ >>> validator('').error_or('')
393
+ 'Name is required'
394
+
395
+ """
396
+
397
+ def validator(value: str) -> Maybe[str]:
398
+ if value.strip():
399
+ return Maybe.success(value)
400
+ return Maybe.failure(error_message or 'String must not be empty')
401
+
402
+ return Validator(validator)
403
+
404
+
405
+ def unique_items(error_message: str | None = None) -> Validator[list[T]]:
406
+ """Create a validator that ensures all items in a list are unique.
407
+
408
+ Validates that a list contains no duplicate elements by comparing
409
+ the list length to the set length.
410
+
411
+ Args:
412
+ error_message: Optional custom error message
413
+
414
+ Returns:
415
+ Validator[list[T]]: A validator function that checks for unique items
416
+
417
+ Examples:
418
+ >>> from valid8r.core.validators import unique_items
419
+ >>> validator = unique_items()
420
+ >>> validator([1, 2, 3, 4, 5])
421
+ Success([1, 2, 3, 4, 5])
422
+ >>> validator([1, 2, 2, 3]).is_failure()
423
+ True
424
+ >>> # Works with strings
425
+ >>> validator(['a', 'b', 'c'])
426
+ Success(['a', 'b', 'c'])
427
+ >>> validator(['a', 'b', 'a']).is_failure()
428
+ True
429
+ >>> # With custom error message
430
+ >>> validator = unique_items(error_message='Duplicate items found')
431
+ >>> validator([1, 1, 2]).error_or('')
432
+ 'Duplicate items found'
433
+
434
+ """
435
+
436
+ def validator(value: list[T]) -> Maybe[list[T]]:
437
+ if len(value) == len(set(value)):
438
+ return Maybe.success(value)
439
+ return Maybe.failure(error_message or 'All items must be unique')
440
+
441
+ return Validator(validator)
442
+
443
+
444
+ def subset_of(allowed_set: set[T], error_message: str | None = None) -> Validator[set[T]]:
445
+ """Create a validator that ensures a set is a subset of allowed values.
446
+
447
+ Validates that all elements in the input set are contained within
448
+ the allowed set. An empty set is always a valid subset.
449
+
450
+ Args:
451
+ allowed_set: The set of allowed values
452
+ error_message: Optional custom error message
453
+
454
+ Returns:
455
+ Validator[set[T]]: A validator function that checks subset relationship
456
+
457
+ Examples:
458
+ >>> from valid8r.core.validators import subset_of
459
+ >>> validator = subset_of({1, 2, 3, 4, 5})
460
+ >>> validator({1, 2, 3})
461
+ Success({1, 2, 3})
462
+ >>> validator({1, 2, 3, 4, 5, 6}).is_failure()
463
+ True
464
+ >>> # Empty set is valid subset
465
+ >>> validator(set())
466
+ Success(set())
467
+ >>> # With custom error message
468
+ >>> validator = subset_of({'a', 'b', 'c'}, error_message='Invalid characters')
469
+ >>> validator({'a', 'd'}).error_or('')
470
+ 'Invalid characters'
471
+
472
+ """
473
+
474
+ def validator(value: set[T]) -> Maybe[set[T]]:
475
+ if value.issubset(allowed_set):
476
+ return Maybe.success(value)
477
+ return Maybe.failure(error_message or f'Value must be a subset of {allowed_set}')
478
+
479
+ return Validator(validator)
480
+
481
+
482
+ def superset_of(required_set: set[T], error_message: str | None = None) -> Validator[set[T]]:
483
+ """Create a validator that ensures a set is a superset of required values.
484
+
485
+ Validates that the input set contains all elements from the required set.
486
+ The input set may contain additional elements beyond those required.
487
+
488
+ Args:
489
+ required_set: The set of required values
490
+ error_message: Optional custom error message
491
+
492
+ Returns:
493
+ Validator[set[T]]: A validator function that checks superset relationship
494
+
495
+ Examples:
496
+ >>> from valid8r.core.validators import superset_of
497
+ >>> validator = superset_of({1, 2, 3})
498
+ >>> validator({1, 2, 3, 4, 5})
499
+ Success({1, 2, 3, 4, 5})
500
+ >>> validator({1, 2}).is_failure()
501
+ True
502
+ >>> # Exact match is valid
503
+ >>> validator({1, 2, 3})
504
+ Success({1, 2, 3})
505
+ >>> # With custom error message
506
+ >>> validator = superset_of({'read', 'write'}, error_message='Missing required permissions')
507
+ >>> validator({'read'}).error_or('')
508
+ 'Missing required permissions'
509
+
510
+ """
511
+
512
+ def validator(value: set[T]) -> Maybe[set[T]]:
513
+ if value.issuperset(required_set):
514
+ return Maybe.success(value)
515
+ return Maybe.failure(error_message or f'Value must be a superset of {required_set}')
516
+
517
+ return Validator(validator)
518
+
519
+
520
+ def is_sorted(*, reverse: bool = False, error_message: str | None = None) -> Validator[list[N]]:
521
+ """Create a validator that ensures a list is sorted.
522
+
523
+ Validates that a list is sorted in either ascending or descending order.
524
+ Uses keyword-only parameters to avoid boolean trap anti-pattern.
525
+
526
+ Args:
527
+ reverse: If True, checks for descending order; otherwise ascending (default)
528
+ error_message: Optional custom error message
529
+
530
+ Returns:
531
+ Validator[list[N]]: A validator function that checks if list is sorted
532
+
533
+ Examples:
534
+ >>> from valid8r.core.validators import is_sorted
535
+ >>> # Ascending order (default)
536
+ >>> validator = is_sorted()
537
+ >>> validator([1, 2, 3, 4, 5])
538
+ Success([1, 2, 3, 4, 5])
539
+ >>> validator([3, 1, 4, 2]).is_failure()
540
+ True
541
+ >>> # Descending order
542
+ >>> validator = is_sorted(reverse=True)
543
+ >>> validator([5, 4, 3, 2, 1])
544
+ Success([5, 4, 3, 2, 1])
545
+ >>> validator([1, 2, 3]).is_failure()
546
+ True
547
+ >>> # Works with strings
548
+ >>> validator = is_sorted()
549
+ >>> validator(['a', 'b', 'c'])
550
+ Success(['a', 'b', 'c'])
551
+ >>> # With custom error message
552
+ >>> validator = is_sorted(error_message='List must be in order')
553
+ >>> validator([3, 1, 2]).error_or('')
554
+ 'List must be in order'
555
+
556
+ """
557
+
558
+ def validator(value: list[N]) -> Maybe[list[N]]:
559
+ sorted_value = sorted(value, reverse=reverse)
560
+ if value == sorted_value:
561
+ return Maybe.success(value)
562
+ direction = 'descending' if reverse else 'ascending'
563
+ return Maybe.failure(error_message or f'List must be sorted in {direction} order')
564
+
565
+ return Validator(validator)
@@ -1,282 +0,0 @@
1
- """Core validators for validating values against specific criteria.
2
-
3
- This module provides a collection of validator functions for common validation scenarios.
4
- All validators follow the same pattern - they take a value and return a Maybe object
5
- that either contains the validated value or an error message.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import (
11
- TYPE_CHECKING,
12
- Generic,
13
- Protocol,
14
- TypeVar,
15
- )
16
-
17
- from valid8r.core.combinators import (
18
- and_then,
19
- not_validator,
20
- or_else,
21
- )
22
- from valid8r.core.maybe import Maybe
23
-
24
- if TYPE_CHECKING:
25
- from collections.abc import Callable
26
-
27
-
28
- class SupportsComparison(Protocol): # noqa: D101
29
- def __le__(self, other: object, /) -> bool: ... # noqa: D105
30
- def __lt__(self, other: object, /) -> bool: ... # noqa: D105
31
- def __ge__(self, other: object, /) -> bool: ... # noqa: D105
32
- def __gt__(self, other: object, /) -> bool: ... # noqa: D105
33
- def __eq__(self, other: object, /) -> bool: ... # noqa: D105
34
- def __ne__(self, other: object, /) -> bool: ... # noqa: D105
35
- def __hash__(self, /) -> int: ... # noqa: D105
36
-
37
-
38
- T = TypeVar('T')
39
- U = TypeVar('U')
40
- N = TypeVar('N', bound=SupportsComparison)
41
-
42
-
43
- class Validator(Generic[T]):
44
- """A wrapper class for validator functions that supports operator overloading."""
45
-
46
- def __init__(self, func: Callable[[T], Maybe[T]]) -> None:
47
- """Initialize a validator with a validation function.
48
-
49
- Args:
50
- func: A function that takes a value and returns a Maybe
51
-
52
- """
53
- self.func = func
54
-
55
- def __call__(self, value: T) -> Maybe[T]:
56
- """Apply the validator to a value.
57
-
58
- Args:
59
- value: The value to validate
60
-
61
- Returns:
62
- A Maybe containing either the validated value or an error
63
-
64
- """
65
- return self.func(value)
66
-
67
- def __and__(self, other: Validator[T]) -> Validator[T]:
68
- """Combine with another validator using logical AND.
69
-
70
- Args:
71
- other: Another validator to combine with
72
-
73
- Returns:
74
- A new validator that passes only if both validators pass
75
-
76
- """
77
- return Validator(lambda value: and_then(self.func, other.func)(value))
78
-
79
- def __or__(self, other: Validator[T]) -> Validator[T]:
80
- """Combine with another validator using logical OR.
81
-
82
- Args:
83
- other: Another validator to combine with
84
-
85
- Returns:
86
- A new validator that passes if either validator passes
87
-
88
- """
89
- return Validator(lambda value: or_else(self.func, other.func)(value))
90
-
91
- def __invert__(self) -> Validator[T]:
92
- """Negate this validator.
93
-
94
- Returns:
95
- A new validator that passes if this validator fails
96
-
97
- """
98
- return Validator(lambda value: not_validator(self.func, 'Negated validation failed')(value))
99
-
100
-
101
- def minimum(min_value: N, error_message: str | None = None) -> Validator[N]:
102
- """Create a validator that ensures a value is at least the minimum.
103
-
104
- Args:
105
- min_value: The minimum allowed value (inclusive)
106
- error_message: Optional custom error message
107
-
108
- Returns:
109
- Validator[N]: A validator function that accepts values >= min_value
110
-
111
- Examples:
112
- >>> from valid8r.core.validators import minimum
113
- >>> validator = minimum(0)
114
- >>> validator(5)
115
- Success(5)
116
- >>> validator(0)
117
- Success(0)
118
- >>> validator(-1).is_failure()
119
- True
120
- >>> # With custom error message
121
- >>> validator = minimum(18, error_message="Must be an adult")
122
- >>> validator(17).error_or("")
123
- 'Must be an adult'
124
-
125
- """
126
-
127
- def validator(value: N) -> Maybe[N]:
128
- if value >= min_value:
129
- return Maybe.success(value)
130
- return Maybe.failure(error_message or f'Value must be at least {min_value}')
131
-
132
- return Validator(validator)
133
-
134
-
135
- def maximum(max_value: N, error_message: str | None = None) -> Validator[N]:
136
- """Create a validator that ensures a value is at most the maximum.
137
-
138
- Args:
139
- max_value: The maximum allowed value (inclusive)
140
- error_message: Optional custom error message
141
-
142
- Returns:
143
- Validator[N]: A validator function that accepts values <= max_value
144
-
145
- Examples:
146
- >>> from valid8r.core.validators import maximum
147
- >>> validator = maximum(100)
148
- >>> validator(50)
149
- Success(50)
150
- >>> validator(100)
151
- Success(100)
152
- >>> validator(101).is_failure()
153
- True
154
- >>> # With custom error message
155
- >>> validator = maximum(120, error_message="Age too high")
156
- >>> validator(150).error_or("")
157
- 'Age too high'
158
-
159
- """
160
-
161
- def validator(value: N) -> Maybe[N]:
162
- if value <= max_value:
163
- return Maybe.success(value)
164
- return Maybe.failure(error_message or f'Value must be at most {max_value}')
165
-
166
- return Validator(validator)
167
-
168
-
169
- def between(min_value: N, max_value: N, error_message: str | None = None) -> Validator[N]:
170
- """Create a validator that ensures a value is between minimum and maximum (inclusive).
171
-
172
- Args:
173
- min_value: The minimum allowed value (inclusive)
174
- max_value: The maximum allowed value (inclusive)
175
- error_message: Optional custom error message
176
-
177
- Returns:
178
- Validator[N]: A validator function that accepts values where min_value <= value <= max_value
179
-
180
- Examples:
181
- >>> from valid8r.core.validators import between
182
- >>> validator = between(0, 100)
183
- >>> validator(50)
184
- Success(50)
185
- >>> validator(0)
186
- Success(0)
187
- >>> validator(100)
188
- Success(100)
189
- >>> validator(-1).is_failure()
190
- True
191
- >>> validator(101).is_failure()
192
- True
193
- >>> # With custom error message
194
- >>> validator = between(1, 10, error_message="Rating must be 1-10")
195
- >>> validator(11).error_or("")
196
- 'Rating must be 1-10'
197
-
198
- """
199
-
200
- def validator(value: N) -> Maybe[N]:
201
- if min_value <= value <= max_value:
202
- return Maybe.success(value)
203
- return Maybe.failure(error_message or f'Value must be between {min_value} and {max_value}')
204
-
205
- return Validator(validator)
206
-
207
-
208
- def predicate(pred: Callable[[T], bool], error_message: str) -> Validator[T]:
209
- """Create a validator using a custom predicate function.
210
-
211
- Allows creating custom validators for any validation logic by providing
212
- a predicate function that returns True for valid values.
213
-
214
- Args:
215
- pred: A function that takes a value and returns True if valid, False otherwise
216
- error_message: Error message to return when validation fails
217
-
218
- Returns:
219
- Validator[T]: A validator function that applies the predicate
220
-
221
- Examples:
222
- >>> from valid8r.core.validators import predicate
223
- >>> # Validate even numbers
224
- >>> is_even = predicate(lambda x: x % 2 == 0, "Must be even")
225
- >>> is_even(4)
226
- Success(4)
227
- >>> is_even(3).is_failure()
228
- True
229
- >>> # Validate string patterns
230
- >>> starts_with_a = predicate(lambda s: s.startswith('a'), "Must start with 'a'")
231
- >>> starts_with_a("apple")
232
- Success('apple')
233
- >>> starts_with_a("banana").error_or("")
234
- "Must start with 'a'"
235
-
236
- """
237
-
238
- def validator(value: T) -> Maybe[T]:
239
- if pred(value):
240
- return Maybe.success(value)
241
- return Maybe.failure(error_message)
242
-
243
- return Validator(validator)
244
-
245
-
246
- def length(min_length: int, max_length: int, error_message: str | None = None) -> Validator[str]:
247
- """Create a validator that ensures a string's length is within bounds.
248
-
249
- Args:
250
- min_length: Minimum length of the string (inclusive)
251
- max_length: Maximum length of the string (inclusive)
252
- error_message: Optional custom error message
253
-
254
- Returns:
255
- Validator[str]: A validator function that checks string length
256
-
257
- Examples:
258
- >>> from valid8r.core.validators import length
259
- >>> validator = length(3, 10)
260
- >>> validator("hello")
261
- Success('hello')
262
- >>> validator("abc")
263
- Success('abc')
264
- >>> validator("abcdefghij")
265
- Success('abcdefghij')
266
- >>> validator("ab").is_failure()
267
- True
268
- >>> validator("abcdefghijk").is_failure()
269
- True
270
- >>> # With custom error message
271
- >>> validator = length(8, 20, error_message="Password must be 8-20 characters")
272
- >>> validator("short").error_or("")
273
- 'Password must be 8-20 characters'
274
-
275
- """
276
-
277
- def validator(value: str) -> Maybe[str]:
278
- if min_length <= len(value) <= max_length:
279
- return Maybe.success(value)
280
- return Maybe.failure(error_message or f'String length must be between {min_length} and {max_length}')
281
-
282
- return Validator(validator)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes