__init__.py 6.96 KB
import calendar
import hashlib
import json
import urllib.request
import urllib.parse
import urllib.error
from datetime import datetime, timedelta

import floraconcierge
from floraconcierge import errors
from floraconcierge.client import handlers
from floraconcierge.client.types import from_dict
from floraconcierge.errors import get_result_error


class Environment(object):
    def __init__(self):
        self.language = 'en'
        self.country = None
        self.city = None
        self._date = ApiClient.get_timestamp()
        self.currency = 'usd'
        self.ip = '127.0.0.1'
        self.promo = None
        self.user_auth_key = None
        self.ref = None
        self.utm = None
        self.browser = None
        self.accept = None
        self.accept_language = None
        self.encoding = None

    def set_date(self, value):
        if isinstance(value, int):
            self._date = value
        else:
            self._date = ApiClient.get_timestamp(value)

    date = property(lambda self: self._date, set_date)

    @property
    def params(self):
        ref = ''
        if getattr(self, 'ref', None):
            ref = self.ref
        utm = ''
        if getattr(self, 'utm', None):
            utm = self.utm
        browser = ''
        if getattr(self, 'browser', None):
            browser = self.browser
        accept = ''
        if getattr(self, 'accept', None):
            accept = self.accept
        accept_language = ''
        if getattr(self, 'accept_language', None):
            accept_language = self.accept_language
        encoding = ''
        if getattr(self, 'encoding', None):
            encoding = self.encoding
        return {
            '_lang': self.language or '',
            '_country': self.country or '',
            '_city': self.city or '',
            '_date': self.date or '',
            '_currency': self.currency or '',
            '_ip': self.ip,
            '_promo': self.promo or '',
            '_user_auth_key': self.user_auth_key or '',
            '_ref': ref,
            '_utm': utm,
            '_browser': browser,
            '_accept': accept,
            '_accept_language': accept_language,
            '_encoding': encoding,
        }

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, state):
        now = ApiClient.get_timestamp()

        if now > state['_date']:
            state['_date'] = now

        self.__dict__.update(state)


class ApiResult(object):
    def __init__(self, func, params=None, post=None):
        self.func = func
        self.params = params
        self.post = post
        self.url = None
        self.body = None
        self.http_code = None
        self.time = None
        self.size = None

        self._data = None

    def process(self, response):
        try:
            self._data = json.loads(response, object_hook=from_dict)
            self.size = len(response)
        except ValueError as e:
            self.body = response

            raise errors.MalformedJSONResultError(self, e.message)

        if self._data.error.code:
            raise get_result_error(self, self._data.error)

    @property
    def value(self):
        if self._data and self._data.result is not None:
            return self._data and self._data.result
        else:
            return None

    def dump(self):
        return self.value


class ApiClient(object):
    def __init__(self, api_id, secret_key):
        self._api_id = api_id
        self._secret_key = secret_key

        self.env = Environment()

        self._opener = urllib.request.build_opener(
            handlers.UserAgentProcessor(floraconcierge.HTTP_CLIENT_UA),
            handlers.GZipProcessor()
        )

        self._api_endpoint = 'http://api.floraconcierge.com/v1/'
        self._debug = False
        self._calls = list()
        self._calls_time = timedelta()

        # importing here avoid import recursion
        from floraconcierge.services import Manager

        self.services = Manager(self)

    @property
    def api_endpoint(self):
        return self._api_endpoint

    @api_endpoint.setter
    def api_endpoint(self, url):
        self._api_endpoint = url

    @property
    def debug(self):
        return self._debug

    @debug.setter
    def debug(self, value):
        self._debug = bool(value)

    @staticmethod
    def get_timestamp(time=None):
        time = time or datetime.now()

        return calendar.timegm(time.timetuple())

    @property
    def calls(self):
        return self._calls

    @calls.deleter
    def calls(self):
        self._calls = list()
        self._calls_time = timedelta()

    def str(self, value):
        return value.decode('utf-8') if type(value) == bytes else str(value)

    def sign(self, **kwargs):
        sign = dict((k, v) for k, v in kwargs.items() if k[0] != '_')

        keys = list(sign.keys())
        keys.sort()

        tosign = []
        for k in keys:
            tosign.append(self.str(k) + '=' + self.str(sign[k]))

        tosign = "".join(tosign)
        tosign = self.str(self._secret_key) + ':' + tosign

        return hashlib.sha1(tosign.encode('utf-8')).hexdigest()

    def fix_type(self, var):
        if isinstance(var, bool):
            return 1 if var else 0
        else:
            return self.str(var).encode('utf-8')

    def call(self, func, get=None, post=None):
        get, post, start = get or dict(), post or dict(), datetime.now()

        assert isinstance(func, str), 'Supported only string function name'
        assert isinstance(get, dict), 'Supported only dict GET data'
        assert isinstance(post, dict), 'Supported only dict POST data'

        _get, _post = get.copy(), post.copy()

        get = dict((self.str(k), self.fix_type(v)) for k, v in get.items() if v is not None)
        get.update({
            '_sign': self.sign(**get),
            '_api_id': self._api_id,
        })
        get.update(dict((self.str(k), self.fix_type(v)) for k, v in self.env.params.items() if v is not None))

        url = "%s%s?%s" % (self._api_endpoint, func.lstrip(), urllib.parse.urlencode(get))
        request = urllib.request.Request(url)
        if _post:
            request.data = json.dumps(_post).encode()

        try:
            response = self._opener.open(request)
            body = response.read()
        except urllib.error.URLError as e:
            raise errors.HTTPError(e, e.reason)

        result = ApiResult(func, _get, _post)
        result.url = response.geturl()
        result.http_code = response.getcode()
        result.time = datetime.now() - start

        if self.debug:
            result.body = body

        self._calls.append(result)
        self._calls_time += result.time

        result.process(body)

        return result.value

    def callstats(self):
        return (self._calls_time, self._calls)


class Service(object):
    """
    @type client: ApiClient
    """

    def __init__(self, client):
        self._client = client

    def _callapi(self, func, get=None, post=None):
        return self._client.call(func, get, post)