ietfparse/algorithms.py
def rewrite_url(input_url, **kwargs):
    """
    Create a new URL from `input_url` with modifications applied.

    :param str input_url: the URL to modify

    :keyword str fragment: if specified, this keyword sets the
        fragment portion of the URL.  A value of :data:`None`
        will remove the fragment portion of the URL.
    :keyword str host: if specified, this keyword sets the host
        portion of the network location.  A value of :data:`None`
        will remove the network location portion of the URL.
    :keyword str password: if specified, this keyword sets the
        password portion of the URL.  A value of :data:`None` will
        remove the password from the URL.
    :keyword str path: if specified, this keyword sets the path
        portion of the URL.  A value of :data:`None` will remove
        the path from the URL.
    :keyword int port: if specified, this keyword sets the port
        portion of the network location.  A value of :data:`None`
        will remove the port from the URL.
    :keyword query: if specified, this keyword sets the query portion
        of the URL.  See the comments for a description of this
        parameter.
    :keyword str scheme: if specified, this keyword sets the scheme
        portion of the URL.  A value of :data:`None` will remove
        the scheme.  Note that this will make the URL relative and
        may have unintended consequences.
    :keyword str user: if specified, this keyword sets the user
        portion of the URL.  A value of :data:`None` will remove
        the user and password portions.

    :keyword bool enable_long_host: if this keyword is specified
        and it is :data:`True`, then the host name length restriction
        from :rfc:`3986#section-3.2.2` is relaxed.
    :keyword bool encode_with_idna: if this keyword is specified
        and it is :data:`True`, then the ``host`` parameter will be
        encoded using IDN.  If this value is provided as :data:`False`,
        then the percent-encoding scheme is used instead.  If this
        parameter is omitted or included with a different value, then
        the ``host`` parameter is processed using :data:`IDNA_SCHEMES`.

    :return: the modified URL
    :raises ValueError: when a keyword parameter is given an invalid
        value

    If the `host` parameter is specified and not :data:`None`, then
    it will be processed as an Internationalized Domain Name (IDN)
    if the scheme appears in :data:`IDNA_SCHEMES`.  Otherwise, it
    will be encoded as UTF-8 and percent encoded.

    The handling of the `query` parameter requires some additional
    explanation.  You can specify a query value in three different
    ways - as a *mapping*, as a *sequence* of pairs, or as a *string*.
    This flexibility makes it possible to meet the wide range of
    finicky use cases.

    *If the query parameter is a mapping*, then the key + value pairs
    are *sorted by the key* before they are encoded.  Use this method
    whenever possible.

    *If the query parameter is a sequence of pairs*, then each pair
    is encoded *in the given order*.  Use this method if you require
    that parameter order is controlled.

    *If the query parameter is a string*, then it is *used as-is*.
    This form SHOULD BE AVOIDED since it can easily result in broken
    URLs since *no URL escaping is performed*.  This is the obvious
    pass through case that is almost always present.

    """
    scheme, netloc, path, query, fragment = parse.urlsplit(input_url)

    if 'scheme' in kwargs:
        scheme = kwargs['scheme']

    ident, host_n_port = parse.splituser(netloc)

    user, password = parse.splitpasswd(ident) if ident else (None, None)
    if 'user' in kwargs:
        user = kwargs['user']
    elif user is not None:
        user = parse.unquote_to_bytes(user).decode('utf-8')
    if 'password' in kwargs:
        password = kwargs['password']
    elif password is not None:
        password = parse.unquote_to_bytes(password).decode('utf-8')
    ident = _create_url_identifier(user, password)

    host, port = parse.splitnport(host_n_port, defport=None)
    if 'host' in kwargs:
        host = kwargs['host']
        if host is not None:
            host = _normalize_host(
                host,
                enable_long_host=kwargs.get('enable_long_host', False),
                encode_with_idna=kwargs.get('encode_with_idna', None),
                scheme=scheme,
            )

    if 'port' in kwargs:
        port = kwargs['port']
        if port is not None:
            port = int(kwargs['port'])
            if port < 0:
                raise ValueError('port is required to be non-negative')

    if host is None or host == '':
        host_n_port = None
    elif port is None:
        host_n_port = host
    else:
        host_n_port = '{0}:{1}'.format(host, port)

    if 'path' in kwargs:
        path = kwargs['path']
        if path is None:
            path = '/'
        else:
            path = parse.quote(path.encode('utf-8'), safe=PATH_SAFE_CHARS)

    netloc = '{0}@{1}'.format(ident, host_n_port) if ident else host_n_port

    if 'query' in kwargs:
        new_query = kwargs['query']
        if new_query is None:
            query = None
        else:
            params = []
            try:
                for param in sorted(new_query.keys()):
                    params.append((param, new_query[param]))
            except AttributeError:  # arg is None or not a dict
                pass

            if not params:  # maybe a sequence of tuples?
                try:
                    params = [(param, value) for param, value in new_query]
                except ValueError:  # guess not...
                    pass

            if params:
                query = parse.urlencode(params)
            else:
                query = new_query

    if 'fragment' in kwargs:
        fragment = kwargs['fragment']
        if fragment is not None:
            fragment = parse.quote(fragment.encode('utf-8'),
                                   safe=FRAGMENT_SAFE_CHARS)

    # The following is necessary to get around some interesting special
    # case code in urllib.parse._coerce_args in Python 3.4.  Setting
    # scheme to None causes urlunsplit to assume that all non-``None``
    # parameters with be byte strings....
    if scheme is None:
        scheme = ''

    return parse.urlunsplit((scheme, netloc, path, query, fragment))
