Complete an OAuth handshake with MediaWiki by exchanging an

    :Parameters:
        mw_uri : `str`
            The base URI of the MediaWiki installation.  Note that the URI
            should end in ``"index.php"``.
        consumer_token : :class:`~mwoauth.ConsumerToken`
            A key/secret pair representing you, the consumer.
        request_token : :class:`~mwoauth.RequestToken`
            A temporary token representing the user.  Returned by
            `initiate()`.
        response_qs : `bytes`
            The query string of the URL that MediaWiki forwards the user back
            after authorization.

    :Returns:
        An `AccessToken` containing an authorized key/secret pair that
        can be stored and used by you.

def complete(mw_uri, consumer_token, request_token, response_qs,
             user_agent=defaults.USER_AGENT):
    """
    Complete an OAuth handshake with MediaWiki by exchanging an

    :Parameters:
        mw_uri : `str`
            The base URI of the MediaWiki installation.  Note that the URI
            should end in ``"index.php"``.
        consumer_token : :class:`~mwoauth.ConsumerToken`
            A key/secret pair representing you, the consumer.
        request_token : :class:`~mwoauth.RequestToken`
            A temporary token representing the user.  Returned by
            `initiate()`.
        response_qs : `bytes`
            The query string of the URL that MediaWiki forwards the user back
            after authorization.

    :Returns:
        An `AccessToken` containing an authorized key/secret pair that
        can be stored and used by you.
    """

    callback_data = parse_qs(_ensure_bytes(response_qs))

    if callback_data is None or callback_data == {}:
        raise OAuthException(
            "Expected URL query string, but got " +
            "something else instead: {0}".format(str(response_qs)))

    elif b('oauth_token') not in callback_data or \
         b('oauth_verifier') not in callback_data:

        raise OAuthException(
            "Query string lacks token information: "
            "{0}".format(repr(callback_data)))

    else:
        # Check if the query string references the right temp resource owner
        # key
        request_token_key = callback_data.get(b("oauth_token"))[0]
        # Get the verifier token
        verifier = callback_data.get(b("oauth_verifier"))[0]

    if not request_token.key == request_token_key:
        raise OAuthException(
            "Unexpect request token key " +
            "{0}, expected {1}.".format(request_token_key, request_token.key))

    # Construct a new auth with the verifier
    auth = OAuth1(consumer_token.key,
                  client_secret=consumer_token.secret,
                  resource_owner_key=request_token.key,
                  resource_owner_secret=request_token.secret,
                  verifier=verifier)

    # Send the verifier and ask for an authorized resource owner key/secret
    r = requests.post(url=mw_uri,
                      params={'title': "Special:OAuth/token"},
                      auth=auth,
                      headers={'User-Agent': user_agent})

    # Parse response and construct an authorized resource owner
    credentials = parse_qs(r.content)

    if credentials is None:
        raise OAuthException(
            "Expected x-www-form-urlencoded response, " +
            "but got some else instead: {0}".format(r.content))

    access_token = AccessToken(
        credentials.get(b('oauth_token'))[0],
        credentials.get(b('oauth_token_secret'))[0]
    )

    return access_token