Skip to content

Commit

Permalink
Wrap raised referencing exceptions in a RefResolutionError subclass.
Browse files Browse the repository at this point in the history
Even though RefResolutionError is deprecated (and already raises a
suitable warning), this should make some additional code warn rather
than blow up (i.e. if someone is catching RefResolutionError, that code
should continue to work though it will raise a warning).

Users can transition to non-deprecated APIs by directly catching
referencing.exceptions.Unresolvable.

Closes: #1069
  • Loading branch information
Julian committed Apr 17, 2023
1 parent 6f271fc commit 3b35731
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 1 deletion.
10 changes: 10 additions & 0 deletions jsonschema/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import itertools
import warnings

from referencing.exceptions import Unresolvable as _Unresolvable
import attr

from jsonschema import _utils
Expand Down Expand Up @@ -210,6 +211,15 @@ def __str__(self):
return str(self._cause)


class _WrappedReferencingError(_RefResolutionError, _Unresolvable):
def __init__(self, cause: _Unresolvable):
object.__setattr__(self, "_cause", cause)

def __getattribute__(self, attr):
cause = object.__getattribute__(self, "_cause")
return getattr(cause, attr)


class UndefinedTypeCheck(Exception):
"""
A type checker was asked to check a type it did not have registered.
Expand Down
34 changes: 34 additions & 0 deletions jsonschema/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import subprocess
import sys

import referencing.exceptions

from jsonschema import FormatChecker, exceptions, validators


Expand Down Expand Up @@ -169,6 +171,38 @@ def test_RefResolutionError(self):
self.assertEqual(RefResolutionError, exceptions._RefResolutionError)
self.assertEqual(w.filename, __file__)

def test_catching_Unresolvable_directly(self):
"""
This behavior is the intended behavior (i.e. it's not deprecated), but
given we do "tricksy" things in the iterim to wrap exceptions in a
multiple inheritance subclass, we need to be extra sure it works and
stays working.
"""
validator = validators.Draft202012Validator({"$ref": "http://foo.com"})

with self.assertRaises(referencing.exceptions.Unresolvable) as e:
validator.validate(12)

expected = referencing.exceptions.Unresolvable(ref="http://foo.com")
self.assertEqual(e.exception, expected)

def test_catching_Unresolvable_via_RefResolutionError(self):
"""
Until RefResolutionError is removed, it is still possible to catch
exceptions from reference resolution using it, even though they may
have been raised by referencing.
"""
with self.assertWarns(DeprecationWarning):
from jsonschema import RefResolutionError

validator = validators.Draft202012Validator({"$ref": "http://foo.com"})

with self.assertRaises(referencing.exceptions.Unresolvable):
validator.validate(12)

with self.assertRaises(RefResolutionError):
validator.validate(12)

def test_Validator_subclassing(self):
"""
As of v4.12.0, subclassing a validator class produces an explicit
Expand Down
7 changes: 6 additions & 1 deletion jsonschema/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from jsonschema_specifications import REGISTRY as SPECIFICATIONS
from referencing import Specification
from rpds import HashTrieMap
import referencing.exceptions
import referencing.jsonschema

from jsonschema import (
Expand Down Expand Up @@ -401,7 +402,11 @@ def is_type(self, instance, type):

def _validate_reference(self, ref, instance):
if self._ref_resolver is None:
resolved = self._resolver.lookup(ref)
try:
resolved = self._resolver.lookup(ref)
except referencing.exceptions.Unresolvable as err:
raise exceptions._WrappedReferencingError(err)

return self.descend(
instance,
resolved.contents,
Expand Down

0 comments on commit 3b35731

Please sign in to comment.