valid8r 0.7.2__py3-none-any.whl → 0.7.3__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-0.7.3.dist-info/METADATA +305 -0
- {valid8r-0.7.2.dist-info → valid8r-0.7.3.dist-info}/RECORD +5 -5
- valid8r-0.7.2.dist-info/METADATA +0 -306
- {valid8r-0.7.2.dist-info → valid8r-0.7.3.dist-info}/WHEEL +0 -0
- {valid8r-0.7.2.dist-info → valid8r-0.7.3.dist-info}/entry_points.txt +0 -0
- {valid8r-0.7.2.dist-info → valid8r-0.7.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: valid8r
|
|
3
|
+
Version: 0.7.3
|
|
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>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,functional-programming,input,maybe-monad,parsing,validation
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Typing :: Typed
|
|
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
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Valid8r
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/valid8r/)
|
|
35
|
+
[](https://pypi.org/project/valid8r/)
|
|
36
|
+
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
|
|
37
|
+
[](https://github.com/mikelane/valid8r/actions)
|
|
38
|
+
[](https://codecov.io/gh/mikelane/valid8r)
|
|
39
|
+
[](https://valid8r.readthedocs.io/)
|
|
40
|
+
|
|
41
|
+
**Clean, composable input validation for Python using functional programming patterns.**
|
|
42
|
+
|
|
43
|
+
Valid8r makes input validation elegant and type-safe by using the Maybe monad for error handling. No more try-except blocks or boolean validation chains—just clean, composable parsers that tell you exactly what went wrong.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from valid8r import parsers, validators, prompt
|
|
47
|
+
|
|
48
|
+
# Parse and validate user input with rich error messages
|
|
49
|
+
age = prompt.ask(
|
|
50
|
+
"Enter your age: ",
|
|
51
|
+
parser=parsers.parse_int,
|
|
52
|
+
validator=validators.minimum(0) & validators.maximum(120)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
print(f"Your age is {age}")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Why Valid8r?
|
|
59
|
+
|
|
60
|
+
**Type-Safe Parsing**: Every parser returns `Maybe[T]` (Success or Failure), making error handling explicit and composable.
|
|
61
|
+
|
|
62
|
+
**Rich Structured Results**: Network parsers return dataclasses with parsed components—no more manual URL/email splitting.
|
|
63
|
+
|
|
64
|
+
**Chainable Validators**: Combine validators using `&` (and), `|` (or), and `~` (not) operators for complex validation logic.
|
|
65
|
+
|
|
66
|
+
**Zero Dependencies**: Core library uses only Python's standard library (with optional `uuid-utils` for faster UUID parsing).
|
|
67
|
+
|
|
68
|
+
**Interactive Prompts**: Built-in user input prompting with automatic retry and validation.
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
### Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install valid8r
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Requirements**: Python 3.11 or higher
|
|
79
|
+
|
|
80
|
+
### Basic Parsing
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from valid8r import parsers
|
|
84
|
+
from valid8r.core.maybe import Success, Failure
|
|
85
|
+
|
|
86
|
+
# Parse integers with automatic error handling
|
|
87
|
+
match parsers.parse_int("42"):
|
|
88
|
+
case Success(value):
|
|
89
|
+
print(f"Parsed: {value}") # Parsed: 42
|
|
90
|
+
case Failure(error):
|
|
91
|
+
print(f"Error: {error}")
|
|
92
|
+
|
|
93
|
+
# Parse dates (ISO 8601 format)
|
|
94
|
+
result = parsers.parse_date("2025-01-15")
|
|
95
|
+
assert result.is_success()
|
|
96
|
+
|
|
97
|
+
# Parse UUIDs with version validation
|
|
98
|
+
result = parsers.parse_uuid("550e8400-e29b-41d4-a716-446655440000", version=4)
|
|
99
|
+
assert result.is_success()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Validation with Combinators
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from valid8r import validators
|
|
106
|
+
|
|
107
|
+
# Combine validators using operators
|
|
108
|
+
age_validator = validators.minimum(0) & validators.maximum(120)
|
|
109
|
+
result = age_validator(42)
|
|
110
|
+
assert result.is_success()
|
|
111
|
+
|
|
112
|
+
# String validation
|
|
113
|
+
password_validator = (
|
|
114
|
+
validators.length(8, 128) &
|
|
115
|
+
validators.matches_regex(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]')
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Set validation
|
|
119
|
+
tags_validator = validators.subset_of({'python', 'rust', 'go', 'typescript'})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Structured Network Parsing
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from valid8r import parsers
|
|
126
|
+
|
|
127
|
+
# Parse URLs into structured components
|
|
128
|
+
match parsers.parse_url("https://user:pass@example.com:8443/path?query=1#fragment"):
|
|
129
|
+
case Success(url):
|
|
130
|
+
print(f"Scheme: {url.scheme}") # https
|
|
131
|
+
print(f"Host: {url.host}") # example.com
|
|
132
|
+
print(f"Port: {url.port}") # 8443
|
|
133
|
+
print(f"Path: {url.path}") # /path
|
|
134
|
+
print(f"Query: {url.query}") # {'query': '1'}
|
|
135
|
+
print(f"Fragment: {url.fragment}") # fragment
|
|
136
|
+
|
|
137
|
+
# Parse emails with normalized domains
|
|
138
|
+
match parsers.parse_email("User@Example.COM"):
|
|
139
|
+
case Success(email):
|
|
140
|
+
print(f"Local: {email.local}") # User
|
|
141
|
+
print(f"Domain: {email.domain}") # example.com (normalized)
|
|
142
|
+
|
|
143
|
+
# Parse phone numbers (NANP format)
|
|
144
|
+
match parsers.parse_phone("+1 (415) 555-2671"):
|
|
145
|
+
case Success(phone):
|
|
146
|
+
print(f"E.164: {phone.e164}") # +14155552671
|
|
147
|
+
print(f"National: {phone.national}") # (415) 555-2671
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Collection Parsing
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from valid8r import parsers
|
|
154
|
+
|
|
155
|
+
# Parse lists with element validation
|
|
156
|
+
result = parsers.parse_list("1,2,3,4,5", element_parser=parsers.parse_int)
|
|
157
|
+
assert result.value_or([]) == [1, 2, 3, 4, 5]
|
|
158
|
+
|
|
159
|
+
# Parse dictionaries with key/value parsers
|
|
160
|
+
result = parsers.parse_dict(
|
|
161
|
+
"name=Alice,age=30",
|
|
162
|
+
key_parser=lambda x: Success(x),
|
|
163
|
+
value_parser=lambda x: parsers.parse_int(x) if x.isdigit() else Success(x)
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Interactive Prompting
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from valid8r import prompt, parsers, validators
|
|
171
|
+
|
|
172
|
+
# Prompt with validation and automatic retry
|
|
173
|
+
email = prompt.ask(
|
|
174
|
+
"Email address: ",
|
|
175
|
+
parser=parsers.parse_email,
|
|
176
|
+
retry=2 # Retry twice on invalid input
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Combine parsing and validation
|
|
180
|
+
port = prompt.ask(
|
|
181
|
+
"Server port: ",
|
|
182
|
+
parser=parsers.parse_int,
|
|
183
|
+
validator=validators.between(1024, 65535),
|
|
184
|
+
retry=3
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Features
|
|
189
|
+
|
|
190
|
+
### Parsers
|
|
191
|
+
|
|
192
|
+
**Basic Types**:
|
|
193
|
+
- `parse_int`, `parse_float`, `parse_bool`, `parse_decimal`, `parse_complex`
|
|
194
|
+
- `parse_date` (ISO 8601), `parse_uuid` (with version validation)
|
|
195
|
+
|
|
196
|
+
**Collections**:
|
|
197
|
+
- `parse_list`, `parse_dict`, `parse_set` (with element parsers)
|
|
198
|
+
|
|
199
|
+
**Network & Communication**:
|
|
200
|
+
- `parse_ipv4`, `parse_ipv6`, `parse_ip`, `parse_cidr`
|
|
201
|
+
- `parse_url` → `UrlParts` (structured URL components)
|
|
202
|
+
- `parse_email` → `EmailAddress` (normalized domain)
|
|
203
|
+
- `parse_phone` → `PhoneNumber` (NANP validation with E.164 formatting)
|
|
204
|
+
|
|
205
|
+
**Advanced**:
|
|
206
|
+
- `parse_enum` (type-safe enum parsing)
|
|
207
|
+
- `create_parser`, `make_parser`, `validated_parser` (custom parser factories)
|
|
208
|
+
|
|
209
|
+
### Validators
|
|
210
|
+
|
|
211
|
+
**Numeric**: `minimum`, `maximum`, `between`
|
|
212
|
+
|
|
213
|
+
**String**: `non_empty_string`, `matches_regex`, `length`
|
|
214
|
+
|
|
215
|
+
**Collection**: `in_set`, `unique_items`, `subset_of`, `superset_of`, `is_sorted`
|
|
216
|
+
|
|
217
|
+
**Custom**: `predicate` (create validators from any function)
|
|
218
|
+
|
|
219
|
+
**Combinators**: Combine validators using `&` (and), `|` (or), `~` (not)
|
|
220
|
+
|
|
221
|
+
### Testing Utilities
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from valid8r.testing import (
|
|
225
|
+
assert_maybe_success,
|
|
226
|
+
assert_maybe_failure,
|
|
227
|
+
MockInputContext,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Test validation logic
|
|
231
|
+
result = validators.minimum(0)(42)
|
|
232
|
+
assert assert_maybe_success(result, 42)
|
|
233
|
+
|
|
234
|
+
result = validators.minimum(0)(-5)
|
|
235
|
+
assert assert_maybe_failure(result, "at least 0")
|
|
236
|
+
|
|
237
|
+
# Mock user input for testing prompts
|
|
238
|
+
with MockInputContext(["invalid", "valid@example.com"]):
|
|
239
|
+
result = prompt.ask("Email: ", parser=parsers.parse_email, retry=1)
|
|
240
|
+
assert result.is_success()
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Documentation
|
|
244
|
+
|
|
245
|
+
**Full documentation**: [valid8r.readthedocs.io](https://valid8r.readthedocs.io/)
|
|
246
|
+
|
|
247
|
+
- [API Reference](https://valid8r.readthedocs.io/en/latest/api.html)
|
|
248
|
+
- [Parser Guide](https://valid8r.readthedocs.io/en/latest/parsers.html)
|
|
249
|
+
- [Validator Guide](https://valid8r.readthedocs.io/en/latest/validators.html)
|
|
250
|
+
- [Testing Guide](https://valid8r.readthedocs.io/en/latest/testing.html)
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
255
|
+
|
|
256
|
+
**Quick links**:
|
|
257
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
258
|
+
- [Development Setup](CONTRIBUTING.md#development-setup)
|
|
259
|
+
- [Commit Message Format](CONTRIBUTING.md#commit-messages)
|
|
260
|
+
- [Pull Request Process](CONTRIBUTING.md#pull-request-process)
|
|
261
|
+
|
|
262
|
+
### Development Quick Start
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Install uv (fast dependency manager)
|
|
266
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
267
|
+
|
|
268
|
+
# Clone and install dependencies
|
|
269
|
+
git clone https://github.com/mikelane/valid8r
|
|
270
|
+
cd valid8r
|
|
271
|
+
uv sync
|
|
272
|
+
|
|
273
|
+
# Run tests
|
|
274
|
+
uv run tox
|
|
275
|
+
|
|
276
|
+
# Run linters
|
|
277
|
+
uv run ruff check .
|
|
278
|
+
uv run ruff format .
|
|
279
|
+
uv run mypy valid8r
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Project Status
|
|
283
|
+
|
|
284
|
+
Valid8r is in active development (v0.7.x). The API is stabilizing but may change before v1.0.0.
|
|
285
|
+
|
|
286
|
+
- ✅ Core parsers and validators
|
|
287
|
+
- ✅ Maybe monad error handling
|
|
288
|
+
- ✅ Interactive prompting
|
|
289
|
+
- ✅ Network parsers (URL, Email, IP, Phone)
|
|
290
|
+
- ✅ Collection parsers
|
|
291
|
+
- ✅ Comprehensive testing utilities
|
|
292
|
+
- 🚧 Additional validators (in progress)
|
|
293
|
+
- 🚧 Custom error types (planned)
|
|
294
|
+
|
|
295
|
+
See [ROADMAP.md](ROADMAP.md) for planned features.
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
300
|
+
|
|
301
|
+
Copyright (c) 2025 Mike Lane
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
**Made with ❤️ for the Python community**
|
|
@@ -11,8 +11,8 @@ valid8r/testing/assertions.py,sha256=9KGz1JooCoyikyxMX7VuXB9VYAtj-4H_LPYFGdvS-ps
|
|
|
11
11
|
valid8r/testing/generators.py,sha256=kAV6NRO9x1gPy0BfGs07ETVxjpTIxOZyV9wH2BA1nHA,8791
|
|
12
12
|
valid8r/testing/mock_input.py,sha256=9GRT7h0PCh9Dea-OcQ5Uls7YqhsTdqMWuX6I6ZlW1aw,2334
|
|
13
13
|
valid8r/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
valid8r-0.7.
|
|
15
|
-
valid8r-0.7.
|
|
16
|
-
valid8r-0.7.
|
|
17
|
-
valid8r-0.7.
|
|
18
|
-
valid8r-0.7.
|
|
14
|
+
valid8r-0.7.3.dist-info/METADATA,sha256=xVe1O26O66LOsr2ehWjdnEKkB8vHcMUhINobS7FJq6g,9506
|
|
15
|
+
valid8r-0.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
valid8r-0.7.3.dist-info/entry_points.txt,sha256=x2MRzcIGcUR5e4GmhLadGdT7YbbohJRMUVubgaR8v_s,82
|
|
17
|
+
valid8r-0.7.3.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
|
|
18
|
+
valid8r-0.7.3.dist-info/RECORD,,
|
valid8r-0.7.2.dist-info/METADATA
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: valid8r
|
|
3
|
-
Version: 0.7.2
|
|
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>
|
|
10
|
-
License: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: cli,functional-programming,input,maybe-monad,parsing,validation
|
|
13
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
-
Classifier: Typing :: Typed
|
|
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
|
-
Description-Content-Type: text/markdown
|
|
31
|
-
|
|
32
|
-
# Valid8r
|
|
33
|
-
|
|
34
|
-
[](https://pypi.org/project/valid8r/)
|
|
35
|
-
[](https://pypi.org/project/valid8r/)
|
|
36
|
-
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
|
|
37
|
-
[](https://github.com/mikelane/valid8r/actions)
|
|
38
|
-
[](https://codecov.io/gh/mikelane/valid8r)
|
|
39
|
-
[](https://valid8r.readthedocs.io/)
|
|
40
|
-
|
|
41
|
-
[](https://pypi.org/project/valid8r/)
|
|
42
|
-
[](https://github.com/mikelane/valid8r/stargazers)
|
|
43
|
-
[](https://github.com/mikelane/valid8r/graphs/contributors)
|
|
44
|
-
[](https://github.com/astral-sh/ruff)
|
|
45
|
-
[](https://github.com/python-semantic-release/python-semantic-release)
|
|
46
|
-
|
|
47
|
-
A clean, flexible input validation library for Python applications.
|
|
48
|
-
|
|
49
|
-
## Features
|
|
50
|
-
|
|
51
|
-
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
|
|
52
|
-
- **Flexible Validation**: Chain validators and create custom validation rules
|
|
53
|
-
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
|
|
54
|
-
- **Input Prompting**: Prompt users for input with built-in validation
|
|
55
|
-
- **Structured Results**: Network parsers return rich dataclasses with parsed components
|
|
56
|
-
|
|
57
|
-
## Available Parsers
|
|
58
|
-
|
|
59
|
-
### Basic Types
|
|
60
|
-
- **Numbers**: `parse_int`, `parse_float`, `parse_complex`, `parse_decimal`
|
|
61
|
-
- **Text**: `parse_bool` (flexible true/false parsing)
|
|
62
|
-
- **Dates**: `parse_date` (ISO 8601 format)
|
|
63
|
-
- **UUIDs**: `parse_uuid` (with optional version validation)
|
|
64
|
-
|
|
65
|
-
### Collections
|
|
66
|
-
- **Lists**: `parse_list` (with element parser)
|
|
67
|
-
- **Dictionaries**: `parse_dict` (with key/value parsers)
|
|
68
|
-
- **Sets**: `parse_set` (with element parser)
|
|
69
|
-
|
|
70
|
-
### Network & Communication
|
|
71
|
-
- **IP Addresses**: `parse_ipv4`, `parse_ipv6`, `parse_ip` (either v4 or v6)
|
|
72
|
-
- **Networks**: `parse_cidr` (IPv4/IPv6 CIDR notation)
|
|
73
|
-
- **Phone Numbers**: `parse_phone` → PhoneNumber (NANP validation)
|
|
74
|
-
- **URLs**: `parse_url` → UrlParts (scheme, host, port, path, query, etc.)
|
|
75
|
-
- **Email**: `parse_email` → EmailAddress (normalized case)
|
|
76
|
-
|
|
77
|
-
### Advanced
|
|
78
|
-
- **Enums**: `parse_enum` (type-safe enum parsing)
|
|
79
|
-
- **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
|
|
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
|
-
|
|
105
|
-
## Installation
|
|
106
|
-
|
|
107
|
-
**Requirements**: Python 3.11 or higher
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
pip install valid8r
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Valid8r supports Python 3.11, 3.12, 3.13, and 3.14.
|
|
114
|
-
|
|
115
|
-
## Quick Start
|
|
116
|
-
|
|
117
|
-
```python
|
|
118
|
-
from valid8r import (
|
|
119
|
-
parsers,
|
|
120
|
-
prompt,
|
|
121
|
-
validators,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
# Simple validation
|
|
125
|
-
age = prompt.ask(
|
|
126
|
-
"Enter your age: ",
|
|
127
|
-
parser=parsers.parse_int,
|
|
128
|
-
validator=validators.minimum(0) & validators.maximum(120)
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
print(f"Your age is {age}")
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### IP parsing helpers
|
|
135
|
-
|
|
136
|
-
```python
|
|
137
|
-
from valid8r.core.maybe import Success, Failure
|
|
138
|
-
from valid8r.core import parsers
|
|
139
|
-
|
|
140
|
-
# IPv4 / IPv6 / generic IP
|
|
141
|
-
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
|
|
142
|
-
match parsers.parse_ip(text):
|
|
143
|
-
case Success(addr):
|
|
144
|
-
print("Parsed:", addr)
|
|
145
|
-
case Failure(err):
|
|
146
|
-
print("Error:", err)
|
|
147
|
-
|
|
148
|
-
# CIDR (strict by default)
|
|
149
|
-
match parsers.parse_cidr("10.0.0.0/8"):
|
|
150
|
-
case Success(net):
|
|
151
|
-
print("Network:", net) # 10.0.0.0/8
|
|
152
|
-
case Failure(err):
|
|
153
|
-
print("Error:", err)
|
|
154
|
-
|
|
155
|
-
# Non-strict masks host bits
|
|
156
|
-
match parsers.parse_cidr("10.0.0.1/24", strict=False):
|
|
157
|
-
case Success(net):
|
|
158
|
-
assert str(net) == "10.0.0.0/24"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### URL and Email helpers
|
|
162
|
-
|
|
163
|
-
```python
|
|
164
|
-
from valid8r.core.maybe import Success, Failure
|
|
165
|
-
from valid8r.core import parsers
|
|
166
|
-
|
|
167
|
-
# URL parsing with structured result (UrlParts)
|
|
168
|
-
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
|
|
169
|
-
case Success(u):
|
|
170
|
-
print(f"Scheme: {u.scheme}") # https
|
|
171
|
-
print(f"Host: {u.host}") # example.com
|
|
172
|
-
print(f"Port: {u.port}") # 8443
|
|
173
|
-
print(f"Path: {u.path}") # /x
|
|
174
|
-
print(f"Query: {u.query}") # {'q': '1'}
|
|
175
|
-
print(f"Fragment: {u.fragment}") # top
|
|
176
|
-
case Failure(err):
|
|
177
|
-
print("Error:", err)
|
|
178
|
-
|
|
179
|
-
# Email parsing with normalized case (EmailAddress)
|
|
180
|
-
match parsers.parse_email("First.Last+tag@Example.COM"):
|
|
181
|
-
case Success(e):
|
|
182
|
-
print(f"Local: {e.local}") # First.Last+tag
|
|
183
|
-
print(f"Domain: {e.domain}") # example.com (normalized)
|
|
184
|
-
case Failure(err):
|
|
185
|
-
print("Error:", err)
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Phone Number Parsing
|
|
189
|
-
|
|
190
|
-
```python
|
|
191
|
-
from valid8r.core.maybe import Success, Failure
|
|
192
|
-
from valid8r.core import parsers
|
|
193
|
-
|
|
194
|
-
# Phone number parsing with NANP validation (PhoneNumber)
|
|
195
|
-
match parsers.parse_phone("+1 (415) 555-2671"):
|
|
196
|
-
case Success(phone):
|
|
197
|
-
print(f"Country: {phone.country_code}") # 1
|
|
198
|
-
print(f"Area: {phone.area_code}") # 415
|
|
199
|
-
print(f"Exchange: {phone.exchange}") # 555
|
|
200
|
-
print(f"Subscriber: {phone.subscriber}") # 2671
|
|
201
|
-
|
|
202
|
-
# Format for display using properties
|
|
203
|
-
print(f"E.164: {phone.e164}") # +14155552671
|
|
204
|
-
print(f"National: {phone.national}") # (415) 555-2671
|
|
205
|
-
case Failure(err):
|
|
206
|
-
print("Error:", err)
|
|
207
|
-
|
|
208
|
-
# Also accepts various formats
|
|
209
|
-
for number in ["4155552671", "(415) 555-2671", "415-555-2671"]:
|
|
210
|
-
result = parsers.parse_phone(number)
|
|
211
|
-
assert result.is_success()
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Testing Support
|
|
215
|
-
|
|
216
|
-
Valid8r includes comprehensive testing utilities to help you verify your validation logic:
|
|
217
|
-
|
|
218
|
-
```python
|
|
219
|
-
from valid8r import Maybe, validators, parsers, prompt
|
|
220
|
-
from valid8r.testing import (
|
|
221
|
-
MockInputContext,
|
|
222
|
-
assert_maybe_success,
|
|
223
|
-
assert_maybe_failure,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
def validate_age(age: int) -> Maybe[int]:
|
|
227
|
-
"""Validate age is between 0 and 120."""
|
|
228
|
-
return (validators.minimum(0) & validators.maximum(120))(age)
|
|
229
|
-
|
|
230
|
-
# Test validation functions with assert helpers
|
|
231
|
-
result = validate_age(42)
|
|
232
|
-
assert assert_maybe_success(result, 42)
|
|
233
|
-
|
|
234
|
-
result = validate_age(-5)
|
|
235
|
-
assert assert_maybe_failure(result, "at least 0")
|
|
236
|
-
|
|
237
|
-
# Test prompts with mock input
|
|
238
|
-
with MockInputContext(["yes", "42", "invalid", "25"]):
|
|
239
|
-
# First prompt - returns Maybe, unwrap with value_or()
|
|
240
|
-
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
241
|
-
assert result.value_or(False) == True
|
|
242
|
-
|
|
243
|
-
# Second prompt - unwrap the Maybe result
|
|
244
|
-
result = prompt.ask(
|
|
245
|
-
"Age? ",
|
|
246
|
-
parser=parsers.parse_int,
|
|
247
|
-
validator=validate_age
|
|
248
|
-
)
|
|
249
|
-
age = result.value_or(None)
|
|
250
|
-
assert age == 42
|
|
251
|
-
|
|
252
|
-
# Third prompt will fail, fourth succeeds - unwrap result
|
|
253
|
-
result = prompt.ask(
|
|
254
|
-
"Age again? ",
|
|
255
|
-
parser=parsers.parse_int,
|
|
256
|
-
retry=1 # Retry once after failure
|
|
257
|
-
)
|
|
258
|
-
age = result.value_or(None)
|
|
259
|
-
assert age == 25
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Testing Utilities Reference
|
|
263
|
-
|
|
264
|
-
- **`assert_maybe_success(result, expected_value)`**: Assert that a Maybe is Success with the expected value
|
|
265
|
-
- **`assert_maybe_failure(result, error_substring)`**: Assert that a Maybe is Failure containing the error substring
|
|
266
|
-
- **`MockInputContext(inputs)`**: Context manager for mocking user input in tests
|
|
267
|
-
|
|
268
|
-
For more examples, see the [documentation](https://valid8r.readthedocs.io/).
|
|
269
|
-
|
|
270
|
-
## Development
|
|
271
|
-
|
|
272
|
-
This project uses `uv` for fast dependency management and `tox` for testing across Python versions.
|
|
273
|
-
|
|
274
|
-
### Setup
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
# Install uv
|
|
278
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
279
|
-
|
|
280
|
-
# Install dependencies
|
|
281
|
-
uv sync
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Running Tests
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
# Run all tests
|
|
288
|
-
uv run tox
|
|
289
|
-
|
|
290
|
-
# Run unit tests only
|
|
291
|
-
uv run pytest tests/unit
|
|
292
|
-
|
|
293
|
-
# Run BDD tests
|
|
294
|
-
uv run tox -e bdd
|
|
295
|
-
|
|
296
|
-
# Run with coverage
|
|
297
|
-
uv run pytest --cov=valid8r tests/unit
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
See [docs/migration-poetry-to-uv.md](docs/migration-poetry-to-uv.md) for the complete migration guide and command reference.
|
|
301
|
-
|
|
302
|
-
## License
|
|
303
|
-
|
|
304
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
305
|
-
|
|
306
|
-
Copyright (c) 2025 Mike Lane
|
|
File without changes
|
|
File without changes
|
|
File without changes
|