url_for / redirect_to RAW mode

16 views
Skip to first unread message

Tycon

unread,
Jan 4, 2009, 5:29:27 PM1/4/09
to pylons-discuss
from the "url_for" doc string :

If no route by that name is found, the string is assumed to be a
raw URL.
Should the raw URL begin with ``/`` then appropriate SCRIPT_NAME
data will
be added if present, otherwise the string will be used as the url
with
keyword args becoming GET query args.

But what if I don't want to SCRIPT_NAME to be prefixed to the URL
because it's a link for a different application ? For example if
pylons app has SCRIPT_NAME of "/pylons" but I want to create a URL for
"/blog/newposts" which is served an a different blog application (so
it should not be converted to /pylons/blog/newposts) ?

Normally I wouldn't need to call url_for in this case, and just use
the external URL directly, but
if I need to do a redirect using pylons redirect_to it will
internally call url_for and add the SCRIPT_NAME
prefix.

Tycon

unread,
Jan 5, 2009, 12:06:58 AM1/5/09
to pylons-discuss
Another issue with url_for is the handling of query string parameters
using keyword arguments. Currently any extra keyword arguments will be
added to the generated URL as query string, for example:

url_for('/page', arg='val') ==> /page?arg=val

The problem is that you can't use keyword arguments for any of the
argument names reserved by the function, such as "host", "protocol",
etc, for example:

params = { 'arg': 'val', 'host': 'machine' }

url_for('/page', **params)

should return:

/page?arg=val&host=machine

but it treats the "host" key in params as the host name so it actually
returns:

http://machine/page?arg=val

Mark Ramm

unread,
Jan 5, 2009, 9:16:11 AM1/5/09
to pylons-...@googlegroups.com
> Another issue with url_for is the handling of query string parameters
> using keyword arguments. Currently any extra keyword arguments will be
> added to the generated URL as query string, for example:
>
> url_for('/page', arg='val') ==> /page?arg=val
>
> The problem is that you can't use keyword arguments for any of the
> argument names reserved by the function, such as "host", "protocol",
> etc, for example:
>
> params = { 'arg': 'val', 'host': 'machine' }
>
> url_for('/page', **params)
>
> should return:
>
> /page?arg=val&host=machine
>
> but it treats the "host" key in params as the host name so it actually
> returns:
>
> http://machine/page?arg=val

In TG1 we allowed you to either pass kwargs for extra url parmeters or
to pass a params dictionary as a keyword argument, or as the second
positional parameter, so that got around the reserved keyword
arguments problem. Perhaps routes could do something similar...

Mark Ramm

unread,
Jan 5, 2009, 9:18:24 AM1/5/09
to pylons-...@googlegroups.com
> But what if I don't want to SCRIPT_NAME to be prefixed to the URL
> because it's a link for a different application ? For example if
> pylons app has SCRIPT_NAME of "/pylons" but I want to create a URL for
> "/blog/newposts" which is served an a different blog application (so
> it should not be converted to /pylons/blog/newposts) ?

Well, the current method is to pass in
"http://mydomain.com/blog/newposts" which will generate the URL you
want.

--Mark

Ben Bangert

unread,
Jan 5, 2009, 12:54:14 PM1/5/09
to pylons-...@googlegroups.com

Right, this is why Pylons will have a redirect() function that does
not pass the args through redirect_to. It's included in the latest
Pylons 0.9.7 tip code (soon to be released as final), but isn't yet
used as the default as we wanted the book to be accurate for at least
one released version of Pylons. :)

Cheers,
Ben

Ben Bangert

unread,
Jan 5, 2009, 12:56:25 PM1/5/09
to pylons-...@googlegroups.com
On Jan 4, 2009, at 9:06 PM, Tycon wrote:

> Another issue with url_for is the handling of query string parameters
> using keyword arguments. Currently any extra keyword arguments will be
> added to the generated URL as query string, for example:
>
> url_for('/page', arg='val') ==> /page?arg=val
>
> The problem is that you can't use keyword arguments for any of the
> argument names reserved by the function, such as "host", "protocol",
> etc, for example:
>
> params = { 'arg': 'val', 'host': 'machine' }
>
> url_for('/page', **params)
>
> should return:
>
> /page?arg=val&host=machine
>
> but it treats the "host" key in params as the host name so it actually
> returns:

Right, the alternative we thought of was forcing all the keyword args
destined for Routes to be in a dict passed in, but that obviously
makes it a little more annoying to use, though it keeps the arguments
separate from the ones url_for itself uses. However, I could see
having it strip off arg's with a trailing underscore for use by
Routes, such that you'd be able to do:
url_for('/page', host_='machine') -> /page?host=machine

Would that work?

Cheers,
Ben

Tycon

unread,
Jan 5, 2009, 1:57:28 PM1/5/09
to pylons-discuss

