valid8r 0.2.7__tar.gz → 0.3.0__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.
- valid8r-0.3.0/LICENSE +21 -0
- valid8r-0.3.0/PKG-INFO +266 -0
- valid8r-0.3.0/README.md +234 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/pyproject.toml +10 -2
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/core/parsers.py +52 -0
- valid8r-0.2.7/PKG-INFO +0 -168
- valid8r-0.2.7/README.md +0 -143
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/__init__.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/core/__init__.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/core/combinators.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/core/maybe.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/core/validators.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/prompt/__init__.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/prompt/basic.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/py.typed +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/testing/__init__.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/testing/assertions.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/testing/generators.py +0 -0
- {valid8r-0.2.7 → valid8r-0.3.0}/valid8r/testing/mock_input.py +0 -0
valid8r-0.3.0/LICENSE
ADDED
|
@@ -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.
|
valid8r-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: valid8r
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Clean, flexible input validation for Python applications
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: validation,input,cli,maybe-monad,parsing,functional-programming
|
|
8
|
+
Author: Mike Lane
|
|
9
|
+
Author-email: mikelane@gmail.com
|
|
10
|
+
Requires-Python: >=3.11,<4.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Dist: email-validator (>=2.3.0,<3.0.0)
|
|
24
|
+
Requires-Dist: pydantic (>=2.0)
|
|
25
|
+
Requires-Dist: pydantic-core (>=2.27.0,<3.0.0)
|
|
26
|
+
Requires-Dist: uuid-utils (>=0.11.0,<0.12.0)
|
|
27
|
+
Project-URL: Documentation, https://valid8r.readthedocs.io/
|
|
28
|
+
Project-URL: Homepage, https://valid8r.readthedocs.io/
|
|
29
|
+
Project-URL: Repository, https://github.com/mikelane/valid8r
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Valid8r
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/valid8r/)
|
|
35
|
+
[](https://pypi.org/project/valid8r/)
|
|
36
|
+
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
|
|
37
|
+
[](https://github.com/mikelane/valid8r/actions)
|
|
38
|
+
[](https://valid8r.readthedocs.io/)
|
|
39
|
+
|
|
40
|
+
A clean, flexible input validation library for Python applications.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
|
|
45
|
+
- **Flexible Validation**: Chain validators and create custom validation rules
|
|
46
|
+
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
|
|
47
|
+
- **Input Prompting**: Prompt users for input with built-in validation
|
|
48
|
+
- **Structured Results**: Network parsers return rich dataclasses with parsed components
|
|
49
|
+
|
|
50
|
+
## Available Parsers
|
|
51
|
+
|
|
52
|
+
### Basic Types
|
|
53
|
+
- **Numbers**: `parse_int`, `parse_float`, `parse_complex`, `parse_decimal`
|
|
54
|
+
- **Text**: `parse_bool` (flexible true/false parsing)
|
|
55
|
+
- **Dates**: `parse_date` (ISO 8601 format)
|
|
56
|
+
- **UUIDs**: `parse_uuid` (with optional version validation)
|
|
57
|
+
|
|
58
|
+
### Collections
|
|
59
|
+
- **Lists**: `parse_list` (with element parser)
|
|
60
|
+
- **Dictionaries**: `parse_dict` (with key/value parsers)
|
|
61
|
+
- **Sets**: `parse_set` (with element parser)
|
|
62
|
+
|
|
63
|
+
### Network & Communication
|
|
64
|
+
- **IP Addresses**: `parse_ipv4`, `parse_ipv6`, `parse_ip` (either v4 or v6)
|
|
65
|
+
- **Networks**: `parse_cidr` (IPv4/IPv6 CIDR notation)
|
|
66
|
+
- **Phone Numbers**: `parse_phone` → PhoneNumber (NANP validation)
|
|
67
|
+
- **URLs**: `parse_url` → UrlParts (scheme, host, port, path, query, etc.)
|
|
68
|
+
- **Email**: `parse_email` → EmailAddress (normalized case)
|
|
69
|
+
|
|
70
|
+
### Advanced
|
|
71
|
+
- **Enums**: `parse_enum` (type-safe enum parsing)
|
|
72
|
+
- **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
**Requirements**: Python 3.11 or higher
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install valid8r
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Valid8r supports Python 3.11, 3.12, 3.13, and 3.14.
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from valid8r import (
|
|
88
|
+
parsers,
|
|
89
|
+
prompt,
|
|
90
|
+
validators,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Simple validation
|
|
94
|
+
age = prompt.ask(
|
|
95
|
+
"Enter your age: ",
|
|
96
|
+
parser=parsers.parse_int,
|
|
97
|
+
validator=validators.minimum(0) & validators.maximum(120)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(f"Your age is {age}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### IP parsing helpers
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from valid8r.core.maybe import Success, Failure
|
|
107
|
+
from valid8r.core import parsers
|
|
108
|
+
|
|
109
|
+
# IPv4 / IPv6 / generic IP
|
|
110
|
+
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
|
|
111
|
+
match parsers.parse_ip(text):
|
|
112
|
+
case Success(addr):
|
|
113
|
+
print("Parsed:", addr)
|
|
114
|
+
case Failure(err):
|
|
115
|
+
print("Error:", err)
|
|
116
|
+
|
|
117
|
+
# CIDR (strict by default)
|
|
118
|
+
match parsers.parse_cidr("10.0.0.0/8"):
|
|
119
|
+
case Success(net):
|
|
120
|
+
print("Network:", net) # 10.0.0.0/8
|
|
121
|
+
case Failure(err):
|
|
122
|
+
print("Error:", err)
|
|
123
|
+
|
|
124
|
+
# Non-strict masks host bits
|
|
125
|
+
match parsers.parse_cidr("10.0.0.1/24", strict=False):
|
|
126
|
+
case Success(net):
|
|
127
|
+
assert str(net) == "10.0.0.0/24"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### URL and Email helpers
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from valid8r.core.maybe import Success, Failure
|
|
134
|
+
from valid8r.core import parsers
|
|
135
|
+
|
|
136
|
+
# URL parsing with structured result (UrlParts)
|
|
137
|
+
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
|
|
138
|
+
case Success(u):
|
|
139
|
+
print(f"Scheme: {u.scheme}") # https
|
|
140
|
+
print(f"Host: {u.host}") # example.com
|
|
141
|
+
print(f"Port: {u.port}") # 8443
|
|
142
|
+
print(f"Path: {u.path}") # /x
|
|
143
|
+
print(f"Query: {u.query}") # {'q': '1'}
|
|
144
|
+
print(f"Fragment: {u.fragment}") # top
|
|
145
|
+
case Failure(err):
|
|
146
|
+
print("Error:", err)
|
|
147
|
+
|
|
148
|
+
# Email parsing with normalized case (EmailAddress)
|
|
149
|
+
match parsers.parse_email("First.Last+tag@Example.COM"):
|
|
150
|
+
case Success(e):
|
|
151
|
+
print(f"Local: {e.local}") # First.Last+tag
|
|
152
|
+
print(f"Domain: {e.domain}") # example.com (normalized)
|
|
153
|
+
case Failure(err):
|
|
154
|
+
print("Error:", err)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Phone Number Parsing
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from valid8r.core.maybe import Success, Failure
|
|
161
|
+
from valid8r.core import parsers
|
|
162
|
+
|
|
163
|
+
# Phone number parsing with NANP validation (PhoneNumber)
|
|
164
|
+
match parsers.parse_phone("+1 (555) 123-4567"):
|
|
165
|
+
case Success(phone):
|
|
166
|
+
print(f"Country: {phone.country_code}") # 1
|
|
167
|
+
print(f"Area: {phone.area_code}") # 555
|
|
168
|
+
print(f"Exchange: {phone.exchange}") # 123
|
|
169
|
+
print(f"Subscriber: {phone.subscriber}") # 4567
|
|
170
|
+
|
|
171
|
+
# Format for display using properties
|
|
172
|
+
print(f"E.164: {phone.e164}") # +15551234567
|
|
173
|
+
print(f"National: {phone.national}") # (555) 123-4567
|
|
174
|
+
case Failure(err):
|
|
175
|
+
print("Error:", err)
|
|
176
|
+
|
|
177
|
+
# Also accepts various formats
|
|
178
|
+
for number in ["5551234567", "(555) 123-4567", "555-123-4567"]:
|
|
179
|
+
result = parsers.parse_phone(number)
|
|
180
|
+
assert result.is_success()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Testing Support
|
|
184
|
+
|
|
185
|
+
Valid8r includes comprehensive testing utilities to help you verify your validation logic:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from valid8r import Maybe, validators, parsers, prompt
|
|
189
|
+
from valid8r.testing import (
|
|
190
|
+
MockInputContext,
|
|
191
|
+
assert_maybe_success,
|
|
192
|
+
assert_maybe_failure,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def validate_age(age: int) -> Maybe[int]:
|
|
196
|
+
"""Validate age is between 0 and 120."""
|
|
197
|
+
return (validators.minimum(0) & validators.maximum(120))(age)
|
|
198
|
+
|
|
199
|
+
# Test validation functions with assert helpers
|
|
200
|
+
result = validate_age(42)
|
|
201
|
+
assert assert_maybe_success(result, 42)
|
|
202
|
+
|
|
203
|
+
result = validate_age(-5)
|
|
204
|
+
assert assert_maybe_failure(result, "at least 0")
|
|
205
|
+
|
|
206
|
+
# Test prompts with mock input
|
|
207
|
+
with MockInputContext(["yes", "42", "invalid", "25"]):
|
|
208
|
+
# First prompt
|
|
209
|
+
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
210
|
+
assert result.value_or(False) == True
|
|
211
|
+
|
|
212
|
+
# Second prompt
|
|
213
|
+
age = prompt.ask(
|
|
214
|
+
"Age? ",
|
|
215
|
+
parser=parsers.parse_int,
|
|
216
|
+
validator=validate_age
|
|
217
|
+
)
|
|
218
|
+
assert age == 42
|
|
219
|
+
|
|
220
|
+
# Third prompt will fail, fourth succeeds
|
|
221
|
+
age = prompt.ask(
|
|
222
|
+
"Age again? ",
|
|
223
|
+
parser=parsers.parse_int,
|
|
224
|
+
retries=1 # Retry once after failure
|
|
225
|
+
)
|
|
226
|
+
assert age == 25
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Testing Utilities Reference
|
|
230
|
+
|
|
231
|
+
- **`assert_maybe_success(result, expected_value)`**: Assert that a Maybe is Success with the expected value
|
|
232
|
+
- **`assert_maybe_failure(result, error_substring)`**: Assert that a Maybe is Failure containing the error substring
|
|
233
|
+
- **`MockInputContext(inputs)`**: Context manager for mocking user input in tests
|
|
234
|
+
|
|
235
|
+
For more examples, see the [documentation](https://valid8r.readthedocs.io/).
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
This project uses Poetry for dependency management and Tox for testing.
|
|
240
|
+
|
|
241
|
+
### Setup
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# Install Poetry
|
|
245
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
246
|
+
|
|
247
|
+
# Install dependencies
|
|
248
|
+
poetry install
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Running Tests
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Run all tests
|
|
255
|
+
poetry run tox
|
|
256
|
+
|
|
257
|
+
# Run BDD tests
|
|
258
|
+
poetry run tox -e bdd
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
264
|
+
|
|
265
|
+
Copyright (c) 2025 Mike Lane
|
|
266
|
+
|
valid8r-0.3.0/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Valid8r
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/valid8r/)
|
|
4
|
+
[](https://pypi.org/project/valid8r/)
|
|
5
|
+
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/mikelane/valid8r/actions)
|
|
7
|
+
[](https://valid8r.readthedocs.io/)
|
|
8
|
+
|
|
9
|
+
A clean, flexible input validation library for Python applications.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
|
|
14
|
+
- **Flexible Validation**: Chain validators and create custom validation rules
|
|
15
|
+
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
|
|
16
|
+
- **Input Prompting**: Prompt users for input with built-in validation
|
|
17
|
+
- **Structured Results**: Network parsers return rich dataclasses with parsed components
|
|
18
|
+
|
|
19
|
+
## Available Parsers
|
|
20
|
+
|
|
21
|
+
### Basic Types
|
|
22
|
+
- **Numbers**: `parse_int`, `parse_float`, `parse_complex`, `parse_decimal`
|
|
23
|
+
- **Text**: `parse_bool` (flexible true/false parsing)
|
|
24
|
+
- **Dates**: `parse_date` (ISO 8601 format)
|
|
25
|
+
- **UUIDs**: `parse_uuid` (with optional version validation)
|
|
26
|
+
|
|
27
|
+
### Collections
|
|
28
|
+
- **Lists**: `parse_list` (with element parser)
|
|
29
|
+
- **Dictionaries**: `parse_dict` (with key/value parsers)
|
|
30
|
+
- **Sets**: `parse_set` (with element parser)
|
|
31
|
+
|
|
32
|
+
### Network & Communication
|
|
33
|
+
- **IP Addresses**: `parse_ipv4`, `parse_ipv6`, `parse_ip` (either v4 or v6)
|
|
34
|
+
- **Networks**: `parse_cidr` (IPv4/IPv6 CIDR notation)
|
|
35
|
+
- **Phone Numbers**: `parse_phone` → PhoneNumber (NANP validation)
|
|
36
|
+
- **URLs**: `parse_url` → UrlParts (scheme, host, port, path, query, etc.)
|
|
37
|
+
- **Email**: `parse_email` → EmailAddress (normalized case)
|
|
38
|
+
|
|
39
|
+
### Advanced
|
|
40
|
+
- **Enums**: `parse_enum` (type-safe enum parsing)
|
|
41
|
+
- **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
**Requirements**: Python 3.11 or higher
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install valid8r
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Valid8r supports Python 3.11, 3.12, 3.13, and 3.14.
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from valid8r import (
|
|
57
|
+
parsers,
|
|
58
|
+
prompt,
|
|
59
|
+
validators,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Simple validation
|
|
63
|
+
age = prompt.ask(
|
|
64
|
+
"Enter your age: ",
|
|
65
|
+
parser=parsers.parse_int,
|
|
66
|
+
validator=validators.minimum(0) & validators.maximum(120)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
print(f"Your age is {age}")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### IP parsing helpers
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from valid8r.core.maybe import Success, Failure
|
|
76
|
+
from valid8r.core import parsers
|
|
77
|
+
|
|
78
|
+
# IPv4 / IPv6 / generic IP
|
|
79
|
+
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
|
|
80
|
+
match parsers.parse_ip(text):
|
|
81
|
+
case Success(addr):
|
|
82
|
+
print("Parsed:", addr)
|
|
83
|
+
case Failure(err):
|
|
84
|
+
print("Error:", err)
|
|
85
|
+
|
|
86
|
+
# CIDR (strict by default)
|
|
87
|
+
match parsers.parse_cidr("10.0.0.0/8"):
|
|
88
|
+
case Success(net):
|
|
89
|
+
print("Network:", net) # 10.0.0.0/8
|
|
90
|
+
case Failure(err):
|
|
91
|
+
print("Error:", err)
|
|
92
|
+
|
|
93
|
+
# Non-strict masks host bits
|
|
94
|
+
match parsers.parse_cidr("10.0.0.1/24", strict=False):
|
|
95
|
+
case Success(net):
|
|
96
|
+
assert str(net) == "10.0.0.0/24"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### URL and Email helpers
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from valid8r.core.maybe import Success, Failure
|
|
103
|
+
from valid8r.core import parsers
|
|
104
|
+
|
|
105
|
+
# URL parsing with structured result (UrlParts)
|
|
106
|
+
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
|
|
107
|
+
case Success(u):
|
|
108
|
+
print(f"Scheme: {u.scheme}") # https
|
|
109
|
+
print(f"Host: {u.host}") # example.com
|
|
110
|
+
print(f"Port: {u.port}") # 8443
|
|
111
|
+
print(f"Path: {u.path}") # /x
|
|
112
|
+
print(f"Query: {u.query}") # {'q': '1'}
|
|
113
|
+
print(f"Fragment: {u.fragment}") # top
|
|
114
|
+
case Failure(err):
|
|
115
|
+
print("Error:", err)
|
|
116
|
+
|
|
117
|
+
# Email parsing with normalized case (EmailAddress)
|
|
118
|
+
match parsers.parse_email("First.Last+tag@Example.COM"):
|
|
119
|
+
case Success(e):
|
|
120
|
+
print(f"Local: {e.local}") # First.Last+tag
|
|
121
|
+
print(f"Domain: {e.domain}") # example.com (normalized)
|
|
122
|
+
case Failure(err):
|
|
123
|
+
print("Error:", err)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Phone Number Parsing
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from valid8r.core.maybe import Success, Failure
|
|
130
|
+
from valid8r.core import parsers
|
|
131
|
+
|
|
132
|
+
# Phone number parsing with NANP validation (PhoneNumber)
|
|
133
|
+
match parsers.parse_phone("+1 (555) 123-4567"):
|
|
134
|
+
case Success(phone):
|
|
135
|
+
print(f"Country: {phone.country_code}") # 1
|
|
136
|
+
print(f"Area: {phone.area_code}") # 555
|
|
137
|
+
print(f"Exchange: {phone.exchange}") # 123
|
|
138
|
+
print(f"Subscriber: {phone.subscriber}") # 4567
|
|
139
|
+
|
|
140
|
+
# Format for display using properties
|
|
141
|
+
print(f"E.164: {phone.e164}") # +15551234567
|
|
142
|
+
print(f"National: {phone.national}") # (555) 123-4567
|
|
143
|
+
case Failure(err):
|
|
144
|
+
print("Error:", err)
|
|
145
|
+
|
|
146
|
+
# Also accepts various formats
|
|
147
|
+
for number in ["5551234567", "(555) 123-4567", "555-123-4567"]:
|
|
148
|
+
result = parsers.parse_phone(number)
|
|
149
|
+
assert result.is_success()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Testing Support
|
|
153
|
+
|
|
154
|
+
Valid8r includes comprehensive testing utilities to help you verify your validation logic:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from valid8r import Maybe, validators, parsers, prompt
|
|
158
|
+
from valid8r.testing import (
|
|
159
|
+
MockInputContext,
|
|
160
|
+
assert_maybe_success,
|
|
161
|
+
assert_maybe_failure,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def validate_age(age: int) -> Maybe[int]:
|
|
165
|
+
"""Validate age is between 0 and 120."""
|
|
166
|
+
return (validators.minimum(0) & validators.maximum(120))(age)
|
|
167
|
+
|
|
168
|
+
# Test validation functions with assert helpers
|
|
169
|
+
result = validate_age(42)
|
|
170
|
+
assert assert_maybe_success(result, 42)
|
|
171
|
+
|
|
172
|
+
result = validate_age(-5)
|
|
173
|
+
assert assert_maybe_failure(result, "at least 0")
|
|
174
|
+
|
|
175
|
+
# Test prompts with mock input
|
|
176
|
+
with MockInputContext(["yes", "42", "invalid", "25"]):
|
|
177
|
+
# First prompt
|
|
178
|
+
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
179
|
+
assert result.value_or(False) == True
|
|
180
|
+
|
|
181
|
+
# Second prompt
|
|
182
|
+
age = prompt.ask(
|
|
183
|
+
"Age? ",
|
|
184
|
+
parser=parsers.parse_int,
|
|
185
|
+
validator=validate_age
|
|
186
|
+
)
|
|
187
|
+
assert age == 42
|
|
188
|
+
|
|
189
|
+
# Third prompt will fail, fourth succeeds
|
|
190
|
+
age = prompt.ask(
|
|
191
|
+
"Age again? ",
|
|
192
|
+
parser=parsers.parse_int,
|
|
193
|
+
retries=1 # Retry once after failure
|
|
194
|
+
)
|
|
195
|
+
assert age == 25
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Testing Utilities Reference
|
|
199
|
+
|
|
200
|
+
- **`assert_maybe_success(result, expected_value)`**: Assert that a Maybe is Success with the expected value
|
|
201
|
+
- **`assert_maybe_failure(result, error_substring)`**: Assert that a Maybe is Failure containing the error substring
|
|
202
|
+
- **`MockInputContext(inputs)`**: Context manager for mocking user input in tests
|
|
203
|
+
|
|
204
|
+
For more examples, see the [documentation](https://valid8r.readthedocs.io/).
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
This project uses Poetry for dependency management and Tox for testing.
|
|
209
|
+
|
|
210
|
+
### Setup
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Install Poetry
|
|
214
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
215
|
+
|
|
216
|
+
# Install dependencies
|
|
217
|
+
poetry install
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Running Tests
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Run all tests
|
|
224
|
+
poetry run tox
|
|
225
|
+
|
|
226
|
+
# Run BDD tests
|
|
227
|
+
poetry run tox -e bdd
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
233
|
+
|
|
234
|
+
Copyright (c) 2025 Mike Lane
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "valid8r"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Clean, flexible input validation for Python applications"
|
|
5
5
|
authors = ["Mike Lane <mikelane@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
|
+
homepage = "https://valid8r.readthedocs.io/"
|
|
8
9
|
repository = "https://github.com/mikelane/valid8r"
|
|
9
|
-
|
|
10
|
+
documentation = "https://valid8r.readthedocs.io/"
|
|
11
|
+
keywords = ["validation", "input", "cli", "maybe-monad", "parsing", "functional-programming"]
|
|
10
12
|
classifiers = [
|
|
11
13
|
"Development Status :: 3 - Alpha",
|
|
12
14
|
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
13
17
|
"Programming Language :: Python :: 3",
|
|
14
18
|
"Programming Language :: Python :: 3.11",
|
|
15
19
|
"Programming Language :: Python :: 3.12",
|
|
16
20
|
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
24
|
+
"Typing :: Typed",
|
|
17
25
|
]
|
|
18
26
|
packages = [{include = "valid8r"}]
|
|
19
27
|
include = [{ path = "valid8r/py.typed", format = "wheel" }]
|
|
@@ -1106,6 +1106,42 @@ def parse_url(
|
|
|
1106
1106
|
- Unsupported URL scheme
|
|
1107
1107
|
- URL requires host
|
|
1108
1108
|
- Invalid host
|
|
1109
|
+
|
|
1110
|
+
Args:
|
|
1111
|
+
text: The URL string to parse
|
|
1112
|
+
allowed_schemes: Iterable of allowed scheme names (default: ('http', 'https'))
|
|
1113
|
+
require_host: Whether to require a host in the URL (default: True)
|
|
1114
|
+
|
|
1115
|
+
Returns:
|
|
1116
|
+
Maybe[UrlParts]: Success with UrlParts containing parsed components, or Failure with error message
|
|
1117
|
+
|
|
1118
|
+
Examples:
|
|
1119
|
+
>>> from valid8r.core.parsers import parse_url
|
|
1120
|
+
>>> from valid8r.core.maybe import Success
|
|
1121
|
+
>>>
|
|
1122
|
+
>>> # Parse a complete URL
|
|
1123
|
+
>>> result = parse_url('https://user:pass@api.example.com:8080/v1/users?active=true#section')
|
|
1124
|
+
>>> isinstance(result, Success)
|
|
1125
|
+
True
|
|
1126
|
+
>>> url = result.value
|
|
1127
|
+
>>> url.scheme
|
|
1128
|
+
'https'
|
|
1129
|
+
>>> url.host
|
|
1130
|
+
'api.example.com'
|
|
1131
|
+
>>> url.port
|
|
1132
|
+
8080
|
|
1133
|
+
>>> url.path
|
|
1134
|
+
'/v1/users'
|
|
1135
|
+
>>> url.query
|
|
1136
|
+
'active=true'
|
|
1137
|
+
>>> url.fragment
|
|
1138
|
+
'section'
|
|
1139
|
+
>>>
|
|
1140
|
+
>>> # Access credentials
|
|
1141
|
+
>>> url.username
|
|
1142
|
+
'user'
|
|
1143
|
+
>>> url.password
|
|
1144
|
+
'pass'
|
|
1109
1145
|
"""
|
|
1110
1146
|
if not isinstance(text, str):
|
|
1111
1147
|
return Maybe.failure('Input must be a string')
|
|
@@ -1189,6 +1225,22 @@ def parse_email(text: str) -> Maybe[EmailAddress]:
|
|
|
1189
1225
|
|
|
1190
1226
|
Returns:
|
|
1191
1227
|
Maybe[EmailAddress]: Success with EmailAddress or Failure with error message
|
|
1228
|
+
|
|
1229
|
+
Examples:
|
|
1230
|
+
>>> from valid8r.core.parsers import parse_email
|
|
1231
|
+
>>> from valid8r.core.maybe import Success
|
|
1232
|
+
>>>
|
|
1233
|
+
>>> # Parse an email with case normalization
|
|
1234
|
+
>>> result = parse_email('User.Name+tag@Example.COM')
|
|
1235
|
+
>>> isinstance(result, Success)
|
|
1236
|
+
True
|
|
1237
|
+
>>> email = result.value
|
|
1238
|
+
>>> # Local part preserves original case
|
|
1239
|
+
>>> email.local
|
|
1240
|
+
'User.Name+tag'
|
|
1241
|
+
>>> # Domain is normalized to lowercase
|
|
1242
|
+
>>> email.domain
|
|
1243
|
+
'example.com'
|
|
1192
1244
|
"""
|
|
1193
1245
|
if not isinstance(text, str):
|
|
1194
1246
|
return Maybe.failure('Input must be a string')
|
valid8r-0.2.7/PKG-INFO
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: valid8r
|
|
3
|
-
Version: 0.2.7
|
|
4
|
-
Summary: Clean, flexible input validation for Python applications
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: validation,input,cli,maybe-monad
|
|
7
|
-
Author: Mike Lane
|
|
8
|
-
Author-email: mikelane@gmail.com
|
|
9
|
-
Requires-Python: >=3.11,<4.0
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
-
Requires-Dist: email-validator (>=2.3.0,<3.0.0)
|
|
19
|
-
Requires-Dist: pydantic (>=2.0)
|
|
20
|
-
Requires-Dist: pydantic-core (>=2.27.0,<3.0.0)
|
|
21
|
-
Requires-Dist: uuid-utils (>=0.11.0,<0.12.0)
|
|
22
|
-
Project-URL: Repository, https://github.com/mikelane/valid8r
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# Valid8r
|
|
26
|
-
|
|
27
|
-
A clean, flexible input validation library for Python applications.
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
|
|
32
|
-
- **Flexible Validation**: Chain validators and create custom validation rules
|
|
33
|
-
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
|
|
34
|
-
- **Input Prompting**: Prompt users for input with built-in validation
|
|
35
|
-
|
|
36
|
-
## Installation
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pip install valid8r
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Quick Start
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
from valid8r import (
|
|
46
|
-
parsers,
|
|
47
|
-
prompt,
|
|
48
|
-
validators,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# Simple validation
|
|
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
|
-
### IP parsing helpers
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from valid8r.core.maybe import Success, Failure
|
|
65
|
-
from valid8r.core import parsers
|
|
66
|
-
|
|
67
|
-
# IPv4 / IPv6 / generic IP
|
|
68
|
-
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
|
|
69
|
-
match parsers.parse_ip(text):
|
|
70
|
-
case Success(addr):
|
|
71
|
-
print("Parsed:", addr)
|
|
72
|
-
case Failure(err):
|
|
73
|
-
print("Error:", err)
|
|
74
|
-
|
|
75
|
-
# CIDR (strict by default)
|
|
76
|
-
match parsers.parse_cidr("10.0.0.0/8"):
|
|
77
|
-
case Success(net):
|
|
78
|
-
print("Network:", net) # 10.0.0.0/8
|
|
79
|
-
case Failure(err):
|
|
80
|
-
print("Error:", err)
|
|
81
|
-
|
|
82
|
-
# Non-strict masks host bits
|
|
83
|
-
match parsers.parse_cidr("10.0.0.1/24", strict=False):
|
|
84
|
-
case Success(net):
|
|
85
|
-
assert str(net) == "10.0.0.0/24"
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### URL and Email helpers
|
|
89
|
-
|
|
90
|
-
```python
|
|
91
|
-
from valid8r.core.maybe import Success, Failure
|
|
92
|
-
from valid8r.core import parsers
|
|
93
|
-
|
|
94
|
-
# URL parsing
|
|
95
|
-
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
|
|
96
|
-
case Success(u):
|
|
97
|
-
print(u.scheme, u.username, u.password, u.host, u.port)
|
|
98
|
-
case Failure(err):
|
|
99
|
-
print("Error:", err)
|
|
100
|
-
|
|
101
|
-
# Email parsing
|
|
102
|
-
match parsers.parse_email("First.Last+tag@Example.COM"):
|
|
103
|
-
case Success(e):
|
|
104
|
-
print(e.local, e.domain) # First.Last+tag example.com
|
|
105
|
-
case Failure(err):
|
|
106
|
-
print("Error:", err)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Testing Support
|
|
110
|
-
|
|
111
|
-
Valid8r includes testing utilities to help you verify your validation logic:
|
|
112
|
-
|
|
113
|
-
```python
|
|
114
|
-
from valid8r import (
|
|
115
|
-
Maybe,
|
|
116
|
-
validators,
|
|
117
|
-
parsers,
|
|
118
|
-
prompt,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
from valid8r.testing import (
|
|
122
|
-
MockInputContext,
|
|
123
|
-
assert_maybe_success,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def validate_age(age: int) -> Maybe[int]:
|
|
127
|
-
return validators.minimum(0) & validators.maximum(120)(age)
|
|
128
|
-
|
|
129
|
-
# Test prompts with mock input
|
|
130
|
-
with MockInputContext(["yes"]):
|
|
131
|
-
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
132
|
-
assert result.is_success()
|
|
133
|
-
assert result.value_or(False) == True
|
|
134
|
-
|
|
135
|
-
# Test validation functions
|
|
136
|
-
result = validate_age(42)
|
|
137
|
-
assert assert_maybe_success(result, 42)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
For more information, see the [Testing with Valid8r](docs/user_guide/testing.rst) guide.
|
|
141
|
-
|
|
142
|
-
## Development
|
|
143
|
-
|
|
144
|
-
This project uses Poetry for dependency management and Tox for testing.
|
|
145
|
-
|
|
146
|
-
### Setup
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
# Install Poetry
|
|
150
|
-
curl -sSL https://install.python-poetry.org | python3 -
|
|
151
|
-
|
|
152
|
-
# Install dependencies
|
|
153
|
-
poetry install
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Running Tests
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Run all tests
|
|
160
|
-
poetry run tox
|
|
161
|
-
|
|
162
|
-
# Run BDD tests
|
|
163
|
-
poetry run tox -e bdd
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## License
|
|
167
|
-
MIT
|
|
168
|
-
|
valid8r-0.2.7/README.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Valid8r
|
|
2
|
-
|
|
3
|
-
A clean, flexible input validation library for Python applications.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
|
|
8
|
-
- **Flexible Validation**: Chain validators and create custom validation rules
|
|
9
|
-
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
|
|
10
|
-
- **Input Prompting**: Prompt users for input with built-in validation
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
pip install valid8r
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Quick Start
|
|
19
|
-
|
|
20
|
-
```python
|
|
21
|
-
from valid8r import (
|
|
22
|
-
parsers,
|
|
23
|
-
prompt,
|
|
24
|
-
validators,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
# Simple validation
|
|
28
|
-
age = prompt.ask(
|
|
29
|
-
"Enter your age: ",
|
|
30
|
-
parser=parsers.parse_int,
|
|
31
|
-
validator=validators.minimum(0) & validators.maximum(120)
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
print(f"Your age is {age}")
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### IP parsing helpers
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
from valid8r.core.maybe import Success, Failure
|
|
41
|
-
from valid8r.core import parsers
|
|
42
|
-
|
|
43
|
-
# IPv4 / IPv6 / generic IP
|
|
44
|
-
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
|
|
45
|
-
match parsers.parse_ip(text):
|
|
46
|
-
case Success(addr):
|
|
47
|
-
print("Parsed:", addr)
|
|
48
|
-
case Failure(err):
|
|
49
|
-
print("Error:", err)
|
|
50
|
-
|
|
51
|
-
# CIDR (strict by default)
|
|
52
|
-
match parsers.parse_cidr("10.0.0.0/8"):
|
|
53
|
-
case Success(net):
|
|
54
|
-
print("Network:", net) # 10.0.0.0/8
|
|
55
|
-
case Failure(err):
|
|
56
|
-
print("Error:", err)
|
|
57
|
-
|
|
58
|
-
# Non-strict masks host bits
|
|
59
|
-
match parsers.parse_cidr("10.0.0.1/24", strict=False):
|
|
60
|
-
case Success(net):
|
|
61
|
-
assert str(net) == "10.0.0.0/24"
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### URL and Email helpers
|
|
65
|
-
|
|
66
|
-
```python
|
|
67
|
-
from valid8r.core.maybe import Success, Failure
|
|
68
|
-
from valid8r.core import parsers
|
|
69
|
-
|
|
70
|
-
# URL parsing
|
|
71
|
-
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
|
|
72
|
-
case Success(u):
|
|
73
|
-
print(u.scheme, u.username, u.password, u.host, u.port)
|
|
74
|
-
case Failure(err):
|
|
75
|
-
print("Error:", err)
|
|
76
|
-
|
|
77
|
-
# Email parsing
|
|
78
|
-
match parsers.parse_email("First.Last+tag@Example.COM"):
|
|
79
|
-
case Success(e):
|
|
80
|
-
print(e.local, e.domain) # First.Last+tag example.com
|
|
81
|
-
case Failure(err):
|
|
82
|
-
print("Error:", err)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Testing Support
|
|
86
|
-
|
|
87
|
-
Valid8r includes testing utilities to help you verify your validation logic:
|
|
88
|
-
|
|
89
|
-
```python
|
|
90
|
-
from valid8r import (
|
|
91
|
-
Maybe,
|
|
92
|
-
validators,
|
|
93
|
-
parsers,
|
|
94
|
-
prompt,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
from valid8r.testing import (
|
|
98
|
-
MockInputContext,
|
|
99
|
-
assert_maybe_success,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
def validate_age(age: int) -> Maybe[int]:
|
|
103
|
-
return validators.minimum(0) & validators.maximum(120)(age)
|
|
104
|
-
|
|
105
|
-
# Test prompts with mock input
|
|
106
|
-
with MockInputContext(["yes"]):
|
|
107
|
-
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
|
|
108
|
-
assert result.is_success()
|
|
109
|
-
assert result.value_or(False) == True
|
|
110
|
-
|
|
111
|
-
# Test validation functions
|
|
112
|
-
result = validate_age(42)
|
|
113
|
-
assert assert_maybe_success(result, 42)
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
For more information, see the [Testing with Valid8r](docs/user_guide/testing.rst) guide.
|
|
117
|
-
|
|
118
|
-
## Development
|
|
119
|
-
|
|
120
|
-
This project uses Poetry for dependency management and Tox for testing.
|
|
121
|
-
|
|
122
|
-
### Setup
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
# Install Poetry
|
|
126
|
-
curl -sSL https://install.python-poetry.org | python3 -
|
|
127
|
-
|
|
128
|
-
# Install dependencies
|
|
129
|
-
poetry install
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Running Tests
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Run all tests
|
|
136
|
-
poetry run tox
|
|
137
|
-
|
|
138
|
-
# Run BDD tests
|
|
139
|
-
poetry run tox -e bdd
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## License
|
|
143
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|