From e69cb7ab504740a58766fad757bb5d01e5e812f6 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Thu, 18 Aug 2022 11:22:16 +0300 Subject: [PATCH] Warn at runtime when subclassing validator classes. 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 --- jsonschema/tests/test_deprecations.py | 25 +++++++++++++++++++++++++ jsonschema/tests/test_validators.py | 9 +++++---- jsonschema/validators.py | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/jsonschema/tests/test_deprecations.py b/jsonschema/tests/test_deprecations.py index 58fd050ac..fcf79023a 100644 --- a/jsonschema/tests/test_deprecations.py +++ b/jsonschema/tests/test_deprecations.py @@ -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 diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 4ebc8db7a..e4f60ea35 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -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]) diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 79a8da3a8..e6d537099 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -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(