def rewrite_url(input_url, **kwargs):
    """
    Create a new URL from `input_url` with modifications applied.

    :param str input_url: the URL to modify

    :keyword str fragment: if specified, this keyword sets the
        fragment portion of the URL.  A value of :data:`None`
        will remove the fragment portion of the URL.
    :keyword str host: if specified, this keyword sets the host
        portion of the network location.  A value of :data:`None`
        will remove the network location portion of the URL.
    :keyword str password: if specified, this keyword sets the
        password portion of the URL.  A value of :data:`None` will
        remove the password from the URL.
    :keyword str path: if specified, this keyword sets the path
        portion of the URL.  A value of :data:`None` will remove
        the path from the URL.
    :keyword int port: if specified, this keyword sets the port
        portion of the network location.  A value of :data:`None`
        will remove the port from the URL.
    :keyword query: if specified, this keyword sets the query portion
        of the URL.  See the comments for a description of this
        parameter.
    :keyword str scheme: if specified, this keyword sets the scheme
        portion of the URL.  A value of :data:`None` will remove
        the scheme.  Note that this will make the URL relative and
        may have unintended consequences.
    :keyword str user: if specified, this keyword sets the user
        portion of the URL.  A value of :data:`None` will remove
        the user and password portions.

    :keyword bool enable_long_host: if this keyword is specified
        and it is :data:`True`, then the host name length restriction
        from :rfc:`3986#section-3.2.2` is relaxed.
    :keyword bool encode_with_idna: if this keyword is specified
        and it is :data:`True`, then the ``host`` parameter will be
        encoded using IDN.  If this value is provided as :data:`False`,
        then the percent-encoding scheme is used instead.  If this
        parameter is omitted or included with a different value, then
        the ``host`` parameter is processed using :data:`IDNA_SCHEMES`.

    :return: the modified URL
    :raises ValueError: when a keyword parameter is given an invalid
        value

    If the `host` parameter is specified and not :data:`None`, then
    it will be processed as an Internationalized Domain Name (IDN)
    if the scheme appears in :data:`IDNA_SCHEMES`.  Otherwise, it
    will be encoded as UTF-8 and percent encoded.

    The handling of the `query` parameter requires some additional
    explanation.  You can specify a query value in three different
    ways - as a *mapping*, as a *sequence* of pairs, or as a *string*.
    This flexibility makes it possible to meet the wide range of
    finicky use cases.

    *If the query parameter is a mapping*, then the key + value pairs
    are *sorted by the key* before they are encoded.  Use this method
    whenever possible.

    *If the query parameter is a sequence of pairs*, then each pair
    is encoded *in the given order*.  Use this method if you require
    that parameter order is controlled.

    *If the query parameter is a string*, then it is *used as-is*.
    This form SHOULD BE AVOIDED since it can easily result in broken
    URLs since *no URL escaping is performed*.  This is the obvious
    pass through case that is almost always present.

    """
    scheme, netloc, path, query, fragment = parse.urlsplit(input_url)

    if 'scheme' in kwargs:
        scheme = kwargs['scheme']

    ident, host_n_port = parse.splituser(netloc)

    user, password = parse.splitpasswd(ident) if ident else (None, None)
    if 'user' in kwargs:
        user = kwargs['user']
    elif user is not None:
        user = parse.unquote_to_bytes(user).decode('utf-8')
    if 'password' in kwargs:
        password = kwargs['password']
    elif password is not None:
        password = parse.unquote_to_bytes(password).decode('utf-8')
    ident = _create_url_identifier(user, password)

    host, port = parse.splitnport(host_n_port, defport=None)
    if 'host' in kwargs:
        host = kwargs['host']
        if host is not None:
            host = _normalize_host(
                host,
                enable_long_host=kwargs.get('enable_long_host', False),
                encode_with_idna=kwargs.get('encode_with_idna', None),
                scheme=scheme,
            )

    if 'port' in kwargs:
        port = kwargs['port']
        if port is not None:
            port = int(kwargs['port'])
            if port < 0:
                raise ValueError('port is required to be non-negative')

    if host is None or host == '':
        host_n_port = None
    elif port is None:
        host_n_port = host
    else:
        host_n_port = '{0}:{1}'.format(host, port)

    if 'path' in kwargs:
        path = kwargs['path']
        if path is None:
            path = '/'
        else:
            path = parse.quote(path.encode('utf-8'), safe=PATH_SAFE_CHARS)

    netloc = '{0}@{1}'.format(ident, host_n_port) if ident else host_n_port

    if 'query' in kwargs:
        new_query = kwargs['query']
        if new_query is None:
            query = None
        else:
            params = []
            try:
                for param in sorted(new_query.keys()):
                    params.append((param, new_query[param]))
            except AttributeError:  # arg is None or not a dict
                pass

            if not params:  # maybe a sequence of tuples?
                try:
                    params = [(param, value) for param, value in new_query]
                except ValueError:  # guess not...
                    pass

            if params:
                query = parse.urlencode(params)
            else:
                query = new_query

    if 'fragment' in kwargs:
        fragment = kwargs['fragment']
        if fragment is not None:
            fragment = parse.quote(fragment.encode('utf-8'),
                                   safe=FRAGMENT_SAFE_CHARS)

    # The following is necessary to get around some interesting special
    # case code in urllib.parse._coerce_args in Python 3.4.  Setting
    # scheme to None causes urlunsplit to assume that all non-``None``
    # parameters with be byte strings....
    if scheme is None:
        scheme = ''

    return parse.urlunsplit((scheme, netloc, path, query, fragment))
