Coverage for /wheeldirectory/casa-6.7.0-12-py3.10.el8/lib/py/lib/python3.10/site-packages/casatools/__cerberus__/validator.py: 60%

749 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-31 19:10 +0000

1""" 

2 Extensible validation for Python dictionaries. 

3 This module implements Cerberus Validator class 

4 

5 :copyright: 2012-2016 by Nicola Iarocci. 

6 :license: ISC, see LICENSE for more details. 

7 

8 Full documentation is available at http://python-cerberus.org 

9""" 

10 

11from __future__ import absolute_import 

12 

13from ast import literal_eval 

14from collections.abc import Hashable, Iterable, Mapping, Sequence 

15from copy import copy 

16from datetime import date, datetime 

17import re 

18from warnings import warn 

19 

20from casatools.__cerberus__ import errors 

21from casatools.__cerberus__.platform import _int_types, _str_type 

22from casatools.__cerberus__.schema import (schema_registry, rules_set_registry, 

23 DefinitionSchema, SchemaError) 

24from casatools.__cerberus__.utils import drop_item_from_tuple, isclass 

25 

26 

27toy_error_handler = errors.ToyErrorHandler() 

28 

29 

30def dummy_for_rule_validation(rule_constraints): 

31 def dummy(self, constraint, field, value): 

32 raise RuntimeError('Dummy method called. Its purpose is to hold just' 

33 'validation constraints for a rule in its ' 

34 'docstring.') 

35 f = dummy 

36 f.__doc__ = rule_constraints 

37 return f 

38 

39 

40class DocumentError(Exception): 

41 """ Raised when the target document is missing or has the wrong format """ 

42 pass 

43 

44 

45class _SchemaRuleTypeError(Exception): 

46 """ Raised when a schema (list) validation encounters a mapping. 

47 Not supposed to be used outside this module. """ 

48 pass 

49 

50 

51class Validator(object): 

52 """ Validator class. Normalizes and/or validates any mapping against a 

53 validation-schema which is provided as an argument at class instantiation 

54 or upon calling the :meth:`~cerberus.Validator.validate`, 

55 :meth:`~cerberus.Validator.validated` or 

56 :meth:`~cerberus.Validator.normalized` method. An instance itself is 

57 callable and executes a validation. 

58 

59 All instantiation parameters are optional. 

60 

61 There are the introspective properties :attr:`types`, :attr:`validators`, 

62 :attr:`coercers`, :attr:`default_setters`, :attr:`rules`, 

63 :attr:`normalization_rules` and :attr:`validation_rules`. 

64 

65 The attributes reflecting the available rules are assembled considering 

66 constraints that are defined in the docstrings of rules' methods and is 

67 effectively used as validation schema for :attr:`schema`. 

68 

69 :param schema: See :attr:`~cerberus.Validator.schema`. 

70 Defaults to :obj:`None`. 

71 :type schema: any :term:`mapping` 

72 :param ignore_none_values: See :attr:`~cerberus.Validator.ignore_none_values`. 

73 Defaults to ``False``. 

74 :type ignore_none_values: :class:`bool` 

75 :param allow_unknown: See :attr:`~cerberus.Validator.allow_unknown`. 

76 Defaults to ``False``. 

77 :type allow_unknown: :class:`bool` or any :term:`mapping` 

78 :param purge_unknown: See :attr:`~cerberus.Validator.purge_unknown`. 

79 Defaults to to ``False``. 

80 :type purge_unknown: :class:`bool` 

81 :param error_handler: The error handler that formats the result of 

82 :attr:`~cerberus.Validator.errors`. 

83 When given as two-value tuple with an error-handler 

84 class and a dictionary, the latter is passed to the 

85 initialization of the error handler. 

86 Default: :class:`~cerberus.errors.BasicErrorHandler`. 

87 :type error_handler: class or instance based on 

88 :class:`~cerberus.errors.BaseErrorHandler` or 

89 :class:`tuple` 

90 """ # noqa: E501 

91 

92 mandatory_validations = ('nullable',) 

93 """ Rules that are evaluated on any field, regardless whether defined in 

94 the schema or not. 

95 Type: :class:`tuple` """ 

96 priority_validations = ('nullable', 'readonly', 'type') 

97 """ Rules that will be processed in that order before any other. 

98 Type: :class:`tuple` """ 

99 _valid_schemas = set() 

100 """ A :class:`set` of hashes derived from validation schemas that are 

101 legit for a particular ``Validator`` class. """ 

102 

103 def __init__(self, *args, **kwargs): 

104 """ The arguments will be treated as with this signature: 

105 

106 __init__(self, schema=None, ignore_none_values=False, 

107 allow_unknown=False, purge_unknown=False, 

108 error_handler=errors.BasicErrorHandler) 

109 """ 

110 

111 self.document = None 

112 """ The document that is or was recently processed. 

113 Type: any :term:`mapping` """ 

114 self._errors = errors.ErrorList() 

115 """ The list of errors that were encountered since the last document 

116 processing was invoked. 

117 Type: :class:`~cerberus.errors.ErrorList` """ 

118 self.recent_error = None 

119 """ The last individual error that was submitted. 

120 Type: :class:`~cerberus.errors.ValidationError` """ 

121 self.document_error_tree = errors.DocumentErrorTree() 

122 """ A tree representiation of encountered errors following the 

123 structure of the document. 

124 Type: :class:`~cerberus.errors.DocumentErrorTree` """ 

125 self.schema_error_tree = errors.SchemaErrorTree() 

126 """ A tree representiation of encountered errors following the 

127 structure of the schema. 

128 Type: :class:`~cerberus.errors.SchemaErrorTree` """ 

129 self.document_path = () 

130 """ The path within the document to the current sub-document. 

131 Type: :class:`tuple` """ 

132 self.schema_path = () 

133 """ The path within the schema to the current sub-schema. 

134 Type: :class:`tuple` """ 

135 self.update = False 

136 self.error_handler = self.__init_error_handler(kwargs) 

137 """ The error handler used to format :attr:`~cerberus.Validator.errors` 

138 and process submitted errors with 

139 :meth:`~cerberus.Validator._error`. 

140 Type: :class:`~cerberus.errors.BaseErrorHandler` """ 

141 self.__store_config(args, kwargs) 

142 self.schema = kwargs.get('schema', None) 

143 self.allow_unknown = kwargs.get('allow_unknown', False) 

144 self._remaining_rules = [] 

145 """ Keeps track of the rules that are next in line to be evaluated 

146 during the validation of a field. 

147 Type: :class:`list` """ 

148 

149 def __init_error_handler(self, kwargs): 

150 error_handler = kwargs.pop('error_handler', errors.BasicErrorHandler) 

151 if isinstance(error_handler, tuple): 

152 error_handler, eh_config = error_handler 

153 else: 

154 eh_config = {} 

155 if isclass(error_handler) and \ 

156 issubclass(error_handler, errors.BaseErrorHandler): 

157 return error_handler(**eh_config) 

