Repository: conda/conda

Issue:
Improve error message on invalid SequenceParameter
For a malformed configuration parameter of type sequence:
```
(test) [hulk@ranganork conda]# cat ~/.condarc 
channels: defaults
```

The following shows the expected error:

```
(test) [hulk@ranganork conda]# conda config --validate 
InvalidTypeError: Parameter _channels = 'defaults' declared in <<merged>> has type <class 'str'>.
Valid types:
  - <class 'tuple'>
```

But the following throws an exception:

```
(test) [hulk@ranganork conda]# conda update conda
Traceback (most recent call last):
  File "/root/conda/conda/exceptions.py", line 632, in conda_exception_handler
    return_value = func(*args, **kwargs)
  File "/root/conda/conda/cli/main.py", line 134, in _main
    exit_code = args.func(args, p)
  File "/root/conda/conda/cli/main_update.py", line 65, in execute
    install(args, parser, 'update')
  File "/root/conda/conda/cli/install.py", line 128, in install
    context.validate_configuration()
  File "/root/conda/conda/common/configuration.py", line 844, in validate_configuration
    raise_errors(tuple(chain.from_iterable((errors, post_errors))))
  File "/root/conda/conda/common/configuration.py", line 842, in <genexpr>
    for name in self.parameter_names)
  File "/root/conda/conda/common/configuration.py", line 835, in _collect_validation_error
    func(*args, **kwargs)
  File "/root/conda/conda/common/configuration.py", line 448, in __get__
    result = typify_data_structure(self._merge(matches) if matches else self.default,
  File "/root/conda/conda/common/configuration.py", line 596, in _merge
    top_deduped = tuple(unique(concatv(top_lines, all_lines)))
  File "/root/conda/conda/_vendor/toolz/itertoolz.py", line 209, in unique
    for item in seq:
  File "/root/conda/conda/common/configuration.py", line 584, in <genexpr>
    top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m in relevant_matches)
  File "/root/conda/conda/common/configuration.py", line 583, in get_marked_lines
    if flag is marker) if match else ()
TypeError: zip argument #2 must support iteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/conda/envs/test/bin/conda", line 11, in <module>
    load_entry_point('conda', 'console_scripts', 'conda')()
  File "/root/conda/conda/cli/main.py", line 162, in main
    return conda_exception_handler(_main, *args)
  File "/root/conda/conda/exceptions.py", line 636, in conda_exception_handler
    return handle_exception(e)
  File "/root/conda/conda/exceptions.py", line 626, in handle_exception
    print_unexpected_error_message(e)
  File "/root/conda/conda/exceptions.py", line 588, in print_unexpected_error_message
    stderrlogger.info(get_main_info_str(get_info_dict()))
  File "/root/conda/conda/cli/main_info.py", line 196, in get_info_dict
    channels = list(prioritize_channels(context.channels).keys())
  File "/root/conda/conda/base/context.py", line 448, in channels
    return self._channels
  File "/root/conda/conda/common/configuration.py", line 448, in __get__
    result = typify_data_structure(self._merge(matches) if matches else self.default,
  File "/root/conda/conda/common/configuration.py", line 596, in _merge
    top_deduped = tuple(unique(concatv(top_lines, all_lines)))
  File "/root/conda/conda/_vendor/toolz/itertoolz.py", line 209, in unique
    for item in seq:
  File "/root/conda/conda/common/configuration.py", line 584, in <genexpr>
    top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m in relevant_matches)
  File "/root/conda/conda/common/configuration.py", line 583, in get_marked_lines
    if flag is marker) if match else ()
TypeError: zip argument #2 must support iteration
```


Improve error message on invalid SequenceParameter
For a malformed configuration parameter of type sequence:
```
(test) [hulk@ranganork conda]# cat ~/.condarc 
channels: defaults
```

The following shows the expected error:

```
(test) [hulk@ranganork conda]# conda config --validate 
InvalidTypeError: Parameter _channels = 'defaults' declared in <<merged>> has type <class 'str'>.
Valid types:
  - <class 'tuple'>
```

But the following throws an exception:

