このサイトについて

みんなのPython Webアプリ編 - バリデータを作る

みんなのPython Webアプリ編 - バリデータを作る

バリデータを作る

バリデーションチェックの処理の実体は単純な文字列の検査です。バリデーションチェックの処理の内容はそれほど難しくなく、バリデータも比較的簡単に作れます。ここでは、簡単なバリデータを作ってみましょう。

まず、バリデータの設計について考えてみます。チェックすべき値を受け取り、正しい値かどうかを結果として返すのがバリデータに求められる機能です。このような単純な機能であれば関数として実装してもよさそうですが、のちのちのことを考えてクラスとして実装することにしましょう。バリデーションチェックを実行するためには、まずバリデータのクラスインスタンスを作り、メソッドにチェックしたい値を渡して結果を受け取る、というような使い方をします。なお、メソッドには文字列のみを渡すことを想定します。

バリデータの戻り値と例外

Webアプリケーションでは、入力として文字列がプログラムに渡されます。プログラム内部では、文字列の入力を必要に応じて数値型などに変換する必要があります。どのデータをどのように変換するかは状況によってさまざまですが、バリデーションチェックに変換方法に関するヒントが隠されていることが多いのです。

たとえば、ある値について文字列が数値だけで構成されているかどうかをチェックしようとする場合を考えましょう。チェックした値は数値型として利用したい場合が多いはずです。このように、クエリ上のデータをどのように変換すべきかということをバリデータが知っている場合が多いのです。以上のようなことを踏まえて、バリデーションチェックに成功し、正しい値であることが分かった場合には、返り値として適切に変換したデータを返すようにしましょう。

最後に、バリデーションチェックに失敗したときにどうするかを考えましょう。変換に失敗したことを示すため、戻り値としてNoneを返す、という方法も考えられます。ただしこの場合、バリデータを使う開発者が「Noneに特別な意味があるというルール」を覚える必要があります。戻り値のNoneに特別な意味がある、というのはPythonの世界で一般的なルールではありません。余計な知識を要求する仕様はよいとは言えませんので、この方法は見送ることにします。

要はバリデーションチェックの最中に状態が変化した(値が不正だった)ことを関数やメソッドの外部に伝える方法があればよいわけです。今回は戻り値を使わず、例外を使ってバリデーションチェックの失敗を外部に伝えるようにしましょう。バリデータを使う側は、必要に応じて例外を捕まえ、処理を続ければよいわけです。

バリデーションに失敗したときに利用する例外は、専用の例外を定義します。Pythonの例外の実体はクラスインスタンスです。Exceptionという、すべての例外の親となるクラスを継承して専用の例外を定義します。このように、専用の例外を定義することで、バリデーションの失敗だけを受け取って、特別な処理を実行することができるようになります。

ValidationErrorクラス(validators.py)

:::python
#!/usr/bin/env python
# coding: utf-8

import re

class ValidationError(Exception):
    """
    バリデーションエラー用の例外クラス
    """

    def __init__(self, msg):
        Exception.__init__(self, msg)
        self.msg=msg

    def get_message(self):
        return self.msg

バリデータの抽象クラスと初期化が不要なバリデータ

次にバリデータ用クラスの親となる抽象クラスを定義します。validate()というメソッドはバリデーションを行うために利用するメソッドです。Pythonには抽象メソッドのような機能がありません。そのため、継承した子クラスで必ず定義する必要があるメソッドを定義できません。しかし、メソッド名と引数の種類を明記しておくことで、継承したクラスがどのようなメソッドを実装すべきかが分かりやすくなるというメリットはあるはずです。

初期化メソッドinit()がないのは、抽象的なバリデータにはインスタンスに保存しておくべきデータがないためです。

BaseValidatorクラス(validators.py)

:::python
class BaseValidator(object):
    """
    バリデータ用のベースクラス
    """

    def validate(self, value):
        return value

抽象クラスを継承して、比較的処理が簡単な2つのバリデータクラスを定義してみましょう。1つは入力項目が空でないかどうかを調べるバリデータです。もう1つは、入力項目が整数値に変換できる文字列だけで構成されているかどうかを調べるバリデータです。どちらのバリデータも、バリデータの条件に合わない場合は例外をraiseする、という処理になっています。例外をraiseする際には、エラーの内容を文字列として渡しています。

どちらのバリデータも、インスタンスを作るときに前処理が必要ありません。そのため、初期化メソッドが定義されていません。

バリデータクラス(1) (validators.py)

:::python
class NotEmpty(BaseValidator):
    """
    項目が空でないことを調べるバリデータ
    """
    errors=(u'この項目は必須です。',)

    def validate(self, value):
        if not value:
            raise ValidationError(self.errors[0])
        return value


