marshmallow-formにfrom_objectを追加しました。

marshmallow-formにfrom_objectを追加しました。

今まで受け取る値が辞書だけでしたが。ORMのmodelなどオブジェクトを受け取りたい場合があります。 そのような場合に対応するためにfrom_objectを追加しました。 以下の様にして使います。

import marshmallow_form as mf
from datetime import date
from collections import namedtuple


Person = namedtuple("Person", "name birth")


class PersonForm(mf.Form):
    name = mf.String()
    birth = mf.Date()

    @mf.Form.accessor
    def access(self, k, ob):
        return getattr(ob, k)

person = Person(name="foo", birth=date(2000, 1, 1))
form = PersonForm.from_object(person)
print(type(form.birth.value), form.birth.value)
# <class 'str'> 2000-01-01

marshmallowのaccessorでobjectをdump(serialize)をする際に、アクセスする関数をしていできます。 これをgetattrを使うようにすることでオブジェクト(namedtuple)のようなものであっても、アクセスすることができます。

from_objectの実装は以下の様になっています。

class FormBase(object):
    @classmethod
    def from_object(cls, ob, *args, **kwargs):
        form = cls(*args, **kwargs)
        result = form.schema.dump(ob)
        if result.errors:
            raise MarshallingError(result.errors)
        form.rawdata = result.data
        form.data = form.rawdata.copy()
        return form

ほとんどmarshmallowの機能を使ってdumpしているだけです。 オブジェクトをdumpを使って辞書化。これをformの初期値として設定するという流れです。

from objectが出来ると以下のようなことが出来るようになります。

from mako.template import Template

people = [
    Person(name="foo", birth=date(2000, 1, 1)),
    Person(name="bar", birth=date(2000, 1, 2)),
    Person(name="boo", birth=date(2000, 1, 3))
]

template = Template("""
%for person in people:
<dl>
  %for f in person:
  <dt>${f.name}</dt>
  <dd>${f.value}</dd>
  %endfor
</dl>
%endfor
""")

print(template.render(people=map(PersonForm.from_object, people)))

これは以下の様な出力になります。

<dl>
  <dt>name</dt>
  <dd>foo</dd>
  <dt>birth</dt>
  <dd>2000-01-01</dd>
</dl>
<dl>
  <dt>name</dt>
  <dd>bar</dd>
  <dt>birth</dt>
  <dd>2000-01-02</dd>
</dl>
<dl>
  <dt>name</dt>
  <dd>boo</dd>
  <dt>birth</dt>
  <dd>2000-01-03</dd>
</dl>

出力形式を変えるのは手軽ではないかもしれないです(TODO)。 理由としては出力形式というのは実質serializeの形式なので、これが変わるとdeserializeも変える必要があるからです。

追記

marshmallowのdefaultのaccessorは高機能でした。上のようなaccessorの定義は不要でした。 marshmallowのdefaultのaccessorは以下の様に動きます。

  1. 値を保持するオブジェクトが辞書かそうでないかで分岐
  2. 辞書ならdict.getを使う
  3. そうでないならgetattrを使う。

本当はもう少し複雑なことを指定ますが雑な説明だとこのような感じ。したがって上の例はこれだけで済みます。

import marshmallow_form as mf
from datetime import date
from collections import namedtuple


Person = namedtuple("Person", "name birth")


class PersonForm(mf.Form):
    name = mf.String()
    birth = mf.Date()


person = Person(name="foo", birth=date(2000, 1, 1))
form = PersonForm.from_object(person)
print(type(form.birth.value), form.birth.value)
# <class 'str'> 2000-01-01