```
(test) [hulk@ranganork conda]# conda update conda
Traceback (most recent call last):
  File "/root/conda/conda/exceptions.py", line 632, in conda_exception_handler
    return_value = func(*args, **kwargs)
  File "/root/conda/conda/cli/main.py", line 134, in _main
    exit_code = args.func(args, p)
  File "/root/conda/conda/cli/main_update.py", line 65, in execute
    install(args, parser, 'update')
  File "/root/conda/conda/cli/install.py", line 128, in install
    context.validate_configuration()
  File "/root/conda/conda/common/configuration.py", line 844, in validate_configuration
    raise_errors(tuple(chain.from_iterable((errors, post_errors))))
  File "/root/conda/conda/common/configuration.py", line 842, in <genexpr>
    for name in self.parameter_names)
  File "/root/conda/conda/common/configuration.py", line 835, in _collect_validation_error
    func(*args, **kwargs)
  File "/root/conda/conda/common/configuration.py", line 448, in __get__
    result = typify_data_structure(self._merge(matches) if matches else self.default,
  File "/root/conda/conda/common/configuration.py", line 596, in _merge
    top_deduped = tuple(unique(concatv(top_lines, all_lines)))
  File "/root/conda/conda/_vendor/toolz/itertoolz.py", line 209, in unique
    for item in seq:
  File "/root/conda/conda/common/configuration.py", line 584, in <genexpr>
    top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m in relevant_matches)
  File "/root/conda/conda/common/configuration.py", line 583, in get_marked_lines
    if flag is marker) if match else ()
TypeError: zip argument #2 must support iteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/conda/envs/test/bin/conda", line 11, in <module>
    load_entry_point('conda', 'console_scripts', 'conda')()
  File "/root/conda/conda/cli/main.py", line 162, in main
    return conda_exception_handler(_main, *args)
  File "/root/conda/conda/exceptions.py", line 636, in conda_exception_handler
    return handle_exception(e)
  File "/root/conda/conda/exceptions.py", line 626, in handle_exception
    print_unexpected_error_message(e)
  File "/root/conda/conda/exceptions.py", line 588, in print_unexpected_error_message
    stderrlogger.info(get_main_info_str(get_info_dict()))
  File "/root/conda/conda/cli/main_info.py", line 196, in get_info_dict
    channels = list(prioritize_channels(context.channels).keys())
  File "/root/conda/conda/base/context.py", line 448, in channels
    return self._channels
  File "/root/conda/conda/common/configuration.py", line 448, in __get__
    result = typify_data_structure(self._merge(matches) if matches else self.default,
  File "/root/conda/conda/common/configuration.py", line 596, in _merge
    top_deduped = tuple(unique(concatv(top_lines, all_lines)))
  File "/root/conda/conda/_vendor/toolz/itertoolz.py", line 209, in unique
    for item in seq:
  File "/root/conda/conda/common/configuration.py", line 584, in <genexpr>
    top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m in relevant_matches)
  File "/root/conda/conda/common/configuration.py", line 583, in get_marked_lines
    if flag is marker) if match else ()
TypeError: zip argument #2 must support iteration
```




Discussion:
Similar to #5184 
Do you want to tackle this one @nehaljwani?
Similar to #5184 
Do you want to tackle this one @nehaljwani?

Resolving patch:
diff --git a/conda/common/configuration.py b/conda/common/configuration.py
--- a/conda/common/configuration.py
+++ b/conda/common/configuration.py
@@ -572,7 +572,12 @@ def collect_errors(self, instance, value, source="<<merged>>"):
     def _merge(self, matches):
         # get matches up to and including first important_match
         #   but if no important_match, then all matches are important_matches
-        relevant_matches = self._first_important_matches(matches)
+        relevant_matches_and_values = tuple((match, match.value(self)) for match in
+                                            self._first_important_matches(matches))
+        for match, value in relevant_matches_and_values:
+            if not isinstance(value, tuple):
+                raise InvalidTypeError(self.name, value, match.source, value.__class__.__name__,
+                                       self._type.__name__)
 
         # get individual lines from important_matches that were marked important
         # these will be prepended to the final result
@@ -581,16 +586,17 @@ def get_marked_lines(match, marker, parameter_obj):
                          for line, flag in zip(match.value(parameter_obj),
                                                match.valueflags(parameter_obj))
                          if flag is marker) if match else ()
-        top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m in relevant_matches)
+        top_lines = concat(get_marked_lines(m, ParameterFlag.top, self) for m, _ in
+                           relevant_matches_and_values)
 
         # also get lines that were marked as bottom, but reverse the match order so that lines
         # coming earlier will ultimately be last
-        bottom_lines = concat(get_marked_lines(m, ParameterFlag.bottom, self) for m in
-                              reversed(relevant_matches))
+        bottom_lines = concat(get_marked_lines(m, ParameterFlag.bottom, self) for m, _ in
+                              reversed(relevant_matches_and_values))
 
         # now, concat all lines, while reversing the matches
         #   reverse because elements closer to the end of search path take precedence
-        all_lines = concat(m.value(self) for m in reversed(relevant_matches))
+        all_lines = concat(v for _, v in reversed(relevant_matches_and_values))
 
         # stack top_lines + all_lines, then de-dupe
         top_deduped = tuple(unique(concatv(top_lines, all_lines)))


Test patch:
diff --git a/tests/common/test_configuration.py b/tests/common/test_configuration.py
--- a/tests/common/test_configuration.py
+++ b/tests/common/test_configuration.py
@@ -432,3 +432,9 @@ def test_invalid_map_parameter(self):
         config = SampleConfiguration()._set_raw_data(data)
         with raises(InvalidTypeError):
             config.proxy_servers
+
+    def test_invalid_seq_parameter(self):
+        data = odict(s1=YamlRawParameter.make_raw_parameters('s1', {'channels': 'y_u_no_tuple'}))
+        config = SampleConfiguration()._set_raw_data(data)
+        with raises(InvalidTypeError):
+            config.channels