Skip to content

Commit

Permalink
Warn at runtime when subclassing validator classes.
Browse files Browse the repository at this point in the history
Doing so was not intended to be public API, though it seems
some downstream libraries do so.

A future version will make this an error, as it is brittle and
better served by composing validator objects instead.

Feel free to reach out if there are any cases where changing
existing code seems difficult and I can try to provide guidance.

Refs: #982
  • Loading branch information
Julian committed Aug 18, 2022
1 parent 549f9e5 commit e69cb7a
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 4 deletions.
25 changes: 25 additions & 0 deletions jsonschema/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,28 @@ def test_Validator_iter_errors_two_arguments(self):
"Passing a schema to Validator.iter_errors is deprecated ",
),
)

def test_Validator_subclassing(self):
"""
As of v4.12.0, subclassing a validator class produces an explicit
deprecation warning.
This was never intended to be public API (and some comments over the
years in issues said so, but obviously that's not a great way to make
sure it's followed).
A future version will explicitly raise an error.
"""

with self.assertWarns(DeprecationWarning) as w:
class Subclass(validators.Draft202012Validator):
pass

self.assertEqual(w.filename, __file__)
self.assertTrue(
str(w.warning).startswith("Subclassing validator classes is "),
)

with self.assertWarns(DeprecationWarning) as w:
class AnotherSubclass(validators.create(meta_schema={})):
pass
9 changes: 5 additions & 4 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1485,10 +1485,11 @@ def test_evolve_with_subclass(self):
the interim, we haven't broken those users.
"""

@attr.s
class OhNo(self.Validator):
foo = attr.ib(factory=lambda: [1, 2, 3])
_bar = attr.ib(default=37)
with self.assertWarns(DeprecationWarning):
@attr.s
class OhNo(self.Validator):
foo = attr.ib(factory=lambda: [1, 2, 3])
_bar = attr.ib(default=37)

validator = OhNo({}, bar=12)
self.assertEqual(validator.foo, [1, 2, 3])
Expand Down
15 changes: 15 additions & 0 deletions jsonschema/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ class Validator:
resolver = attr.ib(default=None, repr=False)
format_checker = attr.ib(default=None)

def __init_subclass__(cls):
warnings.warn(
(
"Subclassing validator classes is not intended to "
"be part of their public API. A future version "
"will make doing so an error, as the behavior of "
"subclasses isn't guaranteed to stay the same "
"between releases of jsonschema. Instead, prefer "
"composition of validators, wrapping them in an object "
"owned entirely by the downstream library."
),
DeprecationWarning,
stacklevel=2,
)

def __attrs_post_init__(self):
if self.resolver is None:
self.resolver = RefResolver.from_schema(
Expand Down

0 comments on commit e69cb7a

Please sign in to comment.