Pull to refresh

Comments 8

Миш, я бы еще добавил чуть про ошибки валидации — это чуть ли не самая интересная тема после самой декларации структуры данных.
Они все таки о разном. Trafaret предлагает легкий путь разобрать некие данные прилетевшие извне, и частично декларативно перегнать их во внутреннюю форму.

А FormEncode это хорошие валидаторы плюс формы. У нее круг задач другой, и шире, и там все сложнее. Поэтому в своем классе задач каждая из библиотек хороша.
Мне кажется, что примерно одинаково. API шире, да. Но никто не заставляет лезть глубоко вовнутрь.

from formencode import Schema, validators as v
from pprint import pprint
import hashlib



MD5Password = v.Wrapper(
    not_empty=True,
    to_python=lambda d: hashlib.md5(d).hexdigest()
)

RolesList = v.Wrapper(
    to_python=lambda d: [s.strip() for s in d.split(',')]
)


class RequiredString(v.UnicodeString):
    not_empty = True


class MapFields(v.FormValidator):
    _fields_mapping = {
        'userNameFirst': 'name',
        'userNameSecond': 'second_name',
        'userPassword': 'password',
        'userEmail': 'email',
        'userTitle': 'title',
        'userRoles':'roles'
    }

    def _to_python(self, fields, state):
    	result = {}
    	for key, value in self._fields_mapping.iteritems():
            result[value] = fields[key]
    	return result


class Converter(Schema):
    # FormEncode's Declarative API attributes
    allow_extra_fields = True
    filter_extra_fields = True

    # Our own attributes and fields
    userNameFirst = RequiredString
    userNameSecond = RequiredString
    userPassword = MD5Password
    userEmail = v.Email
    userTitle = v.UnicodeString(if_missing='Bachelor')
    userRoles = RolesList

    chained_validators = [MapFields]



# Test the module
if __name__ == '__main__':
    sample_data = {
        'userNameFirst': 'Adam',
        'userNameSecond': 'Smith',
        'userPassword': 'supersecretpassword',
        'userEmail': 'adam@smith.math.edu',
        'userRoles': 'teacher, worker, admin',
    }

    desired_data = {
        'name': 'Adam',
        'second_name': 'Smith',
        'password': hashlib.md5('supersecretpassword').hexdigest(),
        'email': 'adam@smith.math.edu',
        'title': 'Bachelor',
        'roles': ['teacher', 'worker', 'admin'],
    }
    data = Converter.to_python(sample_data)
    assert data == desired_data
    pprint(data)



Мне нравится, что в вашем примере библиотека избавляет вас от необходимости дублировать названия полей в нескольких местах. Также, нравится наличие fold/unfold утилит.
Но мне кажется, что вы не совсем удачно выбрали Contract в качестве базовой библиотеки. Сейчас большая функциональность Trafaret, связанная с проверкой и конвертацией, дублирует то, что есть в схемах FormEncode. Однако я не могу использовать схемы FormEncode для тех утилит, которые есть в Trafaret помимо этого. В итоге — плохая интероперабельность, связанная с выбором Contract, которая взамен не предоставляет разработчику никаких плюсов.
Вы можете взять идею и применить ее к FormEncode, я думаю. Я бы посмотрел на результат.
Библиотечка очень понравилась. Предложу ещё добавить:
import itertools
import trafaret as t

class Tuple(t.Trafaret):

    def __init__(self, *args):
        self._trafarets = map(self._trafaret, args)
        self._traf_cnt = len(self._trafarets)

    def _check(self, value):
        try:
            value = tuple(value)
        except TypeError:
            self._failure('value must be convertable to tuple')

        if len(value) != self._traf_cnt:
            self._failure('value must contain exact %s items' % self._traf_cnt)

        result = []
        errors = {}
        for idx, item, traf in itertools.izip(itertools.count(), value, self._trafarets):
            try:
                result.append(traf.check(item))
            except t.DataError as err:
                errors[idx] = err

        if errors:
            self._failure(errors)
        return tuple(result)


if __name__ == '__main__':
    pair = t.Or(Tuple(int, str), Tuple(str, str)) >> (lambda x: '%s%s' % x)
    print pair.check((1, 'a'))
    print pair.check(('2', 'b'))

Черт, а почему я не видел этот комментарий раньше?
Добавлю в мастер.
Sign up to leave a comment.

Articles