Repository: jazzband/pip-tools
Pull request #1915 (2023-07-16 21:19:29)

Refactor multi-value options validation
Move multi-value options validation logic to `_validate_config()`.

##### Contributor checklist

- [ ] Provided the tests for the changes.
- [ ] Assure PR title is short, clear, and good to be included in the user-oriented changelog

##### Maintainer checklist

- [ ] Assure one of these labels is present: `backwards incompatible`, `feature`, `enhancement`, `deprecation`, `bug`, `dependency`, `docs` or `skip-changelog` as they determine changelog listing.
- [ ] Assign the PR to an existing or new milestone for the target version (following [Semantic Versioning](https://blog.versioneye.com/2014/01/16/semantic-versioning/)).



Patch:
diff --git a/piptools/utils.py b/piptools/utils.py
index de24184..a1711a5 100644
--- a/piptools/utils.py
+++ b/piptools/utils.py
@@ -611,11 +611,14 @@ def _validate_config(
         # Validate invalid values
         param = cli_params[key]
         try:
-            param.type.convert(value=value, param=param, ctx=click_context)
+            param.type_cast_value(value=value, ctx=click_context)
         except Exception as e:
             raise click.BadOptionUsage(
                 option_name=key,
-                message=f"Invalid value for config key {key!r}: {value!r}.",
+                message=(
+                    f"Invalid value for config key {key!r}: {value!r}.{os.linesep}"
+                    f"Details: {e}"
+                ),
                 ctx=click_context,
             ) from e
 
@@ -662,17 +665,6 @@ NON_STANDARD_OPTION_DEST_MAP: dict[str, str] = {
 }
 
 
-# Ensure that any default overrides for these click options are lists, supporting multiple values
-MULTIPLE_VALUE_OPTIONS = [
-    "extras",
-    "upgrade_packages",
-    "unsafe_package",
-    "find_links",
-    "extra_index_url",
-    "trusted_host",
-]
-
-
 def get_cli_options(ctx: click.Context) -> dict[str, click.Parameter]:
     cli_opts = {
         opt: option
@@ -701,20 +693,11 @@ def parse_config_file(
 
     # In a TOML file, we expect the config to be under `[tool.pip-tools]`
     piptools_config: dict[str, Any] = config.get("tool", {}).get("pip-tools", {})
-
     piptools_config = _normalize_keys_in_config(piptools_config)
     piptools_config = _invert_negative_bool_options_in_config(
         ctx=click_context,
         config=piptools_config,
     )
-
-    # Any option with multiple values needs to be a list in the pyproject.toml
-    for mv_option in MULTIPLE_VALUE_OPTIONS:
-        if not isinstance(piptools_config.get(mv_option), (list, type(None))):
-            original_option = mv_option.replace("_", "-")
-            raise click.BadOptionUsage(
-                original_option, f"Config key '{original_option}' must be a list"
-            )
     return piptools_config
 
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 76ea0d6..7b3d7ea 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -624,21 +624,25 @@ def test_callback_config_file_defaults(pyproject_param, new_default, make_config
 
 
 @pytest.mark.parametrize(
-    "mv_option",
+    ("param", "value"),
     (
-        "extra",
-        "upgrade-package",
-        "unsafe-package",
-        "find-links",
-        "extra-index-url",
-        "trusted-host",
+        ("extra", "not-a-list"),
+        ("upgrade_package", "not-a-list"),
+        ("unsafe_package", "not-a-list"),
+        ("find_links", "not-a-list"),
+        ("extra_index_url", "not-a-list"),
+        ("trusted_host", "not-a-list"),
+        ("annotate", "not-a-bool"),
+        ("max_rounds", "not-an-int"),
     ),
 )
-def test_callback_config_file_defaults_multi_value_options(mv_option, make_config_file):
-    config_file = make_config_file(mv_option, "not-a-list")
+def test_callback_config_file_defaults_multi_validate_value(
+    param, value, make_config_file
+):
+    config_file = make_config_file(param, value)
     ctx = Context(compile_cli)
     ctx.params["src_files"] = (str(config_file),)
-    with pytest.raises(BadOptionUsage, match="must be a list"):
+    with pytest.raises(BadOptionUsage, match="Invalid value for config key"):
         override_defaults_from_config_file(ctx, "config", None)