Package: marshmallow-form
motivationform library is not validation libraryWhat is form library?anyone says, ‘it is validation library for post/get data’another says, ‘it is rendering library for form element’marshmallow-form is not above one.
form library is ‘a container for presentation metadata’. so, form object is just a container.‘rendering form element’, it is a task of template library(mako, jinja2, …).‘validation post/get data’, it is a task of schema library(colander, marshmallow, …).marshmallow-form is just a metadata container.featureshaving metadata anywherenested field supportaccessing schemabuilding your own form librarygetting startedinstallfrom pypi package repository.# pip install marshmallow-formfrom repositorygit clone git@github.com/podhmo/marshmallow-form.git
cd marshmallow-form
python setup.py developform class definitionthe way of form definition.importmarshmallow_formasmfclassPersonForm(mf.Form):name=mf.String(label="名前",placeholder="foo",widget="str")age=mf.Integer(label="年齢",placeholder="0",widget="int")classParentsForm(mf.Form):father=mf.Nested(PersonForm,label="父親")mother=mf.Nested(PersonForm,label="母親")define form class with marshmallow_form.Formusing field classes, define form fields.label and placeholder is metadata for presentationwith template librarywith template library, form is just a metadata container.
so, a above definition, using form as metadata container.print(form.father.name["label"])# => '名前'  # 'name' in japaneseprint(form.father.name["placeholder"])# => 'foo'print(form.name.value)# => 'foo'accessing metadata with__getitem__.accessing initial data or passed data with.valuewith template(mako). deciding rendering method, usingwidgetmetadata.frommako.templateimportTemplatetemplate=Template("""
<%def name="field(f)">\${getattr(self, "field_" + f["widget"])(f)}
</%def>

<%def name="field_str(f)">\<input type="text" name="${f.name}" value="${f.value}" placeholder="${f["placeholder"]}"/>
</%def>

<%def name="field_int(f)">\<input type="number" name="${f.name}" value="${f.value}" placeholder="${f["placeholder"]}"/>
</%def>

<form action="#" method="POST">%for f in form:
${field(f)}%endfor
</form>
""")print(template.render(form=form))output.<formaction="#"method="POST"><inputtype="text"name="father.name"value=""placeholder="foo"/><inputtype="number"name="father.age"value="0"placeholder="0"/><inputtype="text"name="mother.name"value=""placeholder="foo"/><inputtype="number"name="mother.age"value="0"placeholder="0"/></form>validationfrommarshmallow.validateimportLengthfrommarshmallowimportValidationErrorclassMLength(Length):message_min='Too short!{min}.'message_max='Too long!{max}.'classAuthenticationForm(mf.Form):name=mf.String()password=mf.String(validate=MLength(5))password_confirm=mf.String()@mf.Form.validatordefsame(schema,data):ifdata["password"]!=data["password_confirm"]:raiseValidationError("not same!","password")input_data={"name":"foo","password":"*","password_confirm":"+"}form=AuthenticationForm(input_data)print(form.validate())# Falseprint(form.errors)# {'password': ['Too short! 5.', 'not same!']}{'password':['Too short! 5.','not same!']}detailhaving metadata anywhereform metadatafield metadatametadata inheritancemetadata overrideform metadataimportmarshmallow_formasmfclassMyForm(mf.Form):name=mf.Str()classMeta:metadata={"action":"#"}form=MyForm()form["action"]# => #form.metadata["method"]="post"form["method"]# => "post"MyForm()["method"]# => ""field metadataclassMyForm2(mf.Form):name=mf.Str()ctime=mf.DateTime(disable=True)form=MyForm2()form.ctime["disable"]# => Truemetadata inheritancefromfunctoolsimportpartialDateTime=partial(mf.DateTime,widget="tdcalendar")classMyForm3(mf.Form):ctime=DateTime()utime=DateTime()form=MyForm3()form.ctime["widget"]# => "tdcalendar"form.utime["widget"]# => "tdcalendar"metadata overrideclassMyForm4(MyForm3):classMeta:overrides={"ctime":{"widget":"mycalendar"}}form=MyForm4()form.ctime["widget"]# => "mycalendar"form.utime["widget"]# => "tdcalendar"or with nestedclassPersonForm(mf.Form):name=mf.String(label="名前",placeholder="foo",widget="str")age=mf.Integer(label="年齢",placeholder="0",widget="int")classParentsForm(mf.Form):father=mf.Nested(PersonForm,label="父親",overrides={"name":{"label":"父親の名前"}})mother=mf.Nested(PersonForm,label="母親")form=ParentsForm()form.father["label"]# => "父親"form.father.name["label"]# => "父親の名前"form.mother.name["label"]# => "名前"dynamic formmodify fieldadd fieldremove fieldmodify fieldform=StudentForm()form.color.metadata["pairs"]=[("red","red"),("blue","blue")]form.color["pairs"]# => [('red', 'red'), ('blue', 'blue')]add fieldclassStudentForm(mf.Form):color=mf.Select([])name=mf.Str()form=StudentForm(initial={"grade":3})form.add_field("grade",mf.Int(label="学年"))form.grade.value# => 3form.grade["label"]# => "学年"[f.nameforfinform]# => ['color', 'name', 'grade']remove fieldform=StudentForm()form.remove_field("color")[f.nameforfinform]# => ['name']accessing schemaschema classschema instanceschema classfromcollectionsimportnamedtuplePerson=namedtuple("Person","name age")classPersonForm(mf.Form):name=mf.Str()age=mf.Int()defmake_object(self,data):returnPerson(**data)PersonForm.Schema# => <class 'marshmallow.schema.PersonSchema'>schema=PersonForm.Schema(many=True)schema.dump([Person("foo",20),Person("bar",20)]).data# => OrderedDict([('name', 'foo'), ('age', 20)]), OrderedDict([('name', 'bar'), ('age', 20)])schema instanceform=PersonForm()form.schema.load({"name":"foo","age":20}).data# => Person(name='foo', age=20)building your own form librarydefine your form field classdefine the way of renderingdefine your form field classif just only adding default metadata, using functools.partial.importfunctoolsPositiveInt=functools.partial(mf.Int,validate=lambdax:x>0)classForm(mf.Form):x=PositiveInt()print(Form({"x":"-10"}).load())# UnmarshalResult(data=OrderedDict([('x', None)]), errors={'x': ['Validator <lambda>(-10) is False']})if define your own field classfrommarshmallow.fieldsimportFieldfrommarshmallow.exceptionsimportUnmarshallingErrorimportbase64classBase64(Field):""" tiny base64 field"""def__init__(self,*args,**kwargs):super(Base64,self).__init__(*args,**kwargs)def_serialize(self,value,attr,obj):returnbase64.encodebytes(value)def_deserialize(self,value):try:returnbase64.decodebytes(value.encode("utf-8"))except:raiseUnmarshallingError("oops")MyBase64=mf.field_factory(Base64)classForm(mf.Form):x=MyBase64(label="this is broken")form=Form({"x":"MTEx"})print(form.load())# UnmarshalResult(data=OrderedDict([('x', b'111')]), errors={})define the way of renderingdefinput(field,placeholder=""):fmt='<input name="{name}" value="{value}" placeholder="{placeholder}">'returnfmt.format(name=field["name"],value=field.value,placeholder=placeholder)classForm(mf.Form):name=mf.Str(__call__=input)form=Form()print(form.name(placeholder="foo"))# => <input name="" value="" placeholder="foo">