class IntValidator(BaseValidator):
    """
    項目が整数の数値であることを調べるバリデータ
    """
    errors=(u'この項目には数値を入力してください。',)

    def validate(self, value):
        try:
            value=int(value)
        except ValueError:
            raise ValidationError(self.errors[0])
        if int(abs(value))!=abs(value):
            raise ValidationError(self.errors[0])
        return value

入力値が整数で、指定された範囲にあることを調べるバリデータ

日時や西暦のような数値を受け取るとき、入力値が一定の範囲内にあるかどうかを調べたいことがあります。そのような場合に利用できる汎用のバリデータを定義してみましょう。

バリデータが正しいと判断する数値の範囲は、状況によって異なります。最小値と最大値を、クラスのインスタンスオブジェクトを初期化するときに渡すようにすれば、インスタンスごとに異なる最小値と最大値を設定できます。

このようなバリデータでは初期化メソッドが必要になります。初期化メソッドでは、引数として最大値と最小値を受け取り、インスタンスオブジェクト(self)のアトリビュートに保存しています。インスタンスオブジェクトを作るときにはIntRangeValidator(1900,2007)のように引数に範囲を指定します。

バリデータクラス(2) (validators.py)

:::python
class IntRangeValidator(BaseValidator):
    """
    値が一定の範囲にあることを調べるバリデータ
    """
    errors=(u'入力された数値が設定された範囲を超えています。',)

    def __init__(self, min_val, max_val):
        self.min=min_val
        self.max=max_val

    def validate(self, value):
        value=IntValidator().validate(value)
        if value>self.max or self.min>value:
            raise ValidationError(self.errors[0])
        return value

正規表現を使ったバリデータ:メールアドレス・URLのチェック

正規表現はバリデーションチェックのいろいろな場面で活用できます。インスタンスの初期化時に任意の正規表現パターンを受け取るバリデータがあれば、いろいろな場面で応用できるはずです。ここでは、正規表現を指定できるバリデータを定義してみましょう。

このバリデータでも初期化メソッドを定義します。インスタンスオブジェクトを作るときに、正規表現のパターン文字列を引数として受け取るようにします。初期化メソッドの内部では、受け取った正規表現文字列を元に正規 表現オブジェクトを作り、インスタンスに保存します。

バリデーションチェックを行うvalidate()メソッドの内部では、初期化メソッドで作った正規表現パターンを使って入力値のチェックを行っています。

正規表現を使ったベースクラス(validators.py)

:::python
class RegexValidator(BaseValidator):
    """
    入力値が正規表現にマッチするかどうか調べるバリデータ
    """
    errors=(u'正しい値を入力してください。',)

    def __init__(self, pat):
        self.regex_pat=re.compile(pat)

    def validate(self, value):
        if not self.regex_pat.search(value):
            raise ValidationError(self.errors[0])
        return value

次に、この正規表現バリデータを継承してURLとメールアドレスをチェックするバリデータを定義してみましょう。バリデーションチェックを行うvalidate()メソッドは、RegexValidatorに定義したものが共通して利用できるはずです。継承先のクラスではvalidate()メソッドは定義せず、親クラスに定義されているメソッドをそのまま利用することにします。

新たに定義する必要があるのは初期化メソッドのみです。URL、メールアドレスとも、形式が決まっていますから、初期化メソッドでは引数を受け取る必要がありません。初期化メソッドでは、URL、メールアドレスを判別するための正規表現文字列を決めうちで埋め込みます。

バリデータクラス(3) (validators.py)

:::python
class URLValidator(RegexValidator):
    """
    URLとして正しい文字列かどうかを調べるバリデータ
    """
    errors=(u'正しいURLを入力してください。',)

    def __init__(self):
        self.regex_pat=re.compile(
            r'^(http|https)://[a-z0-9][a-z0-9\-\._]*\.[a-z]+'
            r'(?:[0-9]+)?(?:/.*)?$', re.I)


class EmailValidator(RegexValidator):
    """
    メールアドレスとして正しい文字列かどうかを調べるバリデータ
    """
    errors=(u'正しいメールアドレスを入力してください。',)

    def __init__(self):
        self.regex_pat=re.compile(
            r'([0-9a-zA-Z_&.+-]+!)*[0-9a-zA-Z_&.+-]+@'
            r'(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\.)+'
            r'[a-zA-Z]{2,6}|([0-9]{1,3}\.){3}[0-9]{1,3})$')

これでバリデータモジュールは完成で、上記のコードを1つにまとめて「validators.py」というファイル名でcgi-binフォルダに保存してください。バリデータを実際に利用したサンプルプログラムは、「ウィジェット」を解説した後で示します。

2014-09-03 15:00