158 elif isinstance(error_handler, errors.BaseErrorHandler): 

159 return error_handler 

160 else: 

161 raise RuntimeError('Invalid error_handler.') 

162 

163 def __store_config(self, args, kwargs): 

164 """ Assign args to kwargs and store configuration. """ 

165 signature = ('schema', 'ignore_none_values', 'allow_unknown', 

166 'purge_unknown') 

167 for i, p in enumerate(signature[:len(args)]): 

168 if p in kwargs: 

169 raise TypeError("__init__ got multiple values for argument " 

170 "'%s'" % p) 

171 else: 

172 kwargs[p] = args[i] 

173 self._config = kwargs 

174 """ This dictionary holds the configuration arguments that were used to 

175 initialize the :class:`Validator` instance except the 

176 ``error_handler``. """ 

177 

178 @classmethod 

179 def clear_caches(cls): 

180 """ Purge the cache of known valid schemas. """ 

181 cls._valid_schemas.clear() 

182 

183 def _error(self, *args): 

184 """ Creates and adds one or multiple errors. 

185 

186 :param args: Accepts different argument's signatures. 

187 

188 *1. Bulk addition of errors:* 

189 

190 - :term:`iterable` of 

191 :class:`~cerberus.errors.ValidationError`-instances 

192 

193 The errors will be added to 

194 :attr:`~cerberus.Validator._errors`. 

195 

196 *2. Custom error:* 

197 

198 - the invalid field's name 

199 

200 - the error message 

201 

202 A custom error containing the message will be created and 

203 added to :attr:`~cerberus.Validator._errors`. 

204 There will however be fewer information contained in the 

205 error (no reference to the violated rule and its 

206 constraint). 

207 

208 *3. Defined error:* 

209 

210 - the invalid field's name 

211 

212 - the error-reference, see :mod:`cerberus.errors` 

213 

214 - arbitrary, supplemental information about the error 

215 

216 A :class:`~cerberus.errors.ValidationError` instance will 

217 be created and added to 

218 :attr:`~cerberus.Validator._errors`. 

219 """ 

220 if len(args) == 1: 

221 self._errors.extend(args[0]) 

222 self._errors.sort() 

223 for error in args[0]: 

224 self.document_error_tree += error 

225 self.schema_error_tree += error 

226 self.error_handler.emit(error) 

227 elif len(args) == 2 and isinstance(args[1], _str_type): 

228 self._error(args[0], errors.CUSTOM, args[1]) 

229 elif len(args) >= 2: 

230 field = args[0] 

231 code = args[1].code 

232 rule = args[1].rule 

233 info = args[2:] 

234 

235 document_path = self.document_path + (field, ) 

236 

237 schema_path = self.schema_path 

238 if code != errors.UNKNOWN_FIELD.code and rule is not None: 

239 schema_path += (field, rule) 

240 

241 if not rule: 

242 constraint = None 

243 else: 

244 field_definitions = self._resolve_rules_set(self.schema[field]) 

245 if rule == 'nullable': 

246 constraint = field_definitions.get(rule, False) 

247 else: 

248 constraint = field_definitions[rule] 

249 

250 value = self.document.get(field) 

251 

252 self.recent_error = errors.ValidationError( 

253 document_path, schema_path, code, rule, constraint, value, info 

254 ) 

255 self._error([self.recent_error]) 

256 

257 def _get_child_validator(self, document_crumb=None, schema_crumb=None, 

258 **kwargs): 

259 """ Creates a new instance of Validator-(sub-)class. All initial 

260 parameters of the parent are passed to the initialization, unless 

261 a parameter is given as an explicit *keyword*-parameter. 

262 

263 :param document_crumb: Extends the 

264 :attr:`~cerberus.Validator.document_path` 

265 of the child-validator. 

266 :type document_crumb: :class:`tuple` or :term:`hashable` 

267 :param schema_crumb: Extends the 

268 :attr:`~cerberus.Validator.schema_path` 

269 of the child-validator. 

270 :type schema_crumb: :class:`tuple` or hashable 

271 :param kwargs: Overriding keyword-arguments for initialization. 

272 :type kwargs: :class:`dict` 

273 

274 :return: an instance of ``self.__class__`` 

275 """ 

276 child_config = self._config.copy() 

277 child_config.update(kwargs) 

278 if not self.is_child: 

279 child_config['is_child'] = True 

280 child_config['error_handler'] = toy_error_handler 

281 child_config['root_allow_unknown'] = self.allow_unknown 

282 child_config['root_document'] = self.document 

283 child_config['root_schema'] = self.schema 

284 

285 child_validator = self.__class__(**child_config) 

286 

287 if document_crumb is None: 

288 child_validator.document_path = self.document_path 

289 else: 

290 if not isinstance(document_crumb, tuple): 

291 document_crumb = (document_crumb, ) 

292 child_validator.document_path = self.document_path + document_crumb 

293 

294 if schema_crumb is None: 

295 child_validator.schema_path = self.schema_path 

296 else: 

297 if not isinstance(schema_crumb, tuple): 

298 schema_crumb = (schema_crumb, ) 

299 child_validator.schema_path = self.schema_path + schema_crumb 

300 

301 return child_validator 

302 

303 def __get_rule_handler(self, domain, rule): 

304 methodname = '_{0}_{1}'.format(domain, rule.replace(' ', '_')) 

305 return getattr(self, methodname, None) 

306 

307 def _drop_nodes_from_errorpaths(self, _errors, dp_items, sp_items): 

308 """ Removes nodes by index from an errorpath, relatively to the 

309 basepaths of self. 

310 

311 :param errors: A list of :class:`errors.ValidationError` instances. 

312 :param dp_items: A list of integers, pointing at the nodes to drop from 

313 the :attr:`document_path`. 

314 :param sp_items: Alike ``dp_items``, but for :attr:`schema_path`. 

315 """ 

316 dp_basedepth = len(self.document_path) 

317 sp_basedepth = len(self.schema_path) 

318 for error in _errors: 

319 for i in sorted(dp_items, reverse=True): 

320 error.document_path = \ 

321 drop_item_from_tuple(error.document_path, dp_basedepth + i) 

322 for i in sorted(sp_items, reverse=True): 

323 error.schema_path = \ 

324 drop_item_from_tuple(error.schema_path, sp_basedepth + i) 

325 if error.child_errors: 

326 self._drop_nodes_from_errorpaths(error.child_errors, 

327 dp_items, sp_items) 

328 

329 def _lookup_field(self, path): 

330 """ Searches for a field as defined by path. This method is used by the 

331 ``dependency`` evaluation logic. 

332 

333 :param path: Path elements are separated by a ``.``. A leading ``^`` 

334 indicates that the path relates to the document root, 

335 otherwise it relates to the currently evaluated document, 

336 which is possibly a subdocument. 

337 The sequence ``^^`` at the start will be interpreted as a 

338 literal ``^``. 

339 :type path: :class:`str` 

340 :returns: Either the found field name and its value or :obj:`None` for 

341 both. 

342 :rtype: A two-value :class:`tuple`. 

343 """ 