I think we need to have three new arguments to url_for:

params (dict) - used to create the query string (while deprecating the
keyword args method, just like the comment about turbogears
mentioned).
raw (boolean) - if true then the url is presumed to raw so we don't
try to do a routing map match.
external (boolean) - if true then don't add the SCRIPT_NAME as prefix.

With the above options, you don't need to create a new "redirect"
call, so users can use the current "redirect_to" (which would pass
these args to url_for) and specify options such as raw, external or a
params hash.
>  smime.p7s
> 3KViewDownload

Wyatt Baldwin

unread,
Jan 5, 2009, 2:13:37 PM1/5/09
to pylons-discuss
Personally, I don't like the trailing underscore idea all that much
(it might be hard to spot and its meaning isn't obvious). I don't have
a better suggestion right atm though...

Actually, I think I like the idea of making the query params into a
single argument. It could be a string like a=1&b=2 or a dict. I don't
think url_for('/wherever', host='pants.com', params=dict(a=1, b=2)) is
too annoying. It's only a few more characters more than the current
API but removes all ambiguity.

Too maintain backward compatibility, if ``params`` is present as a kw
arg in the url_for call, url_for would send only those query params,
consuming the remaining kw args itself. Otherwise, if ``params`` isn't
present, the old behaviour would apply.

Note: This is off the top of my head. I haven't reviewed the source/
API extensively too see how feasible this idea really is or to make
sure I covered all the bases.

Mike Orr

unread,
Jan 5, 2009, 2:57:04 PM1/5/09
to pylons-...@googlegroups.com

The trailing undercore syntax is already implemented for dealing with
Python reserved words.

$ paster shell development.ini
>>> from routes import url_for
>>> from pylons import url
>>> url_for("ABC", print_=1)
'ABC?print_=1'
>>> url("ABC", print_=1)
'ABC?print_=1'
>>> url_for("ABC", host_="ab")
'ABC?host=ab'
>>> url("ABC", host_="ab")
'ABC?host=ab'
>>> url_for("ABC", host="example.com")
'http://example.comABC'
>>> url("ABC", host="example.com")
'http://example.comABC'
>>> url_for("ABC", host="example.com/")
'http://example.com/ABC'
>>> url("ABC", host="example.com/")
'http://example.com/ABC'

The last one is a bit surprising, that the 'host' argument needs a
trailing slash, but otherwise it seems to work fine and just needs to
be better documented.


> params (dict) - used to create the query string

That is an interesting idea. I'm not sure if I favor it or not. It
would mean I'd have to change an application I'm about to put into
production. But more importantly, the url_for tradition has been that
arguments not matching path variables are turned into query
parameters. If an expected variable is missing, that makes it obvious
what the problem is if you know what to look for. On the other hand,
I suppose an exception would make it even more obvious.

Part of the tentative plan for Routes 2 is to turn the extra variables
in route definitions into a dict, for the same reason as is suggested
here. I tried it and ended up being lukewarm to the idea. (Do I
really want every route to have 'map.connect("name", "/path",
{"controller": "foo", "action": "fooaction"}'?) On the other hand,
eliminating ambiguity is an important goal.

> Actually, I think I like the idea of making the query params into a
> single argument. It could be a string like a=1&b=2 or a dict.

No! Encouraging the user to do their own interpolation and escaping
would be a step back into the Dark Ages.
It's like the old rails select() helper which took an HTML snippet of
options rather than a list.

--
Mike Orr <slugg...@gmail.com>

Wyatt Baldwin

unread,
Jan 5, 2009, 3:11:24 PM1/5/09
to pylons-discuss
On Jan 5, 11:57 am, "Mike Orr" <sluggos...@gmail.com> wrote:
> On Mon, Jan 5, 2009 at 11:13 AM, Wyatt Baldwin
>
> [snip]
>
> > Actually, I think I like the idea of making the query params into a
> > single argument. It could be a string like a=1&b=2 or a dict.
>
> No!  Encouraging the user to do their own interpolation and escaping
> would be a step back into the Dark Ages.

Sorry if I'm being daft, but you're only vehemently (!) opposing
``params`` as a *string*, correct?

I see your point there, and I can't think of a case where I'd need or
want to use a str instead of a dict--except maybe where the query
string came from elsewhere, but that seems like a fairly unusual case.

Tycon

unread,
Jan 5, 2009, 3:18:10 PM1/5/09
to pylons-discuss
and users can easily concatenate the query string to the url by
themselves, so not much point to allow that option. I like the params
dict suggestion.

Tycon

unread,
Jan 5, 2009, 3:35:32 PM1/5/09
to pylons-discuss
And I dont like the trailing underscore idea, because it would be a
mess to deal with in most common use cases like:

params = dict(request.params)
url = url_for('/page', **params)

With the params dict suggestion this will be written into

url = url_for('/page', params)

But with trailing underscore you would have to do some ugly (and
expensive) manipulation:

for key in params.keys():
new_key = key + '_'
params[new_key] = params[key]
del params[key]

url = url_for('/page', **params)

That is horrible

Mike Orr

unread,
Jan 5, 2009, 4:07:09 PM1/5/09
to pylons-...@googlegroups.com
On Mon, Jan 5, 2009 at 12:35 PM, Tycon <adi...@gmail.com> wrote:
>
> And I dont like the trailing underscore idea, because it would be a
> mess to deal with in most common use cases like:
>
> params = dict(request.params)
> url = url_for('/page', **params)
>
> With the params dict suggestion this will be written into
>
> url = url_for('/page', params)
>
> But with trailing underscore you would have to do some ugly (and
> expensive) manipulation:

Ah, that's a good point. (Although it has been this way for years and
nobody has complained about it till now, so I guess using 'host' as a
bona fide parameter is not very common.)

So what about having a 'params' argument but keeping the current
behavior if 'params' is not set. That would allow backward
compatibility.

Then if 'params' is set and another argument doesn't correspond to any
path variable, I guess you'd raise an error.

Wyatt wrote wrote:
> > > Actually, I think I like the idea of making the query params into a
> > > single argument. It could be a string like a=1&b=2 or a dict.

Mike wrote:
> > No! Encouraging the user to do their own interpolation and escaping
> > would be a step back into the Dark Ages.

Wyatt wrote:
> Sorry if I'm being daft, but you're only vehemently (!) opposing
> ``params`` as a *string*, correct?

Correct.

Wyatt wrote:
> I see your point there, and I can't think of a case where I'd need or
want to use a str instead of a dict--except maybe where the query
string came from elsewhere, but that seems like a fairly unusual case.

Almost always they will come from request.params, which has already
parsed them into a dict. Perhaps reading a log file or something.
But in general, the parsing to a dict and back is a feature of any
good framework, even if it does add some overhead. But that kind of
overhead is why you're using a framework in the first place.

--
Mike Orr <slugg...@gmail.com>

Mike Orr

unread,
Jan 5, 2009, 4:22:09 PM1/5/09
to pylons-...@googlegroups.com
On Mon, Jan 5, 2009 at 1:07 PM, Mike Orr <slugg...@gmail.com> wrote:
> On Mon, Jan 5, 2009 at 12:35 PM, Tycon <adi...@gmail.com> wrote:
>>
>> And I dont like the trailing underscore idea, because it would be a
>> mess to deal with in most common use cases like:
>>
>> params = dict(request.params)
>> url = url_for('/page', **params)
>>
>> With the params dict suggestion this will be written into
>>
>> url = url_for('/page', params)
>>
>> But with trailing underscore you would have to do some ugly (and
>> expensive) manipulation:
>
> Ah, that's a good point. (Although it has been this way for years and
> nobody has complained about it till now, so I guess using 'host' as a
> bona fide parameter is not very common.)
>
> So what about having a 'params' argument but keeping the current
> behavior if 'params' is not set. That would allow backward
> compatibility.
>
> Then if 'params' is set and another argument doesn't correspond to any
> path variable, I guess you'd raise an error.

