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

Proposal: add add_query_params() for appending query parameters to an URL to urllib.parse and urlparse.

Implementation: (feel free to fork and comment).

Behaviour (longish, guided by "simple things are simiple, complex things possible"):

In the simplest form, parameters can be passed via keyword arguments:

>>> add_query_params('foo', bar='baz')

>>> add_query_params('', b='d')

Note that '/', if given in arguments, is encoded:

>>> add_query_params('', b='d', foo='/bar')

Duplicates are discarded:

>>> add_query_params('', a='b')

>>> add_query_params('', a='b', b='d',
... c='q')

But different values for the same key are supported:

>>> add_query_params('', a='c', b='d')

Pass different values for a single key in a list (again, duplicates are

>>> add_query_params('', a=('q', 'b', 'c'),
... b='d')

Keys with no value are respected, pass ``None`` to create one:

>>> add_query_params('', b=None)

But if a value is given, the empty key is considered a duplicate (i.e. the
case of a&a=b is considered nonsensical):

>>> add_query_params('', a='b', c=None)

If you need to pass in key names that are not allowed in keyword arguments,
pass them via a dictionary in second argument:

>>> add_query_params('foo', {"+'|": 'bar'})

Order of original parameters is retained, although similar keys are grouped
together. Order of keyword arguments is not (and can not be) retained:

>>> add_query_params('foo?a=b&b=c&a=b&a=d', a='b')

>>> add_query_params('',
... x='y', e=1, o=2)

If you need to retain the order of the added parameters, use an
:class:`OrderedDict` as the second argument (*params_dict*):

>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> od['xavier'] = 1
>>> od['abacus'] = 2
>>> od['janus'] = 3
>>> add_query_params('', od)

If both *params_dict* and keyword arguments are provided, values from the
former are used before the latter:

>>> add_query_params('', od, xavier=1.1,
... zorg='a', alpha='b', watt='c', borg='d')

Do nothing with a single argument:

>>> add_query_params('a')

>>> add_query_params('arbitrary strange stuff?*()+-=42')
'arbitrary strange stuff?\xc3\xb6\xc3\xa4\xc3\xbc\xc3\xb5*()+-=42'