344 if path.startswith('^'): 

345 path = path[1:] 

346 context = self.document if path.startswith('^') \ 

347 else self.root_document 

348 else: 

349 context = self.document 

350 

351 parts = path.split('.') 

352 for part in parts: 

353 if part not in context: 

354 return None, None 

355 context = context.get(part) 

356 

357 return parts[-1], context 

358 

359 def _resolve_rules_set(self, rules_set): 

360 if isinstance(rules_set, Mapping): 

361 return rules_set 

362 elif isinstance(rules_set, _str_type): 

363 return self.rules_set_registry.get(rules_set) 

364 return None 

365 

366 def _resolve_schema(self, schema): 

367 if isinstance(schema, Mapping): 

368 return schema 

369 elif isinstance(schema, _str_type): 

370 return self.schema_registry.get(schema) 

371 return None 

372 

373 # Properties 

374 

375 @property 

376 def allow_unknown(self): 

377 """ If ``True`` unknown fields that are not defined in the schema will 

378 be ignored. If a mapping with a validation schema is given, any 

379 undefined field will be validated against its rules. 

380 Also see :ref:`allowing-the-unknown`. 

381 Type: :class:`bool` or any :term:`mapping` """ 

382 return self._config.get('allow_unknown', False) 

383 

384 @allow_unknown.setter 

385 def allow_unknown(self, value): 

386 if not (self.is_child or isinstance(value, (bool, DefinitionSchema))): 

387 DefinitionSchema(self, {'allow_unknown': value}) 

388 self._config['allow_unknown'] = value 

389 

390 @property 

391 def errors(self): 

392 """ The errors of the last processing formatted by the handler that is 

393 bound to :attr:`~cerberus.Validator.error_handler`. """ 

394 return self.error_handler(self._errors) 

395 

396 @property 

397 def ignore_none_values(self): 

398 """ Whether to not process :obj:`None`-values in a document or not. 

399 Type: :class:`bool` """ 

400 return self._config.get('ignore_none_values', False) 

401 

402 @ignore_none_values.setter 

403 def ignore_none_values(self, value): 

404 self._config['ignore_none_values'] = value 

405 

406 @property 

407 def is_child(self): 

408 """ ``True`` for child-validators obtained with 

409 :meth:`~cerberus.Validator._get_child_validator`. 

410 Type: :class:`bool` """ 

411 return self._config.get('is_child', False) 

412 

413 @property 

414 def _is_normalized(self): 

415 """ ``True`` if the document is already normalized. """ 

416 return self._config.get('_is_normalized', False) 

417 

418 @_is_normalized.setter 

419 def _is_normalized(self, value): 

420 self._config['_is_normalized'] = value 

421 

422 @property 

423 def purge_unknown(self): 

424 """ If ``True`` unknown fields will be deleted from the document 

425 unless a validation is called with disabled normalization. 

426 Also see :ref:`purging-unknown-fields`. Type: :class:`bool` """ 

427 return self._config.get('purge_unknown', False) 

428 

429 @purge_unknown.setter 

430 def purge_unknown(self, value): 

431 self._config['purge_unknown'] = value 

432 

433 @property 

434 def root_allow_unknown(self): 

435 """ The :attr:`~cerberus.Validator.allow_unknown` attribute of the 

436 first level ancestor of a child validator. """ 

437 return self._config.get('root_allow_unknown', self.allow_unknown) 

438 

439 @property 

440 def root_document(self): 

441 """ The :attr:`~cerberus.Validator.document` attribute of the 

442 first level ancestor of a child validator. """ 

443 return self._config.get('root_document', self.document) 

444 

445 @property 

446 def rules_set_registry(self): 

447 """ The registry that holds referenced rules sets. 

448 Type: :class:`~cerberus.Registry` """ 

449 return self._config.get('rules_set_registry', rules_set_registry) 

450 

451 @rules_set_registry.setter 

452 def rules_set_registry(self, registry): 

453 self._config['rules_set_registry'] = registry 

454 

455 @property 

456 def root_schema(self): 

457 """ The :attr:`~cerberus.Validator.schema` attribute of the 

458 first level ancestor of a child validator. """ 

459 return self._config.get('root_schema', self.schema) 

460 

461 @property 

462 def schema(self): 

463 """ The validation schema of a validator. When a schema is passed to 

464 a method, it replaces this attribute. 

465 Type: any :term:`mapping` or :obj:`None` """ 

466 return self._schema 

467 

468 @schema.setter 

469 def schema(self, schema): 

470 if schema is None: 

471 self._schema = None 

472 elif self.is_child or isinstance(schema, DefinitionSchema): 

473 self._schema = schema 

474 else: 

475 self._schema = DefinitionSchema(self, schema) 

476 

477 @property 

478 def schema_registry(self): 

479 """ The registry that holds referenced schemas. 

480 Type: :class:`~cerberus.Registry` """ 

481 return self._config.get('schema_registry', schema_registry) 

482 

483 @schema_registry.setter 

484 def schema_registry(self, registry): 

485 self._config['schema_registry'] = registry 

486 

487 # Document processing 

488 

489 def __init_processing(self, document, schema=None): 

490 self._errors = errors.ErrorList() 

491 self.recent_error = None 

492 self.document_error_tree = errors.DocumentErrorTree() 

493 self.schema_error_tree = errors.SchemaErrorTree() 

494 self.document = copy(document) 

495 if not self.is_child: 

496 self._is_normalized = False 

497 

498 if schema is not None: 

499 self.schema = DefinitionSchema(self, schema) 

500 elif self.schema is None: 

501 if isinstance(self.allow_unknown, Mapping): 

502 self._schema = {} 

503 else: 

504 raise SchemaError(errors.SCHEMA_ERROR_MISSING) 

505 if document is None: 

506 raise DocumentError(errors.DOCUMENT_MISSING) 

507 if not isinstance(document, Mapping): 

508 raise DocumentError( 

509 errors.DOCUMENT_FORMAT.format(document)) 

510 self.error_handler.start(self) 

511 

512 def _drop_remaining_rules(self, *rules): 

513 """ Drops rules from the queue of the rules that still need to be 

514 evaluated for the currently processed field. 

515 If no arguments are given, the whole queue is emptied. 

516 """ 

517 if rules: 

518 for rule in rules: 

519 try: 

520 self._remaining_rules.remove(rule) 

521 except ValueError: 

522 pass 

523 else: 

524 self._remaining_rules = [] 

525 

526 # # Normalizing 

527 

528 def normalized(self, document, schema=None, always_return_document=False): 

529 """ Returns the document normalized according to the specified rules 

530 of a schema. 

531 

532 :param document: The document to normalize. 

533 :type document: any :term:`mapping` 

534 :param schema: The validation schema. Defaults to :obj:`None`. If not 

535 provided here, the schema must have been provided at 

536 class instantiation. 

537 :type schema: any :term:`mapping` 

538 :param always_return_document: Return the document, even if an error 

539 occurred. Defaults to: ``False``. 

540 :type always_return_document: :class:`bool` 

541 :return: A normalized copy of the provided mapping or :obj:`None` if an 

542 error occurred during normalization. 

543 """ 

