diff env/lib/python3.7/site-packages/boltons/urlutils.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/boltons/urlutils.py	Thu May 14 16:47:39 2020 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1564 +0,0 @@
-# -*- coding: utf-8 -*-
-""":mod:`urlutils` is a module dedicated to one of software's most
-versatile, well-aged, and beloved data structures: the URL, also known
-as the `Uniform Resource Locator`_.
-
-Among other things, this module is a full reimplementation of URLs,
-without any reliance on the :mod:`urlparse` or :mod:`urllib` standard
-library modules. The centerpiece and top-level interface of urlutils
-is the :class:`URL` type. Also featured is the :func:`find_all_links`
-convenience function. Some low-level functions and constants are also
-below.
-
-The implementations in this module are based heavily on `RFC 3986`_ and
-`RFC 3987`_, and incorporates details from several other RFCs and `W3C
-documents`_.
-
-.. _Uniform Resource Locator: https://en.wikipedia.org/wiki/Uniform_Resource_Locator
-.. _RFC 3986: https://tools.ietf.org/html/rfc3986
-.. _RFC 3987: https://tools.ietf.org/html/rfc3987
-.. _W3C documents: https://www.w3.org/TR/uri-clarification/
-
-"""
-
-import re
-import socket
-import string
-from unicodedata import normalize
-
-unicode = type(u'')
-try:
-    unichr
-except NameError:
-    unichr = chr
-
-# The unreserved URI characters (per RFC 3986 Section 2.3)
-_UNRESERVED_CHARS = frozenset('~-._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-                              'abcdefghijklmnopqrstuvwxyz')
-
-# URL parsing regex (based on RFC 3986 Appendix B, with modifications)
-_URL_RE = re.compile(r'^((?P<scheme>[^:/?#]+):)?'
-                     r'((?P<_netloc_sep>//)(?P<authority>[^/?#]*))?'
-                     r'(?P<path>[^?#]*)'
-                     r'(\?(?P<query>[^#]*))?'
-                     r'(#(?P<fragment>.*))?')
-
-
-_HEX_CHAR_MAP = dict([((a + b).encode('ascii'),
-                       unichr(int(a + b, 16)).encode('charmap'))
-                      for a in string.hexdigits for b in string.hexdigits])
-_ASCII_RE = re.compile('([\x00-\x7f]+)')
-
-
-# This port list painstakingly curated by hand searching through
-# https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
-# and
-# https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
-SCHEME_PORT_MAP = {'acap': 674, 'afp': 548, 'dict': 2628, 'dns': 53,
-                   'file': None, 'ftp': 21, 'git': 9418, 'gopher': 70,
-                   'http': 80, 'https': 443, 'imap': 143, 'ipp': 631,
-                   'ipps': 631, 'irc': 194, 'ircs': 6697, 'ldap': 389,
-                   'ldaps': 636, 'mms': 1755, 'msrp': 2855, 'msrps': None,
-                   'mtqp': 1038, 'nfs': 111, 'nntp': 119, 'nntps': 563,
-                   'pop': 110, 'prospero': 1525, 'redis': 6379, 'rsync': 873,
-                   'rtsp': 554, 'rtsps': 322, 'rtspu': 5005, 'sftp': 22,
-                   'smb': 445, 'snmp': 161, 'ssh': 22, 'steam': None,
-                   'svn': 3690, 'telnet': 23, 'ventrilo': 3784, 'vnc': 5900,
-                   'wais': 210, 'ws': 80, 'wss': 443, 'xmpp': None}
-
-# This list of schemes that don't use authorities is also from the link above.
-NO_NETLOC_SCHEMES = set(['urn', 'about', 'bitcoin', 'blob', 'data', 'geo',
-                         'magnet', 'mailto', 'news', 'pkcs11',
-                         'sip', 'sips', 'tel'])
-# As of Mar 11, 2017, there were 44 netloc schemes, and 13 non-netloc
-
-# RFC 3986 section 2.2, Reserved Characters
-_GEN_DELIMS = frozenset(u':/?#[]@')
-_SUB_DELIMS = frozenset(u"!$&'()*+,;=")
-_ALL_DELIMS = _GEN_DELIMS | _SUB_DELIMS
-
-_USERINFO_SAFE = _UNRESERVED_CHARS | _SUB_DELIMS
-_USERINFO_DELIMS = _ALL_DELIMS - _USERINFO_SAFE
-_PATH_SAFE = _UNRESERVED_CHARS | _SUB_DELIMS | set(u':@')
-_PATH_DELIMS = _ALL_DELIMS - _PATH_SAFE
-_FRAGMENT_SAFE = _UNRESERVED_CHARS | _PATH_SAFE | set(u'/?')
-_FRAGMENT_DELIMS = _ALL_DELIMS - _FRAGMENT_SAFE
-_QUERY_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u'&=+')
-_QUERY_DELIMS = _ALL_DELIMS - _QUERY_SAFE
-
-
-class URLParseError(ValueError):
-    """Exception inheriting from :exc:`ValueError`, raised when failing to
-    parse a URL. Mostly raised on invalid ports and IPv6 addresses.
-    """
-    pass
-
-
-DEFAULT_ENCODING = 'utf8'
-
-
-def to_unicode(obj):
-    try:
-        return unicode(obj)
-    except UnicodeDecodeError:
-        return unicode(obj, encoding=DEFAULT_ENCODING)
-
-
-# regex from gruber via tornado
-# doesn't support ipv6
-# doesn't support mailto (netloc-less schemes)
-_FIND_ALL_URL_RE = re.compile(to_unicode(r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()<>]|&amp;|&quot;)*(?:[^!"#$%'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&amp;|&quot;)*\)))+)"""))
-
-
-def find_all_links(text, with_text=False, default_scheme='https', schemes=()):
-    """This function uses heuristics to searches plain text for strings
-    that look like URLs, returning a :class:`list` of :class:`URL`
-    objects. It supports limiting the accepted schemes, and returning
-    interleaved text as well.
-
-    >>> find_all_links('Visit https://boltons.rtfd.org!')
-    [URL(u'https://boltons.rtfd.org')]
-    >>> find_all_links('Visit https://boltons.rtfd.org!', with_text=True)
-    [u'Visit ', URL(u'https://boltons.rtfd.org'), u'!']
-
-    Args:
-       text (str): The text to search.
-
-       with_text (bool): Whether or not to interleave plaintext blocks
-          with the returned URL objects. Having all tokens can be
-          useful for transforming the text, e.g., replacing links with
-          HTML equivalents. Defaults to ``False``.
-
-       default_scheme (str): Many URLs are written without the scheme
-          component. This function can match a reasonable subset of
-          those, provided *default_scheme* is set to a string. Set to
-          ``False`` to disable matching scheme-less URLs. Defaults to
-          ``'https'``.
-
-       schemes (list): A list of strings that a URL's scheme must
-          match in order to be included in the results. Defaults to
-          empty, which matches all schemes.
-
-    .. note:: Currently this function does not support finding IPv6
-      addresses or URLs with netloc-less schemes, like mailto.
-
-    """
-    text = to_unicode(text)
-    prev_end, start, end = 0, None, None
-    ret = []
-    _add = ret.append
-
-    def _add_text(t):
-        if ret and isinstance(ret[-1], unicode):
-            ret[-1] += t
-        else:
-            _add(t)
-
-    for match in _FIND_ALL_URL_RE.finditer(text):
-        start, end = match.start(1), match.end(1)
-        if prev_end < start and with_text:
-            _add(text[prev_end:start])
-        prev_end = end
-        try:
-            cur_url_text = match.group(0)
-            cur_url = URL(cur_url_text)
-            if not cur_url.scheme:
-                if default_scheme:
-                    cur_url = URL(default_scheme + '://' + cur_url_text)
-                else:
-                    _add_text(text[start:end])
-                    continue
-            if schemes and cur_url.scheme not in schemes:
-                _add_text(text[start:end])
-            else:
-                _add(cur_url)
-        except URLParseError:
-            # currently this should only be hit with broken port
-            # strings. the regex above doesn't support ipv6 addresses
-            if with_text:
-                _add_text(text[start:end])
-
-    if with_text:
-        tail = text[prev_end:]
-        if tail:
-            _add_text(tail)
-
-    return ret
-
-
-def _make_quote_map(safe_chars):
-    ret = {}
-    # v is included in the dict for py3 mostly, because bytestrings
-    # are iterables of ints, of course!
-    for i, v in zip(range(256), range(256)):
-        c = chr(v)
-        if c in safe_chars:
-            ret[c] = ret[v] = c
-        else:
-            ret[c] = ret[v] = '%{0:02X}'.format(i)
-    return ret
-
-
-_USERINFO_PART_QUOTE_MAP = _make_quote_map(_USERINFO_SAFE)
-_PATH_PART_QUOTE_MAP = _make_quote_map(_PATH_SAFE)
-_QUERY_PART_QUOTE_MAP = _make_quote_map(_QUERY_SAFE)
-_FRAGMENT_QUOTE_MAP = _make_quote_map(_FRAGMENT_SAFE)
-
-
-def quote_path_part(text, full_quote=True):
-    """
-    Percent-encode a single segment of a URL path.
-    """
-    if full_quote:
-        bytestr = normalize('NFC', to_unicode(text)).encode('utf8')
-        return u''.join([_PATH_PART_QUOTE_MAP[b] for b in bytestr])
-    return u''.join([_PATH_PART_QUOTE_MAP[t] if t in _PATH_DELIMS else t
-                     for t in text])
-
-
-def quote_query_part(text, full_quote=True):
-    """
-    Percent-encode a single query string key or value.
-    """
-    if full_quote:
-        bytestr = normalize('NFC', to_unicode(text)).encode('utf8')
-        return u''.join([_QUERY_PART_QUOTE_MAP[b] for b in bytestr])
-    return u''.join([_QUERY_PART_QUOTE_MAP[t] if t in _QUERY_DELIMS else t
-                     for t in text])
-
-
-def quote_fragment_part(text, full_quote=True):
-    """Quote the fragment part of the URL. Fragments don't have
-    subdelimiters, so the whole URL fragment can be passed.
-    """
-    if full_quote:
-        bytestr = normalize('NFC', to_unicode(text)).encode('utf8')
-        return u''.join([_FRAGMENT_QUOTE_MAP[b] for b in bytestr])
-    return u''.join([_FRAGMENT_QUOTE_MAP[t] if t in _FRAGMENT_DELIMS else t
-                     for t in text])
-
-
-def quote_userinfo_part(text, full_quote=True):
-    """Quote special characters in either the username or password
-    section of the URL. Note that userinfo in URLs is considered
-    deprecated in many circles (especially browsers), and support for
-    percent-encoded userinfo can be spotty.
-    """
-    if full_quote:
-        bytestr = normalize('NFC', to_unicode(text)).encode('utf8')
-        return u''.join([_USERINFO_PART_QUOTE_MAP[b] for b in bytestr])
-    return u''.join([_USERINFO_PART_QUOTE_MAP[t] if t in _USERINFO_DELIMS
-                     else t for t in text])
-
-
-def unquote(string, encoding='utf-8', errors='replace'):
-    """Percent-decode a string, by replacing %xx escapes with their
-    single-character equivalent. The optional *encoding* and *errors*
-    parameters specify how to decode percent-encoded sequences into
-    Unicode characters, as accepted by the :meth:`bytes.decode()` method.  By
-    default, percent-encoded sequences are decoded with UTF-8, and
-    invalid sequences are replaced by a placeholder character.
-
-    >>> unquote(u'abc%20def')
-    u'abc def'
-    """
-    if '%' not in string:
-        string.split
-        return string
-    if encoding is None:
-        encoding = 'utf-8'
-    if errors is None:
-        errors = 'replace'
-    bits = _ASCII_RE.split(string)
-    res = [bits[0]]
-    append = res.append
-    for i in range(1, len(bits), 2):
-        append(unquote_to_bytes(bits[i]).decode(encoding, errors))
-        append(bits[i + 1])
-    return ''.join(res)
-
-
-def unquote_to_bytes(string):
-    """unquote_to_bytes('abc%20def') -> b'abc def'."""
-    # Note: strings are encoded as UTF-8. This is only an issue if it contains
-    # unescaped non-ASCII characters, which URIs should not.
-    if not string:
-        # Is it a string-like object?
-        string.split
-        return b''
-    if isinstance(string, unicode):
-        string = string.encode('utf-8')
-    bits = string.split(b'%')
-    if len(bits) == 1:
-        return string
-    # import pdb;pdb.set_trace()
-    res = [bits[0]]
-    append = res.append
-
-    for item in bits[1:]:
-        try:
-            append(_HEX_CHAR_MAP[item[:2]])
-            append(item[2:])
-        except KeyError:
-            append(b'%')
-            append(item)
-    return b''.join(res)
-
-
-def register_scheme(text, uses_netloc=None, default_port=None):
-    """Registers new scheme information, resulting in correct port and
-    slash behavior from the URL object. There are dozens of standard
-    schemes preregistered, so this function is mostly meant for
-    proprietary internal customizations or stopgaps on missing
-    standards information. If a scheme seems to be missing, please
-    `file an issue`_!
-
-    Args:
-        text (str): Text representing the scheme.
-           (the 'http' in 'http://hatnote.com')
-        uses_netloc (bool): Does the scheme support specifying a
-           network host? For instance, "http" does, "mailto" does not.
-        default_port (int): The default port, if any, for netloc-using
-           schemes.
-
-    .. _file an issue: https://github.com/mahmoud/boltons/issues
-    """
-    text = text.lower()
-    if default_port is not None:
-        try:
-            default_port = int(default_port)
-        except ValueError:
-            raise ValueError('default_port expected integer or None, not %r'
-                             % (default_port,))
-
-    if uses_netloc is True:
-        SCHEME_PORT_MAP[text] = default_port
-    elif uses_netloc is False:
-        if default_port is not None:
-            raise ValueError('unexpected default port while specifying'
-                             ' non-netloc scheme: %r' % default_port)
-        NO_NETLOC_SCHEMES.add(text)
-    elif uses_netloc is not None:
-        raise ValueError('uses_netloc expected True, False, or None')
-
-    return
-
-
-def resolve_path_parts(path_parts):
-    """Normalize the URL path by resolving segments of '.' and '..',
-    resulting in a dot-free path.  See RFC 3986 section 5.2.4, Remove
-    Dot Segments.
-    """
-    # TODO: what to do with multiple slashes
-    ret = []
-
-    for part in path_parts:
-        if part == u'.':
-            pass
-        elif part == u'..':
-            if ret and (len(ret) > 1 or ret[0]):  # prevent unrooting
-                ret.pop()
-        else:
-            ret.append(part)
-
-    if list(path_parts[-1:]) in ([u'.'], [u'..']):
-        ret.append(u'')
-
-    return ret
-
-
-class cachedproperty(object):
-    """The ``cachedproperty`` is used similar to :class:`property`, except
-    that the wrapped method is only called once. This is commonly used
-    to implement lazy attributes.
-
-    After the property has been accessed, the value is stored on the
-    instance itself, using the same name as the cachedproperty. This
-    allows the cache to be cleared with :func:`delattr`, or through
-    manipulating the object's ``__dict__``.
-    """
-    def __init__(self, func):
-        self.__doc__ = getattr(func, '__doc__')
-        self.func = func
-
-    def __get__(self, obj, objtype=None):
-        if obj is None:
-            return self
-        value = obj.__dict__[self.func.__name__] = self.func(obj)
-        return value
-
-    def __repr__(self):
-        cn = self.__class__.__name__
-        return '<%s func=%s>' % (cn, self.func)
-
-
-class URL(object):
-    r"""The URL is one of the most ubiquitous data structures in the
-    virtual and physical landscape. From blogs to billboards, URLs are
-    so common, that it's easy to overlook their complexity and
-    power.
-
-    There are 8 parts of a URL, each with its own semantics and
-    special characters:
-
-      * :attr:`~URL.scheme`
-      * :attr:`~URL.username`
-      * :attr:`~URL.password`
-      * :attr:`~URL.host`
-      * :attr:`~URL.port`
-      * :attr:`~URL.path`
-      * :attr:`~URL.query_params` (query string parameters)
-      * :attr:`~URL.fragment`
-
-    Each is exposed as an attribute on the URL object. RFC 3986 offers
-    this brief structural summary of the main URL components::
-
-        foo://user:pass@example.com:8042/over/there?name=ferret#nose
-        \_/   \_______/ \_________/ \__/\_________/ \_________/ \__/
-         |        |          |        |      |           |        |
-       scheme  userinfo     host     port   path       query   fragment
-
-    And here's how that example can be manipulated with the URL type:
-
-    >>> url = URL('foo://example.com:8042/over/there?name=ferret#nose')
-    >>> print(url.host)
-    example.com
-    >>> print(url.get_authority())
-    example.com:8042
-    >>> print(url.qp['name'])  # qp is a synonym for query_params
-    ferret
-
-    URL's approach to encoding is that inputs are decoded as much as
-    possible, and data remains in this decoded state until re-encoded
-    using the :meth:`~URL.to_text()` method. In this way, it's similar
-    to Python's current approach of encouraging immediate decoding of
-    bytes to text.
-
-    Note that URL instances are mutable objects. If an immutable
-    representation of the URL is desired, the string from
-    :meth:`~URL.to_text()` may be used. For an immutable, but
-    almost-as-featureful, URL object, check out the `hyperlink
-    package`_.
-
-    .. _hyperlink package: https://github.com/mahmoud/hyperlink
-
-    """
-
-    # public attributes (for comparison, see __eq__):
-    _cmp_attrs = ('scheme', 'uses_netloc', 'username', 'password',
-                  'family', 'host', 'port', 'path', 'query_params', 'fragment')
-
-    def __init__(self, url=''):
-        # TODO: encoding param. The encoding that underlies the
-        # percent-encoding is always utf8 for IRIs, but can be Latin-1
-        # for other usage schemes.
-        ud = DEFAULT_PARSED_URL
-        if url:
-            if isinstance(url, URL):
-                url = url.to_text()  # better way to copy URLs?
-            elif isinstance(url, bytes):
-                try:
-                    url = url.decode(DEFAULT_ENCODING)
-                except UnicodeDecodeError as ude:
-                    raise URLParseError('expected text or %s-encoded bytes.'
-                                        ' try decoding the url bytes and'
-                                        ' passing the result. (got: %s)'
-                                        % (DEFAULT_ENCODING, ude))
-            ud = parse_url(url)
-
-        _e = u''
-        self.scheme = ud['scheme'] or _e
-        self._netloc_sep = ud['_netloc_sep'] or _e
-        self.username = (unquote(ud['username'])
-                         if '%' in (ud['username'] or _e) else ud['username'] or _e)
-        self.password = (unquote(ud['password'])
-                         if '%' in (ud['password'] or _e) else ud['password'] or _e)
-        self.family = ud['family']
-
-        if not ud['host']:
-            self.host = _e
-        else:
-            try:
-                self.host = ud['host'].encode("ascii")
-            except UnicodeEncodeError:
-                self.host = ud['host']  # already non-ascii text
-            else:
-                self.host = self.host.decode("idna")
-
-        self.port = ud['port']
-        self.path_parts = tuple([unquote(p) if '%' in p else p for p
-                                 in (ud['path'] or _e).split(u'/')])
-        self._query = ud['query'] or _e
-        self.fragment = (unquote(ud['fragment'])
-                         if '%' in (ud['fragment'] or _e) else ud['fragment'] or _e)
-        # TODO: possibly use None as marker for empty vs missing
-        return
-
-    @classmethod
-    def from_parts(cls, scheme=None, host=None, path_parts=(), query_params=(),
-                   fragment=u'', port=None, username=None, password=None):
-        """Build a new URL from parts. Note that the respective arguments are
-        not in the order they would appear in a URL:
-
-        Args:
-           scheme (str): The scheme of a URL, e.g., 'http'
-           host (str): The host string, e.g., 'hatnote.com'
-           path_parts (tuple): The individual text segments of the
-             path, e.g., ('post', '123')
-           query_params (dict): An OMD, dict, or list of (key, value)
-             pairs representing the keys and values of the URL's query
-             parameters.
-           fragment (str): The fragment of the URL, e.g., 'anchor1'
-           port (int): The integer port of URL, automatic defaults are
-             available for registered schemes.
-           username (str): The username for the userinfo part of the URL.
-           password (str): The password for the userinfo part of the URL.
-
-        Note that this method does relatively little
-        validation. :meth:`URL.to_text()` should be used to check if
-        any errors are produced while composing the final textual URL.
-        """
-        ret = cls()
-
-        ret.scheme = scheme
-        ret.host = host
-        ret.path_parts = tuple(path_parts) or (u'',)
-        ret.query_params.update(query_params)
-        ret.fragment = fragment
-        ret.port = port
-        ret.username = username
-        ret.password = password
-
-        return ret
-
-    @cachedproperty
-    def query_params(self):
-        """The parsed form of the query string of the URL, represented as a
-        :class:`~dictutils.OrderedMultiDict`. Also available as the
-        handy alias ``qp``.
-
-        >>> url = URL('http://boltons.readthedocs.io/?utm_source=doctest&python=great')
-        >>> url.qp.keys()
-        [u'utm_source', u'python']
-        """
-        return QueryParamDict.from_text(self._query)
-
-    qp = query_params
-
-    @property
-    def path(self):
-        "The URL's path, in text form."
-        return u'/'.join([quote_path_part(p, full_quote=False)
-                          for p in self.path_parts])
-
-    @path.setter
-    def path(self, path_text):
-        self.path_parts = tuple([unquote(p) if '%' in p else p
-                                 for p in to_unicode(path_text).split(u'/')])
-        return
-
-    @property
-    def uses_netloc(self):
-        """Whether or not a URL uses :code:`:` or :code:`://` to separate the
-        scheme from the rest of the URL depends on the scheme's own
-        standard definition. There is no way to infer this behavior
-        from other parts of the URL. A scheme either supports network
-        locations or it does not.
-
-        The URL type's approach to this is to check for explicitly
-        registered schemes, with common schemes like HTTP
-        preregistered. This is the same approach taken by
-        :mod:`urlparse`.
-
-        URL adds two additional heuristics if the scheme as a whole is
-        not registered. First, it attempts to check the subpart of the
-        scheme after the last ``+`` character. This adds intuitive
-        behavior for schemes like ``git+ssh``. Second, if a URL with
-        an unrecognized scheme is loaded, it will maintain the
-        separator it sees.
-
-        >>> print(URL('fakescheme://test.com').to_text())
-        fakescheme://test.com
-        >>> print(URL('mockscheme:hello:world').to_text())
-        mockscheme:hello:world
-
-        """
-        default = self._netloc_sep
-        if self.scheme in SCHEME_PORT_MAP:
-            return True
-        if self.scheme in NO_NETLOC_SCHEMES:
-            return False
-        if self.scheme.split('+')[-1] in SCHEME_PORT_MAP:
-            return True
-        return default
-
-    @property
-    def default_port(self):
-        """Return the default port for the currently-set scheme. Returns
-        ``None`` if the scheme is unrecognized. See
-        :func:`register_scheme` above. If :attr:`~URL.port` matches
-        this value, no port is emitted in the output of
-        :meth:`~URL.to_text()`.
-
-        Applies the same '+' heuristic detailed in :meth:`URL.uses_netloc`.
-        """
-        try:
-            return SCHEME_PORT_MAP[self.scheme]
-        except KeyError:
-            return SCHEME_PORT_MAP.get(self.scheme.split('+')[-1])
-
-    def normalize(self, with_case=True):
-        """Resolve any "." and ".." references in the path, as well as
-        normalize scheme and host casing. To turn off case
-        normalization, pass ``with_case=False``.
-
-        More information can be found in `Section 6.2.2 of RFC 3986`_.
-
-        .. _Section 6.2.2 of RFC 3986: https://tools.ietf.org/html/rfc3986#section-6.2.2
-        """
-        self.path_parts = resolve_path_parts(self.path_parts)
-
-        if with_case:
-            self.scheme = self.scheme.lower()
-            self.host = self.host.lower()
-        return
-
-    def navigate(self, dest):
-        """Factory method that returns a _new_ :class:`URL` based on a given
-        destination, *dest*. Useful for navigating those relative
-        links with ease.
-
-        The newly created :class:`URL` is normalized before being returned.
-
-        >>> url = URL('http://boltons.readthedocs.io')
-        >>> url.navigate('en/latest/')
-        URL(u'http://boltons.readthedocs.io/en/latest/')
-
-        Args:
-           dest (str): A string or URL object representing the destination
-
-        More information can be found in `Section 5 of RFC 3986`_.
-
-        .. _Section 5 of RFC 3986: https://tools.ietf.org/html/rfc3986#section-5
-        """
-        orig_dest = None
-        if not isinstance(dest, URL):
-            dest, orig_dest = URL(dest), dest
-        if dest.scheme and dest.host:
-            # absolute URLs replace everything, but don't make an
-            # extra copy if we don't have to
-            return URL(dest) if orig_dest is None else dest
-        query_params = dest.query_params
-
-        if dest.path:
-            if dest.path.startswith(u'/'):   # absolute path
-                new_path_parts = list(dest.path_parts)
-            else:  # relative path
-                new_path_parts = self.path_parts[:-1] + dest.path_parts
-        else:
-            new_path_parts = list(self.path_parts)
-            if not query_params:
-                query_params = self.query_params
-
-        ret = self.from_parts(scheme=dest.scheme or self.scheme,
-                              host=dest.host or self.host,
-                              port=dest.port or self.port,
-                              path_parts=new_path_parts,
-                              query_params=query_params,
-                              fragment=dest.fragment,
-                              username=dest.username or self.username,
-                              password=dest.password or self.password)
-        ret.normalize()
-        return ret
-
-    def get_authority(self, full_quote=False, with_userinfo=False):
-        """Used by URL schemes that have a network location,
-        :meth:`~URL.get_authority` combines :attr:`username`,
-        :attr:`password`, :attr:`host`, and :attr:`port` into one
-        string, the *authority*, that is used for
-        connecting to a network-accessible resource.
-
-        Used internally by :meth:`~URL.to_text()` and can be useful
-        for labeling connections.
-
-        >>> url = URL('ftp://user@ftp.debian.org:2121/debian/README')
-        >>> print(url.get_authority())
-        ftp.debian.org:2121
-        >>> print(url.get_authority(with_userinfo=True))
-        user@ftp.debian.org:2121
-
-        Args:
-           full_quote (bool): Whether or not to apply IDNA encoding.
-              Defaults to ``False``.
-           with_userinfo (bool): Whether or not to include username
-              and password, technically part of the
-              authority. Defaults to ``False``.
-
-        """
-        parts = []
-        _add = parts.append
-        if self.username and with_userinfo:
-            _add(quote_userinfo_part(self.username))
-            if self.password:
-                _add(':')
-                _add(quote_userinfo_part(self.password))
-            _add('@')
-        if self.host:
-            if self.family == socket.AF_INET6:
-                _add('[')
-                _add(self.host)
-                _add(']')
-            elif full_quote:
-                _add(self.host.encode('idna').decode('ascii'))
-            else:
-                _add(self.host)
-            # TODO: 0 port?
-            if self.port and self.port != self.default_port:
-                _add(':')
-                _add(unicode(self.port))
-        return u''.join(parts)
-
-    def to_text(self, full_quote=False):
-        """Render a string representing the current state of the URL
-        object.
-
-        >>> url = URL('http://listen.hatnote.com')
-        >>> url.fragment = 'en'
-        >>> print(url.to_text())
-        http://listen.hatnote.com#en
-
-        By setting the *full_quote* flag, the URL can either be fully
-        quoted or minimally quoted. The most common characteristic of
-        an encoded-URL is the presence of percent-encoded text (e.g.,
-        %60).  Unquoted URLs are more readable and suitable
-        for display, whereas fully-quoted URLs are more conservative
-        and generally necessary for sending over the network.
-        """
-        scheme = self.scheme
-        path = u'/'.join([quote_path_part(p, full_quote=full_quote)
-                          for p in self.path_parts])
-        authority = self.get_authority(full_quote=full_quote,
-                                       with_userinfo=True)
-        query_string = self.query_params.to_text(full_quote=full_quote)
-        fragment = quote_fragment_part(self.fragment, full_quote=full_quote)
-
-        parts = []
-        _add = parts.append
-        if scheme:
-            _add(scheme)
-            _add(':')
-        if authority:
-            _add('//')
-            _add(authority)
-        elif (scheme and path[:2] != '//' and self.uses_netloc):
-            _add('//')
-        if path:
-            if scheme and authority and path[:1] != '/':
-                _add('/')
-                # TODO: i think this is here because relative paths
-                # with absolute authorities = undefined
-            _add(path)
-        if query_string:
-            _add('?')
-            _add(query_string)
-        if fragment:
-            _add('#')
-            _add(fragment)
-        return u''.join(parts)
-
-    def __repr__(self):
-        cn = self.__class__.__name__
-        return u'%s(%r)' % (cn, self.to_text())
-
-    def __str__(self):
-        return self.to_text()
-
-    def __unicode__(self):
-        return self.to_text()
-
-    def __eq__(self, other):
-        for attr in self._cmp_attrs:
-            if not getattr(self, attr) == getattr(other, attr, None):
-                return False
-        return True
-
-    def __ne__(self, other):
-        return not self == other
-
-
-try:
-    from socket import inet_pton
-except ImportError:
-    # from https://gist.github.com/nnemkin/4966028
-    import ctypes
-
-    class _sockaddr(ctypes.Structure):
-        _fields_ = [("sa_family", ctypes.c_short),
-                    ("__pad1", ctypes.c_ushort),
-                    ("ipv4_addr", ctypes.c_byte * 4),
-                    ("ipv6_addr", ctypes.c_byte * 16),
-                    ("__pad2", ctypes.c_ulong)]
-
-    WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
-    WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
-
-    def inet_pton(address_family, ip_string):
-        addr = _sockaddr()
-        ip_string = ip_string.encode('ascii')
-        addr.sa_family = address_family
-        addr_size = ctypes.c_int(ctypes.sizeof(addr))
-
-        if WSAStringToAddressA(ip_string, address_family, None, ctypes.byref(addr), ctypes.byref(addr_size)) != 0:
-            raise socket.error(ctypes.FormatError())
-
-        if address_family == socket.AF_INET:
-            return ctypes.string_at(addr.ipv4_addr, 4)
-        if address_family == socket.AF_INET6:
-            return ctypes.string_at(addr.ipv6_addr, 16)
-        raise socket.error('unknown address family')
-
-
-def parse_host(host):
-    """\
-    Low-level function used to parse the host portion of a URL.
-
-    Returns a tuple of (family, host) where *family* is a
-    :mod:`socket` module constant or ``None``, and host is a string.
-
-    >>> parse_host('googlewebsite.com') == (None, 'googlewebsite.com')
-    True
-    >>> parse_host('[::1]') == (socket.AF_INET6, '::1')
-    True
-    >>> parse_host('192.168.1.1') == (socket.AF_INET, '192.168.1.1')
-    True
-
-    Odd doctest formatting above due to py3's switch from int to enums
-    for :mod:`socket` constants.
-
-    """
-    if not host:
-        return None, u''
-    if u':' in host and u'[' == host[0] and u']' == host[-1]:
-        host = host[1:-1]
-        try:
-            inet_pton(socket.AF_INET6, host)
-        except socket.error as se:
-            raise URLParseError('invalid IPv6 host: %r (%r)' % (host, se))
-        except UnicodeEncodeError:
-            pass  # TODO: this can't be a real host right?
-        else:
-            family = socket.AF_INET6
-            return family, host
-    try:
-        inet_pton(socket.AF_INET, host)
-    except (socket.error, UnicodeEncodeError):
-        family = None  # not an IP
-    else:
-        family = socket.AF_INET
-    return family, host
-
-
-def parse_url(url_text):
-    """\
-    Used to parse the text for a single URL into a dictionary, used
-    internally by the :class:`URL` type.
-
-    Note that "URL" has a very narrow, standards-based
-    definition. While :func:`parse_url` may raise
-    :class:`URLParseError` under a very limited number of conditions,
-    such as non-integer port, a surprising number of strings are
-    technically valid URLs. For instance, the text ``"url"`` is a
-    valid URL, because it is a relative path.
-
-    In short, do not expect this function to validate form inputs or
-    other more colloquial usages of URLs.
-
-    >>> res = parse_url('http://127.0.0.1:3000/?a=1')
-    >>> sorted(res.keys())  # res is a basic dictionary
-    ['_netloc_sep', 'authority', 'family', 'fragment', 'host', 'password', 'path', 'port', 'query', 'scheme', 'username']
-    """
-    url_text = unicode(url_text)
-    # raise TypeError('parse_url expected text, not %r' % url_str)
-    um = _URL_RE.match(url_text)
-    try:
-        gs = um.groupdict()
-    except AttributeError:
-        raise URLParseError('could not parse url: %r' % url_text)
-
-    au_text = gs['authority']
-    user, pw, hostinfo = None, None, au_text
-
-    if au_text:
-        userinfo, sep, hostinfo = au_text.rpartition('@')
-        if sep:
-            # TODO: empty userinfo error?
-            user, _, pw = userinfo.partition(':')
-
-    host, port = None, None
-    if hostinfo:
-        host, sep, port_str = hostinfo.partition(u':')
-        if sep:
-            if host and host[0] == u'[' and u']' in port_str:
-                host_right, _, port_str = port_str.partition(u']')
-                host = host + u':' + host_right + u']'
-                if port_str and port_str[0] == u':':
-                    port_str = port_str[1:]
-
-            try:
-                port = int(port_str)
-            except ValueError:
-                if port_str:  # empty ports ok according to RFC 3986 6.2.3
-                    raise URLParseError('expected integer for port, not %r'
-                                        % port_str)
-                port = None
-
-    family, host = parse_host(host)
-
-    gs['username'] = user
-    gs['password'] = pw
-    gs['family'] = family
-    gs['host'] = host
-    gs['port'] = port
-    return gs
-
-
-DEFAULT_PARSED_URL = parse_url('')
-
-
-def parse_qsl(qs, keep_blank_values=True, encoding=DEFAULT_ENCODING):
-    """
-    Converts a query string into a list of (key, value) pairs.
-    """
-    pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
-    ret = []
-    for pair in pairs:
-        if not pair:
-            continue
-        key, _, value = pair.partition('=')
-        if not value:
-            if keep_blank_values:
-                value = None
-            else:
-                continue
-        key = unquote(key.replace('+', ' '))
-        if value:
-            value = unquote(value.replace('+', ' '))
-        ret.append((key, value))
-    return ret
-
-
-"""
-# What follows is the OrderedMultiDict from dictutils.py, circa
-# 20161021, used for the QueryParamDict, toward the bottom.
-"""
-
-try:
-    from collections.abc import KeysView, ValuesView, ItemsView
-except ImportError:
-    from collections import KeysView, ValuesView, ItemsView
-
-try:
-    from itertools import izip_longest
-except ImportError:
-    from itertools import zip_longest as izip_longest
-
-try:
-    from typeutils import make_sentinel
-    _MISSING = make_sentinel(var_name='_MISSING')
-except ImportError:
-    _MISSING = object()
-
-
-PREV, NEXT, KEY, VALUE, SPREV, SNEXT = range(6)
-
-
-class OrderedMultiDict(dict):
-    """A MultiDict is a dictionary that can have multiple values per key
-    and the OrderedMultiDict (OMD) is a MultiDict that retains
-    original insertion order. Common use cases include:
-
-      * handling query strings parsed from URLs
-      * inverting a dictionary to create a reverse index (values to keys)
-      * stacking data from multiple dictionaries in a non-destructive way
-
-    The OrderedMultiDict constructor is identical to the built-in
-    :class:`dict`, and overall the API is constitutes an intuitive
-    superset of the built-in type:
-
-    >>> omd = OrderedMultiDict()
-    >>> omd['a'] = 1
-    >>> omd['b'] = 2
-    >>> omd.add('a', 3)
-    >>> omd.get('a')
-    3
-    >>> omd.getlist('a')
-    [1, 3]
-
-    Some non-:class:`dict`-like behaviors also make an appearance,
-    such as support for :func:`reversed`:
-
-    >>> list(reversed(omd))
-    ['b', 'a']
-
-    Note that unlike some other MultiDicts, this OMD gives precedence
-    to the most recent value added. ``omd['a']`` refers to ``3``, not
-    ``1``.
-
-    >>> omd
-    OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)])
-    >>> omd.poplast('a')
-    3
-    >>> omd
-    OrderedMultiDict([('a', 1), ('b', 2)])
-    >>> omd.pop('a')
-    1
-    >>> omd
-    OrderedMultiDict([('b', 2)])
-
-    Note that calling :func:`dict` on an OMD results in a dict of keys
-    to *lists* of values:
-
-    >>> from pprint import pprint as pp  # ensuring proper key ordering
-    >>> omd = OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)])
-    >>> pp(dict(omd))
-    {'a': 3, 'b': 2}
-
-    Note that modifying those lists will modify the OMD. If you want a
-    safe-to-modify or flat dictionary, use :meth:`OrderedMultiDict.todict()`.
-
-    >>> pp(omd.todict())
-    {'a': 3, 'b': 2}
-    >>> pp(omd.todict(multi=True))
-    {'a': [1, 3], 'b': [2]}
-
-    With ``multi=False``, items appear with the keys in to original
-    insertion order, alongside the most-recently inserted value for
-    that key.
-
-    >>> OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]).items(multi=False)
-    [('a', 3), ('b', 2)]
-
-    """
-    def __init__(self, *args, **kwargs):
-        if len(args) > 1:
-            raise TypeError('%s expected at most 1 argument, got %s'
-                            % (self.__class__.__name__, len(args)))
-        super(OrderedMultiDict, self).__init__()
-
-        self._clear_ll()
-        if args:
-            self.update_extend(args[0])
-        if kwargs:
-            self.update(kwargs)
-
-    def _clear_ll(self):
-        try:
-            _map = self._map
-        except AttributeError:
-            _map = self._map = {}
-            self.root = []
-        _map.clear()
-        self.root[:] = [self.root, self.root, None]
-
-    def _insert(self, k, v):
-        root = self.root
-        cells = self._map.setdefault(k, [])
-        last = root[PREV]
-        cell = [last, root, k, v]
-        last[NEXT] = root[PREV] = cell
-        cells.append(cell)
-
-    def add(self, k, v):
-        """Add a single value *v* under a key *k*. Existing values under *k*
-        are preserved.
-        """
-        values = super(OrderedMultiDict, self).setdefault(k, [])
-        self._insert(k, v)
-        values.append(v)
-
-    def addlist(self, k, v):
-        """Add an iterable of values underneath a specific key, preserving
-        any values already under that key.
-
-        >>> omd = OrderedMultiDict([('a', -1)])
-        >>> omd.addlist('a', range(3))
-        >>> omd
-        OrderedMultiDict([('a', -1), ('a', 0), ('a', 1), ('a', 2)])
-
-        Called ``addlist`` for consistency with :meth:`getlist`, but
-        tuples and other sequences and iterables work.
-        """
-        self_insert = self._insert
-        values = super(OrderedMultiDict, self).setdefault(k, [])
-        for subv in v:
-            self_insert(k, subv)
-        values.extend(v)
-
-    def get(self, k, default=None):
-        """Return the value for key *k* if present in the dictionary, else
-        *default*. If *default* is not given, ``None`` is returned.
-        This method never raises a :exc:`KeyError`.
-
-        To get all values under a key, use :meth:`OrderedMultiDict.getlist`.
-        """
-        return super(OrderedMultiDict, self).get(k, [default])[-1]
-
-    def getlist(self, k, default=_MISSING):
-        """Get all values for key *k* as a list, if *k* is in the
-        dictionary, else *default*. The list returned is a copy and
-        can be safely mutated. If *default* is not given, an empty
-        :class:`list` is returned.
-        """
-        try:
-            return super(OrderedMultiDict, self).__getitem__(k)[:]
-        except KeyError:
-            if default is _MISSING:
-                return []
-            return default
-
-    def clear(self):
-        "Empty the dictionary."
-        super(OrderedMultiDict, self).clear()
-        self._clear_ll()
-
-    def setdefault(self, k, default=_MISSING):
-        """If key *k* is in the dictionary, return its value. If not, insert
-        *k* with a value of *default* and return *default*. *default*
-        defaults to ``None``. See :meth:`dict.setdefault` for more
-        information.
-        """
-        if not super(OrderedMultiDict, self).__contains__(k):
-            self[k] = None if default is _MISSING else default
-        return self[k]
-
-    def copy(self):
-        "Return a shallow copy of the dictionary."
-        return self.__class__(self.iteritems(multi=True))
-
-    @classmethod
-    def fromkeys(cls, keys, default=None):
-        """Create a dictionary from a list of keys, with all the values
-        set to *default*, or ``None`` if *default* is not set.
-        """
-        return cls([(k, default) for k in keys])
-
-    def update(self, E, **F):
-        """Add items from a dictionary or iterable (and/or keyword arguments),
-        overwriting values under an existing key. See
-        :meth:`dict.update` for more details.
-        """
-        # E and F are throwback names to the dict() __doc__
-        if E is self:
-            return
-        self_add = self.add
-        if isinstance(E, OrderedMultiDict):
-            for k in E:
-                if k in self:
-                    del self[k]
-            for k, v in E.iteritems(multi=True):
-                self_add(k, v)
-        elif hasattr(E, 'keys'):
-            for k in E.keys():
-                self[k] = E[k]
-        else:
-            seen = set()
-            seen_add = seen.add
-            for k, v in E:
-                if k not in seen and k in self:
-                    del self[k]
-                    seen_add(k)
-                self_add(k, v)
-        for k in F:
-            self[k] = F[k]
-        return
-
-    def update_extend(self, E, **F):
-        """Add items from a dictionary, iterable, and/or keyword
-        arguments without overwriting existing items present in the
-        dictionary. Like :meth:`update`, but adds to existing keys
-        instead of overwriting them.
-        """
-        if E is self:
-            iterator = iter(E.items())
-        elif isinstance(E, OrderedMultiDict):
-            iterator = E.iteritems(multi=True)
-        elif hasattr(E, 'keys'):
-            iterator = ((k, E[k]) for k in E.keys())
-        else:
-            iterator = E
-
-        self_add = self.add
-        for k, v in iterator:
-            self_add(k, v)
-
-    def __setitem__(self, k, v):
-        if super(OrderedMultiDict, self).__contains__(k):
-            self._remove_all(k)
-        self._insert(k, v)
-        super(OrderedMultiDict, self).__setitem__(k, [v])
-
-    def __getitem__(self, k):
-        return super(OrderedMultiDict, self).__getitem__(k)[-1]
-
-    def __delitem__(self, k):
-        super(OrderedMultiDict, self).__delitem__(k)
-        self._remove_all(k)
-
-    def __eq__(self, other):
-        if self is other:
-            return True
-        try:
-            if len(other) != len(self):
-                return False
-        except TypeError:
-            return False
-        if isinstance(other, OrderedMultiDict):
-            selfi = self.iteritems(multi=True)
-            otheri = other.iteritems(multi=True)
-            zipped_items = izip_longest(selfi, otheri, fillvalue=(None, None))
-            for (selfk, selfv), (otherk, otherv) in zipped_items:
-                if selfk != otherk or selfv != otherv:
-                    return False
-            if not(next(selfi, _MISSING) is _MISSING
-                   and next(otheri, _MISSING) is _MISSING):
-                # leftovers  (TODO: watch for StopIteration?)
-                return False
-            return True
-        elif hasattr(other, 'keys'):
-            for selfk in self:
-                try:
-                    other[selfk] == self[selfk]
-                except KeyError:
-                    return False
-            return True
-        return False
-
-    def __ne__(self, other):
-        return not (self == other)
-
-    def pop(self, k, default=_MISSING):
-        """Remove all values under key *k*, returning the most-recently
-        inserted value. Raises :exc:`KeyError` if the key is not
-        present and no *default* is provided.
-        """
-        try:
-            return self.popall(k)[-1]
-        except KeyError:
-            if default is _MISSING:
-                raise KeyError(k)
-        return default
-
-    def popall(self, k, default=_MISSING):
-        """Remove all values under key *k*, returning them in the form of
-        a list. Raises :exc:`KeyError` if the key is not present and no
-        *default* is provided.
-        """
-        super_self = super(OrderedMultiDict, self)
-        if super_self.__contains__(k):
-            self._remove_all(k)
-        if default is _MISSING:
-            return super_self.pop(k)
-        return super_self.pop(k, default)
-
-    def poplast(self, k=_MISSING, default=_MISSING):
-        """Remove and return the most-recently inserted value under the key
-        *k*, or the most-recently inserted key if *k* is not
-        provided. If no values remain under *k*, it will be removed
-        from the OMD.  Raises :exc:`KeyError` if *k* is not present in
-        the dictionary, or the dictionary is empty.
-        """
-        if k is _MISSING:
-            if self:
-                k = self.root[PREV][KEY]
-            else:
-                raise KeyError('empty %r' % type(self))
-        try:
-            self._remove(k)
-        except KeyError:
-            if default is _MISSING:
-                raise KeyError(k)
-            return default
-        values = super(OrderedMultiDict, self).__getitem__(k)
-        v = values.pop()
-        if not values:
-            super(OrderedMultiDict, self).__delitem__(k)
-        return v
-
-    def _remove(self, k):
-        values = self._map[k]
-        cell = values.pop()
-        cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV]
-        if not values:
-            del self._map[k]
-
-    def _remove_all(self, k):
-        values = self._map[k]
-        while values:
-            cell = values.pop()
-            cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV]
-        del self._map[k]
-
-    def iteritems(self, multi=False):
-        """Iterate over the OMD's items in insertion order. By default,
-        yields only the most-recently inserted value for each key. Set
-        *multi* to ``True`` to get all inserted items.
-        """
-        root = self.root
-        curr = root[NEXT]
-        if multi:
-            while curr is not root:
-                yield curr[KEY], curr[VALUE]
-                curr = curr[NEXT]
-        else:
-            for key in self.iterkeys():
-                yield key, self[key]
-
-    def iterkeys(self, multi=False):
-        """Iterate over the OMD's keys in insertion order. By default, yields
-        each key once, according to the most recent insertion. Set
-        *multi* to ``True`` to get all keys, including duplicates, in
-        insertion order.
-        """
-        root = self.root
-        curr = root[NEXT]
-        if multi:
-            while curr is not root:
-                yield curr[KEY]
-                curr = curr[NEXT]
-        else:
-            yielded = set()
-            yielded_add = yielded.add
-            while curr is not root:
-                k = curr[KEY]
-                if k not in yielded:
-                    yielded_add(k)
-                    yield k
-                curr = curr[NEXT]
-
-    def itervalues(self, multi=False):
-        """Iterate over the OMD's values in insertion order. By default,
-        yields the most-recently inserted value per unique key.  Set
-        *multi* to ``True`` to get all values according to insertion
-        order.
-        """
-        for k, v in self.iteritems(multi=multi):
-            yield v
-
-    def todict(self, multi=False):
-        """Gets a basic :class:`dict` of the items in this dictionary. Keys
-        are the same as the OMD, values are the most recently inserted
-        values for each key.
-
-        Setting the *multi* arg to ``True`` is yields the same
-        result as calling :class:`dict` on the OMD, except that all the
-        value lists are copies that can be safely mutated.
-        """
-        if multi:
-            return dict([(k, self.getlist(k)) for k in self])
-        return dict([(k, self[k]) for k in self])
-
-    def sorted(self, key=None, reverse=False):
-        """Similar to the built-in :func:`sorted`, except this method returns
-        a new :class:`OrderedMultiDict` sorted by the provided key
-        function, optionally reversed.
-
-        Args:
-            key (callable): A callable to determine the sort key of
-              each element. The callable should expect an **item**
-              (key-value pair tuple).
-            reverse (bool): Set to ``True`` to reverse the ordering.
-
-        >>> omd = OrderedMultiDict(zip(range(3), range(3)))
-        >>> omd.sorted(reverse=True)
-        OrderedMultiDict([(2, 2), (1, 1), (0, 0)])
-
-        Note that the key function receives an **item** (key-value
-        tuple), so the recommended signature looks like:
-
-        >>> omd = OrderedMultiDict(zip('hello', 'world'))
-        >>> omd.sorted(key=lambda i: i[1])  # i[0] is the key, i[1] is the val
-        OrderedMultiDict([('o', 'd'), ('l', 'l'), ('e', 'o'), ('l', 'r'), ('h', 'w')])
-        """
-        cls = self.__class__
-        return cls(sorted(self.iteritems(), key=key, reverse=reverse))
-
-    def sortedvalues(self, key=None, reverse=False):
-        """Returns a copy of the :class:`OrderedMultiDict` with the same keys
-        in the same order as the original OMD, but the values within
-        each keyspace have been sorted according to *key* and
-        *reverse*.
-
-        Args:
-            key (callable): A single-argument callable to determine
-              the sort key of each element. The callable should expect
-              an **item** (key-value pair tuple).
-            reverse (bool): Set to ``True`` to reverse the ordering.
-
-        >>> omd = OrderedMultiDict()
-        >>> omd.addlist('even', [6, 2])
-        >>> omd.addlist('odd', [1, 5])
-        >>> omd.add('even', 4)
-        >>> omd.add('odd', 3)
-        >>> somd = omd.sortedvalues()
-        >>> somd.getlist('even')
-        [2, 4, 6]
-        >>> somd.keys(multi=True) == omd.keys(multi=True)
-        True
-        >>> omd == somd
-        False
-        >>> somd
-        OrderedMultiDict([('even', 2), ('even', 4), ('odd', 1), ('odd', 3), ('even', 6), ('odd', 5)])
-
-        As demonstrated above, contents and key order are
-        retained. Only value order changes.
-        """
-        try:
-            superself_iteritems = super(OrderedMultiDict, self).iteritems()
-        except AttributeError:
-            superself_iteritems = super(OrderedMultiDict, self).items()
-        # (not reverse) because they pop off in reverse order for reinsertion
-        sorted_val_map = dict([(k, sorted(v, key=key, reverse=(not reverse)))
-                               for k, v in superself_iteritems])
-        ret = self.__class__()
-        for k in self.iterkeys(multi=True):
-            ret.add(k, sorted_val_map[k].pop())
-        return ret
-
-    def inverted(self):
-        """Returns a new :class:`OrderedMultiDict` with values and keys
-        swapped, like creating dictionary transposition or reverse
-        index.  Insertion order is retained and all keys and values
-        are represented in the output.
-
-        >>> omd = OMD([(0, 2), (1, 2)])
-        >>> omd.inverted().getlist(2)
-        [0, 1]
-
-        Inverting twice yields a copy of the original:
-
-        >>> omd.inverted().inverted()
-        OrderedMultiDict([(0, 2), (1, 2)])
-        """
-        return self.__class__((v, k) for k, v in self.iteritems(multi=True))
-
-    def counts(self):
-        """Returns a mapping from key to number of values inserted under that
-        key. Like :py:class:`collections.Counter`, but returns a new
-        :class:`OrderedMultiDict`.
-        """
-        # Returns an OMD because Counter/OrderedDict may not be
-        # available, and neither Counter nor dict maintain order.
-        super_getitem = super(OrderedMultiDict, self).__getitem__
-        return self.__class__((k, len(super_getitem(k))) for k in self)
-
-    def keys(self, multi=False):
-        """Returns a list containing the output of :meth:`iterkeys`.  See
-        that method's docs for more details.
-        """
-        return list(self.iterkeys(multi=multi))
-
-    def values(self, multi=False):
-        """Returns a list containing the output of :meth:`itervalues`.  See
-        that method's docs for more details.
-        """
-        return list(self.itervalues(multi=multi))
-
-    def items(self, multi=False):
-        """Returns a list containing the output of :meth:`iteritems`.  See
-        that method's docs for more details.
-        """
-        return list(self.iteritems(multi=multi))
-
-    def __iter__(self):
-        return self.iterkeys()
-
-    def __reversed__(self):
-        root = self.root
-        curr = root[PREV]
-        lengths = {}
-        lengths_sd = lengths.setdefault
-        get_values = super(OrderedMultiDict, self).__getitem__
-        while curr is not root:
-            k = curr[KEY]
-            vals = get_values(k)
-            if lengths_sd(k, 1) == len(vals):
-                yield k
-            lengths[k] += 1
-            curr = curr[PREV]
-
-    def __repr__(self):
-        cn = self.__class__.__name__
-        kvs = ', '.join([repr((k, v)) for k, v in self.iteritems(multi=True)])
-        return '%s([%s])' % (cn, kvs)
-
-    def viewkeys(self):
-        "OMD.viewkeys() -> a set-like object providing a view on OMD's keys"
-        return KeysView(self)
-
-    def viewvalues(self):
-        "OMD.viewvalues() -> an object providing a view on OMD's values"
-        return ValuesView(self)
-
-    def viewitems(self):
-        "OMD.viewitems() -> a set-like object providing a view on OMD's items"
-        return ItemsView(self)
-
-
-try:
-    # try to import the built-in one anyways
-    from boltons.dictutils import OrderedMultiDict
-except ImportError:
-    pass
-
-OMD = OrderedMultiDict
-
-
-class QueryParamDict(OrderedMultiDict):
-    """A subclass of :class:`~dictutils.OrderedMultiDict` specialized for
-    representing query string values. Everything is fully unquoted on
-    load and all parsed keys and values are strings by default.
-
-    As the name suggests, multiple values are supported and insertion
-    order is preserved.
-
-    >>> qp = QueryParamDict.from_text(u'key=val1&key=val2&utm_source=rtd')
-    >>> qp.getlist('key')
-    [u'val1', u'val2']
-    >>> qp['key']
-    u'val2'
-    >>> qp.add('key', 'val3')
-    >>> qp.to_text()
-    'key=val1&key=val2&utm_source=rtd&key=val3'
-
-    See :class:`~dictutils.OrderedMultiDict` for more API features.
-    """
-
-    @classmethod
-    def from_text(cls, query_string):
-        """
-        Parse *query_string* and return a new :class:`QueryParamDict`.
-        """
-        pairs = parse_qsl(query_string, keep_blank_values=True)
-        return cls(pairs)
-
-    def to_text(self, full_quote=False):
-        """
-        Render and return a query string.
-
-        Args:
-           full_quote (bool): Whether or not to percent-quote special
-              characters or leave them decoded for readability.
-        """
-        ret_list = []
-        for k, v in self.iteritems(multi=True):
-            key = quote_query_part(to_unicode(k), full_quote=full_quote)
-            if v is None:
-                ret_list.append(key)
-            else:
-                val = quote_query_part(to_unicode(v), full_quote=full_quote)
-                ret_list.append(u'='.join((key, val)))
-        return u'&'.join(ret_list)
-
-# TODO: cleanup OMD/cachedproperty etc.?
-
-# end urlutils.py