Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

marshmallow-form-0.1.1.tar.gz (15.0 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page