544 self.__init_processing(document, schema) 

545 self.__normalize_mapping(self.document, self.schema) 

546 self.error_handler.end(self) 

547 if self._errors and not always_return_document: 

548 return None 

549 else: 

550 return self.document 

551 

552 def __normalize_mapping(self, mapping, schema): 

553 if isinstance(schema, _str_type): 

554 schema = self._resolve_schema(schema) 

555 schema = schema.copy() 

556 for field in schema: 

557 schema[field] = self._resolve_rules_set(schema[field]) 

558 

559 self.__normalize_rename_fields(mapping, schema) 

560 if self.purge_unknown: 

561 self._normalize_purge_unknown(mapping, schema) 

562 # Check `readonly` fields before applying default values because 

563 # a field's schema definition might contain both `readonly` and 

564 # `default`. 

565 self.__validate_readonly_fields(mapping, schema) 

566 self.__normalize_default_fields(mapping, schema) 

567 self._normalize_coerce(mapping, schema) 

568 self.__normalize_containers(mapping, schema) 

569 self._is_normalized = True 

570 return mapping 

571 

572 def _normalize_coerce(self, mapping, schema): 

573 """ {'oneof': [ 

574 {'type': 'callable'}, 

575 {'type': 'list', 

576 'schema': {'oneof': [{'type': 'callable'}, 

577 {'type': 'string'}]}}, 

578 {'type': 'string'} 

579 ]} """ 

580 

581 error = errors.COERCION_FAILED 

582 for field in mapping: 

583 if field in schema and 'coerce' in schema[field]: 

584 mapping[field] = self.__normalize_coerce( 

585 schema[field]['coerce'], field, mapping[field], 

586 schema[field].get('nullable', False), error) 

587 elif isinstance(self.allow_unknown, Mapping) and \ 

588 'coerce' in self.allow_unknown: 

589 mapping[field] = self.__normalize_coerce( 

590 self.allow_unknown['coerce'], field, mapping[field], 

591 self.allow_unknown.get('nullable', False), error) 

592 

593 def __normalize_coerce(self, processor, field, value, nullable, error): 

594 if isinstance(processor, _str_type): 

595 processor = self.__get_rule_handler('normalize_coerce', processor) 

596 

597 elif isinstance(processor, Iterable): 

598 result = value 

599 for p in processor: 

600 result = self.__normalize_coerce(p, field, result, 

601 nullable, error) 

602 if errors.COERCION_FAILED in \ 

603 self.document_error_tree.fetch_errors_from( 

604 self.document_path + (field,)): 

605 break 

606 return result 

607 

608 try: 

609 return processor(value) 

610 except Exception as e: 

611 if not nullable and e is not TypeError: 

612 self._error(field, error, str(e)) 

613 return value 

614 

615 def __normalize_containers(self, mapping, schema): 

616 for field in mapping: 

617 if field not in schema: 

618 continue 

619 # TODO: This check conflates validation and normalization 

620 if isinstance(mapping[field], Mapping): 

621 if 'keyschema' in schema[field]: 

622 self.__normalize_mapping_per_keyschema( 

623 field, mapping, schema[field]['keyschema']) 

624 if 'valueschema' in schema[field]: 

625 self.__normalize_mapping_per_valueschema( 

626 field, mapping, schema[field]['valueschema']) 

627 if set(schema[field]) & set(('allow_unknown', 'purge_unknown', 

628 'schema')): 

629 try: 

630 self.__normalize_mapping_per_schema( 

631 field, mapping, schema) 

632 except _SchemaRuleTypeError: 

633 pass 

634 elif isinstance(mapping[field], _str_type): 

635 continue 

636 elif isinstance(mapping[field], Sequence) and \ 

637 'schema' in schema[field]: 

638 self.__normalize_sequence(field, mapping, schema) 

639 

640 def __normalize_mapping_per_keyschema(self, field, mapping, property_rules): 

641 schema = dict(((k, property_rules) for k in mapping[field])) 

642 document = dict(((k, k) for k in mapping[field])) 

643 validator = self._get_child_validator( 

644 document_crumb=field, schema_crumb=(field, 'keyschema'), 

645 schema=schema) 

646 result = validator.normalized(document, always_return_document=True) 

647 if validator._errors: 

648 self._drop_nodes_from_errorpaths(validator._errors, [], [2, 4]) 

649 self._error(validator._errors) 

650 for k in result: 

651 if k == result[k]: 

652 continue 

653 if result[k] in mapping[field]: 

654 warn("Normalizing keys of {path}: {key} already exists, " 

655 "its value is replaced." 

656 .format(path='.'.join(self.document_path + (field,)), 

657 key=k)) 

658 mapping[field][result[k]] = mapping[field][k] 

659 else: 

660 mapping[field][result[k]] = mapping[field][k] 

661 del mapping[field][k] 

662 

663 def __normalize_mapping_per_valueschema(self, field, mapping, value_rules): 

664 schema = dict(((k, value_rules) for k in mapping[field])) 

665 validator = self._get_child_validator( 

666 document_crumb=field, schema_crumb=(field, 'valueschema'), 

667 schema=schema) 

668 mapping[field] = validator.normalized(mapping[field], 

669 always_return_document=True) 

670 if validator._errors: 

671 self._drop_nodes_from_errorpaths(validator._errors, [], [2]) 

672 self._error(validator._errors) 

673 

674 def __normalize_mapping_per_schema(self, field, mapping, schema): 

675 validator = self._get_child_validator( 

676 document_crumb=field, schema_crumb=(field, 'schema'), 

677 schema=schema[field].get('schema', {}), 

678 allow_unknown=schema[field].get('allow_unknown', self.allow_unknown), # noqa: E501 

679 purge_unknown=schema[field].get('purge_unknown', self.purge_unknown)) # noqa: E501 

680 value_type = type(mapping[field]) 

681 result_value = validator.normalized(mapping[field], 

682 always_return_document=True) 

683 mapping[field] = value_type(result_value) 

684 if validator._errors: 

685 self._error(validator._errors) 

686 

687 def __normalize_sequence(self, field, mapping, schema): 

688 schema = dict(((k, schema[field]['schema']) 

689 for k in range(len(mapping[field])))) 

690 document = dict((k, v) for k, v in enumerate(mapping[field])) 

691 validator = self._get_child_validator( 

692 document_crumb=field, schema_crumb=(field, 'schema'), 

693 schema=schema) 

694 value_type = type(mapping[field]) 

695 result = validator.normalized(document, always_return_document=True) 

696 mapping[field] = value_type(result.values()) 

697 if validator._errors: 

698 self._drop_nodes_from_errorpaths(validator._errors, [], [2]) 

699 self._error(validator._errors) 

700 

701 @staticmethod 

702 def _normalize_purge_unknown(mapping, schema): 

703 """ {'type': 'boolean'} """ 

