valid8r 0.6.2__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.
- {valid8r-0.6.2 → valid8r-0.7.0}/PKG-INFO +40 -14
- {valid8r-0.6.2 → valid8r-0.7.0}/README.md +39 -13
- {valid8r-0.6.2 → valid8r-0.7.0}/pyproject.toml +1 -1
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/core/maybe.py +8 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/core/parsers.py +341 -44
- valid8r-0.7.0/valid8r/core/validators.py +565 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/prompt/basic.py +51 -12
- valid8r-0.6.2/valid8r/core/validators.py +0 -200
- {valid8r-0.6.2 → valid8r-0.7.0}/LICENSE +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/__init__.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/core/__init__.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/core/combinators.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/prompt/__init__.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/py.typed +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/testing/__init__.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/testing/assertions.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/testing/generators.py +0 -0
- {valid8r-0.6.2 → valid8r-0.7.0}/valid8r/testing/mock_input.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: valid8r
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -168,21 +192,21 @@ from valid8r.core.maybe import Success, Failure
|
|
|
168
192
|
from valid8r.core import parsers
|
|
169
193
|
|
|
170
194
|
# Phone number parsing with NANP validation (PhoneNumber)
|
|
171
|
-
match parsers.parse_phone("+1 (
|
|
195
|
+
match parsers.parse_phone("+1 (415) 555-2671"):
|
|
172
196
|
case Success(phone):
|
|
173
197
|
print(f"Country: {phone.country_code}") # 1
|
|
174
|
-
print(f"Area: {phone.area_code}") #
|
|
175
|
-
print(f"Exchange: {phone.exchange}") #
|
|
176
|
-
print(f"Subscriber: {phone.subscriber}") #
|
|
198
|
+
print(f"Area: {phone.area_code}") # 415
|
|
199
|
+
print(f"Exchange: {phone.exchange}") # 555
|
|
200
|
+
print(f"Subscriber: {phone.subscriber}") # 2671
|
|
177
201
|
|
|
178
202
|
# Format for display using properties
|
|
179
|
-
print(f"E.164: {phone.e164}") # +
|
|
180
|
-
print(f"National: {phone.national}") # (
|
|
203
|
+
print(f"E.164: {phone.e164}") # +14155552671
|
|
204
|
+
print(f"National: {phone.national}") # (415) 555-2671
|
|
181
205
|
case Failure(err):
|
|
182
206
|
print("Error:", err)
|
|
183
207
|
|
|
184
208
|
# Also accepts various formats
|
|
185
|
-
for number in ["
|
|
209
|
+
for number in ["4155552671", "(415) 555-2671", "415-555-2671"]:
|
|
186
210
|
result = parsers.parse_phone(number)
|
|
187
211
|
assert result.is_success()
|
|
188
212
|
```
|
|
@@ -212,24 +236,26 @@ assert assert_maybe_failure(result, "at least 0")
|
|
|
212
236
|
|
|
213
237
|
# Test prompts with mock input
|
|
214
238
|
with MockInputContext(["yes", "42", "invalid", "25"]):
|
|
215
|
-
# First prompt
|
|
239
|
+
# First prompt - returns Maybe, unwrap with value_or()
|
|
216
240
|
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
217
241
|
assert result.value_or(False) == True
|
|
218
242
|
|
|
219
|
-
# Second prompt
|
|
220
|
-
|
|
243
|
+
# Second prompt - unwrap the Maybe result
|
|
244
|
+
result = prompt.ask(
|
|
221
245
|
"Age? ",
|
|
222
246
|
parser=parsers.parse_int,
|
|
223
247
|
validator=validate_age
|
|
224
248
|
)
|
|
249
|
+
age = result.value_or(None)
|
|
225
250
|
assert age == 42
|
|
226
251
|
|
|
227
|
-
# Third prompt will fail, fourth succeeds
|
|
228
|
-
|
|
252
|
+
# Third prompt will fail, fourth succeeds - unwrap result
|
|
253
|
+
result = prompt.ask(
|
|
229
254
|
"Age again? ",
|
|
230
255
|
parser=parsers.parse_int,
|
|
231
|
-
|
|
256
|
+
retry=1 # Retry once after failure
|
|
232
257
|
)
|
|
258
|
+
age = result.value_or(None)
|
|
233
259
|
assert age == 25
|
|
234
260
|
```
|
|
235
261
|
|
|
@@ -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
|
|
@@ -137,21 +161,21 @@ from valid8r.core.maybe import Success, Failure
|
|
|
137
161
|
from valid8r.core import parsers
|
|
138
162
|
|
|
139
163
|
# Phone number parsing with NANP validation (PhoneNumber)
|
|
140
|
-
match parsers.parse_phone("+1 (
|
|
164
|
+
match parsers.parse_phone("+1 (415) 555-2671"):
|
|
141
165
|
case Success(phone):
|
|
142
166
|
print(f"Country: {phone.country_code}") # 1
|
|
143
|
-
print(f"Area: {phone.area_code}") #
|
|
144
|
-
print(f"Exchange: {phone.exchange}") #
|
|
145
|
-
print(f"Subscriber: {phone.subscriber}") #
|
|
167
|
+
print(f"Area: {phone.area_code}") # 415
|
|
168
|
+
print(f"Exchange: {phone.exchange}") # 555
|
|
169
|
+
print(f"Subscriber: {phone.subscriber}") # 2671
|
|
146
170
|
|
|
147
171
|
# Format for display using properties
|
|
148
|
-
print(f"E.164: {phone.e164}") # +
|
|
149
|
-
print(f"National: {phone.national}") # (
|
|
172
|
+
print(f"E.164: {phone.e164}") # +14155552671
|
|
173
|
+
print(f"National: {phone.national}") # (415) 555-2671
|
|
150
174
|
case Failure(err):
|
|
151
175
|
print("Error:", err)
|
|
152
176
|
|
|
153
177
|
# Also accepts various formats
|
|
154
|
-
for number in ["
|
|
178
|
+
for number in ["4155552671", "(415) 555-2671", "415-555-2671"]:
|
|
155
179
|
result = parsers.parse_phone(number)
|
|
156
180
|
assert result.is_success()
|
|
157
181
|
```
|
|
@@ -181,24 +205,26 @@ assert assert_maybe_failure(result, "at least 0")
|
|
|
181
205
|
|
|
182
206
|
# Test prompts with mock input
|
|
183
207
|
with MockInputContext(["yes", "42", "invalid", "25"]):
|
|
184
|
-
# First prompt
|
|
208
|
+
# First prompt - returns Maybe, unwrap with value_or()
|
|
185
209
|
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
186
210
|
assert result.value_or(False) == True
|
|
187
211
|
|
|
188
|
-
# Second prompt
|
|
189
|
-
|
|
212
|
+
# Second prompt - unwrap the Maybe result
|
|
213
|
+
result = prompt.ask(
|
|
190
214
|
"Age? ",
|
|
191
215
|
parser=parsers.parse_int,
|
|
192
216
|
validator=validate_age
|
|
193
217
|
)
|
|
218
|
+
age = result.value_or(None)
|
|
194
219
|
assert age == 42
|
|
195
220
|
|
|
196
|
-
# Third prompt will fail, fourth succeeds
|
|
197
|
-
|
|
221
|
+
# Third prompt will fail, fourth succeeds - unwrap result
|
|
222
|
+
result = prompt.ask(
|
|
198
223
|
"Age again? ",
|
|
199
224
|
parser=parsers.parse_int,
|
|
200
|
-
|
|
225
|
+
retry=1 # Retry once after failure
|
|
201
226
|
)
|
|
227
|
+
age = result.value_or(None)
|
|
202
228
|
assert age == 25
|
|
203
229
|
```
|
|
204
230
|
|
|
@@ -108,6 +108,10 @@ class Success(Maybe[T]):
|
|
|
108
108
|
"""Get a string representation."""
|
|
109
109
|
return f'Success({self.value})'
|
|
110
110
|
|
|
111
|
+
def __repr__(self) -> str:
|
|
112
|
+
"""Get a repr representation for debugging and doctests."""
|
|
113
|
+
return f'Success({self.value!r})'
|
|
114
|
+
|
|
111
115
|
|
|
112
116
|
class Failure(Maybe[T]):
|
|
113
117
|
"""Represents a failed computation with an error message."""
|
|
@@ -160,3 +164,7 @@ class Failure(Maybe[T]):
|
|
|
160
164
|
def __str__(self) -> str:
|
|
161
165
|
"""Get a string representation."""
|
|
162
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})'
|