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 +39 -0
- valid8r/_version.py +34 -0
- valid8r/core/__init__.py +28 -0
- valid8r/core/combinators.py +89 -0
- valid8r/core/maybe.py +170 -0
- valid8r/core/parsers.py +2115 -0
- valid8r/core/validators.py +982 -0
- valid8r/integrations/__init__.py +57 -0
- valid8r/integrations/click.py +143 -0
- valid8r/integrations/env.py +220 -0
- valid8r/integrations/pydantic.py +196 -0
- valid8r/prompt/__init__.py +8 -0
- valid8r/prompt/basic.py +229 -0
- valid8r/py.typed +0 -0
- valid8r/testing/__init__.py +32 -0
- valid8r/testing/assertions.py +67 -0
- valid8r/testing/generators.py +283 -0
- valid8r/testing/mock_input.py +84 -0
- valid8r-1.6.0.dist-info/METADATA +504 -0
- valid8r-1.6.0.dist-info/RECORD +23 -0
- valid8r-1.6.0.dist-info/WHEEL +4 -0
- valid8r-1.6.0.dist-info/entry_points.txt +3 -0
- valid8r-1.6.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: valid8r
|
|
3
|
+
Version: 1.6.0
|
|
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
|
+
Provides-Extra: click
|
|
31
|
+
Requires-Dist: click>=8.0.0; extra == 'click'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Valid8r
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/valid8r/)
|
|
37
|
+
[](https://pypi.org/project/valid8r/)
|
|
38
|
+
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
|
|
39
|
+
[](https://github.com/mikelane/valid8r/actions/workflows/ci.yml)
|
|
40
|
+
[](https://github.com/mikelane/valid8r/actions/workflows/release.yml)
|
|
41
|
+
[](https://codecov.io/gh/mikelane/valid8r)
|
|
42
|
+
[](https://valid8r.readthedocs.io/)
|
|
43
|
+
|
|
44
|
+
**Clean, composable input validation for Python using functional programming patterns.**
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from valid8r import parsers, validators, prompt
|
|
50
|
+
|
|
51
|
+
# Parse and validate user input with rich error messages
|
|
52
|
+
age = prompt.ask(
|
|
53
|
+
"Enter your age: ",
|
|
54
|
+
parser=parsers.parse_int,
|
|
55
|
+
validator=validators.minimum(0) & validators.maximum(120)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(f"Your age is {age}")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why Valid8r?
|
|
62
|
+
|
|
63
|
+
**Type-Safe Parsing**: Every parser returns `Maybe[T]` (Success or Failure), making error handling explicit and composable.
|
|
64
|
+
|
|
65
|
+
**Rich Structured Results**: Network parsers return dataclasses with parsed components—no more manual URL/email splitting.
|
|
66
|
+
|
|
67
|
+
**Chainable Validators**: Combine validators using `&` (and), `|` (or), and `~` (not) operators for complex validation logic.
|
|
68
|
+
|
|
69
|
+
**Framework Integrations**: Built-in support for Pydantic (always included) and optional Click integration for CLI apps.
|
|
70
|
+
|
|
71
|
+
**Interactive Prompts**: Built-in user input prompting with automatic retry and validation.
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
### Installation
|
|
76
|
+
|
|
77
|
+
**Basic installation** (includes Pydantic integration):
|
|
78
|
+
```bash
|
|
79
|
+
pip install valid8r
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**With optional framework integrations**:
|
|
83
|
+
```bash
|
|
84
|
+
# Click integration for CLI applications
|
|
85
|
+
pip install 'valid8r[click]'
|
|
86
|
+
|
|
87
|
+
# All optional integrations
|
|
88
|
+
pip install 'valid8r[click]'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Requirements**: Python 3.11 or higher
|
|
92
|
+
|
|
93
|
+
| Feature | Installation | Import |
|
|
94
|
+
|---------|--------------|--------|
|
|
95
|
+
| Core parsers & validators | `pip install valid8r` | `from valid8r import parsers, validators` |
|
|
96
|
+
| Pydantic integration | _included by default_ | `from valid8r.integrations import validator_from_parser` |
|
|
97
|
+
| Click integration (CLI) | `pip install 'valid8r[click]'` | `from valid8r.integrations import ParamTypeAdapter` |
|
|
98
|
+
|
|
99
|
+
### Basic Parsing
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from valid8r import parsers
|
|
103
|
+
from valid8r.core.maybe import Success, Failure
|
|
104
|
+
|
|
105
|
+
# Parse integers with automatic error handling
|
|
106
|
+
match parsers.parse_int("42"):
|
|
107
|
+
case Success(value):
|
|
108
|
+
print(f"Parsed: {value}") # Parsed: 42
|
|
109
|
+
case Failure(error):
|
|
110
|
+
print(f"Error: {error}")
|
|
111
|
+
|
|
112
|
+
# Parse dates (ISO 8601 format)
|
|
113
|
+
result = parsers.parse_date("2025-01-15")
|
|
114
|
+
assert result.is_success()
|
|
115
|
+
|
|
116
|
+
# Parse UUIDs with version validation
|
|
117
|
+
result = parsers.parse_uuid("550e8400-e29b-41d4-a716-446655440000", version=4)
|
|
118
|
+
assert result.is_success()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Validation with Combinators
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from valid8r import validators
|
|
125
|
+
|
|
126
|
+
# Combine validators using operators
|
|
127
|
+
age_validator = validators.minimum(0) & validators.maximum(120)
|
|
128
|
+
result = age_validator(42)
|
|
129
|
+
assert result.is_success()
|
|
130
|
+
|
|
131
|
+
# String validation
|
|
132
|
+
password_validator = (
|
|
133
|
+
validators.length(8, 128) &
|
|
134
|
+
validators.matches_regex(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]')
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Set validation
|
|
138
|
+
tags_validator = validators.subset_of({'python', 'rust', 'go', 'typescript'})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Structured Network Parsing
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from valid8r import parsers
|
|
145
|
+
|
|
146
|
+
# Parse URLs into structured components
|
|
147
|
+
match parsers.parse_url("https://user:pass@example.com:8443/path?query=1#fragment"):
|
|
148
|
+
case Success(url):
|
|
149
|
+
print(f"Scheme: {url.scheme}") # https
|
|
150
|
+
print(f"Host: {url.host}") # example.com
|
|
151
|
+
print(f"Port: {url.port}") # 8443
|
|
152
|
+
print(f"Path: {url.path}") # /path
|
|
153
|
+
print(f"Query: {url.query}") # {'query': '1'}
|
|
154
|
+
print(f"Fragment: {url.fragment}") # fragment
|
|
155
|
+
|
|
156
|
+
# Parse emails with normalized domains
|
|
157
|
+
match parsers.parse_email("User@Example.COM"):
|
|
158
|
+
case Success(email):
|
|
159
|
+
print(f"Local: {email.local}") # User
|
|
160
|
+
print(f"Domain: {email.domain}") # example.com (normalized)
|
|
161
|
+
|
|
162
|
+
# Parse phone numbers (NANP format)
|
|
163
|
+
match parsers.parse_phone("+1 (415) 555-2671"):
|
|
164
|
+
case Success(phone):
|
|
165
|
+
print(f"E.164: {phone.e164}") # +14155552671
|
|
166
|
+
print(f"National: {phone.national}") # (415) 555-2671
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Collection Parsing
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from valid8r import parsers
|
|
173
|
+
|
|
174
|
+
# Parse lists with element validation
|
|
175
|
+
result = parsers.parse_list("1,2,3,4,5", element_parser=parsers.parse_int)
|
|
176
|
+
assert result.value_or([]) == [1, 2, 3, 4, 5]
|
|
177
|
+
|
|
178
|
+
# Parse dictionaries with key/value parsers
|
|
179
|
+
result = parsers.parse_dict(
|
|
180
|
+
"name=Alice,age=30",
|
|
181
|
+
key_parser=lambda x: Success(x),
|
|
182
|
+
value_parser=lambda x: parsers.parse_int(x) if x.isdigit() else Success(x)
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Interactive Prompting
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from valid8r import prompt, parsers, validators
|
|
190
|
+
|
|
191
|
+
# Prompt with validation and automatic retry
|
|
192
|
+
email = prompt.ask(
|
|
193
|
+
"Email address: ",
|
|
194
|
+
parser=parsers.parse_email,
|
|
195
|
+
retry=2 # Retry twice on invalid input
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Combine parsing and validation
|
|
199
|
+
port = prompt.ask(
|
|
200
|
+
"Server port: ",
|
|
201
|
+
parser=parsers.parse_int,
|
|
202
|
+
validator=validators.between(1024, 65535),
|
|
203
|
+
retry=3
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Environment Variables
|
|
208
|
+
|
|
209
|
+
Load typed, validated configuration from environment variables following 12-factor app principles:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from valid8r.integrations.env import EnvSchema, EnvField, load_env_config
|
|
213
|
+
from valid8r import parsers, validators
|
|
214
|
+
from valid8r.core.maybe import Success, Failure
|
|
215
|
+
|
|
216
|
+
# Define configuration schema
|
|
217
|
+
schema = EnvSchema(fields={
|
|
218
|
+
'port': EnvField(
|
|
219
|
+
parser=lambda x: parsers.parse_int(x).bind(validators.between(1024, 65535)),
|
|
220
|
+
default=8080
|
|
221
|
+
),
|
|
222
|
+
'debug': EnvField(parser=parsers.parse_bool, default=False),
|
|
223
|
+
'database_url': EnvField(parser=parsers.parse_str, required=True),
|
|
224
|
+
'admin_email': EnvField(parser=parsers.parse_email, required=True),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
# Load and validate configuration
|
|
228
|
+
result = load_env_config(schema, prefix='APP_')
|
|
229
|
+
|
|
230
|
+
match result:
|
|
231
|
+
case Success(config):
|
|
232
|
+
# All values are typed and validated
|
|
233
|
+
port = config['port'] # int (validated 1024-65535)
|
|
234
|
+
debug = config['debug'] # bool (not str!)
|
|
235
|
+
db = config['database_url'] # str (required, guaranteed present)
|
|
236
|
+
email = config['admin_email'] # EmailAddress (validated format)
|
|
237
|
+
case Failure(error):
|
|
238
|
+
print(f"Configuration error: {error}")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Features:**
|
|
242
|
+
|
|
243
|
+
- Type-safe parsing (no more string-to-int conversions)
|
|
244
|
+
- Declarative validation with composable parsers
|
|
245
|
+
- Required vs optional fields with sensible defaults
|
|
246
|
+
- Nested schemas for hierarchical configuration
|
|
247
|
+
- Clear error messages for missing or invalid values
|
|
248
|
+
|
|
249
|
+
See [Environment Variables Guide](https://valid8r.readthedocs.io/en/latest/examples/environment_variables.html) for complete examples including FastAPI, Docker, and Kubernetes deployment patterns.
|
|
250
|
+
|
|
251
|
+
### Framework Integrations
|
|
252
|
+
|
|
253
|
+
#### Pydantic Integration (Always Included)
|
|
254
|
+
|
|
255
|
+
Convert valid8r parsers into Pydantic field validators:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from pydantic import BaseModel, field_validator
|
|
259
|
+
from valid8r import parsers, validators
|
|
260
|
+
from valid8r.integrations import validator_from_parser
|
|
261
|
+
|
|
262
|
+
class User(BaseModel):
|
|
263
|
+
age: int
|
|
264
|
+
|
|
265
|
+
@field_validator('age', mode='before')
|
|
266
|
+
@classmethod
|
|
267
|
+
def validate_age(cls, v):
|
|
268
|
+
# Parse string to int, then validate 0-120 range
|
|
269
|
+
age_parser = lambda x: parsers.parse_int(x).bind(
|
|
270
|
+
validators.between(0, 120)
|
|
271
|
+
)
|
|
272
|
+
return validator_from_parser(age_parser)(v)
|
|
273
|
+
|
|
274
|
+
user = User(age="25") # Accepts string, validates, returns int
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Works seamlessly with nested models, lists, and complex Pydantic schemas. See [Pydantic Integration Examples](https://valid8r.readthedocs.io/en/latest/examples/pydantic_integration.html).
|
|
278
|
+
|
|
279
|
+
#### Click Integration (Optional)
|
|
280
|
+
|
|
281
|
+
Install: `pip install 'valid8r[click]'`
|
|
282
|
+
|
|
283
|
+
Integrate valid8r parsers into Click CLI applications:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
import click
|
|
287
|
+
from valid8r import parsers
|
|
288
|
+
from valid8r.integrations import ParamTypeAdapter
|
|
289
|
+
|
|
290
|
+
@click.command()
|
|
291
|
+
@click.option('--email', type=ParamTypeAdapter(parsers.parse_email))
|
|
292
|
+
def send_mail(email):
|
|
293
|
+
"""Send an email with validated address."""
|
|
294
|
+
click.echo(f"Sending to {email.local}@{email.domain}")
|
|
295
|
+
|
|
296
|
+
# Automatically validates email format and provides rich error messages
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
See [Click Integration Examples](https://valid8r.readthedocs.io/en/latest/examples/click_integration.html).
|
|
300
|
+
|
|
301
|
+
## Features
|
|
302
|
+
|
|
303
|
+
### Parsers
|
|
304
|
+
|
|
305
|
+
**Basic Types**:
|
|
306
|
+
- `parse_int`, `parse_float`, `parse_bool`, `parse_decimal`, `parse_complex`
|
|
307
|
+
- `parse_date` (ISO 8601), `parse_uuid` (with version validation)
|
|
308
|
+
|
|
309
|
+
**Collections**:
|
|
310
|
+
- `parse_list`, `parse_dict`, `parse_set` (with element parsers)
|
|
311
|
+
|
|
312
|
+
**Network & Communication**:
|
|
313
|
+
- `parse_ipv4`, `parse_ipv6`, `parse_ip`, `parse_cidr`
|
|
314
|
+
- `parse_url` → `UrlParts` (structured URL components)
|
|
315
|
+
- `parse_email` → `EmailAddress` (normalized domain)
|
|
316
|
+
- `parse_phone` → `PhoneNumber` (NANP validation with E.164 formatting)
|
|
317
|
+
|
|
318
|
+
**Advanced**:
|
|
319
|
+
- `parse_enum` (type-safe enum parsing)
|
|
320
|
+
- `create_parser`, `make_parser`, `validated_parser` (custom parser factories)
|
|
321
|
+
|
|
322
|
+
### Validators
|
|
323
|
+
|
|
324
|
+
**Numeric**: `minimum`, `maximum`, `between`
|
|
325
|
+
|
|
326
|
+
**String**: `non_empty_string`, `matches_regex`, `length`
|
|
327
|
+
|
|
328
|
+
**Collection**: `in_set`, `unique_items`, `subset_of`, `superset_of`, `is_sorted`
|
|
329
|
+
|
|
330
|
+
**Custom**: `predicate` (create validators from any function)
|
|
331
|
+
|
|
332
|
+
**Combinators**: Combine validators using `&` (and), `|` (or), `~` (not)
|
|
333
|
+
|
|
334
|
+
### Testing Utilities
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from valid8r.testing import (
|
|
338
|
+
assert_maybe_success,
|
|
339
|
+
assert_maybe_failure,
|
|
340
|
+
MockInputContext,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Test validation logic
|
|
344
|
+
result = validators.minimum(0)(42)
|
|
345
|
+
assert assert_maybe_success(result, 42)
|
|
346
|
+
|
|
347
|
+
result = validators.minimum(0)(-5)
|
|
348
|
+
assert assert_maybe_failure(result, "at least 0")
|
|
349
|
+
|
|
350
|
+
# Mock user input for testing prompts
|
|
351
|
+
with MockInputContext(["invalid", "valid@example.com"]):
|
|
352
|
+
result = prompt.ask("Email: ", parser=parsers.parse_email, retry=1)
|
|
353
|
+
assert result.is_success()
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Documentation
|
|
357
|
+
|
|
358
|
+
**Full documentation**: [valid8r.readthedocs.io](https://valid8r.readthedocs.io/)
|
|
359
|
+
|
|
360
|
+
- [API Reference](https://valid8r.readthedocs.io/en/latest/api.html)
|
|
361
|
+
- [Parser Guide](https://valid8r.readthedocs.io/en/latest/parsers.html)
|
|
362
|
+
- [Validator Guide](https://valid8r.readthedocs.io/en/latest/validators.html)
|
|
363
|
+
- [Testing Guide](https://valid8r.readthedocs.io/en/latest/testing.html)
|
|
364
|
+
|
|
365
|
+
## Security
|
|
366
|
+
|
|
367
|
+
### Reporting Vulnerabilities
|
|
368
|
+
|
|
369
|
+
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
370
|
+
|
|
371
|
+
Report security issues privately to **mikelane@gmail.com** or via [GitHub Security Advisories](https://github.com/mikelane/valid8r/security/advisories/new).
|
|
372
|
+
|
|
373
|
+
See [SECURITY.md](SECURITY.md) for our complete security policy, supported versions, and response timeline.
|
|
374
|
+
|
|
375
|
+
### Production Deployment
|
|
376
|
+
|
|
377
|
+
Valid8r is designed for parsing **trusted user input** in web applications. For production deployments:
|
|
378
|
+
|
|
379
|
+
1. **Enforce input size limits** at the framework level (recommended: 10KB max request size)
|
|
380
|
+
2. **Implement rate limiting** for validation endpoints (recommended: 10 requests/minute)
|
|
381
|
+
3. **Use defense in depth**: Framework → Application → Parser validation
|
|
382
|
+
4. **Monitor and log** validation failures for security analysis
|
|
383
|
+
|
|
384
|
+
**Example - Flask Defense in Depth:**
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
from flask import Flask, request
|
|
388
|
+
from valid8r import parsers
|
|
389
|
+
|
|
390
|
+
app = Flask(__name__)
|
|
391
|
+
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 # Layer 1: Framework limit
|
|
392
|
+
|
|
393
|
+
@app.route('/submit', methods=['POST'])
|
|
394
|
+
def submit():
|
|
395
|
+
phone = request.form.get('phone', '')
|
|
396
|
+
|
|
397
|
+
# Layer 2: Application validation
|
|
398
|
+
if len(phone) > 100:
|
|
399
|
+
return "Phone too long", 400
|
|
400
|
+
|
|
401
|
+
# Layer 3: Parser validation
|
|
402
|
+
result = parsers.parse_phone(phone)
|
|
403
|
+
if result.is_failure():
|
|
404
|
+
return "Invalid phone format", 400
|
|
405
|
+
|
|
406
|
+
return process_phone(result.value_or(None))
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
See [Production Deployment Guide](docs/security/production-deployment.md) for framework-specific examples (Flask, Django, FastAPI).
|
|
410
|
+
|
|
411
|
+
### Security Boundaries
|
|
412
|
+
|
|
413
|
+
Valid8r provides **input validation**, not protection against:
|
|
414
|
+
|
|
415
|
+
- ❌ SQL injection - Use parameterized queries / ORMs
|
|
416
|
+
- ❌ XSS attacks - Use output encoding / template engines
|
|
417
|
+
- ❌ CSRF attacks - Use CSRF tokens / SameSite cookies
|
|
418
|
+
- ❌ DDoS attacks - Use rate limiting / CDN / WAF
|
|
419
|
+
|
|
420
|
+
**Parser Input Limits:**
|
|
421
|
+
|
|
422
|
+
| Parser | Max Input | Notes |
|
|
423
|
+
|--------|-----------|-------|
|
|
424
|
+
| `parse_email()` | 254 chars | RFC 5321 maximum |
|
|
425
|
+
| `parse_phone()` | 100 chars | International + extension |
|
|
426
|
+
| `parse_url()` | 2048 chars | Browser URL limit |
|
|
427
|
+
| `parse_uuid()` | 36 chars | Standard UUID format |
|
|
428
|
+
| `parse_ip()` | 45 chars | IPv6 maximum |
|
|
429
|
+
|
|
430
|
+
All parsers include built-in DoS protection with early length validation before expensive operations.
|
|
431
|
+
|
|
432
|
+
See [SECURITY.md](SECURITY.md) for complete security documentation.
|
|
433
|
+
|
|
434
|
+
## Contributing
|
|
435
|
+
|
|
436
|
+
We welcome contributions! **All contributions must be made via forks** - please do not create branches directly in the main repository.
|
|
437
|
+
|
|
438
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for complete guidelines.
|
|
439
|
+
|
|
440
|
+
**Quick links**:
|
|
441
|
+
- [Fork-Based Workflow Requirement](CONTRIBUTING.md#fork-based-contributions-required)
|
|
442
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
443
|
+
- [Development Setup](CONTRIBUTING.md#development-setup)
|
|
444
|
+
- [Commit Message Format](CONTRIBUTING.md#commit-messages)
|
|
445
|
+
- [Pull Request Process](CONTRIBUTING.md#pull-request-process)
|
|
446
|
+
|
|
447
|
+
### Development Quick Start
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
# 1. Fork the repository on GitHub
|
|
451
|
+
# Visit: https://github.com/mikelane/valid8r
|
|
452
|
+
|
|
453
|
+
# 2. Clone YOUR fork (not the upstream repo)
|
|
454
|
+
git clone https://github.com/YOUR-USERNAME/valid8r
|
|
455
|
+
cd valid8r
|
|
456
|
+
|
|
457
|
+
# 3. Add upstream remote
|
|
458
|
+
git remote add upstream https://github.com/mikelane/valid8r.git
|
|
459
|
+
|
|
460
|
+
# 4. Install uv (fast dependency manager)
|
|
461
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
462
|
+
|
|
463
|
+
# 5. Install dependencies
|
|
464
|
+
uv sync
|
|
465
|
+
|
|
466
|
+
# 6. Run tests
|
|
467
|
+
uv run tox
|
|
468
|
+
|
|
469
|
+
# 7. Run linters
|
|
470
|
+
uv run ruff check .
|
|
471
|
+
uv run ruff format .
|
|
472
|
+
uv run mypy valid8r
|
|
473
|
+
|
|
474
|
+
# 8. Create a feature branch and make your changes
|
|
475
|
+
git checkout -b feat/your-feature
|
|
476
|
+
|
|
477
|
+
# 9. Push to YOUR fork and create a PR
|
|
478
|
+
git push origin feat/your-feature
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Project Status
|
|
482
|
+
|
|
483
|
+
Valid8r is in active development (v0.7.x). The API is stabilizing but may change before v1.0.0.
|
|
484
|
+
|
|
485
|
+
- ✅ Core parsers and validators
|
|
486
|
+
- ✅ Maybe monad error handling
|
|
487
|
+
- ✅ Interactive prompting
|
|
488
|
+
- ✅ Network parsers (URL, Email, IP, Phone)
|
|
489
|
+
- ✅ Collection parsers
|
|
490
|
+
- ✅ Comprehensive testing utilities
|
|
491
|
+
- 🚧 Additional validators (in progress)
|
|
492
|
+
- 🚧 Custom error types (planned)
|
|
493
|
+
|
|
494
|
+
See [ROADMAP.md](ROADMAP.md) for planned features.
|
|
495
|
+
|
|
496
|
+
## License
|
|
497
|
+
|
|
498
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
499
|
+
|
|
500
|
+
Copyright (c) 2025 Mike Lane
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
**Made with ❤️ for the Python community**
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
valid8r/__init__.py,sha256=7UBvEV2koUQcgQkpXBJe29-P3UYEeW2Vb8zyPI4ySpQ,692
|
|
2
|
+
valid8r/_version.py,sha256=QuifT9FwIKV3Bt8At2-CvH3sl5xacVDBQsWlFAqVjbg,704
|
|
3
|
+
valid8r/core/__init__.py,sha256=ASOdzqCtpZHbHjjYMZkb78Z-nKxtD26ruTY0bd43ImA,520
|
|
4
|
+
valid8r/core/combinators.py,sha256=KvRiDEqoZgH58cBYPO6SW9pdtkyijk0lS8aGSB5DbO4,2349
|
|
5
|
+
valid8r/core/maybe.py,sha256=kuD5SsWc6148FkcLBcwmRTPi6D7H4w1dRT8lPrUvYMw,4643
|
|
6
|
+
valid8r/core/parsers.py,sha256=nAukJFaX4Lk4XQjoFUbPRWnhR5k_LVSfJ87kXn1gJj8,69913
|
|
7
|
+
valid8r/core/validators.py,sha256=COuSb3pA5-oYv5B0UtdtLGc4Gr_XFqhJABUh000Gwiw,32343
|
|
8
|
+
valid8r/integrations/__init__.py,sha256=T40yFLGJUr9HJQIQVawF21A9UqWQ_18XUQl2OCSaGqg,1872
|
|
9
|
+
valid8r/integrations/click.py,sha256=u3jb-SuZyBk7pPAm2MTJoIcxRilDxPiLlQ81dHfLdzo,4333
|
|
10
|
+
valid8r/integrations/env.py,sha256=OPpYSPlgJhBMsK1BM_IN5rd4opw8hqss-drDE5Lo4Rc,6562
|
|
11
|
+
valid8r/integrations/pydantic.py,sha256=xqgn2-XFz_eg7WStoH8xJtCfqajPNjvv6E9dH_j3d-c,6356
|
|
12
|
+
valid8r/prompt/__init__.py,sha256=XYB3NEp-tmqT6fGmETVEeXd7Urj0M4ijlwdRAjj-rG8,175
|
|
13
|
+
valid8r/prompt/basic.py,sha256=fFARuy5nGTE7xM3dB1jpRC3OPNmp4WwaymFMz7BSgdo,7635
|
|
14
|
+
valid8r/testing/__init__.py,sha256=8mk54zt0Ai2dK0a3GMOTfDPsVQWXaS6uvQJDrkRV9hs,779
|
|
15
|
+
valid8r/testing/assertions.py,sha256=9KGz1JooCoyikyxMX7VuXB9VYAtj-4H_LPYFGdvS-ps,1820
|
|
16
|
+
valid8r/testing/generators.py,sha256=kAV6NRO9x1gPy0BfGs07ETVxjpTIxOZyV9wH2BA1nHA,8791
|
|
17
|
+
valid8r/testing/mock_input.py,sha256=9GRT7h0PCh9Dea-OcQ5Uls7YqhsTdqMWuX6I6ZlW1aw,2334
|
|
18
|
+
valid8r/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
valid8r-1.6.0.dist-info/METADATA,sha256=zkkJs84y-tC_4yXAziSC-Z3PEFSWID-q6XXm_WOrsgw,16588
|
|
20
|
+
valid8r-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
+
valid8r-1.6.0.dist-info/entry_points.txt,sha256=x2MRzcIGcUR5e4GmhLadGdT7YbbohJRMUVubgaR8v_s,82
|
|
22
|
+
valid8r-1.6.0.dist-info/licenses/LICENSE,sha256=JpEmJvRYOTIUt0UjgvpDrd3U94Wnbt_Grr5z-xU2jtk,1066
|
|
23
|
+
valid8r-1.6.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mike Lane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|