Create a new URL from `input_url` with modifications applied.

    :param str input_url: the URL to modify

    :keyword str fragment: if specified, this keyword sets the
        fragment portion of the URL.  A value of :data:`None`
        will remove the fragment portion of the URL.
    :keyword str host: if specified, this keyword sets the host
        portion of the network location.  A value of :data:`None`
        will remove the network location portion of the URL.
    :keyword str password: if specified, this keyword sets the
        password portion of the URL.  A value of :data:`None` will
        remove the password from the URL.
    :keyword str path: if specified, this keyword sets the path
        portion of the URL.  A value of :data:`None` will remove
        the path from the URL.
    :keyword int port: if specified, this keyword sets the port
        portion of the network location.  A value of :data:`None`
        will remove the port from the URL.
    :keyword query: if specified, this keyword sets the query portion
        of the URL.  See the comments for a description of this
        parameter.
    :keyword str scheme: if specified, this keyword sets the scheme
        portion of the URL.  A value of :data:`None` will remove
        the scheme.  Note that this will make the URL relative and
        may have unintended consequences.
    :keyword str user: if specified, this keyword sets the user
        portion of the URL.  A value of :data:`None` will remove
        the user and password portions.

    :keyword bool enable_long_host: if this keyword is specified
        and it is :data:`True`, then the host name length restriction
        from :rfc:`3986#section-3.2.2` is relaxed.
    :keyword bool encode_with_idna: if this keyword is specified
        and it is :data:`True`, then the ``host`` parameter will be
        encoded using IDN.  If this value is provided as :data:`False`,
        then the percent-encoding scheme is used instead.  If this
        parameter is omitted or included with a different value, then
        the ``host`` parameter is processed using :data:`IDNA_SCHEMES`.

    :return: the modified URL
    :raises ValueError: when a keyword parameter is given an invalid
        value

    If the `host` parameter is specified and not :data:`None`, then
    it will be processed as an Internationalized Domain Name (IDN)
    if the scheme appears in :data:`IDNA_SCHEMES`.  Otherwise, it
    will be encoded as UTF-8 and percent encoded.

    The handling of the `query` parameter requires some additional
    explanation.  You can specify a query value in three different
    ways - as a *mapping*, as a *sequence* of pairs, or as a *string*.
    This flexibility makes it possible to meet the wide range of
    finicky use cases.

    *If the query parameter is a mapping*, then the key + value pairs
    are *sorted by the key* before they are encoded.  Use this method
    whenever possible.

    *If the query parameter is a sequence of pairs*, then each pair
    is encoded *in the given order*.  Use this method if you require
    that parameter order is controlled.

    *If the query parameter is a string*, then it is *used as-is*.
    This form SHOULD BE AVOIDED since it can easily result in broken
    URLs since *no URL escaping is performed*.  This is the obvious
    pass through case that is almost always present.