読者です 読者をやめる 読者になる 読者になる

FormとSchemaの違いvalidation編

python marshmallow

FormとSchemaの違いvalidation編

FormとSchemaの違いをプレゼンテーション側からみた話を前に書来ましたが validation側から見た場合にも両者は違うようです。

エラー時にSchemaは元の入力値を捨てても良いが、Formは元の入力値にアクセスできることが重要

Schemaライブラリの場合

Schemaライブラリでのvalidationでエラーが発生した時のことを考えましょう。例えばユースケースとしてJSONAPIなどがあげられると思います。 以下のようなschemaがあった場合には、対応したフィールドのエラーメッセージが返ってくれば十分でしょう。

from datetime import date
from marshmallow import Schema, fields, ValidationError


class DateTriple(Schema):
    year = fields.Int()
    month = fields.Int()
    day = fields.Int()

    def make_object(self, data):
        try:
            return date(**data)
        except:
            return None


@DateTriple.validator
def datecheck(self, data):
        try:
            date(**data)
        except:
            raise ValidationError("invalid paramaters: {}".format(data))


class FileSchema(Schema):
    name = fields.String()
    ctime = fields.Nested(DateTriple)


input_data = {
    "name": "foo.txt",
    "ctime": {
        "year": "2000",
        "month": "@@",
        "day": "99"
    }
}

schema = FileSchema()
data, errors = schema.load(input_data)
# {'name': 'foo.txt', 'ctime': None}
# {
#     'ctime': {
#         'month': ["invalid literal for int() with base 10: '@@'"],
#         '_schema': ["invalid paramaters: OrderedDict([('year', 2000), ('month', None), ('day', 99)])"]
#     }
# }

Formライブラリの場合

Formライブラリの場合は少し状況が違います。例えばFormライブラリを使うケースとしてはajax無しのシンプルな投稿フォームを考えてみます。 この場合入力値を間違えた場合に一度入力した箇所の入力値がリセットされるというフォームは使いにくいのではないでしょうか? また、勝手に各フォームのフィールドの型に対応したデフォルト値に置き換わっていても嫌な気持ちになるでしょう。 したがって、Formライブラリでは受け取ったPOSTデータの値を復元して返す必要がありそうです。

from datetime import date
import marshmallow_form as mf


class DateTriple(mf.Form):
    year = mf.Int()
    month = mf.Int()
    day = mf.Int()

    def make_object(self, data):
        try:
            return date(**data)
        except:
            return None

    @mf.Form.validator
    def datecheck(self, data):
        try:
            date(**data)
        except:
            raise ValidationError("invalid paramaters: {}".format(data))


class FileForm(mf.Form):
    name = mf.String()
    ctime = mf.Nested(DateTriple)

input_data = {
    "name": "foo.txt",
    "ctime.year": "2000",
    "ctime.month": "@@",
    "ctime.day": "99"
}

form = FileForm(input_data)
form.validate()  # False

for f in form:
    print(f.name, f.value)
# name foo.txt
# ctime.year 2000
# ctime.month @@
# ctime.day 99

また、各フィールドの脇にエラーメッセージを付記したいという場合もあるため以下の様な形でエラーメッセージを触れると良さそうです。

print(form.ctime.month.errors)
# ["invalid literal for int() with base 10: '@@'"]

print(form.ctime.errors)
# ["invalid paramaters: OrderedDict([('year', 2000), ('month', None), ('day', 99)])"]