コード例 #1
0
class Api(object):
    """
    Base class for API.
    """
    subdomain = None

    def __init__(self, subdomain, session, endpoint, object_type, timeout, ratelimit):
        self.subdomain = subdomain
        self.session = session
        self.timeout = timeout
        self.ratelimit = ratelimit
        self.endpoint = endpoint
        self.object_type = object_type
        self.protocol = 'https'
        self.version = 'v2'
        self.object_manager = ObjectManager(self)
        self.callsafety = {
            'lastcalltime': None,
            'lastlimitremaining': None
        }

    def _ratelimit(self, http_method, url, **kwargs):
        def time_since_last_call():
            if self.callsafety['lastcalltime'] is not None:
                return int(time() - self.callsafety['lastcalltime'])
            else:
                return None

        lastlimitremaining = self.callsafety['lastlimitremaining']

        if time_since_last_call() is None or time_since_last_call() >= 10 or lastlimitremaining >= self.ratelimit:
            response = http_method(url, **kwargs)
        else:
            # We hit our limit floor and aren't quite at 10 seconds yet..
            log.warn(
                "Safety Limit Reached of %s remaining calls and time since last call is under 10 seconds" % self.ratelimit)
            while time_since_last_call() < 10:
                remaining_sleep = int(10 - time_since_last_call())
                log.debug("  -> sleeping: %s more seconds" % remaining_sleep)
                sleep(1)
            response = http_method(url, **kwargs)

        self.callsafety['lastcalltime'] = time()
        if 'X-Rate-Limit-Remaining' in response.headers:
            self.callsafety['lastlimitremaining'] = int(response.headers['X-Rate-Limit-Remaining'])
        else:
            self.callsafety['lastlimitremaining'] = 0
        return response

    def post(self, url, payload, data=None):
        log.debug("POST: %s - %s" % (url, str(payload)))
        headers = None
        if data:
            headers = {'Content-Type': 'application/octet-stream'}
        response = self.session.post(url, json=serialize(payload), data=data, headers=headers, timeout=self.timeout)
        self._check_and_cache_response(response)
        return self._build_response(response.json())

    def _put(self, url, payload):
        return self._call_api(self.session.put, url, json=serialize(payload), timeout=self.timeout)

    def _delete(self, url, payload=None):
        return self._call_api(self.session.delete, url, json=payload, timeout=self.timeout)

    def _get(self, url):
        return self._call_api(self.session.get, url, timeout=self.timeout)

    def _call_api(self, http_method, url, **kwargs):
        log.debug("{}: {} - {}".format(http_method.__name__.upper(), url, kwargs))
        if self.ratelimit is not None:
            # This path indicates we're taking a proactive approach to not hit the rate limit
            response = self._ratelimit(http_method=http_method, url=url, **kwargs)
        else:
            response = http_method(url, **kwargs)

        # If we are being rate-limited, wait the required period before trying again.
        while 'retry-after' in response.headers and int(response.headers['retry-after']) > 0:
            retry_after_seconds = int(response.headers['retry-after'])
            log.warn(
                "Waiting for requested retry-after period: %s seconds" % retry_after_seconds)
            while retry_after_seconds > 0:
                retry_after_seconds -= 1
                log.debug("    -> sleeping: %s more seconds" % retry_after_seconds)
                sleep(1)
            response = http_method(url, **kwargs)
        if self.ratelimit is not None:
            self.callsafety['lastcalltime'] = time()
            self.callsafety['lastlimitremaining'] = int(response.headers['X-Rate-Limit-Remaining'])
        return self._check_and_cache_response(response)

    def _get_items(self, endpoint, object_type, *args, **kwargs):
        sideload = 'sideload' not in kwargs or ('sideload' in kwargs and kwargs['sideload'])

        # If an ID is present a single object has been requested
        if 'id' in kwargs:
            return self._get_item(kwargs['id'], endpoint, object_type, sideload)

        if 'ids' in kwargs:
            cached_objects = []
            # Check to see if we have all objects in the cache.
            # If we are missing even one we need to request them all again.
            for _id in kwargs['ids']:
                obj = self.object_manager.query_cache(object_type, _id)
                if obj:
                    cached_objects.append(obj)
                else:
                    return self._get_paginated(endpoint, object_type, *args, **kwargs)
            return cached_objects

        return self._get_paginated(endpoint, object_type, *args, **kwargs)

    def _get_item(self, _id, endpoint, object_type, sideload=True, skip_cache=False):
        if not skip_cache:
            # Check if we already have this item in the cache
            item = self.object_manager.query_cache(object_type, _id)
            if item:
                return item

        _json = self._query(endpoint=endpoint(id=_id, sideload=sideload))

        # If the result is paginated return a generator
        if 'next_page' in _json:
            return ResultGenerator(self, object_type, _json)
        # Annoyingly, tags is always plural.
        if 'tags' in _json:
            return self.object_manager.object_from_json(object_type, _json[object_type + 's'])
        else:
            return self.object_manager.object_from_json(object_type, _json[object_type])

    def _get_paginated(self, endpoint, object_type, *args, **kwargs):
        _json = self._query(endpoint=endpoint(*args, **kwargs))
        return ResultGenerator(self, object_type, _json)

    def _query(self, endpoint):
        response = self._get(self._get_url(endpoint=endpoint))
        return response.json()

    def _build_response(self, response_json):
        # When updating and deleting API objects various responses can be returned
        # We can figure out what we have by the keys in the returned JSON
        if 'ticket' and 'audit' in response_json:
            return self.object_manager.object_from_json('ticket_audit', response_json)
        elif 'tags' in response_json:
            return response_json['tags']

        known_objects = ('ticket',
                         'user',
                         'job_status',
                         'group',
                         'satisfaction_rating',
                         'request',
                         'organization',
                         'organization_membership',
                         'upload',
                         'result')

        for object_type in known_objects:
            if object_type in response_json:
                return self.object_manager.object_from_json(object_type, response_json[object_type])

        raise ZenpyException("Unknown Response: " + str(response_json))

    def _check_and_cache_response(self, response):
        if response.status_code > 299 or response.status_code < 200:
            log.debug("Received response code [%s] - headers: %s" % (response.status_code, str(response.headers)))
            # If it's just a RecordNotFound error raise the right exception,
            # otherwise try and get a nice error message.
            if 'application/json' in response.headers['content-type']:
                try:
                    _json = response.json()
                    if 'error' in _json and _json['error'] == 'RecordNotFound':
                        raise RecordNotFoundException(json.dumps(_json))
                    else:
                        raise APIException(json.dumps(_json))
                except ValueError:
                    pass

            # No can do, just raise the correct Exception.
            response.raise_for_status()
        else:
            try:
                self.object_manager.update_caches(response.json())
            except ValueError:
                pass
            return response

    def _object_from_json(self, object_type, object_json):
        return self.object_manager.object_from_json(object_type, object_json)

    def _query_cache(self, object_type, _id):
        return self.object_manager.query_cache(object_type, _id)

    def _get_url(self, endpoint=''):
        return "%(protocol)s://%(subdomain)s.zendesk.com/api/%(version)s/" % self.__dict__ + endpoint

    def __call__(self, *args, **kwargs):
        """
        Retrieve API objects. If called with no arguments returns a ResultGenerator of
        all retrievable items. Alternatively, can be called with an id to only return that item.
        """
        return self._get_items(self.endpoint, self.object_type, *args, **kwargs)

    def _get_user(self, _id):
        return self._get_item(_id, endpoint=Endpoint.users, object_type='user', sideload=True)

    def _get_users(self, _ids):
        return self._get_items(endpoint=Endpoint.users, object_type='user', ids=_ids)

    def _get_comment(self, _id):
        return self._get_item(_id, endpoint=Endpoint.tickets.comments, object_type='comment', sideload=True)

    def _get_organization(self, _id):
        return self._get_item(_id, endpoint=Endpoint.organizations, object_type='organization', sideload=True)

    def _get_group(self, _id):
        return self._get_item(_id, endpoint=Endpoint.groups, object_type='group', sideload=True)

    def _get_brand(self, _id):
        return self._get_item(_id, endpoint=Endpoint.brands, object_type='brand', sideload=True)

    def _get_ticket(self, _id, skip_cache=False):
        return self._get_item(_id, endpoint=Endpoint.tickets, object_type='ticket', sideload=False,
                              skip_cache=skip_cache)

    def _get_actions(self, actions):
        for action in actions:
            yield self._object_from_json('action', action)

    def _get_events(self, events):
        for event in events:
            yield self._object_from_json(event['type'].lower(), event)

    def _get_via(self, via):
        return self._object_from_json('via', via)

    def _get_source(self, source):
        return self._object_from_json('source', source)

    def _get_attachments(self, attachments):
        for attachment in attachments:
            yield self._object_from_json('attachment', attachment)

    def _get_thumbnails(self, thumbnails):
        for thumbnail in thumbnails:
            yield self._object_from_json('thumbnail', thumbnail)

    def _get_satisfaction_rating(self, satisfaction_rating):
        return self._object_from_json('satisfaction_rating', satisfaction_rating)

    def _get_sharing_agreements(self, sharing_agreement_ids):
        sharing_agreements = []
        for _id in sharing_agreement_ids:
            sharing_agreement = self._get_item(_id, Endpoint.sharing_agreements, 'sharing_agreement')
            if sharing_agreement:
                sharing_agreements.append(sharing_agreement)
        return sharing_agreements

    def _get_ticket_metric_item(self, metric_item):
        return self._object_from_json('ticket_metric_item', metric_item)

    def _get_metadata(self, metadata):
        return self._object_from_json('metadata', metadata)

    def _get_system(self, system):
        return self._object_from_json('system', system)

    def _get_problem(self, problem_id):
        return self._get_item(problem_id, Endpoint.tickets, 'ticket')

    # This will be deprecated soon - https://developer.zendesk.com/rest_api/docs/web-portal/forums
    def _get_forum(self, forum_id):
        return forum_id

    def _get_user_fields(self, user_fields):
        return user_fields

    def _get_organization_fields(self, organization_fields):
        return organization_fields

    # TODO implement this with Enterprise
    def _get_custom_fields(self, custom_fields):
        return custom_fields

    # This is ticket fields, hopefully it doesn't conflict with another field type
    def _get_fields(self, fields):
        return fields

    def _get_upload(self, upload):
        return self._object_from_json('upload', upload)

    def _get_attachment(self, attachment):
        return self._object_from_json('attachment', attachment)

    def _get_child_events(self, child_events):
        return child_events
