1616"""
1717
1818import re
19+ import sys
1920import string
2021import socket
2122from unicodedata import normalize
23+
24+ try :
25+ from collections .abc import Mapping
26+ except ImportError : # Python 2
27+ from collections import Mapping
2228try :
2329 from socket import inet_pton
2430except ImportError :
@@ -52,6 +58,7 @@ def inet_pton(address_family, ip_string):
5258 raise socket .error ('unknown address family' )
5359
5460
61+ PY2 = (sys .version_info [0 ] == 2 )
5562unicode = type (u'' )
5663try :
5764 unichr
@@ -425,7 +432,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False):
425432 if nullable and value is None :
426433 return value # used by query string values
427434 else :
428- str_name = "unicode" if bytes is str else "str"
435+ str_name = "unicode" if PY2 else "str"
429436 exp = str_name + ' or NoneType' if nullable else str_name
430437 raise TypeError ('expected %s for %s, got %r' % (exp , name , value ))
431438 if delims and set (value ) & set (delims ): # TODO: test caching into regexes
@@ -434,6 +441,19 @@ def _textcheck(name, value, delims=frozenset(), nullable=False):
434441 return value
435442
436443
444+ def iter_pairs (iterable ):
445+ """
446+ Iterate over the (key, value) pairs in ``iterable``.
447+
448+ This handles dictionaries sensibly, and falls back to assuming the
449+ iterable yields (key, value) pairs. This behaviour is similar to
450+ what Python's ``dict()`` constructor does.
451+ """
452+ if isinstance (iterable , Mapping ):
453+ iterable = iterable .items ()
454+ return iter (iterable )
455+
456+
437457def _decode_unreserved (text , normalize_case = False ):
438458 return _percent_decode (text , normalize_case = normalize_case ,
439459 _decode_map = _UNRESERVED_DECODE_MAP )
@@ -639,8 +659,8 @@ class URL(object):
639659 for more info.
640660 path (tuple): A tuple of strings representing the
641661 slash-separated parts of the path.
642- query (tuple): The query parameters, as a tuple of
643- key-value pairs.
662+ query (tuple): The query parameters, as a dictionary or
663+ as an iterable of key-value pairs.
644664 fragment (unicode): The fragment part of the URL.
645665 rooted (bool): Whether or not the path begins with a slash.
646666 userinfo (unicode): The username or colon-separated
@@ -695,8 +715,7 @@ def __init__(self, scheme=None, host=None, path=(), query=(), fragment=u'',
695715 self ._query = tuple (
696716 (_textcheck ("query parameter name" , k , '&=#' ),
697717 _textcheck ("query parameter value" , v , '&#' , nullable = True ))
698- for (k , v ) in query
699- )
718+ for k , v in iter_pairs (query ))
700719 self ._fragment = _textcheck ("fragment" , fragment )
701720 self ._port = _typecheck ("port" , port , int , NoneType )
702721 self ._rooted = _typecheck ("rooted" , rooted , bool )
@@ -912,6 +931,8 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET,
912931 slash-separated parts of the path.
913932 query (tuple): The query parameters, as a tuple of
914933 key-value pairs.
934+ query (tuple): The query parameters, as a dictionary or
935+ as an iterable of key-value pairs.
915936 fragment (unicode): The fragment part of the URL.
916937 rooted (bool): Whether or not the path begins with a slash.
917938 userinfo (unicode): The username or colon-separated
0 commit comments