<commit_before># coding: utf-8
"""
Uploadcare REST client.

It is JSON REST request abstraction layer that is used by the
``pyuploadcare.api_resources``.

"""

from __future__ import unicode_literals
import email.utils
import hashlib
import hmac
import re
import logging
import json
import socket

import requests
import six

if six.PY3:
    from urllib.parse import urljoin, urlsplit
else:
    from urlparse import urljoin, urlsplit

from . import conf, __version__
from .exceptions import (
    APIConnectionError, AuthenticationError, APIError, InvalidRequestError,
)


logger = logging.getLogger("pyuploadcare")

# Use session for keep-alive connections.
session = requests.session()

def _get_timeout(timeout):
    if timeout is not conf.DEFAULT:
        return timeout
    if conf.timeout is not conf.DEFAULT:
        return conf.timeout
    return socket.getdefaulttimeout()


def rest_request(verb, path, data=None, timeout=conf.DEFAULT):
    """Makes REST API request and returns response as ``dict``.

    It provides auth headers as well and takes settings from ``conf`` module.

    Make sure that given ``path`` does not contain leading slash.

    Usage example::

        >>> rest_request('GET', 'files/?limit=10')
        {
            'next': 'https://api.uploadcare.com/files/?limit=10&page=2',
            'total': 1241,
            'page': 1,
            'pages': 125,
            'per_page': 10,
            'previous': None,
            'results': [
                # ...
                {
                    # ...
                    'uuid': 1921953c-5d94-4e47-ba36-c2e1dd165e1a,
                    # ...
                },
                # ...
            ]
        }

    """
    assert not path.startswith('/'), path
    url = urljoin(conf.api_base, path)
    url_parts = urlsplit(url)

    if url_parts.query:
        path = url_parts.path + '?' + url_parts.query
    else:
        path = url_parts.path

    content = ''
    if data is not None:
        content = json.dumps(data)

    content_type = 'application/json'
    content_md5 = hashlib.md5(content.encode('utf-8')).hexdigest()
    date = email.utils.formatdate(usegmt=True)

    sign_string = '\n'.join([
        verb,
        content_md5,
        content_type,
        date,
        path,
    ])
    sign_string_as_bytes = sign_string.encode('utf-8')

    try:
        secret_as_bytes = conf.secret.encode('utf-8')
    except AttributeError:
        secret_as_bytes = bytes()
    sign = hmac.new(secret_as_bytes, sign_string_as_bytes, hashlib.sha1) \
        .hexdigest()

    headers = {
        'Authorization': 'Uploadcare {0}:{1}'.format(conf.pub_key, sign),
        'Date': date,
        'Content-Type': content_type,
        'Accept': 'application/vnd.uploadcare-v{0}+json'.format(conf.api_version),
        'User-Agent': 'pyuploadcare/{0}'.format(__version__),
    }
    logger.debug('''sent:
        verb: {0}
        path: {1}
        headers: {2}
        data: {3}'''.format(verb, path, headers, content))

    try:
        response = session.request(verb, url, allow_redirects=True,
                                   verify=conf.verify_api_ssl,
                                   headers=headers, data=content,
                                   timeout=_get_timeout(timeout))
    except requests.RequestException as exc:
        raise APIConnectionError(exc.args[0])

    logger.debug(
        'got: {0} {1}'.format(response.status_code, response.content)
    )

    if 'warning' in response.headers:
        match = re.search('"(.+)"', response.headers['warning'])
        if match:
            for warning in match.group(1).split('; '):
                logger.warn('API Warning: {0}'.format(warning))

    # TODO: Add check for content-type.
    if response.status_code == 200:
        try:
            return response.json()
        except ValueError as exc:
            raise APIError(exc.args[0])
    # No content.
    if response.status_code == 204:
        return

    if response.status_code == 403:
        raise AuthenticationError(response.content)

    if response.status_code in (400, 404):
        raise InvalidRequestError(response.content)<commit_msg>Treat 401 responses as AuthenticationError.<commit_after># coding: utf-8
"""
Uploadcare REST client.

It is JSON REST request abstraction layer that is used by the
``pyuploadcare.api_resources``.

"""