コード例 #2
0
ファイル: api.py プロジェクト: brightshiny/zenpy
class BaseApi(object):
    """
    Base class for API.
    """
    subdomain = None

    def __init__(self, subdomain, session):
        self.subdomain = subdomain
        self.protocol = 'https'
        self.version = 'v2'
        self.object_manager = ObjectManager(self)
        self.session = session

    def _post(self, url, payload):
        log.debug("POST: %s - %s" % (url, str(payload)))
        payload = json.loads(json.dumps(payload, cls=ApiObjectEncoder))
        response = self.session.post(url, json=payload)
        self._check_and_cache_response(response)
        return self._build_response(response.json())

    def _put(self, url, payload):
        log.debug("PUT: " + url)
        payload = json.loads(json.dumps(payload, cls=ApiObjectEncoder))
        response = self.session.put(url, json=payload)
        self._check_and_cache_response(response)
        return self._build_response(response.json())

    def _delete(self, url, payload=None):
        log.debug("DELETE: " + url)
        if payload:
            response = self.session.delete(url, json=payload)
        else:
            response = self.session.delete(url)
        return self._check_and_cache_response(response)

    def _get(self, url, stream=False):
        log.debug("GET: " + url)
        response = self.session.get(url, stream=stream)

        # If we are being rate-limited, wait the required period before trying again.
        while 'retry-after' in response.headers and int(
                response.headers['retry-after']) > 0:
            retry_after_seconds = int(response.headers['retry-after'])
            log.warn(
                "APIRateLimitExceeded - sleeping for requested retry-after period: %s seconds"
                % retry_after_seconds)
            while retry_after_seconds > 0:
                retry_after_seconds -= 1
                log.debug("APIRateLimitExceeded - sleeping: %s more seconds" %
                          retry_after_seconds)
                sleep(1)
            response = self.session.get(url, stream=stream)
        return self._check_and_cache_response(response)

    def _get_items(self, endpoint, object_type, *args, **kwargs):
        sideload = 'sideload' not in kwargs or ('sideload' in kwargs
                                                and kwargs['sideload'])

        # If an ID is present a single object has been requested
        if 'id' in kwargs:
            return self._get_item(kwargs['id'], endpoint, object_type,
                                  sideload)

        if 'ids' in kwargs:
            cached_objects = []
            # Check to see if we have all objects in the cache.
            # If we are missing even one we need to request them all again.
            for _id in kwargs['ids']:
                obj = self.object_manager.query_cache(object_type, _id)
                if obj:
                    cached_objects.append(obj)
                else:
                    return self._get_paginated(endpoint, object_type, *args,
                                               **kwargs)
            return cached_objects

        return self._get_paginated(endpoint, object_type, *args, **kwargs)

    def _get_item(self,
                  _id,
                  endpoint,
                  object_type,
                  sideload=True,
                  skip_cache=False):
        if not skip_cache:
            # Check if we already have this item in the cache
            item = self.object_manager.query_cache(object_type, _id)
            if item:
                return item

        _json = self._query(endpoint=endpoint(id=_id, sideload=sideload))

        # If the result is paginated return a generator
        if 'next_page' in _json:
            return ResultGenerator(self, object_type, _json)
        # Annoyingly, tags is always plural.
        if 'tags' in _json:
            return self.object_manager.object_from_json(
                object_type, _json[object_type + 's'])
        else:
            return self.object_manager.object_from_json(
                object_type, _json[object_type])

    def _get_paginated(self, endpoint, object_type, *args, **kwargs):
        _json = self._query(endpoint=endpoint(*args, **kwargs))
        return ResultGenerator(self, object_type, _json)

    def _query(self, endpoint):
        response = self._get(self._get_url(endpoint=endpoint))
        return response.json()

    def _build_response(self, response_json):
        # When updating and deleting API objects various responses can be returned
        # We can figure out what we have by the keys in the returned JSON
        if 'ticket' and 'audit' in response_json:
            return self.object_manager.object_from_json(
                'ticket_audit', response_json)
        elif 'tags' in response_json:
            return response_json['tags']

        for object_type in ('ticket', 'user', 'job_status', 'group',
                            'satisfaction_rating', 'request', 'organization'):
            if object_type in response_json:
                return self.object_manager.object_from_json(
                    object_type, response_json[object_type])

        raise ZenpyException("Unknown Response: " + str(response_json))

    def _check_and_cache_response(self, response):
        if response.status_code > 299 or response.status_code < 200:
            # If it's just a RecordNotFound error raise the right exception,
            # otherwise try and get a nice error message.
            if 'application/json' in response.headers['content-type']:
                try:
                    _json = response.json()
                    if 'error' in _json and _json['error'] == 'RecordNotFound':
                        raise RecordNotFoundException(json.dumps(_json))
                    else:
                        raise APIException(json.dumps(_json))
                except ValueError:
                    pass

            # No can do, just raise the correct Exception.
            response.raise_for_status()
        else:
            try:
                self.object_manager.update_caches(response.json())
            except ValueError:
                pass
            return response

    def _get_url(self, endpoint=''):
        return "%(protocol)s://%(subdomain)s.zendesk.com/api/%(version)s/" % self.__dict__ + endpoint
