Comments 8
Миш, я бы еще добавил чуть про ошибки валидации — это чуть ли не самая интересная тема после самой декларации структуры данных.
Какие преимущества перед FormEncode?
Они все таки о разном. Trafaret предлагает легкий путь разобрать некие данные прилетевшие извне, и частично декларативно перегнать их во внутреннюю форму.
А FormEncode это хорошие валидаторы плюс формы. У нее круг задач другой, и шире, и там все сложнее. Поэтому в своем классе задач каждая из библиотек хороша.
А FormEncode это хорошие валидаторы плюс формы. У нее круг задач другой, и шире, и там все сложнее. Поэтому в своем классе задач каждая из библиотек хороша.
Мне кажется, что примерно одинаково. API шире, да. Но никто не заставляет лезть глубоко вовнутрь.
Мне нравится, что в вашем примере библиотека избавляет вас от необходимости дублировать названия полей в нескольких местах. Также, нравится наличие fold/unfold утилит.
Но мне кажется, что вы не совсем удачно выбрали Contract в качестве базовой библиотеки. Сейчас большая функциональность Trafaret, связанная с проверкой и конвертацией, дублирует то, что есть в схемах FormEncode. Однако я не могу использовать схемы FormEncode для тех утилит, которые есть в Trafaret помимо этого. В итоге — плохая интероперабельность, связанная с выбором Contract, которая взамен не предоставляет разработчику никаких плюсов.
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, которая взамен не предоставляет разработчику никаких плюсов.
Библиотечка очень понравилась. Предложу ещё добавить:
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.
Trafaret — библиотека для проверки и преобразования данных