valid8r 0.6.3__py3-none-any.whl → 0.7.1__py3-none-any.whl

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.

valid8r/core/maybe.py CHANGED
@@ -20,7 +20,7 @@ T = TypeVar('T')
20
20
  U = TypeVar('U')
21
21
 
22
22
 
23
- class Maybe(Generic[T], ABC):
23
+ class Maybe(ABC, Generic[T]):
24
24
  """Base class for the Maybe monad."""
25
25
 
26
26
  @staticmethod
@@ -7,6 +7,7 @@ that either contains the validated value or an error message.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import re
10
11
  from typing import (
11
12
  TYPE_CHECKING,
12
13
  Generic,
@@ -280,3 +281,285 @@ def length(min_length: int, max_length: int, error_message: str | None = None) -
280
281
  return Maybe.failure(error_message or f'String length must be between {min_length} and {max_length}')
281
282
 
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,13 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valid8r
3
- Version: 0.6.3
3
+ Version: 0.7.1
4
4
  Summary: Clean, flexible input validation for Python applications
5
+ Project-URL: Homepage, https://valid8r.readthedocs.io/
6
+ Project-URL: Repository, https://github.com/mikelane/valid8r
7
+ Project-URL: Documentation, https://valid8r.readthedocs.io/
8
+ Project-URL: Issues, https://github.com/mikelane/valid8r/issues
9
+ Author-email: Mike Lane <mikelane@gmail.com>
5
10
  License: MIT
6
11
  License-File: LICENSE
7
- Keywords: validation,input,cli,maybe-monad,parsing,functional-programming
8
- Author: Mike Lane
9
- Author-email: mikelane@gmail.com
10
- Requires-Python: >=3.11,<4.0
12
+ Keywords: cli,functional-programming,input,maybe-monad,parsing,validation
11
13
  Classifier: Development Status :: 5 - Production/Stable
12
14
  Classifier: Intended Audience :: Developers
13
15
  Classifier: License :: OSI Approved :: MIT License
@@ -20,13 +22,11 @@ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
23
  Classifier: Topic :: Software Development :: Quality Assurance
22
24
  Classifier: Typing :: Typed
23
- Requires-Dist: email-validator (>=2.3.0,<3.0.0)
24
- Requires-Dist: pydantic (>=2.0)
25
- Requires-Dist: pydantic-core (>=2.27.0,<3.0.0)
26
- Requires-Dist: uuid-utils (>=0.11.0,<0.12.0)
27
- Project-URL: Documentation, https://valid8r.readthedocs.io/
28
- Project-URL: Homepage, https://valid8r.readthedocs.io/
29
- Project-URL: Repository, https://github.com/mikelane/valid8r
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: email-validator>=2.3.0
27
+ Requires-Dist: pydantic-core>=2.27.0
28
+ Requires-Dist: pydantic>=2.0
29
+ Requires-Dist: uuid-utils>=0.11.0
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  # Valid8r
@@ -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
@@ -272,4 +296,3 @@ poetry run tox -e bdd
272
296
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
273
297
 
274
298
  Copyright (c) 2025 Mike Lane
275
-
@@ -1,18 +1,18 @@
1
1
  valid8r/__init__.py,sha256=2fzSl6XtKX44sdqzf0GBTn4oEaCvhmyGkdsPDMJjZz8,447
2
2
  valid8r/core/__init__.py,sha256=ASOdzqCtpZHbHjjYMZkb78Z-nKxtD26ruTY0bd43ImA,520
3
3
  valid8r/core/combinators.py,sha256=KvRiDEqoZgH58cBYPO6SW9pdtkyijk0lS8aGSB5DbO4,2349
4
- valid8r/core/maybe.py,sha256=ifz15tDMwRaQLsYvU3pWGBZBxJ2JyyOW-g5YpOw_-3w,4643
4
+ valid8r/core/maybe.py,sha256=kuD5SsWc6148FkcLBcwmRTPi6D7H4w1dRT8lPrUvYMw,4643
5
5
  valid8r/core/parsers.py,sha256=WYAgwCIh8HyrXr0AoZeqa0c43-_iVIK_S3wb2lha73c,67456
6
- valid8r/core/validators.py,sha256=owzY6s9UWLlC7qZQcu8NmjqzphlxPJpPuaIxujs1YmU,8784
6
+ valid8r/core/validators.py,sha256=e3B_fi7ch5m0Zczg87r8AhrgEdBTcbz-aygrKCvi0dg,18537
7
7
  valid8r/prompt/__init__.py,sha256=XYB3NEp-tmqT6fGmETVEeXd7Urj0M4ijlwdRAjj-rG8,175
8
8
  valid8r/prompt/basic.py,sha256=fFARuy5nGTE7xM3dB1jpRC3OPNmp4WwaymFMz7BSgdo,7635
9
- valid8r/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
9
  valid8r/testing/__init__.py,sha256=8mk54zt0Ai2dK0a3GMOTfDPsVQWXaS6uvQJDrkRV9hs,779
11
10
  valid8r/testing/assertions.py,sha256=9KGz1JooCoyikyxMX7VuXB9VYAtj-4H_LPYFGdvS-ps,1820
12
11
  valid8r/testing/generators.py,sha256=kAV6NRO9x1gPy0BfGs07ETVxjpTIxOZyV9wH2BA1nHA,8791
13
12
  valid8r/testing/mock_input.py,sha256=9GRT7h0PCh9Dea-OcQ5Uls7YqhsTdqMWuX6I6ZlW1aw,2334
14
- valid8r-0.6.3.dist-info/METADATA,sha256=nsJsH6nOR2YwQh4W6jBhFuodpfkODxN8t14Ryyghgf4,9246
15
- valid8r-0.6.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
16
- valid8r-0.6.3.dist-info/entry_points.txt,sha256=H_24A4zUgnKIXAIRosJliIcntyqMfmcgKh5_Prl7W18,79
17
- valid8r-0.6.3.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
18
- valid8r-0.6.3.dist-info/RECORD,,
13
+ valid8r/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ valid8r-0.7.1.dist-info/METADATA,sha256=O1-f1iS7afFkdqpudNeQvk_lbQ8oAalmh0uzJPhnZgE,10476
15
+ valid8r-0.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ valid8r-0.7.1.dist-info/entry_points.txt,sha256=x2MRzcIGcUR5e4GmhLadGdT7YbbohJRMUVubgaR8v_s,82
17
+ valid8r-0.7.1.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
18
+ valid8r-0.7.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ docs-build = scripts.docs:build
3
+ docs-serve = scripts.docs:serve
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- docs-build=scripts.docs:build
3
- docs-serve=scripts.docs:serve
4
-