Skip to content

Commit

Permalink
Fix calling Validator.evolve for some downstream users.
Browse files Browse the repository at this point in the history
The broken case here was subclassing a validator class, something that
isn't really a supported use of the Validator classes, but of course one
can't blame anyone too much since doing so didn't raise an error or emit
any warning.

In the next release subclassing will warn, and at some point afterwards
will become an explicit error. If you're a downstream user of this
library looking for a way to avoid whichever inheritance is currently
needed in your library feel free to reach out and I'll try to help.

Closes: #982
  • Loading branch information
Julian committed Aug 17, 2022
1 parent 0c4aaaf commit c2e86ee
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
v4.10.1
-------

* Fix Validator.evolve (and APIs like ``iter_errors`` which call it) for cases
where the validator class has been subclassed. Doing so wasn't intended to be
public API, but given it didn't warn or raise an error it's of course
understandable. The next release however will make it warn (and a future one
will make it error). If you need help migrating usage of inheriting from a
validator class feel free to open a discussion and I'll try to give some
guidance (#982).

v4.10.0
-------

Expand Down
32 changes: 32 additions & 0 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,38 @@ def test_it_delegates_to_a_ref_resolver(self):
with self.assertRaises(exceptions.ValidationError):
validator.validate(None)

def test_evolve(self):
ref, schema = "someCoolRef", {"type": "integer"}
resolver = validators.RefResolver("", {}, store={ref: schema})

validator = self.Validator(schema, resolver=resolver)
new = validator.evolve(schema={"type": "string"})

expected = self.Validator({"type": "string"}, resolver=resolver)

self.assertEqual(new, expected)
self.assertNotEqual(new, validator)

def test_evolve_with_subclass(self):
"""
Subclassing validators isn't supported public API, but some users have
done it, because we don't actually error entirely when it's done :/
We need to deprecate doing so first to help as many of these users
ensure they can move to supported APIs, but this test ensures that in
the interim, we haven't broken those users.
"""

@attr.s
class OhNo(self.Validator):
foo = attr.ib(factory=lambda: [1, 2, 3])

validator = OhNo({})
self.assertEqual(validator.foo, [1, 2, 3])

new = validator.evolve(schema={"type": "integer"})
self.assertEqual(new.foo, [1, 2, 3])

def test_it_delegates_to_a_legacy_ref_resolver(self):
"""
Legacy RefResolvers support only the context manager form of
Expand Down
2 changes: 1 addition & 1 deletion jsonschema/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def check_schema(cls, schema):

def evolve(self, **changes):
schema = changes.setdefault("schema", self.schema)
NewValidator = validator_for(schema, default=Validator)
NewValidator = validator_for(schema, default=self.__class__)

# Essentially reproduces attr.evolve, but may involve instantiating
# a different class than this one.
Expand Down

0 comments on commit c2e86ee

Please sign in to comment.