exceptions.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. """
  2. Validation errors, and some surrounding helpers.
  3. """
  4. from collections import defaultdict, deque
  5. import itertools
  6. import pprint
  7. import textwrap
  8. import attr
  9. from jsonschema import _utils
  10. from jsonschema.compat import PY3, iteritems
  11. WEAK_MATCHES = frozenset(["anyOf", "oneOf"])
  12. STRONG_MATCHES = frozenset()
  13. _unset = _utils.Unset()
  14. class _Error(Exception):
  15. def __init__(
  16. self,
  17. message,
  18. validator=_unset,
  19. path=(),
  20. cause=None,
  21. context=(),
  22. validator_value=_unset,
  23. instance=_unset,
  24. schema=_unset,
  25. schema_path=(),
  26. parent=None,
  27. ):
  28. super(_Error, self).__init__(
  29. message,
  30. validator,
  31. path,
  32. cause,
  33. context,
  34. validator_value,
  35. instance,
  36. schema,
  37. schema_path,
  38. parent,
  39. )
  40. self.message = message
  41. self.path = self.relative_path = deque(path)
  42. self.schema_path = self.relative_schema_path = deque(schema_path)
  43. self.context = list(context)
  44. self.cause = self.__cause__ = cause
  45. self.validator = validator
  46. self.validator_value = validator_value
  47. self.instance = instance
  48. self.schema = schema
  49. self.parent = parent
  50. for error in context:
  51. error.parent = self
  52. def __repr__(self):
  53. return "<%s: %r>" % (self.__class__.__name__, self.message)
  54. def __unicode__(self):
  55. essential_for_verbose = (
  56. self.validator, self.validator_value, self.instance, self.schema,
  57. )
  58. if any(m is _unset for m in essential_for_verbose):
  59. return self.message
  60. pschema = pprint.pformat(self.schema, width=72)
  61. pinstance = pprint.pformat(self.instance, width=72)
  62. return self.message + textwrap.dedent("""
  63. Failed validating %r in %s%s:
  64. %s
  65. On %s%s:
  66. %s
  67. """.rstrip()
  68. ) % (
  69. self.validator,
  70. self._word_for_schema_in_error_message,
  71. _utils.format_as_index(list(self.relative_schema_path)[:-1]),
  72. _utils.indent(pschema),
  73. self._word_for_instance_in_error_message,
  74. _utils.format_as_index(self.relative_path),
  75. _utils.indent(pinstance),
  76. )
  77. if PY3:
  78. __str__ = __unicode__
  79. else:
  80. def __str__(self):
  81. return unicode(self).encode("utf-8")
  82. @classmethod
  83. def create_from(cls, other):
  84. return cls(**other._contents())
  85. @property
  86. def absolute_path(self):
  87. parent = self.parent
  88. if parent is None:
  89. return self.relative_path
  90. path = deque(self.relative_path)
  91. path.extendleft(reversed(parent.absolute_path))
  92. return path
  93. @property
  94. def absolute_schema_path(self):
  95. parent = self.parent
  96. if parent is None:
  97. return self.relative_schema_path
  98. path = deque(self.relative_schema_path)
  99. path.extendleft(reversed(parent.absolute_schema_path))
  100. return path
  101. def _set(self, **kwargs):
  102. for k, v in iteritems(kwargs):
  103. if getattr(self, k) is _unset:
  104. setattr(self, k, v)
  105. def _contents(self):
  106. attrs = (
  107. "message", "cause", "context", "validator", "validator_value",
  108. "path", "schema_path", "instance", "schema", "parent",
  109. )
  110. return dict((attr, getattr(self, attr)) for attr in attrs)
  111. class ValidationError(_Error):
  112. """
  113. An instance was invalid under a provided schema.
  114. """
  115. _word_for_schema_in_error_message = "schema"
  116. _word_for_instance_in_error_message = "instance"
  117. class SchemaError(_Error):
  118. """
  119. A schema was invalid under its corresponding metaschema.
  120. """
  121. _word_for_schema_in_error_message = "metaschema"
  122. _word_for_instance_in_error_message = "schema"
  123. @attr.s(hash=True)
  124. class RefResolutionError(Exception):
  125. """
  126. A ref could not be resolved.
  127. """
  128. _cause = attr.ib()
  129. def __str__(self):
  130. return str(self._cause)
  131. class UndefinedTypeCheck(Exception):
  132. """
  133. A type checker was asked to check a type it did not have registered.
  134. """
  135. def __init__(self, type):
  136. self.type = type
  137. def __unicode__(self):
  138. return "Type %r is unknown to this type checker" % self.type
  139. if PY3:
  140. __str__ = __unicode__
  141. else:
  142. def __str__(self):
  143. return unicode(self).encode("utf-8")
  144. class UnknownType(Exception):
  145. """
  146. A validator was asked to validate an instance against an unknown type.
  147. """
  148. def __init__(self, type, instance, schema):
  149. self.type = type
  150. self.instance = instance
  151. self.schema = schema
  152. def __unicode__(self):
  153. pschema = pprint.pformat(self.schema, width=72)
  154. pinstance = pprint.pformat(self.instance, width=72)
  155. return textwrap.dedent("""
  156. Unknown type %r for validator with schema:
  157. %s
  158. While checking instance:
  159. %s
  160. """.rstrip()
  161. ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance))
  162. if PY3:
  163. __str__ = __unicode__
  164. else:
  165. def __str__(self):
  166. return unicode(self).encode("utf-8")
  167. class FormatError(Exception):
  168. """
  169. Validating a format failed.
  170. """
  171. def __init__(self, message, cause=None):
  172. super(FormatError, self).__init__(message, cause)
  173. self.message = message
  174. self.cause = self.__cause__ = cause
  175. def __unicode__(self):
  176. return self.message
  177. if PY3:
  178. __str__ = __unicode__
  179. else:
  180. def __str__(self):
  181. return self.message.encode("utf-8")
  182. class ErrorTree(object):
  183. """
  184. ErrorTrees make it easier to check which validations failed.
  185. """
  186. _instance = _unset
  187. def __init__(self, errors=()):
  188. self.errors = {}
  189. self._contents = defaultdict(self.__class__)
  190. for error in errors:
  191. container = self
  192. for element in error.path:
  193. container = container[element]
  194. container.errors[error.validator] = error
  195. container._instance = error.instance
  196. def __contains__(self, index):
  197. """
  198. Check whether ``instance[index]`` has any errors.
  199. """
  200. return index in self._contents
  201. def __getitem__(self, index):
  202. """
  203. Retrieve the child tree one level down at the given ``index``.
  204. If the index is not in the instance that this tree corresponds to and
  205. is not known by this tree, whatever error would be raised by
  206. ``instance.__getitem__`` will be propagated (usually this is some
  207. subclass of `exceptions.LookupError`.
  208. """
  209. if self._instance is not _unset and index not in self:
  210. self._instance[index]
  211. return self._contents[index]
  212. def __setitem__(self, index, value):
  213. """
  214. Add an error to the tree at the given ``index``.
  215. """
  216. self._contents[index] = value
  217. def __iter__(self):
  218. """
  219. Iterate (non-recursively) over the indices in the instance with errors.
  220. """
  221. return iter(self._contents)
  222. def __len__(self):
  223. """
  224. Return the `total_errors`.
  225. """
  226. return self.total_errors
  227. def __repr__(self):
  228. return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
  229. @property
  230. def total_errors(self):
  231. """
  232. The total number of errors in the entire tree, including children.
  233. """
  234. child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
  235. return len(self.errors) + child_errors
  236. def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
  237. """
  238. Create a key function that can be used to sort errors by relevance.
  239. Arguments:
  240. weak (set):
  241. a collection of validator names to consider to be "weak".
  242. If there are two errors at the same level of the instance
  243. and one is in the set of weak validator names, the other
  244. error will take priority. By default, :validator:`anyOf` and
  245. :validator:`oneOf` are considered weak validators and will
  246. be superseded by other same-level validation errors.
  247. strong (set):
  248. a collection of validator names to consider to be "strong"
  249. """
  250. def relevance(error):
  251. validator = error.validator
  252. return -len(error.path), validator not in weak, validator in strong
  253. return relevance
  254. relevance = by_relevance()
  255. def best_match(errors, key=relevance):
  256. """
  257. Try to find an error that appears to be the best match among given errors.
  258. In general, errors that are higher up in the instance (i.e. for which
  259. `ValidationError.path` is shorter) are considered better matches,
  260. since they indicate "more" is wrong with the instance.
  261. If the resulting match is either :validator:`oneOf` or :validator:`anyOf`,
  262. the *opposite* assumption is made -- i.e. the deepest error is picked,
  263. since these validators only need to match once, and any other errors may
  264. not be relevant.
  265. Arguments:
  266. errors (collections.Iterable):
  267. the errors to select from. Do not provide a mixture of
  268. errors from different validation attempts (i.e. from
  269. different instances or schemas), since it won't produce
  270. sensical output.
  271. key (collections.Callable):
  272. the key to use when sorting errors. See `relevance` and
  273. transitively `by_relevance` for more details (the default is
  274. to sort with the defaults of that function). Changing the
  275. default is only useful if you want to change the function
  276. that rates errors but still want the error context descent
  277. done by this function.
  278. Returns:
  279. the best matching error, or ``None`` if the iterable was empty
  280. .. note::
  281. This function is a heuristic. Its return value may change for a given
  282. set of inputs from version to version if better heuristics are added.
  283. """
  284. errors = iter(errors)
  285. best = next(errors, None)
  286. if best is None:
  287. return
  288. best = max(itertools.chain([best], errors), key=key)
  289. while best.context:
  290. best = min(best.context, key=key)
  291. return best