704 for field in tuple(mapping): 

705 if field not in schema: 

706 del mapping[field] 

707 return mapping 

708 

709 def __normalize_rename_fields(self, mapping, schema): 

710 for field in tuple(mapping): 

711 if field in schema: 

712 self._normalize_rename(mapping, schema, field) 

713 self._normalize_rename_handler(mapping, schema, field) 

714 elif isinstance(self.allow_unknown, Mapping) and \ 

715 'rename_handler' in self.allow_unknown: 

716 self._normalize_rename_handler( 

717 mapping, {field: self.allow_unknown}, field) 

718 return mapping 

719 

720 def _normalize_rename(self, mapping, schema, field): 

721 """ {'type': 'hashable'} """ 

722 if 'rename' in schema[field]: 

723 mapping[schema[field]['rename']] = mapping[field] 

724 del mapping[field] 

725 

726 def _normalize_rename_handler(self, mapping, schema, field): 

727 """ {'oneof': [ 

728 {'type': 'callable'}, 

729 {'type': 'list', 

730 'schema': {'oneof': [{'type': 'callable'}, 

731 {'type': 'string'}]}}, 

732 {'type': 'string'} 

733 ]} """ 

734 if 'rename_handler' not in schema[field]: 

735 return 

736 new_name = self.__normalize_coerce( 

737 schema[field]['rename_handler'], field, field, 

738 False, errors.RENAMING_FAILED) 

739 if new_name != field: 

740 mapping[new_name] = mapping[field] 

741 del mapping[field] 

742 

743 def __validate_readonly_fields(self, mapping, schema): 

744 for field in (x for x in schema if x in mapping and 

745 self._resolve_rules_set(schema[x]).get('readonly')): 

746 self._validate_readonly(schema[field]['readonly'], field, 

747 mapping[field]) 

748 

749 def __normalize_default_fields(self, mapping, schema): 

750 fields = [x for x in schema if x not in mapping or 

751 mapping[x] is None and not schema[x].get('nullable', False)] 

752 try: 

753 fields_with_default = [x for x in fields if 'default' in schema[x]] 

754 except TypeError: 

755 raise _SchemaRuleTypeError 

756 for field in fields_with_default: 

757 self._normalize_default(mapping, schema, field) 

758 

759 known_fields_states = set() 

760 fields = [x for x in fields if 'default_setter' in schema[x]] 

761 while fields: 

762 field = fields.pop(0) 

763 try: 

764 self._normalize_default_setter(mapping, schema, field) 

765 except KeyError: 

766 fields.append(field) 

767 except Exception as e: 

768 self._error(field, errors.SETTING_DEFAULT_FAILED, str(e)) 

769 

770 fields_state = tuple(fields) 

771 if fields_state in known_fields_states: 

772 for field in fields: 

773 self._error(field, errors.SETTING_DEFAULT_FAILED, 

774 'Circular dependencies of default setters.') 

775 break 

776 else: 

777 known_fields_states.add(fields_state) 

778 

779 def _normalize_default(self, mapping, schema, field): 

780 """ {'nullable': True} """ 

781 mapping[field] = schema[field]['default'] 

782 

783 def _normalize_default_setter(self, mapping, schema, field): 

784 """ {'oneof': [ 

785 {'type': 'callable'}, 

786 {'type': 'string'} 

787 ]} """ 

788 if 'default_setter' in schema[field]: 

789 setter = schema[field]['default_setter'] 

790 if isinstance(setter, _str_type): 

791 setter = self.__get_rule_handler('normalize_default_setter', 

792 setter) 

793 mapping[field] = setter(mapping) 

794 

795 # # Validating 

796 

797 def validate(self, document, schema=None, update=False, normalize=True): 

798 """ Normalizes and validates a mapping against a validation-schema of 

799 defined rules. 

800 

801 :param document: The document to normalize. 

802 :type document: any :term:`mapping` 

803 :param schema: The validation schema. Defaults to :obj:`None`. If not 

804 provided here, the schema must have been provided at 

805 class instantiation. 

806 :type schema: any :term:`mapping` 

807 :param update: If ``True``, required fields won't be checked. 

808 :type update: :class:`bool` 

809 :param normalize: If ``True``, normalize the document before validation. 

810 :type normalize: :class:`bool` 

811 

812 :return: ``True`` if validation succeeds, otherwise ``False``. Check 

813 the :func:`errors` property for a list of processing errors. 

814 :rtype: :class:`bool` 

815 """ 

816 self.update = update 

817 self._unrequired_by_excludes = set() 

818 

819 self.__init_processing(document, schema) 

820 if normalize: 

821 self.__normalize_mapping(self.document, self.schema) 

822 

823 for field in self.document: 

824 if self.ignore_none_values and self.document[field] is None: 

825 continue 

826 definitions = self.schema.get(field) 

827 if definitions is not None: 

828 self.__validate_definitions(definitions, field) 

829 else: 

830 self.__validate_unknown_fields(field) 

831 

832 if not self.update: 

833 self.__validate_required_fields(self.document) 

834 

835 self.error_handler.end(self) 

836 

837 return not bool(self._errors) 

838 

839 __call__ = validate 

840 

841 def validated(self, *args, **kwargs): 

842 """ Wrapper around :meth:`~cerberus.Validator.validate` that returns 

843 the normalized and validated document or :obj:`None` if validation 

844 failed. """ 

845 always_return_document = kwargs.pop('always_return_document', False) 

846 self.validate(*args, **kwargs) 

847 if self._errors and not always_return_document: 

848 return None 

849 else: 

850 return self.document 

851 

852 def __validate_unknown_fields(self, field): 

853 if self.allow_unknown: 

854 value = self.document[field] 

855 if isinstance(self.allow_unknown, (Mapping, _str_type)): 

856 # validate that unknown fields matches the schema 

857 # for unknown_fields 

858 schema_crumb = 'allow_unknown' if self.is_child \ 

859 else '__allow_unknown__' 

860 validator = self._get_child_validator( 

861 schema_crumb=schema_crumb, 

862 schema={field: self.allow_unknown}) 

863 if not validator({field: value}, normalize=False): 

864 self._error(validator._errors) 

865 else: 

866 self._error(field, errors.UNKNOWN_FIELD) 

867 

868 def __validate_definitions(self, definitions, field): 

869 """ Validate a field's value against its defined rules. """ 

870 

871 def validate_rule(rule): 

872 validator = self.__get_rule_handler('validate', rule) 

873 if validator: 

874 return validator(definitions.get(rule, None), field, value) 

875 

876 definitions = self._resolve_rules_set(definitions) 

877 value = self.document[field] 

878 

879 rules_queue = [x for x in self.priority_validations 

880 if x in definitions or x in self.mandatory_validations] 

881 rules_queue.extend(x for x in self.mandatory_validations 

882 if x not in rules_queue) 

883 rules_queue.extend(x for x in definitions 

884 if x not in rules_queue and 

885 x not in self.normalization_rules and 

886 x not in ('allow_unknown', 'required')) 

