a wrapper of marshmallow for form library like behavior
Project description
motivation
form library is not validation library
What 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.
features
having metadata anywhere
nested field support
accessing schema
building your own form library
getting started
install
from pypi package repository.
# pip install marshmallow-form
from repository
git clone git@github.com/podhmo/marshmallow-form.git cd marshmallow-form python setup.py develop
form class definition
the way of form definition.
import marshmallow_form as mf
class PersonForm(mf.Form):
name = mf.String(label="名前", placeholder="foo", widget="str")
age = mf.Integer(label="年齢", placeholder="0", widget="int")
class ParentsForm(mf.Form):
father = mf.Nested(PersonForm, label="父親")
mother = mf.Nested(PersonForm, label="母親")
define form class with marshmallow_form.Form
using field classes, define form fields.
label and placeholder is metadata for presentation
with template library
with template library, form is just a metadata container. so, a above definition, using form as metadata container.
print(form.father.name["label"]) # => '名前' # 'name' in japanese
print(form.father.name["placeholder"]) # => 'foo'
print(form.name.value) # => 'foo'
accessing metadata with __getitem__.
accessing initial data or passed data with .value
with template(mako). deciding rendering method, using widget metadata.
from mako.template import Template
template = 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.
<form action="#" method="POST">
<input type="text" name="father.name" value="" placeholder="foo"/>
<input type="number" name="father.age" value="0" placeholder="0"/>
<input type="text" name="mother.name" value="" placeholder="foo"/>
<input type="number" name="mother.age" value="0" placeholder="0"/>
</form>
validation
from marshmallow.validate import Length
from marshmallow import ValidationError
class MLength(Length):
message_min = 'Too short! {min}.'
message_max = 'Too long! {max}.'
class AuthenticationForm(mf.Form):
name = mf.String()
password = mf.String(validate=MLength(5))
password_confirm = mf.String()
@mf.Form.validator
def same(schema, data):
if data["password"] != data["password_confirm"]:
raise ValidationError("not same!", "password")
input_data = {"name": "foo", "password": "*", "password_confirm": "+"}
form = AuthenticationForm(input_data)
print(form.validate()) # False
print(form.errors) # {'password': ['Too short! 5.', 'not same!']}
{'password': ['Too short! 5.', 'not same!']}
detail
having metadata anywhere
form metadata
field metadata
metadata inheritance
metadata override
form metadata
import marshmallow_form as mf
class MyForm(mf.Form):
name = mf.Str()
class Meta:
metadata = {"action": "#"}
form = MyForm()
form["action"] # => #
form.metadata["method"] = "post"
form["method"] # => "post"
MyForm()["method"] # => ""
field metadata
class MyForm2(mf.Form):
name = mf.Str()
ctime = mf.DateTime(disable=True)
form = MyForm2()
form.ctime["disable"] # => True
metadata inheritance
from functools import partial
DateTime = partial(mf.DateTime, widget="tdcalendar")
class MyForm3(mf.Form):
ctime = DateTime()
utime = DateTime()
form = MyForm3()
form.ctime["widget"] # => "tdcalendar"
form.utime["widget"] # => "tdcalendar"
metadata override
class MyForm4(MyForm3):
class Meta:
overrides = {"ctime": {"widget": "mycalendar"}}
form = MyForm4()
form.ctime["widget"] # => "mycalendar"
form.utime["widget"] # => "tdcalendar"
or with nested
class PersonForm(mf.Form):
name = mf.String(label="名前", placeholder="foo", widget="str")
age = mf.Integer(label="年齢", placeholder="0", widget="int")
class ParentsForm(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 form
modify field
add field
remove field
modify field
form = StudentForm()
form.color.metadata["pairs"] = [("red", "red"), ("blue", "blue")]
form.color["pairs"] # => [('red', 'red'), ('blue', 'blue')]
add field
class StudentForm(mf.Form):
color = mf.Select([])
name = mf.Str()
form = StudentForm(initial={"grade": 3})
form.add_field("grade", mf.Int(label="学年"))
form.grade.value # => 3
form.grade["label"] # => "学年"
[f.name for f in form] # => ['color', 'name', 'grade']
remove field
form = StudentForm()
form.remove_field("color")
[f.name for f in form] # => ['name']
accessing schema
schema class
schema instance
schema class
from collections import namedtuple
Person = namedtuple("Person", "name age")
class PersonForm(mf.Form):
name = mf.Str()
age = mf.Int()
def make_object(self, data):
return Person(**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 instance
form = PersonForm()
form.schema.load({"name": "foo", "age": 20}).data # => Person(name='foo', age=20)
building your own form library
define your form field class
define the way of rendering
define your form field class
if just only adding default metadata, using functools.partial.
import functools
PositiveInt = functools.partial(mf.Int, validate=lambda x: x > 0)
class Form(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 class
from marshmallow.fields import Field
from marshmallow.exceptions import UnmarshallingError
import base64
class Base64(Field):
""" tiny base64 field"""
def __init__(self, *args, **kwargs):
super(Base64, self).__init__(*args, **kwargs)
def _serialize(self, value, attr, obj):
return base64.encodebytes(value)
def _deserialize(self, value):
try:
return base64.decodebytes(value.encode("utf-8"))
except:
raise UnmarshallingError("oops")
MyBase64 = mf.field_factory(Base64)
class Form(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 rendering
def input(field, placeholder=""):
fmt = '<input name="{name}" value="{value}" placeholder="{placeholder}">'
return fmt.format(name=field["name"], value=field.value, placeholder=placeholder)
class Form(mf.Form):
name = mf.Str(__call__=input)
form = Form()
print(form.name(placeholder="foo"))
# => <input name="" value="" placeholder="foo">
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.