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.
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/valid8r.svg)](https://pypi.org/project/valid8r/)
37
+ [![Python versions](https://img.shields.io/pypi/pyversions/valid8r.svg)](https://pypi.org/project/valid8r/)
38
+ [![License](https://img.shields.io/github/license/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/blob/main/LICENSE)
39
+ [![CI](https://github.com/mikelane/valid8r/actions/workflows/ci.yml/badge.svg)](https://github.com/mikelane/valid8r/actions/workflows/ci.yml)
40
+ [![Release](https://github.com/mikelane/valid8r/actions/workflows/release.yml/badge.svg)](https://github.com/mikelane/valid8r/actions/workflows/release.yml)
41
+ [![codecov](https://codecov.io/gh/mikelane/valid8r/branch/main/graph/badge.svg)](https://codecov.io/gh/mikelane/valid8r)
42
+ [![Documentation](https://img.shields.io/readthedocs/valid8r.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ docs-build = scripts.docs:build
3
+ docs-serve = scripts.docs:serve
@@ -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.