887 self._remaining_rules = rules_queue 

888 

889 while self._remaining_rules: 

890 rule = self._remaining_rules.pop(0) 

891 try: 

892 result = validate_rule(rule) 

893 # TODO remove on next breaking release 

894 if result: 

895 break 

896 except _SchemaRuleTypeError: 

897 break 

898 

899 self._drop_remaining_rules() 

900 

901 # Remember to keep the validation methods below this line 

902 # sorted alphabetically 

903 

904 _validate_allow_unknown = dummy_for_rule_validation( 

905 """ {'oneof': [{'type': 'boolean'}, 

906 {'type': ['dict', 'string'], 

907 'validator': 'bulk_schema'}]} """) 

908 

909 def _validate_allowed(self, allowed_values, field, value): 

910 """ {'type': 'list'} """ 

911 if isinstance(value, Iterable) and not isinstance(value, _str_type): 

912 unallowed = set(value) - set(allowed_values) 

913 if unallowed: 

914 self._error(field, errors.UNALLOWED_VALUES, list(unallowed)) 

915 else: 

916 if value not in allowed_values: 

917 self._error(field, errors.UNALLOWED_VALUE, value) 

918 

919 def _validate_dependencies(self, dependencies, field, value): 

920 """ {'type': ['dict', 'hashable', 'hashables']} """ 

921 if isinstance(dependencies, _str_type): 

922 dependencies = (dependencies,) 

923 

924 if isinstance(dependencies, Sequence): 

925 self.__validate_dependencies_sequence(dependencies, field) 

926 elif isinstance(dependencies, Mapping): 

927 self.__validate_dependencies_mapping(dependencies, field) 

928 

929 if self.document_error_tree.fetch_node_from( 

930 self.schema_path + (field, 'dependencies')) is not None: 

931 return True 

932 

933 def __validate_dependencies_mapping(self, dependencies, field): 

934 validated_dependencies_counter = 0 

935 error_info = {} 

936 for dependency_name, dependency_values in dependencies.items(): 

937 if (not isinstance(dependency_values, Sequence) or 

938 isinstance(dependency_values, _str_type)): 

939 dependency_values = [dependency_values] 

940 

941 wanted_field, wanted_field_value = \ 

942 self._lookup_field(dependency_name) 

943 if wanted_field_value in dependency_values: 

944 validated_dependencies_counter += 1 

945 else: 

946 error_info.update({dependency_name: wanted_field_value}) 

947 

948 if validated_dependencies_counter != len(dependencies): 

949 self._error(field, errors.DEPENDENCIES_FIELD_VALUE, error_info) 

950 

951 def __validate_dependencies_sequence(self, dependencies, field): 

952 for dependency in dependencies: 

953 if self._lookup_field(dependency)[0] is None: 

954 self._error(field, errors.DEPENDENCIES_FIELD, dependency) 

955 

956 def _validate_empty(self, empty, field, value): 

957 """ {'type': 'boolean'} """ 

958 if isinstance(value, Iterable) and len(value) == 0 and not empty: 

959 self._error(field, errors.EMPTY_NOT_ALLOWED) 

960 

961 def _validate_excludes(self, excludes, field, value): 

962 """ {'type': ['hashable', 'hashables']} """ 

963 if isinstance(excludes, Hashable): 

964 excludes = [excludes] 

965 

966 # Save required field to be checked latter 

967 if 'required' in self.schema[field] and self.schema[field]['required']: 

968 self._unrequired_by_excludes.add(field) 

969 for exclude in excludes: 

970 if (exclude in self.schema and 

971 'required' in self.schema[exclude] and 

972 self.schema[exclude]['required']): 

973 

974 self._unrequired_by_excludes.add(exclude) 

975 

976 if [True for key in excludes if key in self.document]: 

977 # Wrap each field in `excludes` list between quotes 

978 exclusion_str = ', '.join("'{0}'" 

979 .format(word) for word in excludes) 

980 self._error(field, errors.EXCLUDES_FIELD, exclusion_str) 

981 

982 def _validate_forbidden(self, forbidden_values, field, value): 

983 """ {'type': 'list'} """ 

984 if isinstance(value, _str_type): 

985 if value in forbidden_values: 

986 self._error(field, errors.FORBIDDEN_VALUE, value) 

987 elif isinstance(value, Sequence): 

988 forbidden = set(value) & set(forbidden_values) 

989 if forbidden: 

990 self._error(field, errors.FORBIDDEN_VALUES, list(forbidden)) 

991 elif isinstance(value, int): 

992 if value in forbidden_values: 

993 self._error(field, errors.FORBIDDEN_VALUE, value) 

994 

995 def _validate_items(self, items, field, values): 

996 """ {'type': 'list', 'validator': 'items'} """ 

997 if len(items) != len(values): 

998 self._error(field, errors.ITEMS_LENGTH, len(items), len(values)) 

999 else: 

1000 schema = dict((i, definition) for i, definition in enumerate(items)) # noqa: E501 

1001 validator = self._get_child_validator(document_crumb=field, 

1002 schema_crumb=(field, 'items'), # noqa: E501 

1003 schema=schema) 

1004 if not validator(dict((i, value) for i, value in enumerate(values)), 

1005 update=self.update, normalize=False): 

1006 self._error(field, errors.BAD_ITEMS, validator._errors) 

1007 

1008 def __validate_logical(self, operator, definitions, field, value): 

1009 """ Validates value against all definitions and logs errors according 

1010 to the operator. """ 

1011 valid_counter = 0 

1012 _errors = errors.ErrorList() 

1013 

1014 for i, definition in enumerate(definitions): 

1015 schema = {field: definition.copy()} 

1016 for rule in ('allow_unknown', 'type'): 

1017 if rule not in schema[field] and rule in self.schema[field]: 

1018 schema[field][rule] = self.schema[field][rule] 

1019 if 'allow_unknown' not in schema[field]: 

1020 schema[field]['allow_unknown'] = self.allow_unknown 

1021 

1022 validator = self._get_child_validator( 

1023 schema_crumb=(field, operator, i), 

1024 schema=schema, allow_unknown=True) 

1025 if validator(self.document, update=self.update, normalize=False): 

1026 valid_counter += 1 

1027 else: 

1028 self._drop_nodes_from_errorpaths(validator._errors, [], [3]) 

1029 _errors.extend(validator._errors) 

1030 

1031 return valid_counter, _errors 

1032 

1033 def _validate_anyof(self, definitions, field, value): 

1034 """ {'type': 'list', 'logical': 'anyof'} """ 

1035 valids, _errors = \ 

1036 self.__validate_logical('anyof', definitions, field, value) 

1037 if valids < 1: 

1038 self._error(field, errors.ANYOF, _errors, 

1039 valids, len(definitions)) 

1040 

1041 def _validate_allof(self, definitions, field, value): 

1042 """ {'type': 'list', 'logical': 'allof'} """ 

1043 valids, _errors = \ 

1044 self.__validate_logical('allof', definitions, field, value) 

