def urlencode(query, enc='utf-8', sort=False, doseq=1): """Encode a sequence of two-element tuples or dictionary into a URL query string. If any values in the query arg are sequences and doseq is true, each sequence element is converted to a separate parameter. If the query arg is a sequence of two-element tuples, the order of the parameters in the output will match the order of parameters in the input. """ if hasattr(query, "iteritems"): # mapping objects if sort: query = SortedDict(query) query = query.iteritems() else: # it's a bother at times that strings and string-like objects are # sequences... try: # non-sequence items should not work with len() # non-empty strings will fail this if len(query) and not isinstance(query[0], tuple): raise TypeError # zero-length sequences of all types will get here and succeed, # but that's a minor nit - since the original implementation # allowed empty dicts that type of behavior probably should be # preserved for consistency if sort: query = sorted(query, lambda x, y: cmp(x[0], y[0])) except TypeError: _, _, tb = sys.exc_info() raise TypeError, "not a valid non-string sequence or mapping object", tb l = [] if not doseq: # preserve old behavior for k, v in query: k = encode(k, safe='', enc=enc) v = encode(v, safe='', enc=enc) l.append(k + '=' + v) else: for k, v in query: k = encode(k, safe='', enc=enc) if isinstance(v, (str, unicode)): v = encode(v, safe='', enc=enc) l.append(k + '=' + v) else: try: # is this a sufficient test for sequence-ness? _ = len(v) except TypeError: # not a sequence v = encode(v, safe='') l.append(k + '=' + v) else: # loop over the sequence for elt in v: l.append(k + '=' + encode(elt, safe='', enc=enc)) return '&'.join(l)
def compose_qs(params, sort=False, pattern='%s=%s', join='&', wrap=None): """ Compose a single string using RFC3986 specified escaping using `urlencoding.escape`_ for keys and values. Arguments: `params` The dict of parameters to encode into a query string. `sort` Boolean indicating if the key/values should be sorted. >>> urlencoding.compose_qs({'a': '1', 'b': ' c d'}) 'a=1&b=%20c%20d' >>> urlencoding.compose_qs({'a': ['2', '1']}) 'a=2&a=1' >>> urlencoding.compose_qs({'a': ['2', '1', '3']}, sort=True) 'a=1&a=2&a=3' >>> urlencoding.compose_qs({'a': '1', 'b': {'c': 2, 'd': 3}}, sort=True) 'a=1&b%5Bc%5D=2&b%5Bd%5D=3' """ if sort: params = SortedDict(params) pieces = [] for key, value in params.iteritems(): escaped_key = escape(str(key)) if wrap: escaped_key = wrap + ENCODED_OPEN_BRACKET + escaped_key + ENCODED_CLOSE_BRACKET if isinstance(value, collections.Mapping): p = compose_qs(value, sort, pattern, join, escaped_key) elif is_nonstring_iterable(value): p = join.join([pattern % (escaped_key, escape(str(v))) for v in value]) else: p = pattern % (escaped_key, escape(str(value))) pieces.append(p) return join.join(pieces)