validators

バリデーションPythonライブラリデータ検証URL検証メール検証

バリデーションライブラリ

validators

概要

validatorsは、「Python Data Validation for Humans™」をコンセプトとした、シンプルで使いやすいPythonのバリデーションライブラリです。複雑なスキーマやフォームを定義することなく、単一の値を簡単に検証できることが特徴です。

URL、メールアドレス、IPアドレス、UUID、ドメイン名など、Webアプリケーション開発で頻繁に必要となる様々な形式の検証機能を提供しています。各バリデータは独立した関数として実装されており、検証が成功した場合はTrueを、失敗した場合はValidationFailureオブジェクトを返すという一貫したインターフェースを持っています。

詳細

validatorsは、Pythonのデータ検証を簡潔かつ直感的に行うために設計されたライブラリです。多くの検証ライブラリがスキーマの定義を要求する中、validatorsは単純な値の検証にフォーカスし、最小限のコードで実装できる設計となっています。

主な特徴

1. シンプルなAPI設計

import validators

# メールアドレスの検証
validators.email('[email protected]')  # True を返す

2. 豊富な検証機能

  • URL検証: dperiniの高精度なURL検証アルゴリズムを採用
  • メールアドレス検証: DjangoのEmail validatorベース
  • IPアドレス検証: IPv4とIPv6の両方に対応(WTForms準拠)
  • ドメイン名検証: 国際化ドメイン名(IDN)もサポート
  • UUID検証: RFC 4122準拠のUUID形式を検証
  • IBAN検証: 国際銀行口座番号の検証
  • MAC検証: MACアドレスの形式検証
  • BTCアドレス検証: Bitcoinアドレスの検証(P2PKH、P2SH完全対応)
  • スラッグ検証: URLフレンドリーな文字列の検証
  • 長さ検証: 文字列長の範囲検証
  • 範囲検証: 数値、日付などの範囲検証

3. 一貫した戻り値の設計

すべてのバリデータ関数は検証成功時にTrueを返し、失敗時はValidationFailureオブジェクトを返します。ValidationFailure__bool__メソッドを実装しているため、通常のif文で簡単にエラーチェックが可能です。

4. 最新バージョンの改善点(v0.35.0)

  • .onionドメインの検証サポート
  • URLフラグメント内のハッシュタグ文字の許可
  • メール正規表現の修正
  • 特殊なDOIケースのサポート
  • URI検証の改善

メリット・デメリット

メリット

  1. 学習コストが低い

    • スキーマ定義不要で、すぐに使い始められる
    • 各関数が独立しており、必要な機能だけを選択的に使用可能
  2. 高速かつ軽量

    • 依存関係が少なく、パフォーマンスへの影響が最小限
    • シンプルな実装により高速な検証処理を実現
  3. 実績のある実装

    • DjangoやWTFormsなど、実績のあるフレームワークのバリデータをベースに実装
    • dperiniのURL検証など、業界標準のアルゴリズムを採用
  4. 柔軟な統合

    • 既存のプロジェクトへの導入が容易
    • 他のバリデーションライブラリと併用可能
  5. 国際化対応

    • 国際化ドメイン名(IDN)のサポート
    • 様々な国の標準規格(IBAN等)に対応

デメリット

  1. 高度な検証機能の不足

    • 複雑な条件付き検証やカスタムルールの定義が困難
    • ネストしたデータ構造の検証には不向き
  2. エラーメッセージのカスタマイズ制限

    • エラーメッセージの詳細なカスタマイズオプションが限定的
    • 多言語対応のエラーメッセージは自前で実装が必要
  3. 型アノテーション非対応

    • Pydanticのような型ベースの検証はサポートしていない
    • 静的型チェックとの統合が弱い
  4. スキーマベース検証の欠如

    • 複数フィールドの相互検証が必要な場合は別途実装が必要
    • データモデルの定義と検証の統合がない

参考ページ

書き方の例

基本的な使い方

import validators

# インストール
# pip install validators

# 基本的な検証パターン
if validators.email('[email protected]'):
    print("有効なメールアドレスです")
else:
    print("無効なメールアドレスです")

# ValidationFailureオブジェクトの扱い
result = validators.url('not-a-url')
if not result:
    print(f"検証失敗: {result}")

URL検証

import validators

# 基本的なURL検証
print(validators.url('https://example.com'))  # True
print(validators.url('http://localhost:8080'))  # True
print(validators.url('ftp://files.example.com'))  # True

# 無効なURLの例
result = validators.url('not a url')
if not result:
    print("URLが無効です")

# HTTPSのみを許可する場合(カスタムロジック)
def validate_https_url(url):
    if validators.url(url) and url.startswith('https://'):
        return True
    return False

print(validate_https_url('https://secure.example.com'))  # True
print(validate_https_url('http://insecure.example.com'))  # False

メールアドレス検証

import validators

# 基本的なメール検証
emails = [
    '[email protected]',
    '[email protected]',
    'invalid.email@',
    '@example.com',
    '[email protected]'
]

for email in emails:
    result = validators.email(email)
    status = "有効" if result else "無効"
    print(f"{email}: {status}")

