valid8r 0.7.2__tar.gz → 0.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of valid8r might be problematic. Click here for more details.

Files changed (128) hide show
  1. valid8r-0.7.3/PKG-INFO +305 -0
  2. valid8r-0.7.3/README.md +274 -0
  3. {valid8r-0.7.2 → valid8r-0.7.3}/pyproject.toml +1 -1
  4. {valid8r-0.7.2 → valid8r-0.7.3}/uv.lock +1 -1
  5. valid8r-0.7.2/PKG-INFO +0 -306
  6. valid8r-0.7.2/README.md +0 -275
  7. {valid8r-0.7.2 → valid8r-0.7.3}/.coveragerc +0 -0
  8. {valid8r-0.7.2 → valid8r-0.7.3}/.cursorrules +0 -0
  9. {valid8r-0.7.2 → valid8r-0.7.3}/.github/CICD_TEST.md +0 -0
  10. {valid8r-0.7.2 → valid8r-0.7.3}/.github/CODEOWNERS +0 -0
  11. {valid8r-0.7.2 → valid8r-0.7.3}/.github/CONVENTIONAL_COMMITS.md +0 -0
  12. {valid8r-0.7.2 → valid8r-0.7.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {valid8r-0.7.2 → valid8r-0.7.3}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  14. {valid8r-0.7.2 → valid8r-0.7.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {valid8r-0.7.2 → valid8r-0.7.3}/.github/PYPI_TOKEN_SETUP_GUIDE.md +0 -0
  16. {valid8r-0.7.2 → valid8r-0.7.3}/.github/QUICK_REFERENCE.md +0 -0
  17. {valid8r-0.7.2 → valid8r-0.7.3}/.github/README.md +0 -0
  18. {valid8r-0.7.2 → valid8r-0.7.3}/.github/SETUP_CHECKLIST.md +0 -0
  19. {valid8r-0.7.2 → valid8r-0.7.3}/.github/WORKFLOWS.md +0 -0
  20. {valid8r-0.7.2 → valid8r-0.7.3}/.github/WORKFLOW_DIAGRAM.md +0 -0
  21. {valid8r-0.7.2 → valid8r-0.7.3}/.github/dependabot.yml +0 -0
  22. {valid8r-0.7.2 → valid8r-0.7.3}/.github/labeler.yml +0 -0
  23. {valid8r-0.7.2 → valid8r-0.7.3}/.github/pull_request_template.md +0 -0
  24. {valid8r-0.7.2 → valid8r-0.7.3}/.github/release.yml +0 -0
  25. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/ci.yml +0 -0
  26. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/labeler.yml +0 -0
  27. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/publish-pypi.yml +0 -0
  28. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/semantic-release.yml +0 -0
  29. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/size-label.yml +0 -0
  30. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/stale.yml +0 -0
  31. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/version-and-release.yml +0 -0
  32. {valid8r-0.7.2 → valid8r-0.7.3}/.github/workflows/welcome.yml +0 -0
  33. {valid8r-0.7.2 → valid8r-0.7.3}/.gitignore +0 -0
  34. {valid8r-0.7.2 → valid8r-0.7.3}/.pre-commit-config.yaml +0 -0
  35. {valid8r-0.7.2 → valid8r-0.7.3}/.python-version +0 -0
  36. {valid8r-0.7.2 → valid8r-0.7.3}/.readthedocs.yaml +0 -0
  37. {valid8r-0.7.2 → valid8r-0.7.3}/CICD_SETUP_SUMMARY.md +0 -0
  38. {valid8r-0.7.2 → valid8r-0.7.3}/CLAUDE.md +0 -0
  39. {valid8r-0.7.2 → valid8r-0.7.3}/CODE_OF_CONDUCT.md +0 -0
  40. {valid8r-0.7.2 → valid8r-0.7.3}/CONTRIBUTING.md +0 -0
  41. {valid8r-0.7.2 → valid8r-0.7.3}/LICENSE +0 -0
  42. {valid8r-0.7.2 → valid8r-0.7.3}/QA_REPORT_WEB_PARSERS_V0.6.0.md +0 -0
  43. {valid8r-0.7.2 → valid8r-0.7.3}/QA_VALIDATION_SUMMARY.md +0 -0
  44. {valid8r-0.7.2 → valid8r-0.7.3}/ROADMAP.md +0 -0
  45. {valid8r-0.7.2 → valid8r-0.7.3}/SECURITY.md +0 -0
  46. {valid8r-0.7.2 → valid8r-0.7.3}/docs/__init__.py +0 -0
  47. {valid8r-0.7.2 → valid8r-0.7.3}/docs/_static/css/custom.css +0 -0
  48. {valid8r-0.7.2 → valid8r-0.7.3}/docs/api/core.rst +0 -0
  49. {valid8r-0.7.2 → valid8r-0.7.3}/docs/api/prompt.rst +0 -0
  50. {valid8r-0.7.2 → valid8r-0.7.3}/docs/conf.py +0 -0
  51. {valid8r-0.7.2 → valid8r-0.7.3}/docs/development/changelog.rst +0 -0
  52. {valid8r-0.7.2 → valid8r-0.7.3}/docs/development/contributing.rst +0 -0
  53. {valid8r-0.7.2 → valid8r-0.7.3}/docs/development/testing.rst +0 -0
  54. {valid8r-0.7.2 → valid8r-0.7.3}/docs/examples/basic_example.rst +0 -0
  55. {valid8r-0.7.2 → valid8r-0.7.3}/docs/examples/chaining_validators.rst +0 -0
  56. {valid8r-0.7.2 → valid8r-0.7.3}/docs/examples/custom_validators.rst +0 -0
  57. {valid8r-0.7.2 → valid8r-0.7.3}/docs/examples/fastapi_integration.rst +0 -0
  58. {valid8r-0.7.2 → valid8r-0.7.3}/docs/examples/interactive_prompts.rst +0 -0
  59. {valid8r-0.7.2 → valid8r-0.7.3}/docs/index.rst +0 -0
  60. {valid8r-0.7.2 → valid8r-0.7.3}/docs/migration-poetry-to-uv.md +0 -0
  61. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/advanced_usage.rst +0 -0
  62. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/getting_started.rst +0 -0
  63. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/maybe_monad.rst +0 -0
  64. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/parsers.rst +0 -0
  65. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/prompting.rst +0 -0
  66. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/testing.rst +0 -0
  67. {valid8r-0.7.2 → valid8r-0.7.3}/docs/user_guide/validators.rst +0 -0
  68. {valid8r-0.7.2 → valid8r-0.7.3}/scripts/__init__.py +0 -0
  69. {valid8r-0.7.2 → valid8r-0.7.3}/scripts/docs.py +0 -0
  70. {valid8r-0.7.2 → valid8r-0.7.3}/smoke_test.py +0 -0
  71. {valid8r-0.7.2 → valid8r-0.7.3}/tests/__init__.py +0 -0
  72. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/__init__.py +0 -0
  73. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/conftest.py +0 -0
  74. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/environment.py +0 -0
  75. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/clean_type_parsing.feature +0 -0
  76. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/collection_parsing.feature +0 -0
  77. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/interactive_prompts.feature +0 -0
  78. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/phone_parsing.feature +0 -0
  79. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/testing_utilities.feature +0 -0
  80. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/url_email_parsing.feature +0 -0
  81. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/validator_combinators.feature +0 -0
  82. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/validators.feature +0 -0
  83. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/features/web_parsers.feature +0 -0
  84. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/__init__.py +0 -0
  85. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/clean_type_parsing_steps.py +0 -0
  86. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/collection_parsing_steps.py +0 -0
  87. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/interactive_prompts_steps.py +0 -0
  88. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/phone_parsing_steps.py +0 -0
  89. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/testing_utilities_steps.py +0 -0
  90. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/url_email_parsing_steps.py +0 -0
  91. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/validator_combinators_steps.py +0 -0
  92. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/validators_steps.py +0 -0
  93. {valid8r-0.7.2 → valid8r-0.7.3}/tests/bdd/steps/web_parsers_steps.py +0 -0
  94. {valid8r-0.7.2 → valid8r-0.7.3}/tests/integration/__init__.py +0 -0
  95. {valid8r-0.7.2 → valid8r-0.7.3}/tests/integration/test_validator.py +0 -0
  96. {valid8r-0.7.2 → valid8r-0.7.3}/tests/qa_security_web_parsers.py +0 -0
  97. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/__init__.py +0 -0
  98. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/conftest.py +0 -0
  99. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_combinators.py +0 -0
  100. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_decimal_parser.py +0 -0
  101. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_dict_parser.py +0 -0
  102. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_generators.py +0 -0
  103. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_ip_parsers.py +0 -0
  104. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_list_parser.py +0 -0
  105. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_maybe.py +0 -0
  106. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_parsers.py +0 -0
  107. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_phone_parsing.py +0 -0
  108. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_prompt.py +0 -0
  109. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_public_api.py +0 -0
  110. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_testing_utilities.py +0 -0
  111. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_url_email_parsers.py +0 -0
  112. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_uuid_parser.py +0 -0
  113. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_validators.py +0 -0
  114. {valid8r-0.7.2 → valid8r-0.7.3}/tests/unit/test_web_parsers.py +0 -0
  115. {valid8r-0.7.2 → valid8r-0.7.3}/tox.ini +0 -0
  116. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/__init__.py +0 -0
  117. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/core/__init__.py +0 -0
  118. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/core/combinators.py +0 -0
  119. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/core/maybe.py +0 -0
  120. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/core/parsers.py +0 -0
  121. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/core/validators.py +0 -0
  122. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/prompt/__init__.py +0 -0
  123. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/prompt/basic.py +0 -0
  124. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/py.typed +0 -0
  125. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/testing/__init__.py +0 -0
  126. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/testing/assertions.py +0 -0
  127. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/testing/generators.py +0 -0
  128. {valid8r-0.7.2 → valid8r-0.7.3}/valid8r/testing/mock_input.py +0 -0
valid8r-0.7.3/PKG-INFO ADDED
@@ -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**
@@ -0,0 +1,274 @@
1
+ # Valid8r
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/valid8r.svg)](https://pypi.org/project/valid8r/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/valid8r.svg)](https://pypi.org/project/valid8r/)
5
+ [![License](https://img.shields.io/github/license/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/blob/main/LICENSE)
6
+ [![CI Status](https://img.shields.io/github/actions/workflow/status/mikelane/valid8r/ci.yml?branch=main)](https://github.com/mikelane/valid8r/actions)
7
+ [![codecov](https://codecov.io/gh/mikelane/valid8r/branch/main/graph/badge.svg)](https://codecov.io/gh/mikelane/valid8r)
8
+ [![Documentation](https://img.shields.io/readthedocs/valid8r.svg)](https://valid8r.readthedocs.io/)
9
+
10
+ **Clean, composable input validation for Python using functional programming patterns.**
11
+
12
+ 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.
13
+
14
+ ```python
15
+ from valid8r import parsers, validators, prompt
16
+
17
+ # Parse and validate user input with rich error messages
18
+ age = prompt.ask(
19
+ "Enter your age: ",
20
+ parser=parsers.parse_int,
21
+ validator=validators.minimum(0) & validators.maximum(120)
22
+ )
23
+
24
+ print(f"Your age is {age}")
25
+ ```
26
+
27
+ ## Why Valid8r?
28
+
29
+ **Type-Safe Parsing**: Every parser returns `Maybe[T]` (Success or Failure), making error handling explicit and composable.
30
+
31
+ **Rich Structured Results**: Network parsers return dataclasses with parsed components—no more manual URL/email splitting.
32
+
33
+ **Chainable Validators**: Combine validators using `&` (and), `|` (or), and `~` (not) operators for complex validation logic.
34
+
35
+ **Zero Dependencies**: Core library uses only Python's standard library (with optional `uuid-utils` for faster UUID parsing).
36
+
37
+ **Interactive Prompts**: Built-in user input prompting with automatic retry and validation.
38
+
39
+ ## Quick Start
40
+
41
+ ### Installation
42
+
43
+ ```bash
44
+ pip install valid8r
45
+ ```
46
+
47
+ **Requirements**: Python 3.11 or higher
48
+
49
+ ### Basic Parsing
50
+
51
+ ```python
52
+ from valid8r import parsers
53
+ from valid8r.core.maybe import Success, Failure
54
+
55
+ # Parse integers with automatic error handling
56
+ match parsers.parse_int("42"):
57
+ case Success(value):
58
+ print(f"Parsed: {value}") # Parsed: 42
59
+ case Failure(error):
60
+ print(f"Error: {error}")
61
+
62
+ # Parse dates (ISO 8601 format)
63
+ result = parsers.parse_date("2025-01-15")
64
+ assert result.is_success()
65
+
66
+ # Parse UUIDs with version validation
67
+ result = parsers.parse_uuid("550e8400-e29b-41d4-a716-446655440000", version=4)
68
+ assert result.is_success()
69
+ ```
70
+
71
+ ### Validation with Combinators
72
+
73
+ ```python
74
+ from valid8r import validators
75
+
76
+ # Combine validators using operators
77
+ age_validator = validators.minimum(0) & validators.maximum(120)
78
+ result = age_validator(42)
79
+ assert result.is_success()
80
+
81
+ # String validation
82
+ password_validator = (
83
+ validators.length(8, 128) &
84
+ validators.matches_regex(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]')
85
+ )
86
+
87
+ # Set validation
88
+ tags_validator = validators.subset_of({'python', 'rust', 'go', 'typescript'})
89
+ ```
90
+
91
+ ### Structured Network Parsing
92
+
93
+ ```python
94
+ from valid8r import parsers
95
+
96
+ # Parse URLs into structured components
97
+ match parsers.parse_url("https://user:pass@example.com:8443/path?query=1#fragment"):
98
+ case Success(url):
99
+ print(f"Scheme: {url.scheme}") # https
100
+ print(f"Host: {url.host}") # example.com
101
+ print(f"Port: {url.port}") # 8443
102
+ print(f"Path: {url.path}") # /path
103
+ print(f"Query: {url.query}") # {'query': '1'}
104
+ print(f"Fragment: {url.fragment}") # fragment
105
+
106
+ # Parse emails with normalized domains
107
+ match parsers.parse_email("User@Example.COM"):
108
+ case Success(email):
109
+ print(f"Local: {email.local}") # User
110
+ print(f"Domain: {email.domain}") # example.com (normalized)
111
+
112
+ # Parse phone numbers (NANP format)
113
+ match parsers.parse_phone("+1 (415) 555-2671"):
114
+ case Success(phone):
115
+ print(f"E.164: {phone.e164}") # +14155552671
116
+ print(f"National: {phone.national}") # (415) 555-2671
117
+ ```
118
+
119
+ ### Collection Parsing
120
+
121
+ ```python
122
+ from valid8r import parsers
123
+
124
+ # Parse lists with element validation
125
+ result = parsers.parse_list("1,2,3,4,5", element_parser=parsers.parse_int)
126
+ assert result.value_or([]) == [1, 2, 3, 4, 5]
127
+
128
+ # Parse dictionaries with key/value parsers
129
+ result = parsers.parse_dict(
130
+ "name=Alice,age=30",
131
+ key_parser=lambda x: Success(x),
132
+ value_parser=lambda x: parsers.parse_int(x) if x.isdigit() else Success(x)
133
+ )
134
+ ```
135
+
136
+ ### Interactive Prompting
137
+
138
+ ```python
139
+ from valid8r import prompt, parsers, validators
140
+
141
+ # Prompt with validation and automatic retry
142
+ email = prompt.ask(
143
+ "Email address: ",
144
+ parser=parsers.parse_email,
145
+ retry=2 # Retry twice on invalid input
146
+ )
147
+
148
+ # Combine parsing and validation
149
+ port = prompt.ask(
150
+ "Server port: ",
151
+ parser=parsers.parse_int,
152
+ validator=validators.between(1024, 65535),
153
+ retry=3
154
+ )
155
+ ```
156
+
157
+ ## Features
158
+
159
+ ### Parsers
160
+
161
+ **Basic Types**:
162
+ - `parse_int`, `parse_float`, `parse_bool`, `parse_decimal`, `parse_complex`
163
+ - `parse_date` (ISO 8601), `parse_uuid` (with version validation)
164
+
165
+ **Collections**:
166
+ - `parse_list`, `parse_dict`, `parse_set` (with element parsers)
167
+
168
+ **Network & Communication**:
169
+ - `parse_ipv4`, `parse_ipv6`, `parse_ip`, `parse_cidr`
170
+ - `parse_url` → `UrlParts` (structured URL components)
171
+ - `parse_email` → `EmailAddress` (normalized domain)
172
+ - `parse_phone` → `PhoneNumber` (NANP validation with E.164 formatting)
173
+
174
+ **Advanced**:
175
+ - `parse_enum` (type-safe enum parsing)
176
+ - `create_parser`, `make_parser`, `validated_parser` (custom parser factories)
177
+
178
+ ### Validators
179
+
180
+ **Numeric**: `minimum`, `maximum`, `between`
181
+
182
+ **String**: `non_empty_string`, `matches_regex`, `length`
183
+
184
+ **Collection**: `in_set`, `unique_items`, `subset_of`, `superset_of`, `is_sorted`
185
+
186
+ **Custom**: `predicate` (create validators from any function)
187
+
188
+ **Combinators**: Combine validators using `&` (and), `|` (or), `~` (not)
189
+
190
+ ### Testing Utilities
191
+
192
+ ```python
193
+ from valid8r.testing import (
194
+ assert_maybe_success,
195
+ assert_maybe_failure,
196
+ MockInputContext,
197
+ )
198
+
199
+ # Test validation logic
200
+ result = validators.minimum(0)(42)
201
+ assert assert_maybe_success(result, 42)
202
+
203
+ result = validators.minimum(0)(-5)
204
+ assert assert_maybe_failure(result, "at least 0")
205
+
206
+ # Mock user input for testing prompts
207
+ with MockInputContext(["invalid", "valid@example.com"]):
208
+ result = prompt.ask("Email: ", parser=parsers.parse_email, retry=1)
209
+ assert result.is_success()
210
+ ```
211
+
212
+ ## Documentation
213
+
214
+ **Full documentation**: [valid8r.readthedocs.io](https://valid8r.readthedocs.io/)
215
+
216
+ - [API Reference](https://valid8r.readthedocs.io/en/latest/api.html)
217
+ - [Parser Guide](https://valid8r.readthedocs.io/en/latest/parsers.html)
218
+ - [Validator Guide](https://valid8r.readthedocs.io/en/latest/validators.html)
219
+ - [Testing Guide](https://valid8r.readthedocs.io/en/latest/testing.html)
220
+
221
+ ## Contributing
222
+
223
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
224
+
225
+ **Quick links**:
226
+ - [Code of Conduct](CODE_OF_CONDUCT.md)
227
+ - [Development Setup](CONTRIBUTING.md#development-setup)
228
+ - [Commit Message Format](CONTRIBUTING.md#commit-messages)
229
+ - [Pull Request Process](CONTRIBUTING.md#pull-request-process)
230
+
231
+ ### Development Quick Start
232
+
233
+ ```bash
234
+ # Install uv (fast dependency manager)
235
+ curl -LsSf https://astral.sh/uv/install.sh | sh
236
+
237
+ # Clone and install dependencies
238
+ git clone https://github.com/mikelane/valid8r
239
+ cd valid8r
240
+ uv sync
241
+
242
+ # Run tests
243
+ uv run tox
244
+
245
+ # Run linters
246
+ uv run ruff check .
247
+ uv run ruff format .
248
+ uv run mypy valid8r
249
+ ```
250
+
251
+ ## Project Status
252
+
253
+ Valid8r is in active development (v0.7.x). The API is stabilizing but may change before v1.0.0.
254
+
255
+ - ✅ Core parsers and validators
256
+ - ✅ Maybe monad error handling
257
+ - ✅ Interactive prompting
258
+ - ✅ Network parsers (URL, Email, IP, Phone)
259
+ - ✅ Collection parsers
260
+ - ✅ Comprehensive testing utilities
261
+ - 🚧 Additional validators (in progress)
262
+ - 🚧 Custom error types (planned)
263
+
264
+ See [ROADMAP.md](ROADMAP.md) for planned features.
265
+
266
+ ## License
267
+
268
+ MIT License - see [LICENSE](LICENSE) for details.
269
+
270
+ Copyright (c) 2025 Mike Lane
271
+
272
+ ---
273
+
274
+ **Made with ❤️ for the Python community**
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "valid8r"
3
- version = "0.7.2"
3
+ version = "0.7.3"
4
4
  description = "Clean, flexible input validation for Python applications"
5
5
  authors = [
6
6
  {name = "Mike Lane", email = "mikelane@gmail.com"}
@@ -1576,7 +1576,7 @@ wheels = [
1576
1576
 
1577
1577
  [[package]]
1578
1578
  name = "valid8r"
1579
- version = "0.7.1"
1579
+ version = "0.7.2"
1580
1580
  source = { editable = "." }
1581
1581
  dependencies = [
1582
1582
  { name = "email-validator" },