from __future__ import unicode_literals
import email.utils
import hashlib
import hmac
import re
import logging
import json
import socket

import requests
import six

if six.PY3:
    from urllib.parse import urljoin, urlsplit
else:
    from urlparse import urljoin, urlsplit

from . import conf, __version__
from .exceptions import (
    APIConnectionError, AuthenticationError, APIError, InvalidRequestError,
)


logger = logging.getLogger("pyuploadcare")

# Use session for keep-alive connections.
session = requests.session()

def _get_timeout(timeout):
    if timeout is not conf.DEFAULT:
        return timeout
    if conf.timeout is not conf.DEFAULT:
        return conf.timeout
    return socket.getdefaulttimeout()


def rest_request(verb, path, data=None, timeout=conf.DEFAULT):
    """Makes REST API request and returns response as ``dict``.

    It provides auth headers as well and takes settings from ``conf`` module.

    Make sure that given ``path`` does not contain leading slash.

    Usage example::

        >>> rest_request('GET', 'files/?limit=10')
        {
            'next': 'https://api.uploadcare.com/files/?limit=10&page=2',
            'total': 1241,
            'page': 1,
            'pages': 125,
            'per_page': 10,
            'previous': None,
            'results': [
                # ...
                {
                    # ...
                    'uuid': 1921953c-5d94-4e47-ba36-c2e1dd165e1a,
                    # ...
                },
                # ...
            ]
        }

    """
    assert not path.startswith('/'), path
    url = urljoin(conf.api_base, path)
    url_parts = urlsplit(url)

    if url_parts.query:
        path = url_parts.path + '?' + url_parts.query
    else:
        path = url_parts.path

    content = ''
    if data is not None:
        content = json.dumps(data)

    content_type = 'application/json'
    content_md5 = hashlib.md5(content.encode('utf-8')).hexdigest()
    date = email.utils.formatdate(usegmt=True)

    sign_string = '\n'.join([
        verb,
        content_md5,
        content_type,
        date,
        path,
    ])
    sign_string_as_bytes = sign_string.encode('utf-8')

    try:
        secret_as_bytes = conf.secret.encode('utf-8')
    except AttributeError:
        secret_as_bytes = bytes()
    sign = hmac.new(secret_as_bytes, sign_string_as_bytes, hashlib.sha1) \
        .hexdigest()

    headers = {
        'Authorization': 'Uploadcare {0}:{1}'.format(conf.pub_key, sign),
        'Date': date,
        'Content-Type': content_type,
        'Accept': 'application/vnd.uploadcare-v{0}+json'.format(conf.api_version),
        'User-Agent': 'pyuploadcare/{0}'.format(__version__),
    }
    logger.debug('''sent:
        verb: {0}
        path: {1}
        headers: {2}
        data: {3}'''.format(verb, path, headers, content))

    try:
        response = session.request(verb, url, allow_redirects=True,
                                   verify=conf.verify_api_ssl,
                                   headers=headers, data=content,
                                   timeout=_get_timeout(timeout))
    except requests.RequestException as exc:
        raise APIConnectionError(exc.args[0])

    logger.debug(
        'got: {0} {1}'.format(response.status_code, response.content)
    )

    if 'warning' in response.headers:
        match = re.search('"(.+)"', response.headers['warning'])
        if match:
            for warning in match.group(1).split('; '):
                logger.warn('API Warning: {0}'.format(warning))

    # TODO: Add check for content-type.
    if response.status_code == 200:
        try:
            return response.json()
        except ValueError as exc:
            raise APIError(exc.args[0])
    # No content.
    if response.status_code == 204:
        return

    if response.status_code in (401, 403):
        raise AuthenticationError(response.content)

    if response.status_code in (400, 404):
        raise InvalidRequestError(response.content)
{'fraction_non_alphanumeric': 0.11716978880801814, 'fraction_numerical': 0.018374895597184106, 'mean_word_length': 2.188284518828452, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '309372', 'n_tokens_mistral': 2592, 'n_tokens_neox': 2436, 'n_words': 595}