🚀 DAY 5 OF LAUNCH WEEK:Introducing Webhook Events for Alert Changes.Learn more →
Socket
Book a DemoInstallSign in
Socket

valid8r

Package Overview
Dependencies
Maintainers
1
Versions
52
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

valid8r

Clean, flexible input validation for Python applications

pipPyPI
Version
1.16.0
Maintainers
1

Valid8r

PyPI version Python versions License CI Release codecov Documentation

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}")

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.

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.

Quick Start

Installation

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

FeatureInstallationImport
Core parsers & validatorspip install valid8rfrom valid8r import parsers, validators
Pydantic integrationincluded by defaultfrom valid8r.integrations import validator_from_parser
Click integration (CLI)pip install 'valid8r[click]'from valid8r.integrations import ParamTypeAdapter

Basic Parsing

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()

Temporal Parsing

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

Validation with Combinators

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

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

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)
)

Filesystem Parsing and Validation

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

Interactive Prompting

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
)

Structured Error Handling

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:

  • Error codes for programmatic handling (e.g., ErrorCode.INVALID_EMAIL, ErrorCode.OUT_OF_RANGE)
  • JSON serialization for API responses via to_dict()
  • Field paths for multi-field validation (e.g., .user.email)
  • Debugging context with validation parameters
  • 100% backward compatible with string errors

See the Error Handling Guide for comprehensive examples and best practices.

Environment Variables

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:

  • Type-safe parsing (no more string-to-int conversions)
  • Declarative validation with composable parsers
  • Required vs optional fields with sensible defaults
  • Nested schemas for hierarchical configuration
  • Clear error messages for missing or invalid values

See Environment Variables Guide for complete examples including FastAPI, Docker, and Kubernetes deployment patterns.

Framework Integrations

Pydantic Integration (Always Included)

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.

Click Integration (Optional)

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.

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)

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)

Validators

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)

Testing Utilities

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

Security

Reporting Vulnerabilities

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.

Production Deployment

Valid8r is designed for parsing trusted user input in web applications. For production deployments:

  • Enforce input size limits at the framework level (recommended: 10KB max request size)
  • Implement rate limiting for validation endpoints (recommended: 10 requests/minute)
  • Use defense in depth: Framework → Application → Parser validation
  • Monitor and log validation failures for security analysis

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).

Security Boundaries

Valid8r provides input validation, not protection against:

  • ❌ SQL injection - Use parameterized queries / ORMs
  • ❌ XSS attacks - Use output encoding / template engines
  • ❌ CSRF attacks - Use CSRF tokens / SameSite cookies
  • ❌ DDoS attacks - Use rate limiting / CDN / WAF

Parser Input Limits:

ParserMax InputNotes
parse_email()254 charsRFC 5321 maximum
parse_phone()100 charsInternational + extension
parse_url()2048 charsBrowser URL limit
parse_uuid()36 charsStandard UUID format
parse_ip()45 charsIPv6 maximum

All parsers include built-in DoS protection with early length validation before expensive operations.

See SECURITY.md for complete security documentation.

Community

Join the Valid8r community on GitHub Discussions:

  • Questions? Start a discussion in Q&A
  • Feature ideas? Share them in Ideas
  • Built something cool? Show it off in Show and Tell
  • Announcements: Watch Announcements for updates

When to use Discussions vs Issues:

  • Use Discussions for questions, ideas, and general conversation
  • Use Issues for bug reports and feature requests with technical specifications

See the Welcome Discussion for community guidelines.

Contributing

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:

Development Quick Start

# 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

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 for planned features.

License

MIT License - see LICENSE for details.

Copyright (c) 2025 Mike Lane

Made with ❤️ for the Python community

Keywords

cli

FAQs

Did you know?

Socket

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.

Install

Related posts