Skip to content

Commit 58f2848

Browse files
committed
Restrict --validator-class to types (no functions)
In order to better guarantee that `--validator-class` provides the name of a `jsonschema.protocols.Validator` class, require that the value passed is a type and not an alternative function. This requirement does not lose significant expressive power and allows us to more safely assume that the validator really is an implementation of the protocol which can be extended with `--fill-defaults` and other validator-related hooks. There is no real way to guarantee the safety of using the user's Validator. After all -- it could always raise an error at any time -- but this matches check-jsonschema's expectations about the argument better. This change can always be reverted in the future if there is significant demand for a function-as-a-validator-class option.
1 parent 11f7dfa commit 58f2848

File tree

2 files changed

+10
-44
lines changed

2 files changed

+10
-44
lines changed

src/check_jsonschema/cli/param_types.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import importlib
44
import re
55
import typing as t
6-
import warnings
76

87
import click
98
import jsonschema
@@ -101,17 +100,7 @@ def convert(
101100
ctx,
102101
)
103102

104-
if not callable(result):
105-
self.fail(
106-
f"'{classname}' in '{pkg}' is not a class or callable", param, ctx
107-
)
108-
109103
if not isinstance(result, type):
110-
warnings.warn(
111-
f"'{classname}' in '{pkg}' is not a class. If it is a function "
112-
f"returning a Validator, it still might work, but this usage "
113-
"is not recommended.",
114-
stacklevel=1,
115-
)
104+
self.fail(f"'{classname}' in '{pkg}' is not a class", param, ctx)
116105

117106
return t.cast(t.Type[jsonschema.protocols.Validator], result)

tests/unit/test_cli_parse.py

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -307,34 +307,9 @@ def test_can_specify_custom_validator_class(runner, mock_parse_result, mock_modu
307307
assert mock_parse_result.validator_class == foo.MyValidator
308308

309309

310-
def test_warns_on_validator_function(runner, mock_parse_result, mock_module):
311-
mock_module(
312-
"foo/bar.py",
313-
"""\
314-
class MyValidator: pass
315-
316-
def validator(*args, **kwargs):
317-
return MyValidator(*args, **kwargs)
318-
""",
319-
)
320-
import foo.bar
321-
322-
with pytest.warns(UserWarning, match="'validator' in 'foo.bar' is not a class"):
323-
result = runner.invoke(
324-
cli_main,
325-
[
326-
"--schemafile",
327-
"schema.json",
328-
"foo.json",
329-
"--validator-class",
330-
"foo.bar:validator",
331-
],
332-
)
333-
assert result.exit_code == 0
334-
assert mock_parse_result.validator_class == foo.bar.validator
335-
336-
337-
@pytest.mark.parametrize("failmode", ("syntax", "import", "attr", "callability"))
310+
@pytest.mark.parametrize(
311+
"failmode", ("syntax", "import", "attr", "function", "non_callable")
312+
)
338313
def test_can_custom_validator_class_fails(
339314
runner, mock_parse_result, mock_module, failmode
340315
):
@@ -343,7 +318,7 @@ def test_can_custom_validator_class_fails(
343318
"""\
344319
class MyValidator: pass
345320
346-
def validator(*args, **kwargs):
321+
def validator_func(*args, **kwargs):
347322
return MyValidator(*args, **kwargs)
348323
349324
other_thing = 100
@@ -356,7 +331,9 @@ def validator(*args, **kwargs):
356331
arg = "foo.bar:MyValidator"
357332
elif failmode == "attr":
358333
arg = "foo:no_such_attr"
359-
elif failmode == "callability":
334+
elif failmode == "function":
335+
arg = "foo:validator_func"
336+
elif failmode == "non_callable":
360337
arg = "foo:other_thing"
361338
else:
362339
raise NotImplementedError
@@ -373,7 +350,7 @@ def validator(*args, **kwargs):
373350
assert "was not an importable module" in result.stderr
374351
elif failmode == "attr":
375352
assert "was not resolvable to a class" in result.stderr
376-
elif failmode == "callability":
377-
assert "is not a class or callable" in result.stderr
353+
elif failmode in ("function", "non_callable"):
354+
assert "is not a class" in result.stderr
378355
else:
379356
raise NotImplementedError

0 commit comments

Comments
 (0)