valid8r 1.6.0__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.
valid8r/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ # valid8r/__init__.py
2
+ """Valid8r: A clean, flexible input validation library for Python."""
3
+
4
+ from __future__ import annotations
5
+
6
+ # Import version from generated file
7
+ try:
8
+ from valid8r._version import __version__
9
+ except ImportError:
10
+ __version__ = '1.6.0'
11
+
12
+ # Public API re-exports for concise imports
13
+ # Modules
14
+ from . import prompt
15
+ from .core import (
16
+ combinators,
17
+ parsers,
18
+ validators,
19
+ )
20
+ from .core.maybe import Maybe
21
+ from .core.parsers import (
22
+ EmailAddress,
23
+ PhoneNumber,
24
+ UrlParts,
25
+ )
26
+
27
+ # Types
28
+
29
+ __all__ = [
30
+ 'EmailAddress',
31
+ 'Maybe',
32
+ 'PhoneNumber',
33
+ 'UrlParts',
34
+ '__version__',
35
+ 'combinators',
36
+ 'parsers',
37
+ 'prompt',
38
+ 'validators',
39
+ ]
valid8r/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '1.6.0'
32
+ __version_tuple__ = version_tuple = (1, 6, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -0,0 +1,28 @@
1
+ # valid8r/core/__init__.py
2
+ """Core validation components."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from valid8r.core.parsers import (
7
+ EmailAddress,
8
+ UrlParts,
9
+ parse_cidr,
10
+ parse_email,
11
+ parse_ip,
12
+ parse_ipv4,
13
+ parse_ipv6,
14
+ parse_url,
15
+ )
16
+
17
+ __all__ = [
18
+ 'EmailAddress',
19
+ 'UrlParts',
20
+ 'parse_cidr',
21
+ 'parse_email',
22
+ 'parse_ip',
23
+ # existing exports may be defined elsewhere; explicitly expose IP helpers
24
+ 'parse_ipv4',
25
+ 'parse_ipv6',
26
+ # URL/Email helpers
27
+ 'parse_url',
28
+ ]
@@ -0,0 +1,89 @@
1
+ """Combinators for creating complex validation rules.
2
+
3
+ This module provides functions to combine validators using logical operations like AND, OR, and NOT.
4
+ These combinators allow for creation of complex validation chains.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ TypeVar,
12
+ )
13
+
14
+ from valid8r.core.maybe import (
15
+ Maybe,
16
+ Success,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable
21
+
22
+ T = TypeVar('T')
23
+
24
+
25
+ def and_then(first: Callable[[T], Maybe[T]], second: Callable[[T], Maybe[T]]) -> Callable[[T], Maybe[T]]:
26
+ """Combine two validators with logical AND (both must succeed).
27
+
28
+ Args:
29
+ first: The first validator function
30
+ second: The second validator function
31
+
32
+ Returns:
33
+ A new validator function that passes only if both validators pass
34
+
35
+ """
36
+
37
+ def combined_validator(value: T) -> Maybe[T]:
38
+ result = first(value)
39
+ match result:
40
+ case Success(value):
41
+ return second(value)
42
+ case _:
43
+ return result
44
+
45
+ return combined_validator
46
+
47
+
48
+ def or_else(first: Callable[[T], Maybe[T]], second: Callable[[T], Maybe[T]]) -> Callable[[T], Maybe[T]]:
49
+ """Combine two validators with logical OR (either can succeed).
50
+
51
+ Args:
52
+ first: The first validator function
53
+ second: The second validator function
54
+
55
+ Returns:
56
+ A new validator function that passes if either validator passes
57
+
58
+ """
59
+
60
+ def combined_validator(value: T) -> Maybe[T]:
61
+ result = first(value)
62
+ match result:
63
+ case Success(value):
64
+ return result
65
+ case _:
66
+ return second(value)
67
+
68
+ return combined_validator
69
+
70
+
71
+ def not_validator(validator: Callable[[T], Maybe[T]], error_message: str) -> Callable[[T], Maybe[T]]:
72
+ """Negate a validator (success becomes failure and vice versa).
73
+
74
+ Args:
75
+ validator: The validator function to negate
76
+ error_message: Error message for when the negated validator fails
77
+
78
+ Returns:
79
+ A new validator function that passes if the original validator fails
80
+
81
+ """
82
+
83
+ def negated_validator(value: T) -> Maybe[T]:
84
+ result = validator(value)
85
+ if result.is_failure():
86
+ return Maybe.success(value)
87
+ return Maybe.failure(error_message)
88
+
89
+ return negated_validator
valid8r/core/maybe.py ADDED
@@ -0,0 +1,170 @@
1
+ """Maybe monad for clean error handling using Success and Failure types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import (
6
+ ABC,
7
+ abstractmethod,
8
+ )
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Generic,
12
+ TypeVar,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+
19
+ T = TypeVar('T')
20
+ U = TypeVar('U')
21
+
22
+
23
+ class Maybe(ABC, Generic[T]):
24
+ """Base class for the Maybe monad."""
25
+
26
+ @staticmethod
27
+ def success(value: T) -> Success[T]:
28
+ """Create a Success containing a value."""
29
+ return Success(value)
30
+
31
+ @staticmethod
32
+ def failure(error: str) -> Failure[T]:
33
+ """Create a Failure containing an error message."""
34
+ return Failure(error)
35
+
36
+ @abstractmethod
37
+ def is_success(self) -> bool:
38
+ """Check if the Maybe is a Success."""
39
+
40
+ @abstractmethod
41
+ def is_failure(self) -> bool:
42
+ """Check if the Maybe is a Failure."""
43
+
44
+ @abstractmethod
45
+ def bind(self, f: Callable[[T], Maybe[U]]) -> Maybe[U]:
46
+ """Chain operations that might fail."""
47
+
48
+ @abstractmethod
49
+ def map(self, f: Callable[[T], U]) -> Maybe[U]:
50
+ """Transform the value if present."""
51
+
52
+ @abstractmethod
53
+ def value_or(self, default: T) -> T:
54
+ """Return the contained value or the provided default if this is a Failure."""
55
+
56
+ @abstractmethod
57
+ def error_or(self, default: str) -> str:
58
+ """Return the error message or the provided default if this is a Success."""
59
+
60
+ @abstractmethod
61
+ def get_error(self) -> str | None:
62
+ """Get the error message if present, otherwise None."""
63
+
64
+
65
+ class Success(Maybe[T]):
66
+ """Represents a successful computation with a value."""
67
+
68
+ __match_args__ = ('value',)
69
+
70
+ def __init__(self, value: T) -> None:
71
+ """Initialize a Success with a value.
72
+
73
+ Args:
74
+ value: The successful result value
75
+
76
+ """
77
+ self.value = value
78
+
79
+ def is_success(self) -> bool:
80
+ """Check if the Maybe is a Success."""
81
+ return True
82
+
83
+ def is_failure(self) -> bool:
84
+ """Check if the Maybe is a Failure."""
85
+ return False
86
+
87
+ def bind(self, f: Callable[[T], Maybe[U]]) -> Maybe[U]:
88
+ """Chain operations that might fail."""
89
+ return f(self.value)
90
+
91
+ def map(self, f: Callable[[T], U]) -> Maybe[U]:
92
+ """Transform the value."""
93
+ return Success(f(self.value))
94
+
95
+ def value_or(self, _default: T) -> T:
96
+ """Return the contained value (default is ignored for Success)."""
97
+ return self.value
98
+
99
+ def error_or(self, default: str) -> str:
100
+ """Return the provided default since Success has no error."""
101
+ return default
102
+
103
+ def get_error(self) -> str | None:
104
+ """Get None since Success has no error."""
105
+ return None
106
+
107
+ def __str__(self) -> str:
108
+ """Get a string representation."""
109
+ return f'Success({self.value})'
110
+
111
+ def __repr__(self) -> str:
112
+ """Get a repr representation for debugging and doctests."""
113
+ return f'Success({self.value!r})'
114
+
115
+
116
+ class Failure(Maybe[T]):
117
+ """Represents a failed computation with an error message."""
118
+
119
+ __match_args__ = ('error',)
120
+
121
+ def __init__(self, error: str) -> None:
122
+ """Initialize a Failure with an error message.
123
+
124
+ Args:
125
+ error: The error message explaining the failure
126
+
127
+ """
128
+ self.error = error
129
+
130
+ def is_success(self) -> bool:
131
+ """Check if the Maybe is a Success."""
132
+ return False
133
+
134
+ def is_failure(self) -> bool:
135
+ """Check if the Maybe is a Failure."""
136
+ return True
137
+
138
+ def bind(self, _f: Callable[[T], Maybe[U]]) -> Maybe[U]:
139
+ """Chain operations that might fail.
140
+
141
+ Function is unused in Failure case as we always propagate the error.
142
+ """
143
+ return Failure(self.error)
144
+
145
+ def map(self, _f: Callable[[T], U]) -> Maybe[U]:
146
+ """Transform the value if present.
147
+
148
+ Function is unused in Failure case as we always propagate the error.
149
+ """
150
+ return Failure(self.error)
151
+
152
+ def value_or(self, default: T) -> T:
153
+ """Return the provided default for Failure."""
154
+ return default
155
+
156
+ def error_or(self, default: str) -> str:
157
+ """Return the error message for Failure (or provided default if empty)."""
158
+ return self.error or default
159
+
160
+ def get_error(self) -> str | None:
161
+ """Get the error message."""
162
+ return self.error
163
+
164
+ def __str__(self) -> str:
165
+ """Get a string representation."""
166
+ return f'Failure({self.error})'
167
+
168
+ def __repr__(self) -> str:
169
+ """Get a repr representation for debugging and doctests."""
170
+ return f'Failure({self.error!r})'