1
from interfaces import *
8
class Schema(FancyValidator):
11
A schema validates a dictionary of values, applying different
12
validators (be key) to the different values. If
13
allow_extra_fields=True, keys without validators will be allowed;
14
otherwise they will raise Invalid. If filter_extra_fields is
15
set to true, then extra fields are not passed back in the results.
17
Validators are associated with keys either with a class syntax, or
18
as keyword arguments (class syntax is usually easier). Something
21
class MySchema(Schema):
22
name = Validators.PlainText()
23
phone = Validators.PhoneNumber()
25
These will not be available as actual instance variables, but will
26
be collected in a dictionary. To remove a validator in a subclass
27
that is present in a superclass, set it to None, like::
29
class MySubSchema(MySchema):
32
Note that missing fields are handled at the Schema level. Missing
33
fields can have the 'missing' message set to specify the error
34
message, or if that does not exist the *schema* message
35
'missingValue' is used.
38
# These validators will be applied before this schema:
40
# These validators will be applied after this schema:
41
chained_validators = []
42
# If true, then it is not an error when keys that aren't
43
# associated with a validator are present:
44
allow_extra_fields = False
45
# If true, then keys that aren't associated with a validator
47
filter_extra_fields = False
48
# If this is given, then any keys that aren't available but
49
# are expected will be replaced with this value (and then
50
# validated!) This does not override a present .if_missing
51
# attribute on validators:
52
if_key_missing = NoDefault
53
# If true, then missing keys will be missing in the result,
54
# if the validator doesn't have if_missing on it already:
55
ignore_key_missing = False
61
'notExpected': _('The input field %(name)s was not expected.'),
62
'missingValue': _("Missing value"),
63
'badDictType': _("The input must be dict-like (not a %(type)s: %(value)r)"),
66
__mutableattributes__ = ('fields', 'chained_validators',
69
def __classinit__(cls, new_attrs):
70
FancyValidator.__classinit__(cls, new_attrs)
71
# Don't bother doing anything if this is the most parent
72
# Schema class (which is the only class with just
73
# FancyValidator as a superclass):
74
if cls.__bases__ == (FancyValidator,):
76
# Scan through the class variables we've defined *just*
77
# for this subclass, looking for validators (both classes
79
for key, value in new_attrs.items():
80
if key in ('pre_validators', 'chained_validators',
83
if is_validator(value):
84
cls.fields[key] = value
86
# This last case means we're overwriting a validator
88
elif cls.fields.has_key(key):
90
for name, value in cls.fields.items():
91
cls.add_field(name, value)
93
def __initargs__(self, new_attrs):
94
for key, value in new_attrs.items():
95
if key in ('pre_validators', 'chained_validators',
98
if is_validator(value):
99
self.fields[key] = value
101
# This last case means we're overwriting a validator
103
elif self.fields.has_key(key):
105
for name, value in self.fields.items():
106
self.add_field(name, value)
108
def assert_dict(self, value, state):
110
Helper to assure we have proper input
112
if not hasattr(value, 'items'):
113
# Not a dict or dict-like object
114
raise Invalid(self.message('badDictType', state, type=type(value), value=value),
117
def _to_python(self, value_dict, state):
119
if self.if_empty is not NoDefault:
124
for validator in self.pre_validators:
125
value_dict = validator.to_python(value_dict, state)
127
self.assert_dict(value_dict, state)
131
unused = self.fields.keys()
132
if state is not None:
133
previous_key = getattr(state, 'key', None)
134
previous_full_dict = getattr(state, 'full_dict', None)
135
state.full_dict = value_dict
137
for name, value in value_dict.items():
141
if not self.allow_extra_fields:
143
self.message('notExpected', state,
147
if not self.filter_extra_fields:
150
validator = self.fields[name]
153
new[name] = validator.to_python(value, state)
158
validator = self.fields[name]
160
if_missing = validator.if_missing
161
except AttributeError:
162
if_missing = NoDefault
163
if if_missing is NoDefault:
164
if self.ignore_key_missing:
166
if self.if_key_missing is NoDefault:
168
message = validator.message('missing', state)
170
message = self.message('missingValue', state)
171
errors[name] = Invalid(message, None, state)
174
new[name] = validator.to_python(self.if_key_missing, state)
178
new[name] = validator.if_missing
180
for validator in self.chained_validators:
181
if (not hasattr(validator, 'validate_partial')
182
or not getattr(validator, 'validate_partial_form', False)):
185
validator.validate_partial(value_dict, state)
187
sub_errors = e.unpack_errors()
188
if not isinstance(sub_errors, dict):
189
# Can't do anything here
191
merge_dicts(errors, sub_errors)
195
format_compound_error(errors),
199
for validator in self.chained_validators:
200
new = validator.to_python(new, state)
205
if state is not None:
206
state.key = previous_key
207
state.full_dict = previous_full_dict
209
def _from_python(self, value_dict, state):
210
chained = self.chained_validators[:]
213
for validator in chained:
214
__traceback_info__ = 'for_python chained_validator %s (finished %s)' % (validator, ', '.join(map(repr, finished)) or 'none')
215
finished.append(validator)
216
value_dict = validator.from_python(value_dict, state)
217
self.assert_dict(value_dict, state)
220
unused = self.fields.keys()
221
if state is not None:
222
previous_key = getattr(state, 'key', None)
223
previous_full_dict = getattr(state, 'full_dict', None)
224
state.full_dict = value_dict
226
__traceback_info__ = None
227
for name, value in value_dict.items():
228
__traceback_info__ = 'for_python in %s' % name
232
if not self.allow_extra_fields:
234
self.message('notExpected', state,
237
if not self.filter_extra_fields:
241
new[name] = self.fields[name].from_python(value, state)
245
del __traceback_info__
248
validator = self.fields[name]
250
new[name] = validator.from_python(None, state)
256
format_compound_error(errors),
260
pre = self.pre_validators[:]
262
for validator in pre:
263
__traceback_info__ = 'for_python pre_validator %s' % validator
264
new = validator.from_python(new, state)
269
if state is not None:
270
state.key = previous_key
271
state.full_dict = previous_full_dict
275
def add_chained_validator(self, cls, validator):
277
if self.chained_validators is cls.chained_validators:
278
self.chained_validators = cls.chained_validators[:]
279
self.chained_validators.append(validator)
281
cls.chained_validators.append(validator)
283
add_chained_validator = declarative.classinstancemethod(
284
add_chained_validator)
286
def add_field(self, cls, name, validator):
288
if self.fields is cls.fields:
289
self.fields = cls.fields.copy()
290
self.fields[name] = validator
292
cls.fields[name] = validator
294
add_field = declarative.classinstancemethod(add_field)
296
def add_pre_validator(self, cls, validator):
298
if self.pre_validators is cls.pre_validators:
299
self.pre_validators = cls.pre_validators[:]
300
self.pre_validators.append(validator)
302
cls.pre_validators.append(validator)
304
add_pre_validator = declarative.classinstancemethod(add_pre_validator)
306
def subvalidators(self):
308
result.extend(self.pre_validators)
309
result.extend(self.chained_validators)
310
result.extend(self.fields.values())
313
def is_empty(self, value):
314
## Generally nothing is empty for us
317
def empty_value(self, value):
320
def format_compound_error(v, indent=0):
321
if isinstance(v, Exception):
324
except (UnicodeDecodeError, UnicodeEncodeError):
325
# There doesn't seem to be a better way to get a str()
326
# version if possible, and unicode() if necessary, because
327
# testing for the presence of a __unicode__ method isn't
330
elif isinstance(v, dict):
333
return ('%s\n' % (' '*indent)).join(
334
["%s: %s" % (k, format_compound_error(value, indent=len(k)+2))
336
if value is not None])
337
elif isinstance(v, list):
338
return ('%s\n' % (' '*indent)).join(
339
['%s' % (format_compound_error(value, indent=indent))
341
if value is not None])
342
elif isinstance(v, basestring):
345
assert 0, "I didn't expect something like %s" % repr(v)
347
def merge_dicts(d1, d2):
350
d1[key] = merge_values(d1[key], d2[key])
355
def merge_values(v1, v2):
356
if (isinstance(v1, (str, unicode))
357
and isinstance(v2, (str, unicode))):
358
return v1 + '\n' + v2
359
elif (isinstance(v1, (list, tuple))
360
and isinstance(v2, (list, tuple))):
361
return merge_lists(v1, v2)
362
elif isinstance(v1, dict) and isinstance(v2, dict):
363
return merge_dicts(v1, v2)
365
# @@: Should we just ignore errors? Seems we do...
368
def merge_lists(l1, l2):
369
if len(l1) < len(l2):
370
l1 = l1 + [None]*(len(l2)-len(l1))
371
elif len(l2) < len(l1):
372
l2 = l2 + [None]*(len(l1)-len(l2))
374
for l1item, l2item in zip(l1, l2):
381
item = merge_values(l1item, l2item)
385
class SimpleFormValidator(FancyValidator):
387
This validator wraps a simple function that validates the form.
389
The function looks something like this::
391
>>> def validate(form_values, state, validator):
392
... if form_values.get('country', 'US') == 'US':
393
... if not form_values.get('state'):
394
... return {'state': 'You must enter a state'}
395
... if not form_values.get('country'):
396
... form_values['country'] = 'US'
398
This tests that the field 'state' must be filled in if the country
399
is US, and defaults that country value to 'US'. The ``validator``
400
argument is the SimpleFormValidator instance, which you can use to
401
format messages or keep configuration state in if you like (for
402
simple ad hoc validation you are unlikely to need it).
404
To create a validator from that function, you would do::
406
>>> from formencode.schema import SimpleFormValidator
407
>>> validator = SimpleFormValidator(validate)
408
>>> validator.to_python({'country': 'US', 'state': ''}, None)
409
Traceback (most recent call last):
411
Invalid: state: You must enter a state
412
>>> validator.to_python({'state': 'IL'}, None)
413
{'country': 'US', 'state': 'IL'}
415
The validate function can either return a single error message
416
(that applies to the whole form), a dictionary that applies to the
417
fields, None which means the form is valid, or it can raise
420
Note that you may update the value_dict *in place*, but you cannot
423
Another way to instantiate a validator is like this::
425
>>> @SimpleFormValidator.decorate()
426
... def MyValidator(value_dict, state):
427
... return None # or some more useful validation
429
After this ``MyValidator`` will be a ``SimpleFormValidator``
430
instance (it won't be your function).
433
__unpackargs__ = ('func',)
435
validate_partial_form = False
437
def __initargs__(self, new_attrs):
438
self.__doc__ = getattr(self.func, '__doc__', None)
440
def to_python(self, value_dict, state):
441
# Since we aren't really supposed to modify things in-place,
442
# we'll give the validation function a copy:
443
value_dict = value_dict.copy()
444
errors = self.func(value_dict, state, self)
447
if isinstance(errors, basestring):
448
raise Invalid(errors, value_dict, state)
449
elif isinstance(errors, dict):
450
raise Invalid(format_compound_error(errors),
451
value_dict, state, error_dict=errors)
452
elif isinstance(errors, Invalid):
456
"Invalid error value: %r" % errors)
459
validate_partial = to_python
461
def decorate(cls, **kw):
463
return cls(func, **kw)
466
decorate = classmethod(decorate)