# Valid8r
[](https://pypi.org/project/valid8r/)
[](https://pypi.org/project/valid8r/)
[](https://github.com/mikelane/valid8r/blob/main/LICENSE)
[](https://github.com/mikelane/valid8r/actions)
[](https://codecov.io/gh/mikelane/valid8r)
[](https://valid8r.readthedocs.io/)
[](https://pypi.org/project/valid8r/)
[](https://github.com/mikelane/valid8r/stargazers)
[](https://github.com/mikelane/valid8r/graphs/contributors)
[](https://github.com/astral-sh/ruff)
[](https://github.com/python-semantic-release/python-semantic-release)
A clean, flexible input validation library for Python applications.
## Features
- **Clean Type Parsing**: Parse strings to various Python types with robust error handling
- **Flexible Validation**: Chain validators and create custom validation rules
- **Monadic Error Handling**: Use Maybe monad for clean error propagation
- **Input Prompting**: Prompt users for input with built-in validation
- **Structured Results**: Network parsers return rich dataclasses with parsed components
## Available Parsers
### Basic Types
- **Numbers**: `parse_int`, `parse_float`, `parse_complex`, `parse_decimal`
- **Text**: `parse_bool` (flexible true/false parsing)
- **Dates**: `parse_date` (ISO 8601 format)
- **UUIDs**: `parse_uuid` (with optional version validation)
### Collections
- **Lists**: `parse_list` (with element parser)
- **Dictionaries**: `parse_dict` (with key/value parsers)
- **Sets**: `parse_set` (with element parser)
### Network & Communication
- **IP Addresses**: `parse_ipv4`, `parse_ipv6`, `parse_ip` (either v4 or v6)
- **Networks**: `parse_cidr` (IPv4/IPv6 CIDR notation)
- **Phone Numbers**: `parse_phone` → PhoneNumber (NANP validation)
- **URLs**: `parse_url` → UrlParts (scheme, host, port, path, query, etc.)
- **Email**: `parse_email` → EmailAddress (normalized case)
### Advanced
- **Enums**: `parse_enum` (type-safe enum parsing)
- **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)
## Available Validators
### Numeric Validators
- **`minimum(min_value)`** - Ensures value is at least the minimum (inclusive)
- **`maximum(max_value)`** - Ensures value is at most the maximum (inclusive)
- **`between(min_value, max_value)`** - Ensures value is within range (inclusive)
### String Validators
- **`non_empty_string()`** - Rejects empty strings and whitespace-only strings
- **`matches_regex(pattern)`** - Validates string matches regex pattern (string or compiled)
- **`length(min_length, max_length)`** - Validates string length within bounds
### Collection Validators
- **`in_set(allowed_values)`** - Ensures value is in set of allowed values
- **`unique_items()`** - Ensures all items in a list are unique
- **`subset_of(allowed_set)`** - Validates set is subset of allowed values
- **`superset_of(required_set)`** - Validates set is superset of required values
- **`is_sorted(reverse=False)`** - Ensures list is sorted (ascending or descending)
### Custom Validators
- **`predicate(func, error_message)`** - Create custom validator from any predicate function
**Note**: All validators support custom error messages and can be combined using `&` (and), `|` (or), and `~` (not) operators.
## Installation
**Requirements**: Python 3.11 or higher
```bash
pip install valid8r
```
Valid8r supports Python 3.11, 3.12, 3.13, and 3.14.
## Quick Start
```python
from valid8r import (
parsers,
prompt,
validators,
)
# Simple validation
age = prompt.ask(
"Enter your age: ",
parser=parsers.parse_int,
validator=validators.minimum(0) & validators.maximum(120)
)
print(f"Your age is {age}")
```
### IP parsing helpers
```python
from valid8r.core.maybe import Success, Failure
from valid8r.core import parsers
# IPv4 / IPv6 / generic IP
for text in ["192.168.0.1", "::1", " 10.0.0.1 "]:
match parsers.parse_ip(text):
case Success(addr):
print("Parsed:", addr)
case Failure(err):
print("Error:", err)
# CIDR (strict by default)
match parsers.parse_cidr("10.0.0.0/8"):
case Success(net):
print("Network:", net) # 10.0.0.0/8
case Failure(err):
print("Error:", err)
# Non-strict masks host bits
match parsers.parse_cidr("10.0.0.1/24", strict=False):
case Success(net):
assert str(net) == "10.0.0.0/24"
```
### URL and Email helpers
```python
from valid8r.core.maybe import Success, Failure
from valid8r.core import parsers
# URL parsing with structured result (UrlParts)
match parsers.parse_url("https://alice:pw@example.com:8443/x?q=1#top"):
case Success(u):
print(f"Scheme: {u.scheme}") # https
print(f"Host: {u.host}") # example.com
print(f"Port: {u.port}") # 8443
print(f"Path: {u.path}") # /x
print(f"Query: {u.query}") # {'q': '1'}
print(f"Fragment: {u.fragment}") # top
case Failure(err):
print("Error:", err)
# Email parsing with normalized case (EmailAddress)
match parsers.parse_email("First.Last+tag@Example.COM"):
case Success(e):
print(f"Local: {e.local}") # First.Last+tag
print(f"Domain: {e.domain}") # example.com (normalized)
case Failure(err):
print("Error:", err)
```
### Phone Number Parsing
```python
from valid8r.core.maybe import Success, Failure
from valid8r.core import parsers
# Phone number parsing with NANP validation (PhoneNumber)
match parsers.parse_phone("+1 (415) 555-2671"):
case Success(phone):
print(f"Country: {phone.country_code}") # 1
print(f"Area: {phone.area_code}") # 415
print(f"Exchange: {phone.exchange}") # 555
print(f"Subscriber: {phone.subscriber}") # 2671
# Format for display using properties
print(f"E.164: {phone.e164}") # +14155552671
print(f"National: {phone.national}") # (415) 555-2671
case Failure(err):
print("Error:", err)
# Also accepts various formats
for number in ["4155552671", "(415) 555-2671", "415-555-2671"]:
result = parsers.parse_phone(number)
assert result.is_success()
```
## Testing Support
Valid8r includes comprehensive testing utilities to help you verify your validation logic:
```python
from valid8r import Maybe, validators, parsers, prompt
from valid8r.testing import (
MockInputContext,
assert_maybe_success,
assert_maybe_failure,
)
def validate_age(age: int) -> Maybe[int]:
"""Validate age is between 0 and 120."""
return (validators.minimum(0) & validators.maximum(120))(age)
# Test validation functions with assert helpers
result = validate_age(42)
assert assert_maybe_success(result, 42)
result = validate_age(-5)
assert assert_maybe_failure(result, "at least 0")
# Test prompts with mock input
with MockInputContext(["yes", "42", "invalid", "25"]):
# First prompt - returns Maybe, unwrap with value_or()
result = prompt.ask("Continue? ", parser=parsers.parse_bool)
assert result.value_or(False) == True
# Second prompt - unwrap the Maybe result
result = prompt.ask(
"Age? ",
parser=parsers.parse_int,
validator=validate_age
)
age = result.value_or(None)
assert age == 42
# Third prompt will fail, fourth succeeds - unwrap result
result = prompt.ask(
"Age again? ",
parser=parsers.parse_int,
retry=1 # Retry once after failure
)
age = result.value_or(None)
assert age == 25
```
### Testing Utilities Reference
- **`assert_maybe_success(result, expected_value)`**: Assert that a Maybe is Success with the expected value
- **`assert_maybe_failure(result, error_substring)`**: Assert that a Maybe is Failure containing the error substring
- **`MockInputContext(inputs)`**: Context manager for mocking user input in tests
For more examples, see the [documentation](https://valid8r.readthedocs.io/).
## Development
This project uses Poetry for dependency management and Tox for testing.
### Setup
```bash
# Install Poetry
curl -sSL https://install.python-poetry.org | python3 -
# Install dependencies
poetry install
```
### Running Tests
```bash
# Run all tests
poetry run tox
# Run BDD tests
poetry run tox -e bdd
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Copyright (c) 2025 Mike Lane
Raw data
{
"_id": null,
"home_page": null,
"name": "valid8r",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": "validation, input, cli, maybe-monad, parsing, functional-programming",
"author": "Mike Lane",
"author_email": "mikelane@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/b4/eb/39829e6123ca4a8aeca23653b120a0fbdad908919866284d1007bbcc1d6f/valid8r-0.7.0.tar.gz",
"platform": null,
"description": "# Valid8r\n\n[](https://pypi.org/project/valid8r/)\n[](https://pypi.org/project/valid8r/)\n[](https://github.com/mikelane/valid8r/blob/main/LICENSE)\n[](https://github.com/mikelane/valid8r/actions)\n[](https://codecov.io/gh/mikelane/valid8r)\n[](https://valid8r.readthedocs.io/)\n\n[](https://pypi.org/project/valid8r/)\n[](https://github.com/mikelane/valid8r/stargazers)\n[](https://github.com/mikelane/valid8r/graphs/contributors)\n[](https://github.com/astral-sh/ruff)\n[](https://github.com/python-semantic-release/python-semantic-release)\n\nA clean, flexible input validation library for Python applications.\n\n## Features\n\n- **Clean Type Parsing**: Parse strings to various Python types with robust error handling\n- **Flexible Validation**: Chain validators and create custom validation rules\n- **Monadic Error Handling**: Use Maybe monad for clean error propagation\n- **Input Prompting**: Prompt users for input with built-in validation\n- **Structured Results**: Network parsers return rich dataclasses with parsed components\n\n## Available Parsers\n\n### Basic Types\n- **Numbers**: `parse_int`, `parse_float`, `parse_complex`, `parse_decimal`\n- **Text**: `parse_bool` (flexible true/false parsing)\n- **Dates**: `parse_date` (ISO 8601 format)\n- **UUIDs**: `parse_uuid` (with optional version validation)\n\n### Collections\n- **Lists**: `parse_list` (with element parser)\n- **Dictionaries**: `parse_dict` (with key/value parsers)\n- **Sets**: `parse_set` (with element parser)\n\n### Network & Communication\n- **IP Addresses**: `parse_ipv4`, `parse_ipv6`, `parse_ip` (either v4 or v6)\n- **Networks**: `parse_cidr` (IPv4/IPv6 CIDR notation)\n- **Phone Numbers**: `parse_phone` \u2192 PhoneNumber (NANP validation)\n- **URLs**: `parse_url` \u2192 UrlParts (scheme, host, port, path, query, etc.)\n- **Email**: `parse_email` \u2192 EmailAddress (normalized case)\n\n### Advanced\n- **Enums**: `parse_enum` (type-safe enum parsing)\n- **Custom**: `create_parser`, `make_parser`, `validated_parser` (parser factories)\n\n## Available Validators\n\n### Numeric Validators\n- **`minimum(min_value)`** - Ensures value is at least the minimum (inclusive)\n- **`maximum(max_value)`** - Ensures value is at most the maximum (inclusive)\n- **`between(min_value, max_value)`** - Ensures value is within range (inclusive)\n\n### String Validators\n- **`non_empty_string()`** - Rejects empty strings and whitespace-only strings\n- **`matches_regex(pattern)`** - Validates string matches regex pattern (string or compiled)\n- **`length(min_length, max_length)`** - Validates string length within bounds\n\n### Collection Validators\n- **`in_set(allowed_values)`** - Ensures value is in set of allowed values\n- **`unique_items()`** - Ensures all items in a list are unique\n- **`subset_of(allowed_set)`** - Validates set is subset of allowed values\n- **`superset_of(required_set)`** - Validates set is superset of required values\n- **`is_sorted(reverse=False)`** - Ensures list is sorted (ascending or descending)\n\n### Custom Validators\n- **`predicate(func, error_message)`** - Create custom validator from any predicate function\n\n**Note**: All validators support custom error messages and can be combined using `&` (and), `|` (or), and `~` (not) operators.\n\n## Installation\n\n**Requirements**: Python 3.11 or higher\n\n```bash\npip install valid8r\n```\n\nValid8r supports Python 3.11, 3.12, 3.13, and 3.14.\n\n## Quick Start\n\n```python\nfrom valid8r import (\n parsers,\n prompt,\n validators,\n)\n\n# Simple validation\nage = prompt.ask(\n \"Enter your age: \",\n parser=parsers.parse_int,\n validator=validators.minimum(0) & validators.maximum(120)\n)\n\nprint(f\"Your age is {age}\")\n```\n\n### IP parsing helpers\n\n```python\nfrom valid8r.core.maybe import Success, Failure\nfrom valid8r.core import parsers\n\n# IPv4 / IPv6 / generic IP\nfor text in [\"192.168.0.1\", \"::1\", \" 10.0.0.1 \"]:\n match parsers.parse_ip(text):\n case Success(addr):\n print(\"Parsed:\", addr)\n case Failure(err):\n print(\"Error:\", err)\n\n# CIDR (strict by default)\nmatch parsers.parse_cidr(\"10.0.0.0/8\"):\n case Success(net):\n print(\"Network:\", net) # 10.0.0.0/8\n case Failure(err):\n print(\"Error:\", err)\n\n# Non-strict masks host bits\nmatch parsers.parse_cidr(\"10.0.0.1/24\", strict=False):\n case Success(net):\n assert str(net) == \"10.0.0.0/24\"\n```\n\n### URL and Email helpers\n\n```python\nfrom valid8r.core.maybe import Success, Failure\nfrom valid8r.core import parsers\n\n# URL parsing with structured result (UrlParts)\nmatch parsers.parse_url(\"https://alice:pw@example.com:8443/x?q=1#top\"):\n case Success(u):\n print(f\"Scheme: {u.scheme}\") # https\n print(f\"Host: {u.host}\") # example.com\n print(f\"Port: {u.port}\") # 8443\n print(f\"Path: {u.path}\") # /x\n print(f\"Query: {u.query}\") # {'q': '1'}\n print(f\"Fragment: {u.fragment}\") # top\n case Failure(err):\n print(\"Error:\", err)\n\n# Email parsing with normalized case (EmailAddress)\nmatch parsers.parse_email(\"First.Last+tag@Example.COM\"):\n case Success(e):\n print(f\"Local: {e.local}\") # First.Last+tag\n print(f\"Domain: {e.domain}\") # example.com (normalized)\n case Failure(err):\n print(\"Error:\", err)\n```\n\n### Phone Number Parsing\n\n```python\nfrom valid8r.core.maybe import Success, Failure\nfrom valid8r.core import parsers\n\n# Phone number parsing with NANP validation (PhoneNumber)\nmatch parsers.parse_phone(\"+1 (415) 555-2671\"):\n case Success(phone):\n print(f\"Country: {phone.country_code}\") # 1\n print(f\"Area: {phone.area_code}\") # 415\n print(f\"Exchange: {phone.exchange}\") # 555\n print(f\"Subscriber: {phone.subscriber}\") # 2671\n\n # Format for display using properties\n print(f\"E.164: {phone.e164}\") # +14155552671\n print(f\"National: {phone.national}\") # (415) 555-2671\n case Failure(err):\n print(\"Error:\", err)\n\n# Also accepts various formats\nfor number in [\"4155552671\", \"(415) 555-2671\", \"415-555-2671\"]:\n result = parsers.parse_phone(number)\n assert result.is_success()\n```\n\n## Testing Support\n\nValid8r includes comprehensive testing utilities to help you verify your validation logic:\n\n```python\nfrom valid8r import Maybe, validators, parsers, prompt\nfrom valid8r.testing import (\n MockInputContext,\n assert_maybe_success,\n assert_maybe_failure,\n)\n\ndef validate_age(age: int) -> Maybe[int]:\n \"\"\"Validate age is between 0 and 120.\"\"\"\n return (validators.minimum(0) & validators.maximum(120))(age)\n\n# Test validation functions with assert helpers\nresult = validate_age(42)\nassert assert_maybe_success(result, 42)\n\nresult = validate_age(-5)\nassert assert_maybe_failure(result, \"at least 0\")\n\n# Test prompts with mock input\nwith MockInputContext([\"yes\", \"42\", \"invalid\", \"25\"]):\n # First prompt - returns Maybe, unwrap with value_or()\n result = prompt.ask(\"Continue? \", parser=parsers.parse_bool)\n assert result.value_or(False) == True\n\n # Second prompt - unwrap the Maybe result\n result = prompt.ask(\n \"Age? \",\n parser=parsers.parse_int,\n validator=validate_age\n )\n age = result.value_or(None)\n assert age == 42\n\n # Third prompt will fail, fourth succeeds - unwrap result\n result = prompt.ask(\n \"Age again? \",\n parser=parsers.parse_int,\n retry=1 # Retry once after failure\n )\n age = result.value_or(None)\n assert age == 25\n```\n\n### Testing Utilities Reference\n\n- **`assert_maybe_success(result, expected_value)`**: Assert that a Maybe is Success with the expected value\n- **`assert_maybe_failure(result, error_substring)`**: Assert that a Maybe is Failure containing the error substring\n- **`MockInputContext(inputs)`**: Context manager for mocking user input in tests\n\nFor more examples, see the [documentation](https://valid8r.readthedocs.io/).\n\n## Development\n\nThis project uses Poetry for dependency management and Tox for testing.\n\n### Setup\n\n```bash\n# Install Poetry\ncurl -sSL https://install.python-poetry.org | python3 -\n\n# Install dependencies\npoetry install\n```\n\n### Running Tests\n\n```bash\n# Run all tests\npoetry run tox\n\n# Run BDD tests\npoetry run tox -e bdd\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\nCopyright (c) 2025 Mike Lane\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Clean, flexible input validation for Python applications",
"version": "0.7.0",
"project_urls": {
"Documentation": "https://valid8r.readthedocs.io/",
"Homepage": "https://valid8r.readthedocs.io/",
"Repository": "https://github.com/mikelane/valid8r"
},
"split_keywords": [
"validation",
" input",
" cli",
" maybe-monad",
" parsing",
" functional-programming"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "d70940f25ebeab2aa642b4ce5ee409ce719a5353fd8ddc7464948836906a2d2b",
"md5": "95c47814661ba3475545fab7281b7b56",
"sha256": "f1d2a2aa963750b3d69ef80dc11a695e55cd83ed2585b5acf67c7a714c195e4b"
},
"downloads": -1,
"filename": "valid8r-0.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "95c47814661ba3475545fab7281b7b56",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 34438,
"upload_time": "2025-10-31T05:16:17",
"upload_time_iso_8601": "2025-10-31T05:16:17.914578Z",
"url": "https://files.pythonhosted.org/packages/d7/09/40f25ebeab2aa642b4ce5ee409ce719a5353fd8ddc7464948836906a2d2b/valid8r-0.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b4eb39829e6123ca4a8aeca23653b120a0fbdad908919866284d1007bbcc1d6f",
"md5": "27423238b45349b5522553dc92b05593",
"sha256": "073b869a4aac60d0b098a11233fb09ce2cd82984b94f291378d8473668d6ffeb"
},
"downloads": -1,
"filename": "valid8r-0.7.0.tar.gz",
"has_sig": false,
"md5_digest": "27423238b45349b5522553dc92b05593",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 34266,
"upload_time": "2025-10-31T05:16:18",
"upload_time_iso_8601": "2025-10-31T05:16:18.768859Z",
"url": "https://files.pythonhosted.org/packages/b4/eb/39829e6123ca4a8aeca23653b120a0fbdad908919866284d1007bbcc1d6f/valid8r-0.7.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-31 05:16:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "mikelane",
"github_project": "valid8r",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"tox": true,
"lcname": "valid8r"
}