1045 if valids < len(definitions): 

1046 self._error(field, errors.ALLOF, _errors, 

1047 valids, len(definitions)) 

1048 

1049 def _validate_noneof(self, definitions, field, value): 

1050 """ {'type': 'list', 'logical': 'noneof'} """ 

1051 valids, _errors = \ 

1052 self.__validate_logical('noneof', definitions, field, value) 

1053 if valids > 0: 

1054 self._error(field, errors.NONEOF, _errors, 

1055 valids, len(definitions)) 

1056 

1057 def _validate_oneof(self, definitions, field, value): 

1058 """ {'type': 'list', 'logical': 'oneof'} """ 

1059 valids, _errors = \ 

1060 self.__validate_logical('oneof', definitions, field, value) 

1061 if valids != 1: 

1062 self._error(field, errors.ONEOF, _errors, 

1063 valids, len(definitions)) 

1064 

1065 def _validate_max(self, max_value, field, value): 

1066 """ {'nullable': False } """ 

1067 try: 

1068 if value > max_value: 

1069 self._error(field, errors.MAX_VALUE) 

1070 except TypeError: 

1071 pass 

1072 

1073 def _validate_min(self, min_value, field, value): 

1074 """ {'nullable': False } """ 

1075 try: 

1076 if value < min_value: 

1077 self._error(field, errors.MIN_VALUE) 

1078 except TypeError: 

1079 pass 

1080 

1081 def _validate_maxlength(self, max_length, field, value): 

1082 """ {'type': 'integer'} """ 

1083 if isinstance(value, Iterable) and len(value) > max_length: 

1084 self._error(field, errors.MAX_LENGTH, len(value)) 

1085 

1086 def _validate_minlength(self, min_length, field, value): 

1087 """ {'type': 'integer'} """ 

1088 if isinstance(value, Iterable) and len(value) < min_length: 

1089 self._error(field, errors.MIN_LENGTH, len(value)) 

1090 

1091 def _validate_nullable(self, nullable, field, value): 

1092 """ {'type': 'boolean'} """ 

1093 if value is None: 

1094 if not nullable: 

1095 self._error(field, errors.NOT_NULLABLE) 

1096 self._drop_remaining_rules( 

1097 'empty', 'forbidden', 'items', 'keyschema', 'min', 'max', 

1098 'minlength', 'maxlength', 'regex', 'schema', 'type', 

1099 'valueschema') 

1100 

1101 def _validate_keyschema(self, schema, field, value): 

1102 """ {'type': ['dict', 'string'], 'validator': 'bulk_schema', 

1103 'forbidden': ['rename', 'rename_handler']} """ 

1104 if isinstance(value, Mapping): 

1105 validator = self._get_child_validator( 

1106 document_crumb=field, 

1107 schema_crumb=(field, 'keyschema'), 

1108 schema=dict(((k, schema) for k in value.keys()))) 

1109 if not validator(dict(((k, k) for k in value.keys())), 

1110 normalize=False): 

1111 self._drop_nodes_from_errorpaths(validator._errors, 

1112 [], [2, 4]) 

1113 self._error(field, errors.KEYSCHEMA, validator._errors) 

1114 

1115 def _validate_readonly(self, readonly, field, value): 

1116 """ {'type': 'boolean'} """ 

1117 if readonly: 

1118 if not self._is_normalized: 

1119 self._error(field, errors.READONLY_FIELD) 

1120 # If the document was normalized (and therefore already been 

1121 # checked for readonly fields), we still have to return True 

1122 # if an error was filed. 

1123 has_error = errors.READONLY_FIELD in \ 

1124 self.document_error_tree.fetch_errors_from( 

1125 self.document_path + (field,)) 

1126 if self._is_normalized and has_error: 

1127 self._drop_remaining_rules() 

1128 

1129 def _validate_regex(self, pattern, field, value): 

1130 """ {'type': 'string'} """ 

1131 if not isinstance(value, _str_type): 

1132 return 

1133 if not pattern.endswith('$'): 

1134 pattern += '$' 

1135 re_obj = re.compile(pattern) 

1136 if not re_obj.match(value): 

1137 self._error(field, errors.REGEX_MISMATCH) 

1138 

1139 _validate_required = dummy_for_rule_validation(""" {'type': 'boolean'} """) 

1140 

1141 def __validate_required_fields(self, document): 

1142 """ Validates that required fields are not missing. 

1143 

1144 :param document: The document being validated. 

1145 """ 

1146 try: 

1147 required = set(field for field, definition in self.schema.items() 

1148 if self._resolve_rules_set(definition). 

1149 get('required') is True) 

1150 except AttributeError: 

1151 if self.is_child and self.schema_path[-1] == 'schema': 

1152 raise _SchemaRuleTypeError 

1153 else: 

1154 raise 

1155 required -= self._unrequired_by_excludes 

1156 missing = required - set(field for field in document 

1157 if document.get(field) is not None or 

1158 not self.ignore_none_values) 

1159 

1160 for field in missing: 

1161 self._error(field, errors.REQUIRED_FIELD) 

1162 

1163 # At least on field from self._unrequired_by_excludes should be 

1164 # present in document 

1165 if self._unrequired_by_excludes: 

1166 fields = set(field for field in document 

1167 if document.get(field) is not None) 

1168 if self._unrequired_by_excludes.isdisjoint(fields): 

1169 for field in self._unrequired_by_excludes - fields: 

1170 self._error(field, errors.REQUIRED_FIELD) 

1171 

1172 def _validate_schema(self, schema, field, value): 

1173 """ {'type': ['dict', 'string'], 

1174 'anyof': [{'validator': 'schema'}, 

1175 {'validator': 'bulk_schema'}]} """ 

1176 if schema is None: 

1177 return 

1178 

1179 if isinstance(value, Sequence) and not isinstance(value, _str_type): 

1180 self.__validate_schema_sequence(field, schema, value) 

1181 elif isinstance(value, Mapping): 

1182 self.__validate_schema_mapping(field, schema, value) 

1183 

1184 def __validate_schema_mapping(self, field, schema, value): 

1185 schema = self._resolve_schema(schema) 

1186 allow_unknown = self.schema[field].get('allow_unknown', 

1187 self.allow_unknown) 

1188 validator = self._get_child_validator(document_crumb=field, 

1189 schema_crumb=(field, 'schema'), 

1190 schema=schema, 

1191 allow_unknown=allow_unknown) 

1192 try: 

1193 if not validator(value, update=self.update, normalize=False): 

1194 self._error(validator._errors) 

1195 except _SchemaRuleTypeError: 

1196 self._error(field, errors.BAD_TYPE_FOR_SCHEMA) 

1197 raise 

1198 

1199 def __validate_schema_sequence(self, field, schema, value): 

1200 schema = dict(((i, schema) for i in range(len(value)))) 

1201 validator = self._get_child_validator( 

1202 document_crumb=field, schema_crumb=(field, 'schema'), 

1203 schema=schema, allow_unknown=self.allow_unknown) 

