Skip to content

Commit ea93b54

Browse files
authored
restrict containers accepted by multi (#2995)
2 parents 1a1728e + 598bb1d commit ea93b54

File tree

4 files changed

+50
-28
lines changed

4 files changed

+50
-28
lines changed

CHANGES.rst

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Version 3.1.3
55

66
Unreleased
77

8+
- Initial data passed to ``MultiDict`` and similar interfaces only accepts
9+
``list``, ``tuple``, or ``set`` when passing multiple values. It had been
10+
changed to accept any ``Collection``, but this matched types that should be
11+
treated as single values, such as ``bytes``. :issue:`2994`
12+
813

914
Version 3.1.2
1015
-------------

src/werkzeug/datastructures/headers.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262
defaults: (
6363
Headers
6464
| MultiDict[str, t.Any]
65-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
65+
| cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | set[t.Any]]
6666
| cabc.Iterable[tuple[str, t.Any]]
6767
| None
6868
) = None,
@@ -227,7 +227,7 @@ def extend(
227227
arg: (
228228
Headers
229229
| MultiDict[str, t.Any]
230-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
230+
| cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | set[t.Any]]
231231
| cabc.Iterable[tuple[str, t.Any]]
232232
| None
233233
) = None,
@@ -491,12 +491,14 @@ def update(
491491
arg: (
492492
Headers
493493
| MultiDict[str, t.Any]
494-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
494+
| cabc.Mapping[
495+
str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]
496+
]
495497
| cabc.Iterable[tuple[str, t.Any]]
496498
| None
497499
) = None,
498500
/,
499-
**kwargs: t.Any | cabc.Collection[t.Any],
501+
**kwargs: t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any],
500502
) -> None:
501503
"""Replace headers in this object with items from another
502504
headers object and keyword arguments.
@@ -516,9 +518,7 @@ def update(
516518
self.setlist(key, arg.getlist(key))
517519
elif isinstance(arg, cabc.Mapping):
518520
for key, value in arg.items():
519-
if isinstance(value, cabc.Collection) and not isinstance(
520-
value, str
521-
):
521+
if isinstance(value, (list, tuple, set)):
522522
self.setlist(key, value)
523523
else:
524524
self.set(key, value)
@@ -527,13 +527,16 @@ def update(
527527
self.set(key, value)
528528

529529
for key, value in kwargs.items():
530-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
530+
if isinstance(value, (list, tuple, set)):
531531
self.setlist(key, value)
532532
else:
533533
self.set(key, value)
534534

535535
def __or__(
536-
self, other: cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
536+
self,
537+
other: cabc.Mapping[
538+
str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]
539+
],
537540
) -> te.Self:
538541
if not isinstance(other, cabc.Mapping):
539542
return NotImplemented
@@ -545,13 +548,11 @@ def __or__(
545548
def __ior__(
546549
self,
547550
other: (
548-
cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
551+
cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]]
549552
| cabc.Iterable[tuple[str, t.Any]]
550553
),
551554
) -> te.Self:
552-
if not isinstance(other, (cabc.Mapping, cabc.Iterable)) or isinstance(
553-
other, str
554-
):
555+
if not isinstance(other, (cabc.Mapping, cabc.Iterable)):
555556
return NotImplemented
556557

557558
self.update(other)

src/werkzeug/datastructures/structures.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
def iter_multi_items(
2323
mapping: (
2424
MultiDict[K, V]
25-
| cabc.Mapping[K, V | cabc.Collection[V]]
25+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
2626
| cabc.Iterable[tuple[K, V]]
2727
),
2828
) -> cabc.Iterator[tuple[K, V]]:
@@ -33,11 +33,11 @@ def iter_multi_items(
3333
yield from mapping.items(multi=True)
3434
elif isinstance(mapping, cabc.Mapping):
3535
for key, value in mapping.items():
36-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
36+
if isinstance(value, (list, tuple, set)):
3737
for v in value:
3838
yield key, v
3939
else:
40-
yield key, value # type: ignore[misc]
40+
yield key, value
4141
else:
4242
yield from mapping
4343

@@ -182,7 +182,7 @@ def __init__(
182182
self,
183183
mapping: (
184184
MultiDict[K, V]
185-
| cabc.Mapping[K, V | cabc.Collection[V]]
185+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
186186
| cabc.Iterable[tuple[K, V]]
187187
| None
188188
) = None,
@@ -194,7 +194,7 @@ def __init__(
194194
elif isinstance(mapping, cabc.Mapping):
195195
tmp = {}
196196
for key, value in mapping.items():
197-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
197+
if isinstance(value, (list, tuple, set)):
198198
value = list(value)
199199

200200
if not value:
@@ -419,7 +419,7 @@ def update( # type: ignore[override]
419419
self,
420420
mapping: (
421421
MultiDict[K, V]
422-
| cabc.Mapping[K, V | cabc.Collection[V]]
422+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
423423
| cabc.Iterable[tuple[K, V]]
424424
),
425425
) -> None:
@@ -444,7 +444,7 @@ def update( # type: ignore[override]
444444
self.add(key, value)
445445

446446
def __or__( # type: ignore[override]
447-
self, other: cabc.Mapping[K, V | cabc.Collection[V]]
447+
self, other: cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
448448
) -> MultiDict[K, V]:
449449
if not isinstance(other, cabc.Mapping):
450450
return NotImplemented
@@ -455,11 +455,12 @@ def __or__( # type: ignore[override]
455455

456456
def __ior__( # type: ignore[override]
457457
self,
458-
other: cabc.Mapping[K, V | cabc.Collection[V]] | cabc.Iterable[tuple[K, V]],
458+
other: (
459+
cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
460+
| cabc.Iterable[tuple[K, V]]
461+
),
459462
) -> te.Self:
460-
if not isinstance(other, (cabc.Mapping, cabc.Iterable)) or isinstance(
461-
other, str
462-
):
463+
if not isinstance(other, (cabc.Mapping, cabc.Iterable)):
463464
return NotImplemented
464465

465466
self.update(other)
@@ -600,7 +601,7 @@ def __init__(
600601
self,
601602
mapping: (
602603
MultiDict[K, V]
603-
| cabc.Mapping[K, V | cabc.Collection[V]]
604+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
604605
| cabc.Iterable[tuple[K, V]]
605606
| None
606607
) = None,
@@ -744,7 +745,7 @@ def update( # type: ignore[override]
744745
self,
745746
mapping: (
746747
MultiDict[K, V]
747-
| cabc.Mapping[K, V | cabc.Collection[V]]
748+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
748749
| cabc.Iterable[tuple[K, V]]
749750
),
750751
) -> None:
@@ -1009,7 +1010,7 @@ def __init__(
10091010
self,
10101011
mapping: (
10111012
MultiDict[K, V]
1012-
| cabc.Mapping[K, V | cabc.Collection[V]]
1013+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
10131014
| cabc.Iterable[tuple[K, V]]
10141015
| None
10151016
) = None,

tests/test_datastructures.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from __future__ import annotations
2+
13
import io
24
import pickle
35
import tempfile
6+
import typing as t
47
from contextlib import contextmanager
58
from copy import copy
69
from copy import deepcopy
@@ -43,7 +46,7 @@ def items(self, multi=1):
4346

4447

4548
class _MutableMultiDictTests:
46-
storage_class: type["ds.MultiDict"]
49+
storage_class: type[ds.MultiDict]
4750

4851
def test_pickle(self):
4952
cls = self.storage_class
@@ -1280,3 +1283,15 @@ def test_range_to_header(ranges):
12801283
def test_range_validates_ranges(ranges):
12811284
with pytest.raises(ValueError):
12821285
ds.Range("bytes", ranges)
1286+
1287+
1288+
@pytest.mark.parametrize(
1289+
("value", "expect"),
1290+
[
1291+
({"a": "ab"}, [("a", "ab")]),
1292+
({"a": ["a", "b"]}, [("a", "a"), ("a", "b")]),
1293+
({"a": b"ab"}, [("a", b"ab")]),
1294+
],
1295+
)
1296+
def test_iter_multi_data(value: t.Any, expect: list[tuple[t.Any, t.Any]]) -> None:
1297+
assert list(ds.iter_multi_items(value)) == expect

0 commit comments

Comments
 (0)