Created Routes ticket #88 for this.
http://routes.groovie.org/trac/routes/ticket/88

--
Mike Orr <slugg...@gmail.com>

Tycon

unread,
Jan 5, 2009, 4:56:47 PM1/5/09
to pylons-discuss
right and don't forget about adding the new "raw" (to avoid mapper
lookup if true) and "external" (to avoid adding SCRIPT_NAME prefix if
true) arguments, in addition to the "params" dict, so you will have a
much better url_for

On Jan 5, 1:07 pm, "Mike Orr" <sluggos...@gmail.com> wrote:
> Mike Orr <sluggos...@gmail.com>

Mark Ramm

unread,
Jan 5, 2009, 8:57:16 PM1/5/09
to pylons-...@googlegroups.com

> So what about having a 'params' argument but keeping the current
> behavior if 'params' is not set.  That would allow backward
> compatibility.
>
> Then if 'params' is set and another argument doesn't correspond to any
> path variable, I guess you'd raise an error.

Well, what we did in tg2 was merge the values from the params dict (if it's present) into the kwargs that was passed into url and redirect.   This allows you to either passin thing as kwargs, or pass the params dict, or, if you're slightly crazy do both ;)

Tycon

unread,
Jan 5, 2009, 10:49:41 PM1/5/09
to pylons-discuss
Also lets not forget that keyword args can't represent a multi-dict
which is allowed for the request parameters. That's why it was a bad
idea from the start to use keyword args for the params.

Wyatt Baldwin

