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.

@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/valid8r.svg)](https://pypi.org/project/valid8r/)
35
+ [![Python versions](https://img.shields.io/pypi/pyversions/valid8r.svg)](https://pypi.org/project/valid8r/)
36
+ [![License](https://img.shields.io/github/license/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/blob/main/LICENSE)
37
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/mikelane/valid8r/ci.yml?branch=main)](https://github.com/mikelane/valid8r/actions)
38
+ [![codecov](https://codecov.io/gh/mikelane/valid8r/branch/main/graph/badge.svg)](https://codecov.io/gh/mikelane/valid8r)
39
+ [![Documentation](https://img.shields.io/readthedocs/valid8r.svg)](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.2.dist-info/METADATA,sha256=TKweTlPgU5pAjM8lQGCyESnabf-42B6-n-ataTX13Es,10711
15
- valid8r-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- valid8r-0.7.2.dist-info/entry_points.txt,sha256=x2MRzcIGcUR5e4GmhLadGdT7YbbohJRMUVubgaR8v_s,82
17
- valid8r-0.7.2.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
18
- valid8r-0.7.2.dist-info/RECORD,,
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,,
@@ -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
- [![PyPI version](https://img.shields.io/pypi/v/valid8r.svg)](https://pypi.org/project/valid8r/)
35
- [![Python versions](https://img.shields.io/pypi/pyversions/valid8r.svg)](https://pypi.org/project/valid8r/)
36
- [![License](https://img.shields.io/github/license/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/blob/main/LICENSE)
37
- [![CI Status](https://img.shields.io/github/actions/workflow/status/mikelane/valid8r/ci.yml?branch=main)](https://github.com/mikelane/valid8r/actions)
38
- [![codecov](https://codecov.io/gh/mikelane/valid8r/branch/main/graph/badge.svg)](https://codecov.io/gh/mikelane/valid8r)
39
- [![Documentation](https://img.shields.io/readthedocs/valid8r.svg)](https://valid8r.readthedocs.io/)
40
-
41
- [![PyPI downloads](https://img.shields.io/pypi/dm/valid8r.svg)](https://pypi.org/project/valid8r/)
42
- [![GitHub stars](https://img.shields.io/github/stars/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/stargazers)
43
- [![GitHub contributors](https://img.shields.io/github/contributors/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/graphs/contributors)
44
- [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
45
- [![Semantic Release](https://img.shields.io/badge/semantic--release-python-blue)](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