Metadata-Version: 2.4
Name: valid8r
Version: 0.9.1
Summary: Clean, flexible input validation for Python applications
Project-URL: Homepage, https://valid8r.readthedocs.io/
Project-URL: Repository, https://github.com/mikelane/valid8r
Project-URL: Documentation, https://valid8r.readthedocs.io/
Project-URL: Issues, https://github.com/mikelane/valid8r/issues
Author-email: Mike Lane <mikelane@gmail.com>
License: MIT
License-File: LICENSE
Keywords: cli,functional-programming,input,maybe-monad,parsing,validation
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: email-validator>=2.3.0
Requires-Dist: pydantic-core>=2.27.0
Requires-Dist: pydantic>=2.0
Requires-Dist: uuid-utils>=0.11.0
Description-Content-Type: text/markdown

# Valid8r

[![PyPI version](https://img.shields.io/pypi/v/valid8r.svg)](https://pypi.org/project/valid8r/)
[![Python versions](https://img.shields.io/pypi/pyversions/valid8r.svg)](https://pypi.org/project/valid8r/)
[![License](https://img.shields.io/github/license/mikelane/valid8r.svg)](https://github.com/mikelane/valid8r/blob/main/LICENSE)
[![CI Status](https://img.shields.io/github/actions/workflow/status/mikelane/valid8r/ci.yml?branch=main)](https://github.com/mikelane/valid8r/actions)
[![codecov](https://codecov.io/gh/mikelane/valid8r/branch/main/graph/badge.svg)](https://codecov.io/gh/mikelane/valid8r)
[![Documentation](https://img.shields.io/readthedocs/valid8r.svg)](https://valid8r.readthedocs.io/)

**Clean, composable input validation for Python using functional programming patterns.**

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.

```python
from valid8r import parsers, validators, prompt

# Parse and validate user input with rich error messages
age = prompt.ask(
    "Enter your age: ",
    parser=parsers.parse_int,
    validator=validators.minimum(0) & validators.maximum(120)
)

print(f"Your age is {age}")
```

## Why Valid8r?

**Type-Safe Parsing**: Every parser returns `Maybe[T]` (Success or Failure), making error handling explicit and composable.

**Rich Structured Results**: Network parsers return dataclasses with parsed components—no more manual URL/email splitting.

**Chainable Validators**: Combine validators using `&` (and), `|` (or), and `~` (not) operators for complex validation logic.

**Zero Dependencies**: Core library uses only Python's standard library (with optional `uuid-utils` for faster UUID parsing).

**Interactive Prompts**: Built-in user input prompting with automatic retry and validation.

## Quick Start

### Installation

```bash
pip install valid8r
```

**Requirements**: Python 3.11 or higher

### Basic Parsing

```python
from valid8r import parsers
from valid8r.core.maybe import Success, Failure

# Parse integers with automatic error handling
match parsers.parse_int("42"):
    case Success(value):
        print(f"Parsed: {value}")  # Parsed: 42
    case Failure(error):
        print(f"Error: {error}")

# Parse dates (ISO 8601 format)
result = parsers.parse_date("2025-01-15")
assert result.is_success()

# Parse UUIDs with version validation
result = parsers.parse_uuid("550e8400-e29b-41d4-a716-446655440000", version=4)
assert result.is_success()
```

### Validation with Combinators

```python
from valid8r import validators

# Combine validators using operators
age_validator = validators.minimum(0) & validators.maximum(120)
result = age_validator(42)
assert result.is_success()

# String validation
password_validator = (
    validators.length(8, 128) &
    validators.matches_regex(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]')
)

# Set validation
tags_validator = validators.subset_of({'python', 'rust', 'go', 'typescript'})
```

### Structured Network Parsing

```python
from valid8r import parsers

# Parse URLs into structured components
match parsers.parse_url("https://user:pass@example.com:8443/path?query=1#fragment"):
    case Success(url):
        print(f"Scheme: {url.scheme}")      # https
        print(f"Host: {url.host}")          # example.com
        print(f"Port: {url.port}")          # 8443
        print(f"Path: {url.path}")          # /path
        print(f"Query: {url.query}")        # {'query': '1'}
        print(f"Fragment: {url.fragment}")  # fragment

# Parse emails with normalized domains
match parsers.parse_email("User@Example.COM"):
    case Success(email):
        print(f"Local: {email.local}")    # User
        print(f"Domain: {email.domain}")  # example.com (normalized)

# Parse phone numbers (NANP format)
match parsers.parse_phone("+1 (415) 555-2671"):
    case Success(phone):
        print(f"E.164: {phone.e164}")      # +14155552671
        print(f"National: {phone.national}")  # (415) 555-2671
```

### Collection Parsing

```python
from valid8r import parsers

# Parse lists with element validation
result = parsers.parse_list("1,2,3,4,5", element_parser=parsers.parse_int)
assert result.value_or([]) == [1, 2, 3, 4, 5]

# Parse dictionaries with key/value parsers
result = parsers.parse_dict(
    "name=Alice,age=30",
    key_parser=lambda x: Success(x),
    value_parser=lambda x: parsers.parse_int(x) if x.isdigit() else Success(x)
)
```

### Interactive Prompting

```python
from valid8r import prompt, parsers, validators

# Prompt with validation and automatic retry
email = prompt.ask(
    "Email address: ",
    parser=parsers.parse_email,
    retry=2  # Retry twice on invalid input
)

# Combine parsing and validation
port = prompt.ask(
    "Server port: ",
    parser=parsers.parse_int,
    validator=validators.between(1024, 65535),
    retry=3
)
```

## Features

### Parsers

**Basic Types**:
- `parse_int`, `parse_float`, `parse_bool`, `parse_decimal`, `parse_complex`
- `parse_date` (ISO 8601), `parse_uuid` (with version validation)

**Collections**:
- `parse_list`, `parse_dict`, `parse_set` (with element parsers)

**Network & Communication**:
- `parse_ipv4`, `parse_ipv6`, `parse_ip`, `parse_cidr`
- `parse_url` → `UrlParts` (structured URL components)
- `parse_email` → `EmailAddress` (normalized domain)
- `parse_phone` → `PhoneNumber` (NANP validation with E.164 formatting)

**Advanced**:
- `parse_enum` (type-safe enum parsing)
- `create_parser`, `make_parser`, `validated_parser` (custom parser factories)

### Validators

**Numeric**: `minimum`, `maximum`, `between`

**String**: `non_empty_string`, `matches_regex`, `length`

**Collection**: `in_set`, `unique_items`, `subset_of`, `superset_of`, `is_sorted`

**Custom**: `predicate` (create validators from any function)

**Combinators**: Combine validators using `&` (and), `|` (or), `~` (not)

### Testing Utilities

```python
from valid8r.testing import (
    assert_maybe_success,
    assert_maybe_failure,
    MockInputContext,
)

# Test validation logic
result = validators.minimum(0)(42)
assert assert_maybe_success(result, 42)

result = validators.minimum(0)(-5)
assert assert_maybe_failure(result, "at least 0")

# Mock user input for testing prompts
with MockInputContext(["invalid", "valid@example.com"]):
    result = prompt.ask("Email: ", parser=parsers.parse_email, retry=1)
    assert result.is_success()
```

## Documentation

**Full documentation**: [valid8r.readthedocs.io](https://valid8r.readthedocs.io/)

- [API Reference](https://valid8r.readthedocs.io/en/latest/api.html)
- [Parser Guide](https://valid8r.readthedocs.io/en/latest/parsers.html)
- [Validator Guide](https://valid8r.readthedocs.io/en/latest/validators.html)
- [Testing Guide](https://valid8r.readthedocs.io/en/latest/testing.html)

## Contributing

We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

**Quick links**:
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Development Setup](CONTRIBUTING.md#development-setup)
- [Commit Message Format](CONTRIBUTING.md#commit-messages)
- [Pull Request Process](CONTRIBUTING.md#pull-request-process)

### Development Quick Start

```bash
# Install uv (fast dependency manager)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone and install dependencies
git clone https://github.com/mikelane/valid8r
cd valid8r
uv sync

# Run tests
uv run tox

# Run linters
uv run ruff check .
uv run ruff format .
uv run mypy valid8r
```

## Project Status

Valid8r is in active development (v0.7.x). The API is stabilizing but may change before v1.0.0.

- ✅ Core parsers and validators
- ✅ Maybe monad error handling
- ✅ Interactive prompting
- ✅ Network parsers (URL, Email, IP, Phone)
- ✅ Collection parsers
- ✅ Comprehensive testing utilities
- 🚧 Additional validators (in progress)
- 🚧 Custom error types (planned)

See [ROADMAP.md](ROADMAP.md) for planned features.

## License

MIT License - see [LICENSE](LICENSE) for details.

Copyright (c) 2025 Mike Lane

---

**Made with ❤️ for the Python community**
