The general consensus in python-ideas is that the following is needed, so I bring it to python-dev to final discussions before I file a feature request in <a href="http://bugs.python.org">bugs.python.org</a>.<br><br>Proposal: add add_query_params() for appending query parameters to an URL to urllib.parse and urlparse.<br>
<br>Implementation: <a href="http://github.com/mrts/qparams/blob/83d1ec287ec10934b5e637455819cf796b1b421c/qparams.py">http://github.com/mrts/qparams/blob/83d1ec287ec10934b5e637455819cf796b1b421c/qparams.py</a> (feel free to fork and comment).<br>
<br>Behaviour (longish, guided by "simple things are simiple, complex things possible"):<br><br>In the simplest form, parameters can be passed via keyword arguments:<br><br>    >>> add_query_params('foo', bar='baz')<br>
    'foo?bar=baz'<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', b='d')<br>    '<a href="http://example.com/a/b/c?a=b&b=d">http://example.com/a/b/c?a=b&b=d</a>'<br>
<br>Note that '/', if given in arguments, is encoded:<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', b='d', foo='/bar')<br>
    '<a href="http://example.com/a/b/c?a=b&b=d&foo=%2Fbar">http://example.com/a/b/c?a=b&b=d&foo=%2Fbar</a>'<br><br>Duplicates are discarded:<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', a='b')<br>
    '<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>'<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b&c=q">http://example.com/a/b/c?a=b&c=q</a>', a='b', b='d',<br>
    ...  c='q')<br>    '<a href="http://example.com/a/b/c?a=b&c=q&b=d">http://example.com/a/b/c?a=b&c=q&b=d</a>'<br><br>But different values for the same key are supported:<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', a='c', b='d')<br>
    '<a href="http://example.com/a/b/c?a=b&a=c&b=d">http://example.com/a/b/c?a=b&a=c&b=d</a>'<br><br>Pass different values for a single key in a list (again, duplicates are<br>removed):<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', a=('q', 'b', 'c'),<br>
    ... b='d')<br>    '<a href="http://example.com/a/b/c?a=b&a=q&a=c&b=d">http://example.com/a/b/c?a=b&a=q&a=c&b=d</a>'<br><br>Keys with no value are respected, pass ``None`` to create one:<br>
<br>    >>> add_query_params('<a href="http://example.com/a/b/c?a">http://example.com/a/b/c?a</a>', b=None)<br>    '<a href="http://example.com/a/b/c?a&b">http://example.com/a/b/c?a&b</a>'<br>
<br>But if a value is given, the empty key is considered a duplicate (i.e. the<br>case of a&a=b is considered nonsensical):<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a">http://example.com/a/b/c?a</a>', a='b', c=None)<br>
    '<a href="http://example.com/a/b/c?a=b&c">http://example.com/a/b/c?a=b&c</a>'<br><br>If you need to pass in key names that are not allowed in keyword arguments,<br>pass them via a dictionary in second argument:<br>
<br>    >>> add_query_params('foo', {"+'|äüö": 'bar'})<br>    'foo?%2B%27%7C%C3%A4%C3%BC%C3%B6=bar'<br><br>Order of original parameters is retained, although similar keys are grouped<br>
together. Order of keyword arguments is not (and can not be) retained:<br><br>    >>> add_query_params('foo?a=b&b=c&a=b&a=d', a='b')<br>    'foo?a=b&a=d&b=c'<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b&q=c&e=d">http://example.com/a/b/c?a=b&q=c&e=d</a>',<br>
    ... x='y', e=1, o=2)<br>    '<a href="http://example.com/a/b/c?a=b&q=c&e=d&e=1&x=y&o=2">http://example.com/a/b/c?a=b&q=c&e=d&e=1&x=y&o=2</a>'<br><br>If you need to retain the order of the added parameters, use an<br>
:class:`OrderedDict` as the second argument (*params_dict*):<br><br>    >>> from collections import OrderedDict<br>    >>> od = OrderedDict()<br>    >>> od['xavier'] = 1<br>    >>> od['abacus'] = 2<br>
    >>> od['janus'] = 3<br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', od)<br>    '<a href="http://example.com/a/b/c?a=b&xavier=1&abacus=2&janus=3">http://example.com/a/b/c?a=b&xavier=1&abacus=2&janus=3</a>'<br>
<br>If both *params_dict* and keyword arguments are provided, values from the<br>former are used before the latter:<br><br>    >>> add_query_params('<a href="http://example.com/a/b/c?a=b">http://example.com/a/b/c?a=b</a>', od, xavier=1.1,<br>
    ... zorg='a', alpha='b', watt='c', borg='d')<br>    '<a href="http://example.com/a/b/c?a=b&xavier=1&xavier=1.1&abacus=2&janus=3&zorg=a&borg=d&watt=c&alpha=b">http://example.com/a/b/c?a=b&xavier=1&xavier=1.1&abacus=2&janus=3&zorg=a&borg=d&watt=c&alpha=b</a>'<br>
<br>Do nothing with a single argument:<br><br>    >>> add_query_params('a')<br>    'a'<br><br>    >>> add_query_params('arbitrary strange stuff?öäüõ*()+-=42')<br>    'arbitrary strange stuff?\xc3\xb6\xc3\xa4\xc3\xbc\xc3\xb5*()+-=42'<br>