diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab4bb291a..d1e75e9aa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Unreleased - Update vendored schemas: bitbucket-pipelines, circle-ci, compose-spec, dependabot, github-workflows, gitlab-ci, mergify, renovate, woodpecker-ci (2025-05-11) +- Fix: support ``click==8.2.0`` 0.33.0 ------ diff --git a/src/check_jsonschema/cli/param_types.py b/src/check_jsonschema/cli/param_types.py index 41ae6846c..66bce92de 100644 --- a/src/check_jsonschema/cli/param_types.py +++ b/src/check_jsonschema/cli/param_types.py @@ -1,5 +1,6 @@ from __future__ import annotations +import functools import importlib import os import re @@ -10,6 +11,18 @@ import jsonschema from click._compat import open_stream +C = t.TypeVar("C", bound=t.Callable[..., t.Any]) + + +def _shim_click_8_2_get_metavar(func: C) -> C: + @functools.wraps(func) + def wrapper(*args: t.Any, **kwargs: t.Any) -> None: + if len(args) > 1 or "ctx" in kwargs: + return func(*args, **kwargs) + return func(*args, ctx=None, **kwargs) + + return wrapper # type: ignore[return-value] + class CommaDelimitedList(click.ParamType): name = "comma_delimited" @@ -24,7 +37,8 @@ def __init__( self.convert_values = convert_values self.choices = list(choices) if choices is not None else None - def get_metavar(self, param: click.Parameter) -> str: + @_shim_click_8_2_get_metavar + def get_metavar(self, param: click.Parameter, ctx: click.Context | None) -> str: if self.choices is not None: return "{" + ",".join(self.choices) + "}" return "TEXT,TEXT,..." diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index e47b088a1..369eee467 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -1,7 +1,6 @@ import textwrap import pytest -from click.testing import CliRunner from check_jsonschema import main as cli_main @@ -16,11 +15,6 @@ def _render_result(result): """ -@pytest.fixture -def cli_runner(): - return CliRunner(mix_stderr=False) - - @pytest.fixture def run_line(cli_runner): def func(cli_args, *args, **kwargs): diff --git a/tests/conftest.py b/tests/conftest.py index b0022a8d3..e8fc84176 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,20 @@ +import inspect import os import pathlib import sys import pytest import responses +from click.testing import CliRunner + + +@pytest.fixture +def cli_runner(): + # compatibility for click==8.2.0 vs click<=8.1 + sig = inspect.signature(CliRunner) + if "mix_stderr" in sig.parameters: + return CliRunner(mix_stderr=False) + return CliRunner() @pytest.fixture(autouse=True) diff --git a/tests/unit/cli/conftest.py b/tests/unit/cli/conftest.py deleted file mode 100644 index 98a793137..000000000 --- a/tests/unit/cli/conftest.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest -from click.testing import CliRunner - - -@pytest.fixture -def runner() -> CliRunner: - return CliRunner(mix_stderr=False) diff --git a/tests/unit/cli/test_callbacks.py b/tests/unit/cli/test_callbacks.py index b2813144d..56120c13a 100644 --- a/tests/unit/cli/test_callbacks.py +++ b/tests/unit/cli/test_callbacks.py @@ -22,23 +22,23 @@ def mycli(bar, baz): print(baz) -def test_deprecation_warning_callback_on_missing_opts(runner): - result = runner.invoke(mycli, []) +def test_deprecation_warning_callback_on_missing_opts(cli_runner): + result = cli_runner.invoke(mycli, []) assert result.exit_code == 0 assert result.stdout == "False\n" -def test_deprecation_warning_callback_on_flag(runner): +def test_deprecation_warning_callback_on_flag(cli_runner): with pytest.warns( UserWarning, match="'--bar' is deprecated and will be removed in a future release", ): - result = runner.invoke(mycli, ["--bar"], catch_exceptions=False) + result = cli_runner.invoke(mycli, ["--bar"], catch_exceptions=False) assert result.exit_code == 0, result.stdout assert result.stdout == "True\n" -def test_deprecation_warning_callback_added_message(runner): +def test_deprecation_warning_callback_added_message(cli_runner): with pytest.warns( UserWarning, match=( @@ -46,6 +46,6 @@ def test_deprecation_warning_callback_added_message(runner): "Use --frob instead!" ), ): - result = runner.invoke(mycli, ["--baz", "ok"], catch_exceptions=False) + result = cli_runner.invoke(mycli, ["--baz", "ok"], catch_exceptions=False) assert result.exit_code == 0, result.stdout assert result.stdout == "False\nok\n" diff --git a/tests/unit/cli/test_parse.py b/tests/unit/cli/test_parse.py index 3b486f1b6..e7846220c 100644 --- a/tests/unit/cli/test_parse.py +++ b/tests/unit/cli/test_parse.py @@ -67,14 +67,16 @@ def test_parse_result_set_schema( assert args.schema_path is None -def test_requires_some_args(runner): - result = runner.invoke(cli_main, []) +def test_requires_some_args(cli_runner): + result = cli_runner.invoke(cli_main, []) assert result.exit_code == 2 -def test_schemafile_and_instancefile(runner, mock_parse_result, in_tmp_dir, tmp_path): +def test_schemafile_and_instancefile( + cli_runner, mock_parse_result, in_tmp_dir, tmp_path +): touch_files(tmp_path, "foo.json") - runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) + cli_runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) assert mock_parse_result.schema_mode == SchemaLoadingMode.filepath assert mock_parse_result.schema_path == "schema.json" assert isinstance(mock_parse_result.instancefiles, tuple) @@ -83,25 +85,27 @@ def test_schemafile_and_instancefile(runner, mock_parse_result, in_tmp_dir, tmp_ assert tuple(f.name for f in mock_parse_result.instancefiles) == ("foo.json",) -def test_requires_at_least_one_instancefile(runner): - result = runner.invoke(cli_main, ["--schemafile", "schema.json"]) +def test_requires_at_least_one_instancefile(cli_runner): + result = cli_runner.invoke(cli_main, ["--schemafile", "schema.json"]) assert result.exit_code == 2 -def test_requires_schemafile(runner, in_tmp_dir, tmp_path): +def test_requires_schemafile(cli_runner, in_tmp_dir, tmp_path): touch_files(tmp_path, "foo.json") - result = runner.invoke(cli_main, ["foo.json"]) + result = cli_runner.invoke(cli_main, ["foo.json"]) assert result.exit_code == 2 -def test_no_cache_defaults_false(runner, mock_parse_result): - runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) +def test_no_cache_defaults_false(cli_runner, mock_parse_result): + cli_runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) assert mock_parse_result.disable_cache is False -def test_no_cache_flag_is_true(runner, mock_parse_result, in_tmp_dir, tmp_path): +def test_no_cache_flag_is_true(cli_runner, mock_parse_result, in_tmp_dir, tmp_path): touch_files(tmp_path, "foo.json") - runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json", "--no-cache"]) + cli_runner.invoke( + cli_main, ["--schemafile", "schema.json", "foo.json", "--no-cache"] + ) assert mock_parse_result.disable_cache is True @@ -133,9 +137,9 @@ def test_no_cache_flag_is_true(runner, mock_parse_result, in_tmp_dir, tmp_path): ], ], ) -def test_mutex_schema_opts(runner, cmd_args, in_tmp_dir, tmp_path): +def test_mutex_schema_opts(cli_runner, cmd_args, in_tmp_dir, tmp_path): touch_files(tmp_path, "foo.json") - result = runner.invoke(cli_main, cmd_args + ["foo.json"]) + result = cli_runner.invoke(cli_main, cmd_args + ["foo.json"]) assert result.exit_code == 2 assert "are mutually exclusive" in result.stderr @@ -148,8 +152,8 @@ def test_mutex_schema_opts(runner, cmd_args, in_tmp_dir, tmp_path): ["-h"], ], ) -def test_supports_common_option(runner, cmd_args): - result = runner.invoke(cli_main, cmd_args) +def test_supports_common_option(cli_runner, cmd_args): + result = cli_runner.invoke(cli_main, cmd_args) assert result.exit_code == 0 @@ -157,7 +161,7 @@ def test_supports_common_option(runner, cmd_args): "setting,expect_value", [(None, None), ("1", False), ("0", False)] ) def test_no_color_env_var( - runner, monkeypatch, setting, expect_value, boxed_context, in_tmp_dir, tmp_path + cli_runner, monkeypatch, setting, expect_value, boxed_context, in_tmp_dir, tmp_path ): if setting is None: monkeypatch.delenv("NO_COLOR", raising=False) @@ -165,7 +169,7 @@ def test_no_color_env_var( monkeypatch.setenv("NO_COLOR", setting) touch_files(tmp_path, "foo.json") - runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) + cli_runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) assert boxed_context.ref.color == expect_value @@ -174,22 +178,22 @@ def test_no_color_env_var( [(None, None), ("auto", None), ("always", True), ("never", False)], ) def test_color_cli_option( - runner, setting, expected_value, boxed_context, in_tmp_dir, tmp_path + cli_runner, setting, expected_value, boxed_context, in_tmp_dir, tmp_path ): args = ["--schemafile", "schema.json", "foo.json"] if setting: args.extend(("--color", setting)) touch_files(tmp_path, "foo.json") - runner.invoke(cli_main, args) + cli_runner.invoke(cli_main, args) assert boxed_context.ref.color == expected_value def test_no_color_env_var_overrides_cli_option( - runner, monkeypatch, mock_cli_exec, boxed_context, in_tmp_dir, tmp_path + cli_runner, monkeypatch, mock_cli_exec, boxed_context, in_tmp_dir, tmp_path ): monkeypatch.setenv("NO_COLOR", "1") touch_files(tmp_path, "foo.json") - runner.invoke( + cli_runner.invoke( cli_main, ["--color=always", "--schemafile", "schema.json", "foo.json"] ) assert boxed_context.ref.color is False @@ -200,11 +204,11 @@ def test_no_color_env_var_overrides_cli_option( [("auto", 0), ("always", 0), ("never", 0), ("anything_else", 2)], ) def test_color_cli_option_is_choice( - runner, setting, expected_value, in_tmp_dir, tmp_path + cli_runner, setting, expected_value, in_tmp_dir, tmp_path ): touch_files(tmp_path, "foo.json") assert ( - runner.invoke( + cli_runner.invoke( cli_main, ["--color", setting, "--schemafile", "schema.json", "foo.json"], ).exit_code @@ -212,9 +216,11 @@ def test_color_cli_option_is_choice( ) -def test_formats_default_to_enabled(runner, mock_parse_result, in_tmp_dir, tmp_path): +def test_formats_default_to_enabled( + cli_runner, mock_parse_result, in_tmp_dir, tmp_path +): touch_files(tmp_path, "foo.json") - runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) + cli_runner.invoke(cli_main, ["--schemafile", "schema.json", "foo.json"]) assert mock_parse_result.disable_all_formats is False assert mock_parse_result.disable_formats == () @@ -232,10 +238,10 @@ def test_formats_default_to_enabled(runner, mock_parse_result, in_tmp_dir, tmp_p ), ) def test_disable_selected_formats( - runner, mock_parse_result, addargs, in_tmp_dir, tmp_path + cli_runner, mock_parse_result, addargs, in_tmp_dir, tmp_path ): touch_files(tmp_path, "foo.json") - runner.invoke( + cli_runner.invoke( cli_main, [ "--schemafile", @@ -263,10 +269,12 @@ def test_disable_selected_formats( ["--disable-formats", "*,email"], ), ) -def test_disable_all_formats(runner, mock_parse_result, addargs, in_tmp_dir, tmp_path): +def test_disable_all_formats( + cli_runner, mock_parse_result, addargs, in_tmp_dir, tmp_path +): touch_files(tmp_path, "foo.json") # this should be an override, with or without other args - runner.invoke( + cli_runner.invoke( cli_main, [ "--schemafile", @@ -279,13 +287,13 @@ def test_disable_all_formats(runner, mock_parse_result, addargs, in_tmp_dir, tmp def test_can_specify_custom_validator_class( - runner, mock_parse_result, mock_module, in_tmp_dir, tmp_path + cli_runner, mock_parse_result, mock_module, in_tmp_dir, tmp_path ): mock_module("foo.py", "class MyValidator: pass") import foo touch_files(tmp_path, "foo.json") - result = runner.invoke( + result = cli_runner.invoke( cli_main, [ "--schemafile", @@ -303,7 +311,7 @@ def test_can_specify_custom_validator_class( "failmode", ("syntax", "import", "attr", "function", "non_callable") ) def test_custom_validator_class_fails( - runner, mock_parse_result, mock_module, failmode, in_tmp_dir, tmp_path + cli_runner, mock_parse_result, mock_module, failmode, in_tmp_dir, tmp_path ): mock_module( "foo.py", @@ -331,7 +339,7 @@ def validator_func(*args, **kwargs): raise NotImplementedError touch_files(tmp_path, "foo.json") - result = runner.invoke( + result = cli_runner.invoke( cli_main, ["--schemafile", "schema.json", "foo.json", "--validator-class", arg], ) diff --git a/tests/unit/test_lazy_file_handling.py b/tests/unit/test_lazy_file_handling.py index dd69eac60..030c975eb 100644 --- a/tests/unit/test_lazy_file_handling.py +++ b/tests/unit/test_lazy_file_handling.py @@ -2,21 +2,15 @@ import platform import pytest -from click.testing import CliRunner from check_jsonschema.cli.main_command import build_checker from check_jsonschema.cli.main_command import main as cli_main -@pytest.fixture -def runner() -> CliRunner: - return CliRunner(mix_stderr=False) - - @pytest.mark.skipif( platform.system() != "Linux", reason="test requires /proc/self/ mechanism" ) -def test_open_file_usage_never_exceeds_1000(runner, monkeypatch, tmp_path): +def test_open_file_usage_never_exceeds_1000(cli_runner, monkeypatch, tmp_path): schema_path = tmp_path / "schema.json" schema_path.write_text("{}") @@ -37,7 +31,7 @@ def fake_execute(argv): checker = build_checker(argv) monkeypatch.setattr("check_jsonschema.cli.main_command.execute", fake_execute) - res = runner.invoke(cli_main, args) + res = cli_runner.invoke(cli_main, args) assert res.exit_code == 0, res.stderr assert checker is not None