# フォーム検証での使用例
def validate_registration_form(form_data):
    errors = {}
    
    if not validators.email(form_data.get('email', '')):
        errors['email'] = 'メールアドレスが無効です'
    
    if not validators.length(form_data.get('password', ''), min=8):
        errors['password'] = 'パスワードは8文字以上必要です'
    
    return errors

IPアドレス検証

import validators

# IPv4アドレスの検証
ipv4_addresses = [
    '192.168.1.1',
    '10.0.0.1',
    '256.256.256.256',  # 無効
    '192.168.1'         # 無効
]

for ip in ipv4_addresses:
    if validators.ipv4(ip):
        print(f"{ip} は有効なIPv4アドレスです")
    else:
        print(f"{ip} は無効なIPv4アドレスです")

# IPv6アドレスの検証
ipv6_addresses = [
    '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
    '2001:db8:85a3::8a2e:370:7334',  # 短縮形
    'fe80::1',
    'invalid::address::format'  # 無効
]

for ip in ipv6_addresses:
    if validators.ipv6(ip):
        print(f"{ip} は有効なIPv6アドレスです")

# IPアドレスの種類を判定
def get_ip_type(ip_address):
    if validators.ipv4(ip_address):
        return "IPv4"
    elif validators.ipv6(ip_address):
        return "IPv6"
    else:
        return "無効なIPアドレス"

ドメイン名とUUID検証

import validators
import uuid

# ドメイン名の検証
domains = [
    'example.com',
    'sub.example.com',
    'example.co.jp',
    '日本.jp',  # 国際化ドメイン名
    'example.onion',  # Torドメイン
    'invalid domain.com',  # 無効(スペース含む)
    '.com'  # 無効
]

for domain in domains:
    if validators.domain(domain):
        print(f"{domain} は有効なドメイン名です")

# UUIDの検証
test_uuids = [
    str(uuid.uuid4()),  # 有効なUUID
    '550e8400-e29b-41d4-a716-446655440000',  # 有効
    'not-a-uuid',  # 無効
    '550e8400-e29b-41d4-a716'  # 無効(短すぎる)
]

for test_uuid in test_uuids:
    if validators.uuid(test_uuid):
        print(f"{test_uuid} は有効なUUIDです")

# スラッグの検証(URLフレンドリーな文字列)
slugs = [
    'my-blog-post',
    'article_2024',
    'post-123',
    'invalid slug!',  # 無効(特殊文字)
    'my.post'  # 無効(ドット含む)
]

for slug in slugs:
    if validators.slug(slug):
        print(f"'{slug}' は有効なスラッグです")

高度な検証パターン

import validators
from datetime import datetime, date

# 文字列長の検証
passwords = [
    'short',
    'minimum8chars',
    'this_is_a_very_long_password_that_exceeds_maximum'
]

for password in passwords:
    if validators.length(password, min=8, max=32):
        print(f"パスワード '{password}' は有効です")
    else:
        print(f"パスワード '{password}' は8-32文字である必要があります")

# 数値範囲の検証
ages = [15, 18, 25, 65, 120]

for age in ages:
    if validators.between(age, min=18, max=100):
        print(f"年齢 {age} は有効です")
    else:
        print(f"年齢 {age} は18-100の範囲外です")

# 日付範囲の検証
dates = [
    date(2020, 1, 1),
    date(2024, 6, 15),
    date(2025, 12, 31)
]

min_date = date(2023, 1, 1)
max_date = date(2025, 1, 1)

for d in dates:
    if validators.between(d, min=min_date, max=max_date):
        print(f"{d} は有効な日付範囲内です")

# 複数の検証を組み合わせた例
def validate_user_input(data):
    """ユーザー入力の包括的な検証"""
    errors = []
    
    # メールアドレスの検証
    if not validators.email(data.get('email', '')):
        errors.append('メールアドレスが無効です')
    
    # URLの検証(オプション)
    if data.get('website') and not validators.url(data['website']):
        errors.append('ウェブサイトURLが無効です')
    
    # 年齢の検証
    age = data.get('age', 0)
    if not validators.between(age, min=13, max=120):
        errors.append('年齢は13歳以上120歳以下である必要があります')
    
    # ユーザー名(スラッグ)の検証
    if not validators.slug(data.get('username', '')):
        errors.append('ユーザー名は英数字、ハイフン、アンダースコアのみ使用可能です')
    
    return errors

# 使用例
user_data = {
    'email': '[email protected]',
    'website': 'https://mysite.com',
    'age': 25,
    'username': 'john_doe'
}

errors = validate_user_input(user_data)
if errors:
    print("検証エラー:")
    for error in errors:
        print(f"  - {error}")
else:
    print("すべての入力が有効です")

# カスタムバリデータとの組み合わせ
def validate_japanese_phone(phone):
    """日本の電話番号を検証するカスタム関数"""
    import re
    # 簡易的な日本の電話番号パターン
    pattern = r'^0\d{1,4}-\d{1,4}-\d{4}$|^0\d{9,10}$'
    return bool(re.match(pattern, phone))

# validatorsと組み合わせて使用
def validate_contact_info(info):
    errors = {}
    
    if not validators.email(info.get('email', '')):
        errors['email'] = '有効なメールアドレスを入力してください'
    
    if not validate_japanese_phone(info.get('phone', '')):
        errors['phone'] = '有効な日本の電話番号を入力してください'
    
    return errors