コード例 #3
0
ファイル: api.py プロジェクト: bingimar/zenpy
class BaseApi(object):
    """
    Base class for API.
    """
    subdomain = None

    def __init__(self, subdomain, session):
        self.subdomain = subdomain
        self.protocol = 'https'
        self.version = 'v2'
        self.object_manager = ObjectManager(self)
        self.session = session

    def _post(self, url, payload):
        log.debug("POST: %s - %s" % (url, str(payload)))
        payload = json.loads(json.dumps(payload, cls=ApiObjectEncoder))
        response = self.session.post(url, json=payload)
        self._check_and_cache_response(response)
        return self._build_response(response.json())

    def _put(self, url, payload):
        log.debug("PUT: " + url)
        payload = json.loads(json.dumps(payload, cls=ApiObjectEncoder))
        response = self.session.put(url, json=payload)
        self._check_and_cache_response(response)
        return self._build_response(response.json())

    def _delete(self, url, payload=None):
        log.debug("DELETE: " + url)
        if payload:
            response = self.session.delete(url, json=payload)
        else:
            response = self.session.delete(url)
        return self._check_and_cache_response(response)

    def _get(self, url, stream=False):
        log.debug("GET: " + url)
        response = self.session.get(url, stream=stream)

        # If we are being rate-limited, wait the required period before trying again.
        while 'retry-after' in response.headers and int(response.headers['retry-after']) > 0:
            retry_after_seconds = int(response.headers['retry-after'])
            log.warn(
                    "APIRateLimitExceeded - sleeping for requested retry-after period: %s seconds" % retry_after_seconds)
            while retry_after_seconds > 0:
                retry_after_seconds -= 1
                log.debug("APIRateLimitExceeded - sleeping: %s more seconds" % retry_after_seconds)
                sleep(1)
            response = self.session.get(url, stream=stream)
        return self._check_and_cache_response(response)

    def _get_items(self, endpoint, object_type, *args, **kwargs):
        sideload = 'sideload' not in kwargs or ('sideload' in kwargs and kwargs['sideload'])

        # If an ID is present a single object has been requested
        if 'id' in kwargs:
            return self._get_item(kwargs['id'], endpoint, object_type, sideload)

        if 'ids' in kwargs:
            cached_objects = []
            # Check to see if we have all objects in the cache.
            # If we are missing even one we need to request them all again.
            for _id in kwargs['ids']:
                obj = self.object_manager.query_cache(object_type, _id)
                if obj:
                    cached_objects.append(obj)
                else:
                    return self._get_paginated(endpoint, object_type, *args, **kwargs)
            return cached_objects

        return self._get_paginated(endpoint, object_type, *args, **kwargs)

    def _get_item(self, _id, endpoint, object_type, sideload=True, skip_cache=False):
        if not skip_cache:
            # Check if we already have this item in the cache
            item = self.object_manager.query_cache(object_type, _id)
            if item:
                return item

        _json = self._query(endpoint=endpoint(id=_id, sideload=sideload))

        # If the result is paginated return a generator
        if 'next_page' in _json:
            return ResultGenerator(self, object_type, _json)
        # Annoyingly, tags is always plural.
        if 'tags' in _json:
            return self.object_manager.object_from_json(object_type, _json[object_type + 's'])
        else:
            return self.object_manager.object_from_json(object_type, _json[object_type])

    def _get_paginated(self, endpoint, object_type, *args, **kwargs):
        _json = self._query(endpoint=endpoint(*args, **kwargs))
        return ResultGenerator(self, object_type, _json)

    def _query(self, endpoint):
        response = self._get(self._get_url(endpoint=endpoint))
        return response.json()

    def _build_response(self, response_json):
        # When updating and deleting API objects various responses can be returned
        # We can figure out what we have by the keys in the returned JSON
        if 'ticket' and 'audit' in response_json:
            return self.object_manager.object_from_json('ticket_audit', response_json)
        elif 'tags' in response_json:
            return response_json['tags']

        for object_type in ('ticket', 'user', 'job_status', 'group', 'satisfaction_rating', 'request', 'organization'):
            if object_type in response_json:
                return self.object_manager.object_from_json(object_type, response_json[object_type])

        raise ZenpyException("Unknown Response: " + str(response_json))

    def _check_and_cache_response(self, response):
        if response.status_code > 299 or response.status_code < 200:
            # If it's just a RecordNotFound error raise the right exception,
            # otherwise try and get a nice error message.
            if 'application/json' in response.headers['content-type']:
                try:
                    _json = response.json()
                    if 'error' in _json and _json['error'] == 'RecordNotFound':
                        raise RecordNotFoundException(json.dumps(_json))
                    else:
                        raise APIException(json.dumps(_json))
                except ValueError:
                    pass

            # No can do, just raise the correct Exception.
            response.raise_for_status()
        else:
            try:
                self.object_manager.update_caches(response.json())
            except ValueError:
                pass
            return response

    def _get_url(self, endpoint=''):
        return "%(protocol)s://%(subdomain)s.zendesk.com/api/%(version)s/" % self.__dict__ + endpoint