unread,
Jan 5, 2009, 11:40:51 PM1/5/09
to pylons-discuss
I'm not sure it was such a "bad idea." It was a first pass and works
well for a large number of cases. It's unrealistic to expect
perfection from the get-go (or ever, really), and for all you or I
know, the current implementation *is* perfect, for some value of
perfect, for some users.

Regardless, I'm glad we can all chime in and improve the software as a
community, whether with suggestions, patches, or shining light into
dark corners. There's no doubt that Pylons and friends aren't perfect,
but they're sure a hell of a lot better on the whole than anything I
would have written on my own (and just imagine implementing *all* that
functionality on your own).

Ben Bangert

unread,
Jan 6, 2009, 1:07:43 PM1/6/09
to pylons-...@googlegroups.com
On Jan 5, 2009, at 1:56 PM, Tycon wrote:

> right and don't forget about adding the new "raw" (to avoid mapper
> lookup if true) and "external" (to avoid adding SCRIPT_NAME prefix if
> true) arguments, in addition to the "params" dict, so you will have a
> much better url_for

One of your issues with proto, and host, was that you couldn't then
use them in Routes names. By adding three more, it means you can't have:
map.connect('/host/{raw}/{params}'), etc.
In addition to not having host, proto, etc.

So the routing section for Pylons users gets a longer list of reserved
words that can't be used, and host still can't be used. Was that the
desired result?

Cheers,
Ben

Mike Orr

unread,
Jan 6, 2009, 1:33:36 PM1/6/09
to pylons-...@googlegroups.com

Why are those names not allowed in the path? The issue isn't one of
variable collisions but keyword arg collisions.

Although I'm still not sure of the intrinsic merits of the 'raw' and
'external' proposals, which is why I wanted your opinion. In Routes
2, 'external' is marked at route definition, not in the URL call. And
paths that contain a colon before any slash ("http:") are
automatically marked external. So perhaps we want to avoid piling
kludge upon kludge in Routes 1 to accomplish the same thing, which is
my concern about 'raw' and 'external'.

--
Mike Orr <slugg...@gmail.com>

Tycon

unread,
Jan 6, 2009, 3:31:03 PM1/6/09
to pylons-discuss
How will a ":" help the case when I want to create a url for my non-
pylons blog like this:

params = {'id': 1234, 'host': 'junglekid' }

url = url_for('/blog/showpost', protocol='http', **params)

This SHOULD return:

http://www.mysite.com/blog/showpost?id=1234&host=junglekid

But currently, url_for will try to do the following unwanted things
for the above case:

1. It will try to match it against "named" routes and other stuff in
the routing map.
2. It will add the SCRIPT_NAME as a prefix.
3. If the params dict contains any duplicates or reserved keywords
(such as "host"), then it will be treated differently and not added to
the query string.

So to fix all these issues, the user can use the new raw, external,
and params arguments:

url = url_for('/blog/showpost', protocol='http', raw=True,
external=True, params=params)
> Mike Orr <sluggos...@gmail.com>

Mike Orr

unread,
Jan 6, 2009, 3:59:40 PM1/6/09
to pylons-...@googlegroups.com
On Tue, Jan 6, 2009 at 12:31 PM, Tycon <adi...@gmail.com> wrote:
>
> How will a ":" help the case when I want to create a url for my non-
> pylons blog like this:
>
> params = {'id': 1234, 'host': 'junglekid' }
>
> url = url_for('/blog/showpost', protocol='http', **params)
>
> This SHOULD return:
>
> http://www.mysite.com/blog/showpost?id=1234&host=junglekid
>
> But currently, url_for will try to do the following unwanted things
> for the above case:
>
> 1. It will try to match it against "named" routes and other stuff in
> the routing map.
> 2. It will add the SCRIPT_NAME as a prefix.
> 3. If the params dict contains any duplicates or reserved keywords
> (such as "host"), then it will be treated differently and not added to
> the query string.
>
> So to fix all these issues, the user can use the new raw, external,
> and params arguments:
>
> url = url_for('/blog/showpost', protocol='http', raw=True,
> external=True, params=params)

(1) would be solved by separating route lookups from literal URL
generation, for which Routes 2 has suggested something like
url.literal("/path", **params).

(2) is really a variation of an external URL, so I'm hesitant to add a
keyword arg just for it.

(3) is covered by the 'params' proposal.

Routes has several problems. Some of these have to be dealt with by
not using url_for() in certain edge cases, but just using a literal
URL. My concern is a long-term solution. If we add 'raw' and
'external' now, it will be a temporary kludge until Routes is
restructured, and then we'll either have to support it forever or
abandon it. Supporting it means bloated code and a convoluted API;
abandoning it means people would have to change their applications.
Vs the status quo of just not using url_for in certain situations.
The advantage of that is we aren't *adding* to the number of changes
people may have to make in the future when a long-term solution is
reached.

--
Mike Orr <slugg...@gmail.com>

Reply all
Reply to author
Forward
0 new messages