
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
valid8r
Advanced tools
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.
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}")
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.
Security-First Design: All parsers include DoS protection via input length validation and automated ReDoS detection prevents vulnerable regex patterns.
Framework Integrations: Built-in support for Pydantic (always included) and optional Click integration for CLI apps.
Interactive Prompts: Built-in user input prompting with automatic retry and validation.
Basic installation (includes Pydantic integration):
pip install valid8r
With optional framework integrations:
# Click integration for CLI applications
pip install 'valid8r[click]'
# All optional integrations
pip install 'valid8r[click]'
Requirements: Python 3.11 or higher
| Feature | Installation | Import |
|---|---|---|
| Core parsers & validators | pip install valid8r | from valid8r import parsers, validators |
| Pydantic integration | included by default | from valid8r.integrations import validator_from_parser |
| Click integration (CLI) | pip install 'valid8r[click]' | from valid8r.integrations import ParamTypeAdapter |
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()
from valid8r import parsers
from datetime import UTC
# Parse timezone-aware datetime (ISO 8601)
result = parsers.parse_datetime("2024-01-15T10:30:00Z")
match result:
case Success(dt):
print(f"DateTime: {dt}") # 2024-01-15 10:30:00+00:00
print(f"Timezone: {dt.tzinfo}") # UTC
case Failure(error):
print(f"Error: {error}")
# Parse with timezone offset
result = parsers.parse_datetime("2024-01-15T10:30:00+05:30")
assert result.is_success()
# Parse duration/timedelta in multiple formats
result = parsers.parse_timedelta("1h 30m") # Simple format
assert result.value_or(None).total_seconds() == 5400
result = parsers.parse_timedelta("PT1H30M") # ISO 8601 duration
assert result.value_or(None).total_seconds() == 5400
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'})
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
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)
)
from valid8r import parsers, validators
from valid8r.core.maybe import Success, Failure
# Parse and validate file paths
match parsers.parse_path("/etc/hosts").bind(validators.exists()).bind(validators.is_file()):
case Success(path):
print(f"Valid file: {path}")
case Failure(err):
print(f"Error: {err}")
# Validate uploaded files
def validate_upload(file_path: str):
return (
parsers.parse_path(file_path)
.bind(validators.exists())
.bind(validators.is_file())
.bind(validators.has_extension(['.pdf', '.docx']))
.bind(validators.max_size(10 * 1024 * 1024)) # 10MB limit
)
# Path expansion and resolution
match parsers.parse_path("~/Documents", expand_user=True):
case Success(path):
print(f"Expanded: {path}") # /Users/username/Documents
match parsers.parse_path("./data/file.txt", resolve=True):
case Success(path):
print(f"Absolute: {path}") # /full/path/to/data/file.txt
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
)
Valid8r provides machine-readable error codes and structured error information for programmatic error handling and API responses:
from valid8r import parsers
from valid8r.core.maybe import Success, Failure
from valid8r.core.errors import ErrorCode
# Programmatic error handling using error codes
def process_email(email_str: str):
result = parsers.parse_email(email_str)
match result:
case Success(email):
return f"Valid: {email.local}@{email.domain}"
case Failure():
# Access structured error for programmatic handling
detail = result.error_detail()
# Switch on error codes for different handling
match detail.code:
case ErrorCode.INVALID_EMAIL:
return "Please enter a valid email address"
case ErrorCode.EMPTY_STRING:
return "Email is required"
case ErrorCode.INPUT_TOO_LONG:
return "Email is too long"
case _:
return f"Error: {detail.message}"
# Convert errors to JSON for API responses
result = parsers.parse_int("not-a-number")
match result:
case Failure():
error_dict = result.error_detail().to_dict()
# {
# 'code': 'INVALID_TYPE',
# 'message': 'Input must be a valid integer',
# 'path': '',
# 'context': {}
# }
Features:
ErrorCode.INVALID_EMAIL, ErrorCode.OUT_OF_RANGE)to_dict().user.email)See the Error Handling Guide for comprehensive examples and best practices.
Load typed, validated configuration from environment variables following 12-factor app principles:
from valid8r.integrations.env import EnvSchema, EnvField, load_env_config
from valid8r import parsers, validators
from valid8r.core.maybe import Success, Failure
# Define configuration schema
schema = EnvSchema(fields={
'port': EnvField(
parser=lambda x: parsers.parse_int(x).bind(validators.between(1024, 65535)),
default=8080
),
'debug': EnvField(parser=parsers.parse_bool, default=False),
'database_url': EnvField(parser=parsers.parse_str, required=True),
'admin_email': EnvField(parser=parsers.parse_email, required=True),
})
# Load and validate configuration
result = load_env_config(schema, prefix='APP_')
match result:
case Success(config):
# All values are typed and validated
port = config['port'] # int (validated 1024-65535)
debug = config['debug'] # bool (not str!)
db = config['database_url'] # str (required, guaranteed present)
email = config['admin_email'] # EmailAddress (validated format)
case Failure(error):
print(f"Configuration error: {error}")
Features:
See Environment Variables Guide for complete examples including FastAPI, Docker, and Kubernetes deployment patterns.
Convert valid8r parsers into Pydantic field validators:
from pydantic import BaseModel, field_validator
from valid8r import parsers, validators
from valid8r.integrations import validator_from_parser
class User(BaseModel):
age: int
@field_validator('age', mode='before')
@classmethod
def validate_age(cls, v):
# Parse string to int, then validate 0-120 range
age_parser = lambda x: parsers.parse_int(x).bind(
validators.between(0, 120)
)
return validator_from_parser(age_parser)(v)
user = User(age="25") # Accepts string, validates, returns int
Works seamlessly with nested models, lists, and complex Pydantic schemas. See Pydantic Integration Examples.
Install: pip install 'valid8r[click]'
Integrate valid8r parsers into Click CLI applications:
import click
from valid8r import parsers
from valid8r.integrations import ParamTypeAdapter
@click.command()
@click.option('--email', type=ParamTypeAdapter(parsers.parse_email))
def send_mail(email):
"""Send an email with validated address."""
click.echo(f"Sending to {email.local}@{email.domain}")
# Automatically validates email format and provides rich error messages
See Click Integration Examples.
Basic Types:
parse_int, parse_float, parse_bool, parse_decimal, parse_complexparse_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_cidrparse_url → UrlParts (structured URL components)parse_email → EmailAddress (normalized domain)parse_phone → PhoneNumber (NANP validation with E.164 formatting)Filesystem:
parse_path → pathlib.Path (with expansion and resolution options)Advanced:
parse_enum (type-safe enum parsing)create_parser, make_parser, validated_parser (custom parser factories)Numeric: minimum, maximum, between
String: non_empty_string, matches_regex, length
Collection: in_set, unique_items, subset_of, superset_of, is_sorted
Filesystem: exists, is_file, is_dir, is_readable, is_writable, is_executable, max_size, min_size, has_extension
Custom: predicate (create validators from any function)
Combinators: Combine validators using & (and), | (or), ~ (not)
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()
Full documentation: valid8r.readthedocs.io
Please do not report security vulnerabilities through public GitHub issues.
Report security issues privately to mikelane@gmail.com or via GitHub Security Advisories.
See SECURITY.md for our complete security policy, supported versions, and response timeline.
Valid8r is designed for parsing trusted user input in web applications. For production deployments:
Example - Flask Defense in Depth:
from flask import Flask, request
from valid8r import parsers
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 # Layer 1: Framework limit
@app.route('/submit', methods=['POST'])
def submit():
phone = request.form.get('phone', '')
# Layer 2: Application validation
if len(phone) > 100:
return "Phone too long", 400
# Layer 3: Parser validation
result = parsers.parse_phone(phone)
if result.is_failure():
return "Invalid phone format", 400
return process_phone(result.value_or(None))
See Production Deployment Guide for framework-specific examples (Flask, Django, FastAPI).
Valid8r provides input validation, not protection against:
Parser Input Limits:
| Parser | Max Input | Notes |
|---|---|---|
parse_email() | 254 chars | RFC 5321 maximum |
parse_phone() | 100 chars | International + extension |
parse_url() | 2048 chars | Browser URL limit |
parse_uuid() | 36 chars | Standard UUID format |
parse_ip() | 45 chars | IPv6 maximum |
All parsers include built-in DoS protection with early length validation before expensive operations.
See SECURITY.md for complete security documentation.
Join the Valid8r community on GitHub Discussions:
When to use Discussions vs Issues:
See the Welcome Discussion for community guidelines.
We welcome contributions! All contributions must be made via forks - please do not create branches directly in the main repository.
See CONTRIBUTING.md for complete guidelines.
Quick links:
# 1. Fork the repository on GitHub
# Visit: https://github.com/mikelane/valid8r
# 2. Clone YOUR fork (not the upstream repo)
git clone https://github.com/YOUR-USERNAME/valid8r
cd valid8r
# 3. Add upstream remote
git remote add upstream https://github.com/mikelane/valid8r.git
# 4. Install uv (fast dependency manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 5. Install dependencies
uv sync
# 6. Run tests
uv run tox
# 7. Run linters
uv run ruff check .
uv run ruff format .
uv run mypy valid8r
# 8. Create a feature branch and make your changes
git checkout -b feat/your-feature
# 9. Push to YOUR fork and create a PR
git push origin feat/your-feature
Valid8r is in active development (v0.7.x). The API is stabilizing but may change before v1.0.0.
See ROADMAP.md for planned features.
MIT License - see LICENSE for details.
Copyright (c) 2025 Mike Lane
Made with ❤️ for the Python community
FAQs
Clean, flexible input validation for Python applications
We found that valid8r demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.