Repository: pallets/werkzeug
Pull request #2357 (2022-03-25 21:22:58)

deprecate `parse_options_header` `multiple` parameter
This parameter does not appear to be used anywhere. The two tests for it seem to be better served by `parse_accept_header`. It complicates the implementation and type signature.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b1cee54c..f34ec9ba 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -47,6 +47,8 @@ Unreleased
     -   Remove ``format_string``, use :class:`string.Template` instead.
     -   Remove ``escape`` and ``unescape``. Use MarkupSafe instead.
 
+-   The ``multiple`` parameter of ``parse_options_header`` is
+    deprecated. :pr:`2357`
 -   Rely on :pep:`538` and :pep:`540` to handle decoding file names
     with the correct filesystem encoding. The ``filesystem`` module is
     removed. :issue:`1760`
diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py
index cf1ed164..93699001 100644
--- a/src/werkzeug/http.py
+++ b/src/werkzeug/http.py
@@ -376,43 +376,39 @@ def parse_dict_header(value: str, cls: t.Type[dict] = dict) -> t.Dict[str, str]:
     return result
 
 
-@typing.overload
 def parse_options_header(
-    value: t.Optional[str], multiple: "te.Literal[False]" = False
+    value: t.Optional[str], multiple: "te.Literal[None]" = None
 ) -> t.Tuple[str, t.Dict[str, str]]:
-    ...
-
-
-@typing.overload
-def parse_options_header(
-    value: t.Optional[str], multiple: "te.Literal[True]"
-) -> t.Tuple[t.Any, ...]:
-    ...
-
-
-def parse_options_header(
-    value: t.Optional[str], multiple: bool = False
-) -> t.Union[t.Tuple[str, t.Dict[str, str]], t.Tuple[t.Any, ...]]:
-    """Parse a ``Content-Type`` like header into a tuple with the content
-    type and the options:
+    """Parse a ``Content-Type``-like header into a tuple with the
+    value and any options:
 
     >>> parse_options_header('text/html; charset=utf8')
     ('text/html', {'charset': 'utf8'})
 
-    This should not be used to parse ``Cache-Control`` like headers that use
-    a slightly different format.  For these headers use the
-    :func:`parse_dict_header` function.
+    This should is not for ``Cache-Control``-like headers, which use a
+    different format. For those, use :func:`parse_dict_header`.
+
+    :param value: The header value to parse.
+
+    .. versionchanged:: 2.1
+        The ``multiple`` parameter is deprecated and will be removed in
+        Werkzeug 2.2.
 
     .. versionchanged:: 0.15
         :rfc:`2231` parameter continuations are handled.
 
     .. versionadded:: 0.5
-
-    :param value: the header to parse.
-    :param multiple: Whether try to parse and return multiple MIME types
-    :return: (mimetype, options) or (mimetype, options, mimetype, options, …)
-             if multiple=True
     """
+    if multiple is not None:
+        import warnings
+
+        warnings.warn(
+            "The 'multiple' parameter of 'parse_options_header' is"
+            " deprecated and will be removed in Werkzeug 2.2.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
     if not value:
         return "", {}
 
@@ -463,11 +459,11 @@ def parse_options_header(
 
             rest = rest[optmatch.end() :]
         result.append(options)
-        if multiple is False:
-            return tuple(result)
+        if not multiple:
+            return tuple(result)  # type: ignore[return-value]
         value = rest
 
-    return tuple(result) if result else ("", {})
+    return tuple(result) if result else ("", {})  # type: ignore[return-value]
 
 
 _TAnyAccept = t.TypeVar("_TAnyAccept", bound="ds.Accept")


Test patch:
diff --git a/tests/test_http.py b/tests/test_http.py
index 853df547..dfa6dd11 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -301,22 +301,27 @@ class TestHTTPUtility:
             "audio/*",
             {"q": "0.2"},
         )
-        assert http.parse_options_header(
-            "audio/*; q=0.2, audio/basic", multiple=True
-        ) == ("audio/*", {"q": "0.2"}, "audio/basic", {})
-        assert http.parse_options_header(
-            "text/plain; q=0.5, text/html\n        text/x-dvi; q=0.8, text/x-c",
-            multiple=True,
-        ) == (
-            "text/plain",
-            {"q": "0.5"},
-            "text/html",
-            {},
-            "text/x-dvi",
-            {"q": "0.8"},
-            "text/x-c",
-            {},
-        )
+
+        with pytest.warns(DeprecationWarning):
+            assert http.parse_options_header(
+                "audio/*; q=0.2, audio/basic", multiple=True
+            ) == ("audio/*", {"q": "0.2"}, "audio/basic", {})
+
+        with pytest.warns(DeprecationWarning):
+            assert http.parse_options_header(
+                "text/plain; q=0.5, text/html\n        text/x-dvi; q=0.8, text/x-c",
+                multiple=True,
+            ) == (
+                "text/plain",
+                {"q": "0.5"},
+                "text/html",
+                {},
+                "text/x-dvi",
+                {"q": "0.8"},
+                "text/x-c",
+                {},
+            )
+
         assert http.parse_options_header(
             "text/plain; q=0.5, text/html\n        text/x-dvi; q=0.8, text/x-c"
         ) == ("text/plain", {"q": "0.5"})