1204 validator(dict(((i, v) for i, v in enumerate(value))), 

1205 update=self.update, normalize=False) 

1206 

1207 if validator._errors: 

1208 self._drop_nodes_from_errorpaths(validator._errors, [], [2]) 

1209 self._error(field, errors.SEQUENCE_SCHEMA, validator._errors) 

1210 

1211 def _validate_type(self, data_type, field, value): 

1212 """ {'type': ['string', 'list']} """ 

1213 types = [data_type] if isinstance(data_type, _str_type) else data_type 

1214 if not any(self.__get_rule_handler('validate_type', x)(value) 

1215 for x in types): 

1216 self._error(field, errors.BAD_TYPE) 

1217 self._drop_remaining_rules() 

1218 

1219 def _validate_type_boolean(self, value): 

1220 if isinstance(value, bool): 

1221 return True 

1222 

1223 def _validate_type_date(self, value): 

1224 if isinstance(value, date): 

1225 return True 

1226 

1227 def _validate_type_datetime(self, value): 

1228 if isinstance(value, datetime): 

1229 return True 

1230 

1231 def _validate_type_dict(self, value): 

1232 if isinstance(value, Mapping): 

1233 return True 

1234 

1235 def _validate_type_float(self, value): 

1236 if isinstance(value, (float, _int_types)): 

1237 return True 

1238 

1239 def _validate_type_integer(self, value): 

1240 if isinstance(value, _int_types): 

1241 return True 

1242 

1243 def _validate_type_binary(self, value): 

1244 if isinstance(value, (bytes, bytearray)): 

1245 return True 

1246 

1247 def _validate_type_list(self, value): 

1248 if isinstance(value, Sequence) and not isinstance( 

1249 value, _str_type): 

1250 return True 

1251 

1252 def _validate_type_number(self, value): 

1253 if isinstance(value, (_int_types, float)) \ 

1254 and not isinstance(value, bool): 

1255 return True 

1256 

1257 def _validate_type_set(self, value): 

1258 if isinstance(value, set): 

1259 return True 

1260 

1261 def _validate_type_string(self, value): 

1262 if isinstance(value, _str_type): 

1263 return True 

1264 

1265 def _validate_validator(self, validator, field, value): 

1266 """ {'oneof': [ 

1267 {'type': 'callable'}, 

1268 {'type': 'list', 

1269 'schema': {'oneof': [{'type': 'callable'}, 

1270 {'type': 'string'}]}}, 

1271 {'type': 'string'} 

1272 ]} """ 

1273 if isinstance(validator, _str_type): 

1274 validator = self.__get_rule_handler('validator', validator) 

1275 validator(field, value) 

1276 elif isinstance(validator, Iterable): 

1277 for v in validator: 

1278 self._validate_validator(v, field, value) 

1279 else: 

1280 validator(field, value, self._error) 

1281 

1282 def _validate_valueschema(self, schema, field, value): 

1283 """ {'type': ['dict', 'string'], 'validator': 'bulk_schema', 

1284 'forbidden': ['rename', 'rename_handler']} """ 

1285 schema_crumb = (field, 'valueschema') 

1286 if isinstance(value, Mapping): 

1287 validator = self._get_child_validator( 

1288 document_crumb=field, schema_crumb=schema_crumb, 

1289 schema=dict((k, schema) for k in value)) 

1290 validator(value, update=self.update, normalize=False) 

1291 if validator._errors: 

1292 self._drop_nodes_from_errorpaths(validator._errors, [], [2]) 

1293 self._error(field, errors.VALUESCHEMA, validator._errors) 

1294 

1295 

1296RULE_SCHEMA_SEPARATOR = \ 

1297 "The rule's arguments are validated against this schema:" 

1298 

1299 

1300class InspectedValidator(type): 

1301 """ Metaclass for all validators """ 

1302 def __new__(cls, *args): 

1303 if '__doc__' not in args[2]: 

1304 args[2].update({'__doc__': args[1][0].__doc__}) 

1305 return super(InspectedValidator, cls).__new__(cls, *args) 

1306 

1307 def __init__(cls, *args): 

1308 def attributes_with_prefix(prefix): 

1309 return tuple(x.split('_', 2)[-1] for x in dir(cls) 

1310 if x.startswith('_' + prefix)) 

1311 

1312 super(InspectedValidator, cls).__init__(*args) 

1313 

1314 cls.types, cls.validation_rules = (), {} 

1315 for attribute in attributes_with_prefix('validate'): 

1316 if attribute.startswith('type_'): 

1317 cls.types += (attribute[len('type_'):],) 

1318 else: 

1319 cls.validation_rules[attribute] = \ 

1320 cls.__get_rule_schema('_validate_' + attribute) 

1321 cls.validation_rules['type']['allowed'] = cls.types 

1322 

1323 cls.validators = tuple(x for x in attributes_with_prefix('validator')) 

1324 x = cls.validation_rules['validator']['oneof'] 

1325 x[1]['schema']['oneof'][1]['allowed'] = x[2]['allowed'] = cls.validators 

1326 

1327 for rule in (x for x in cls.mandatory_validations if x != 'nullable'): 

1328 cls.validation_rules[rule]['required'] = True 

1329 

1330 cls.coercers, cls.default_setters, cls.normalization_rules = (), (), {} 

1331 for attribute in attributes_with_prefix('normalize'): 

1332 if attribute.startswith('coerce_'): 

1333 cls.coercers += (attribute[len('coerce_'):],) 

1334 elif attribute.startswith('default_setter_'): 

1335 cls.default_setters += (attribute[len('default_setter_'):],) 

1336 else: 

1337 cls.normalization_rules[attribute] = \ 

1338 cls.__get_rule_schema('_normalize_' + attribute) 

1339 

1340 for rule in ('coerce', 'rename_handler'): 

1341 x = cls.normalization_rules[rule]['oneof'] 

1342 x[1]['schema']['oneof'][1]['allowed'] = \ 

1343 x[2]['allowed'] = cls.coercers 

1344 cls.normalization_rules['default_setter']['oneof'][1]['allowed'] = \ 

1345 cls.default_setters 

1346 

1347 cls.rules = {} 

1348 cls.rules.update(cls.validation_rules) 

1349 cls.rules.update(cls.normalization_rules) 

1350 

1351 def __get_rule_schema(cls, method_name): 

1352 docstring = getattr(cls, method_name).__doc__ 

1353 if docstring is None: 

1354 result = {} 

1355 else: 

1356 if RULE_SCHEMA_SEPARATOR in docstring: 

1357 docstring = docstring.split(RULE_SCHEMA_SEPARATOR)[1] 

1358 try: 

1359 result = literal_eval(docstring.strip()) 

1360 except Exception: 

1361 result = {} 

1362 

1363 if not result: 

1364 warn("No validation schema is defined for the arguments of rule " 

1365 "'%s'" % method_name.split('_', 2)[-1]) 

1366 

1367 return result 

1368 

1369 

1370Validator = InspectedValidator('Validator', (Validator,), {})