Repository: pallets/werkzeug

Issue:
`url_for` with a tuple does not work!
Code example:

``` python
#...
@blueprint.route('/<value:val>/')
def settings(value):
#...

#...
class SettingsConverter(BaseConverter):
    def to_url(self, value):
        return '%s/%s' % (value[0], value[1])
#...

value = ('username', 'project1')
url = url_for('.settings', val=value)
# url will be "u/s" instad of "username/project1"
```

At some point we are loosing the second element of the **tuple** and I'm pretty sure it happens here: https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/routing.py#L1712-L1716
## Versions
- python 2.7
- werkzeug 0.10

Maybe @tswicegood can provide more information.



Discussion:
The example above leaves out the `to_python()` code that can get to this state.  I've posted [a gist](https://gist.github.com/tswicegood/be35921386651103337e) with this example.  This relies on Flask, but swapping out the Werkzeug underneath triggers the error.  To test, run the example and load `http://localhost:5000/foo/bar/`.  As of 0.9.6 (last I had handy), it returns `/foo/bar/`, but as of 0.10.1 it returns `/f/b/`.

Err -- sorry, the error state is `/f/o/`, which is even worse as my second value is completely gone.

All of this has been caused by #660, looking into it.

Well, this is quite an awkward situation. As noted [here](https://github.com/mitsuhiko/werkzeug/issues/658#issuecomment-72022911), lists can be (intentionally) used for multiple query values.

I guess what happened then is that, since we switched to MultiDict, both lists and tuples are now accepted as a notation for multiple values (since MultiDict behaves like that).

I think I'll just reverse #660.

Well, at least I'm not entirely crazy.  I ended up on that same test, but I wasn't 100% sure on it.  It seems like the behavior of #660 is good if there's a way to keep it around.  There is a work around for getting the correct URL, it's `url_for("handler", foo=[foo])`.

Is there a situation where you might ever hand a non-`MultiDict` object that has multiple values where you want those treated like a dictionary?

> Is there a situation where you might ever hand a non-MultiDict object that has multiple values where you want those treated like a dictionary?

You mean literally using a dictionary as a value (`url_for(..., foo=dict(...))`)?


Resolving patch:
diff --git a/werkzeug/routing.py b/werkzeug/routing.py
--- a/werkzeug/routing.py
+++ b/werkzeug/routing.py
@@ -1634,6 +1634,13 @@ def build(self, endpoint, values=None, method=None, force_external=False,
         >>> urls.build("index", {'q': 'My Searchstring'})
         '/?q=My+Searchstring'
 
+        When processing those additional values, lists are furthermore
+        interpreted as multiple values (as per
+        :py:class:`werkzeug.datastructures.MultiDict`):
+
+        >>> urls.build("index", {'q': ['a', 'b', 'c']})
+        '/?q=a&q=b&q=c'
+
         If a rule does not exist when building a `BuildError` exception is
         raised.
 
@@ -1658,10 +1665,11 @@ def build(self, endpoint, values=None, method=None, force_external=False,
         """
         self.map.update()
         if values:
-            if not isinstance(values, MultiDict):
-                values = MultiDict(values)
-            valueiter = iteritems(values, multi=True)
-            values = MultiDict((k, v) for k, v in valueiter if v is not None)
+            if isinstance(values, MultiDict):
+                valueiter = iteritems(values, multi=True)
+            else:
+                valueiter = iteritems(values)
+            values = dict((k, v) for k, v in valueiter if v is not None)
         else:
             values = {}
 


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -418,6 +418,29 @@ def test_uuid_converter():
     rooute, kwargs =  a.match('/a/a8098c1a-f86e-11da-bd1a-00112444be1e')
     assert type(kwargs['a_uuid']) == uuid.UUID
 
+def test_converter_with_tuples():
+    '''
+    Regression test for https://github.com/mitsuhiko/werkzeug/issues/709
+    '''
+    class TwoValueConverter(r.BaseConverter):
+        def __init__(self, *args, **kwargs):
+            super(TwoValueConverter, self).__init__(*args, **kwargs)
+            self.regex = r'(\w\w+)/(\w\w+)'
+
+        def to_python(self, two_values):
+            one, two = two_values.split('/')
+            return one, two
+
+        def to_url(self, values):
+            return "%s/%s" % (values[0], values[1])
+
+    map = r.Map([
+        r.Rule('/<two:foo>/', endpoint='handler')
+    ], converters={'two': TwoValueConverter})
+    a = map.bind('example.org', '/')
+    route, kwargs = a.match('/qwert/yuiop/')
+    assert kwargs['foo'] == ('qwert', 'yuiop')
+
 def test_build_append_unknown():
     map = r.Map([
         r.Rule('/bar/<float:bazf>', endpoint='barf')
@@ -428,15 +451,12 @@ def test_build_append_unknown():
     assert adapter.build('barf', {'bazf': 0.815, 'bif' : 1.0},
         append_unknown=False) == 'http://example.org/bar/0.815'
 
-@pytest.mark.parametrize('params', [
-    MultiDict((('bazf', 0.815), ('bif', 1.0), ('pof', 2.0), ('bif', 3.0))),
-    {'bazf': 0.815, 'bif': [1.0, 3.0], 'pof': 2.0}
-])
-def test_build_append_multiple(params):
+def test_build_append_multiple():
     map = r.Map([
         r.Rule('/bar/<float:bazf>', endpoint='barf')
     ])
     adapter = map.bind('example.org', '/', subdomain='blah')
+    params = {'bazf': 0.815, 'bif': [1.0, 3.0], 'pof': 2.0}
     a, b = adapter.build('barf', params).split('?')
     assert a == 'http://example.org/bar/0.815'
     assert set(b.split('&')) == set('pof=2.0&bif=1.0&bif=3.0'.split('&'))