Skip to content

Commit 6bf8212

Browse files
committed
don't filter or collapse lists when building urls
1 parent 8431de6 commit 6bf8212

File tree

3 files changed

+88
-45
lines changed

3 files changed

+88
-45
lines changed

CHANGES.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ Unreleased
5656
convert the value to an int. :issue:`2230`
5757
- Always use ``socket.fromfd`` when restarting the dev server.
5858
:pr:`2287`
59-
- The behavior of the ``build`` function has been changed,
60-
it is no longer flatten a value passed as single-item-list.
61-
:issue:`2249`
59+
- When passing a dict of URL values to ``Map.build``, list values do
60+
not filter out ``None`` or collapse to a single value. Passing a
61+
``MultiDict`` still has these behaviors. This undoes a previous
62+
change that made it difficult to pass a list, or ``None`` values in
63+
a list, to custom URL converters. :issue:`2249`
6264

6365

6466
Version 2.0.3

src/werkzeug/routing.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -545,10 +545,10 @@ def _prefix_names(src: str) -> ast.stmt:
545545
_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()"
546546
_IF_KWARGS_URL_ENCODE_CODE = """\
547547
if kwargs:
548-
q = '?'
549548
params = self._encode_query_vars(kwargs)
549+
q = "?" if params else ""
550550
else:
551-
q = params = ''
551+
q = params = ""
552552
"""
553553
_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE)
554554
_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params"))
@@ -2283,30 +2283,14 @@ def build(
22832283
self.map.update()
22842284

22852285
if values:
2286-
temp_values: t.Dict[str, t.Union[t.List[t.Any], t.Any]] = {}
2287-
always_list = isinstance(values, MultiDict)
2288-
key: str
2289-
value: t.Optional[t.Union[t.List[t.Any], t.Any]]
2290-
2291-
# For MultiDict, dict.items(values) is like values.lists()
2292-
# without the call or list coercion overhead.
2293-
for key, value in dict.items(values): # type: ignore
2294-
if value is None:
2295-
continue
2296-
2297-
if always_list or isinstance(value, (list, tuple)):
2298-
value = [v for v in value if v is not None]
2299-
2300-
if not value:
2301-
continue
2302-
2303-
if len(value) == 1:
2304-
if always_list:
2305-
value = value[0]
2306-
2307-
temp_values[key] = value
2308-
2309-
values = temp_values
2286+
if isinstance(values, MultiDict):
2287+
values = {
2288+
k: (v[0] if len(v) == 1 else v)
2289+
for k, v in dict.items(values)
2290+
if len(v) != 0
2291+
}
2292+
else: # plain dict
2293+
values = {k: v for k, v in values.items() if v is not None}
23102294
else:
23112295
values = {}
23122296

tests/test_routing.py

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import gc
2+
import typing as t
23
import uuid
34

45
import pytest
@@ -753,31 +754,87 @@ def test_anyconverter():
753754
assert a.match("/a.2") == ("yes_dot", {"a": "a.2"})
754755

755756

757+
@pytest.mark.parametrize(
758+
("endpoint", "value", "expect"),
759+
[
760+
("int", 1, "/1"),
761+
("int", None, r.BuildError),
762+
("int", [1], TypeError),
763+
("list", [1], "/1"),
764+
("list", [1, None, 2], "/1.None.2"),
765+
("list", 1, TypeError),
766+
],
767+
)
768+
def test_build_values_dict(endpoint, value, expect):
769+
class ListConverter(r.BaseConverter):
770+
def to_url(self, value: t.Any) -> str:
771+
return super().to_url(".".join(map(str, value)))
772+
773+
url_map = r.Map(
774+
[r.Rule("/<int:v>", endpoint="int"), r.Rule("/<list:v>", endpoint="list")],
775+
converters={"list": ListConverter},
776+
)
777+
adapter = url_map.bind("localhost")
778+
779+
if isinstance(expect, str):
780+
assert adapter.build(endpoint, {"v": value}) == expect
781+
else:
782+
with pytest.raises(expect):
783+
adapter.build(endpoint, {"v": value})
784+
785+
786+
@pytest.mark.parametrize(
787+
("endpoint", "value", "expect"),
788+
[
789+
("int", 1, "/1"),
790+
("int", [1], "/1"),
791+
("int", [], r.BuildError),
792+
("int", None, TypeError),
793+
("int", [None], TypeError),
794+
("list", 1, TypeError),
795+
("list", [1], TypeError),
796+
("list", [[1]], "/1"),
797+
("list", [1, None, 2], "/1.None.2"),
798+
],
799+
)
800+
def test_build_values_multidict(endpoint, value, expect):
801+
class ListConverter(r.BaseConverter):
802+
def to_url(self, value: t.Any) -> str:
803+
return super().to_url(".".join(map(str, value)))
804+
805+
url_map = r.Map(
806+
[r.Rule("/<int:v>", endpoint="int"), r.Rule("/<list:v>", endpoint="list")],
807+
converters={"list": ListConverter},
808+
)
809+
adapter = url_map.bind("localhost")
810+
811+
if isinstance(expect, str):
812+
assert adapter.build(endpoint, MultiDict({"v": value})) == expect
813+
else:
814+
with pytest.raises(expect):
815+
adapter.build(endpoint, MultiDict({"v": value}))
816+
817+
756818
@pytest.mark.parametrize(
757819
("value", "expect"),
758820
[
759821
(None, ""),
760822
([None], ""),
761823
([None, None], ""),
762-
("", "?extra="),
763-
([""], "?extra="),
764-
(1.0, "?extra=1.0"),
765-
([1, 2], "?extra=1&extra=2"),
766-
([1, None, 2], "?extra=1&extra=2"),
767-
([1, "", 2], "?extra=1&extra=&extra=2"),
824+
("", "?v="),
825+
([""], "?v="),
826+
(0, "?v=0"),
827+
(1.0, "?v=1.0"),
828+
([1, 2], "?v=1&v=2"),
829+
([1, None, 2], "?v=1&v=2"),
830+
([1, "", 2], "?v=1&v=&v=2"),
768831
],
769832
)
770833
def test_build_append_unknown(value, expect):
771-
map = r.Map([r.Rule("/foo/<name>", endpoint="foo")])
772-
adapter = map.bind("example.org", "/", subdomain="test")
773-
assert (
774-
adapter.build("foo", {"name": "bar", "extra": value})
775-
== f"http://example.org/foo/bar{expect}"
776-
)
777-
assert (
778-
adapter.build("foo", {"name": "bar", "extra": value}, append_unknown=False)
779-
== "http://example.org/foo/bar"
780-
)
834+
map = r.Map([r.Rule("/", endpoint="a")])
835+
adapter = map.bind("localhost")
836+
assert adapter.build("a", {"v": value}) == f"/{expect}"
837+
assert adapter.build("a", {"v": value}, append_unknown=False) == "/"
781838

782839

783840
def test_build_append_multiple():

0 commit comments

Comments
 (0)