Example #1
0
    def _init_session(self, email, token, oath_token, password, session):
        if not session or not hasattr(session, 'authorized') \
                or not session.authorized:
            # session is not an OAuth session that has been authorized,
            # so create a new Session.
            if not password and not token and not oath_token:
                raise ZenpyException(
                    "password, token or oauth_token are required!")
            elif password and token:
                raise ZenpyException("password and token "
                                     "are mutually exclusive!")
            session = session if session else requests.Session()
            if password:
                session.auth = (email, password)
            elif token:
                session.auth = ('%s/token' % email, token)
            elif oath_token:
                session.headers.update(
                    {'Authorization': 'Bearer %s' % oath_token})
            else:
                raise ZenpyException("Invalid arguments to _init_session()!")

        headers = {
            'Content-type': 'application/json',
            'User-Agent': 'Zenpy/1.0.9'
        }
        session.headers.update(headers)
        return session
Example #2
0
    def post(self, fp, token=None, target_name=None, content_type=None):
        if hasattr(fp, 'read'):
            # File-like objects such as:
            #   PY3: io.StringIO, io.TextIOBase, io.BufferedIOBase
            #   PY2: file, io.StringIO, StringIO.StringIO, cStringIO.StringIO

            if not hasattr(fp, 'name') and not target_name:
                raise ZenpyException("upload requires a target file name")
            else:
                target_name = target_name or fp.name

        elif isinstance(fp, str):
            if os.path.isfile(fp):
                fp = open(fp, 'rb')
                target_name = target_name or fp.name
            elif not target_name:
                # Valid string, which is not a path, and without a target name
                raise ZenpyException("upload requires a target file name")

        elif not target_name:
            # Other serializable types accepted by requests (like dict)
            raise ZenpyException("upload requires a target file name")

        url = self.api._build_url(
            self.api.endpoint.upload(filename=target_name, token=token))
        return self.api._post(url,
                              data=fp,
                              payload={},
                              content_type=content_type)
Example #3
0
    def _init_session(self, email, token, oath_token, password, session):
        if not session:
            session = requests.Session()
            # Workaround for possible race condition - https://github.com/kennethreitz/requests/issues/3661
            session.mount('https://',
                          HTTPAdapter(**self.http_adapter_kwargs()))

        if not hasattr(session, 'authorized') or not session.authorized:
            # session is not an OAuth session that has been authorized, so authorize the session.
            if not password and not token and not oath_token:
                raise ZenpyException(
                    "password, token or oauth_token are required!")
            elif password and token:
                raise ZenpyException("password and token "
                                     "are mutually exclusive!")
            if password:
                session.auth = (email, password)
            elif token:
                session.auth = ('%s/token' % email, token)
            elif oath_token:
                session.headers.update(
                    {'Authorization': 'Bearer %s' % oath_token})
            else:
                raise ZenpyException("Invalid arguments to _init_session()!")

        headers = {'User-Agent': 'Zenpy/{}'.format(__version__)}
        session.headers.update(headers)
        return session
Example #4
0
    def __call__(self, sort_order=None, sort_by=None, **kwargs):
        if sort_order and sort_order not in ('asc', 'desc'):
            raise ZenpyException("sort_order must be one of (asc, desc)")
        if sort_by and sort_by not in ('alphabetical', 'created_at',
                                       'updated_at', 'usage_1h', 'usage_24h',
                                       'usage_7d'):
            raise ZenpyException(
                "sort_by is invalid - https://developer.zendesk.com/rest_api/docs/core/macros#available-parameters"
            )

        if 'id' in kwargs:
            if len(kwargs) > 1:
                raise ZenpyException(
                    "When specifying an id it must be the only parameter")

        params = dict()
        path = self.endpoint
        for key, value in kwargs.items():
            if isinstance(value, bool):
                value = str(value).lower()
            if key == 'id':
                path += "/{}.json".format(value)
            else:
                params[key] = value

        if sort_order:
            params['sort_order'] = sort_order
        if sort_by:
            params['sort_by'] = sort_by

        if path == self.endpoint:
            path += '.json'

        return Url(path, params=params)
Example #5
0
    def __call__(self, sort_order=None, sort_by=None, **kwargs):
        kwargs.pop('sideload', None)
        if sort_order and sort_order not in ('asc', 'desc'):
            raise ZenpyException("sort_order must be one of (asc, desc)")
        if sort_by and sort_by not in ('alphabetical', 'created_at',
                                       'updated_at', 'usage_1h', 'usage_24h',
                                       'usage_7d'):
            raise ZenpyException(
                "sort_by is invalid - https://developer.zendesk.com/rest_api/docs/core/macros#available-parameters"
            )

        if 'id' in kwargs:
            if len(kwargs) > 1:
                raise ZenpyException(
                    "When specifying an id it must be the only parameter")
            url_out = ''
        else:
            url_out = self.endpoint + '?'

        for key, value in kwargs.items():
            if isinstance(value, bool):
                value = str(value).lower()
            if key == 'id':
                url_out += self._single(self.endpoint, value)
            else:
                url_out += '&{}={}'.format(key, value)

        if sort_order:
            url_out += '&sort_order={}'.format(sort_order)
        if sort_by:
            url_out += '&sort_by={}'.format(sort_by)
        return url_out
Example #6
0
 def _check_type(self, items):
     # We don't want people passing, for example, a Group object to a Ticket endpoint.
     expected_class = self.object_manager.class_manager.class_for_type(self.object_type)
     if isinstance(items, list):
         if any((o.__class__ is not expected_class for o in items)):
             raise ZenpyException("Invalid type - expected %(expected_class)s" % locals())
     else:
         if items.__class__ is not expected_class:
             raise ZenpyException("Invalid type - expected %(expected_class)s" % locals())
Example #7
0
 def format_between(self, key, values):
     if not isinstance(values, list) and not isinstance(values, tuple):
         raise ZenpyException("*_between requires a list or tuple!")
     elif not len(values) == 2:
         raise ZenpyException("*_between requires exactly 2 items!")
     elif not all([isinstance(d, datetime) for d in values]):
         raise ZenpyException("*_between only works with dates!")
     key = key.replace('_between', '')
     dates = [v.strftime(self.ZENDESK_DATE_FORMAT) for v in values]
     return "%s>%s %s<%s" % (key, dates[0], key, dates[1])
Example #8
0
 def format_between(self, key, values):
     if not is_iterable_but_not_string(values):
         raise ZenpyException("*_between requires an iterable (list, set, tuple etc)")
     elif not len(values) == 2:
         raise ZenpyException("*_between requires exactly 2 items!")
     elif not all([isinstance(d, datetime) for d in values]):
         raise ZenpyException("*_between only works with dates!")
     key = key.replace('_between', '')
     if values[0].tzinfo is None or values[1].tzinfo is None:
         dates = [v.strftime(self.ISO_8601_FORMAT) for v in values]
     else:
         dates = [str(v.replace(microsecond=0).isoformat()) for v in values]
     return "%s>%s %s<%s" % (key, dates[0], key, dates[1])
Example #9
0
    def post(self,
             fp,
             token=None,
             target_name=None,
             content_type=None,
             api_object=None):
        if hasattr(fp, 'read'):
            # File-like objects such as:
            #   PY3: io.StringIO, io.TextIOBase, io.BufferedIOBase
            #   PY2: file, io.StringIO, StringIO.StringIO, cStringIO.StringIO

            if not hasattr(fp, 'name') and not target_name:
                raise ZenpyException("upload requires a target file name")
            else:
                target_name = target_name or fp.name

        elif hasattr(fp, 'name'):
            # Path objects (pathlib.Path)
            if fp.name == '':
                raise ZenpyException("upload requires a target file name")

            target_name = target_name or fp.name
            # PathLike objects only compatible with python3.6 and above, so
            # we convert to string at this point
            # https://stackoverflow.com/a/42694113/4664727
            fp = open(str(fp), 'rb')

        elif isinstance(fp, str):
            if os.path.isfile(fp):
                fp = open(fp, 'rb')
                target_name = target_name or fp.name
            elif not target_name:
                # Valid string, which is not a path, and without a target name
                raise ZenpyException("upload requires a target file name")

        elif not target_name:
            # Other serializable types accepted by requests (like dict)
            raise ZenpyException("upload requires a target file name")

        url = self.api._build_url(
            self.api.endpoint.upload(filename=target_name, token=token))
        response = self.api._post(url,
                                  data=fp,
                                  payload={},
                                  content_type=content_type)

        if hasattr(fp, "close"):
            fp.close()

        return response
Example #10
0
 def add_cache(self, object_type, cache_impl_name, maxsize, **kwargs):
     """
     Add a new cache for the named object type and cache implementation
     """
     if object_type not in ZendeskObjectMapping.class_mapping:
         raise ZenpyException("No such object type: %s" % object_type)
     cache_mapping[object_type] = ZenpyCache(cache_impl_name, maxsize, **kwargs)
Example #11
0
    def build(self, response):
        """
        Deserialize the returned objects and return either a single Zenpy object, or a ResultGenerator in
        the case of multiple results.

        :param response: the requests Response object.
        """
        response_json = response.json()

        zenpy_objects = self.deserialize(response_json)

        # Collection of objects (eg, users/tickets)
        plural_object_type = as_plural(self.api.object_type)
        if plural_object_type in zenpy_objects:
            return ZendeskResultGenerator(self, response_json)

        # Here the response matches the API object_type, seems legit.
        if self.api.object_type in zenpy_objects:
            return zenpy_objects[self.api.object_type]

        # Could be anything, if we know of this object then return it.
        for zenpy_object_name in self.api._object_mapping.class_mapping:
            if zenpy_object_name in zenpy_objects:
                return zenpy_objects[zenpy_object_name]

        # Maybe a collection of known objects?
        for zenpy_object_name in self.api._object_mapping.class_mapping:
            plural_zenpy_object_name = as_plural(zenpy_object_name)
            if plural_zenpy_object_name in zenpy_objects:
                return ZendeskResultGenerator(
                    self, response_json, object_type=plural_zenpy_object_name)
        # Bummer, bail out with an informative message.
        raise ZenpyException("Unknown Response: " + str(response_json))
Example #12
0
    def post(self,
             endpoint,
             attachment,
             article=None,
             inline=False,
             file_name=None,
             content_type=None):
        if article:
            url = self.api._build_url(endpoint(id=article))
        else:
            url = self.api._build_url(endpoint())

        use_file_name = bool(file_name) and bool(content_type)
        if (bool(file_name) or bool(content_type)) and not use_file_name:
            raise ZenpyException(
                "Content_type and file_name are expected together!")

        if hasattr(attachment, 'read'):
            file = attachment if not use_file_name else (file_name, attachment,
                                                         content_type)
            return self.api._post(url,
                                  payload={},
                                  files=dict(inline=(None, inline), file=file))
        elif os.path.isfile(attachment):
            with open(attachment, 'rb') as fp:
                file = fp if not use_file_name else (file_name, fp,
                                                     content_type)
                return self.api._post(url,
                                      payload={},
                                      files=dict(inline=(None, inline),
                                                 file=file))

        raise ValueError("Attachment is not a file-like object or valid path!")
Example #13
0
 def __call__(self, *args, **kwargs):
     params = list()
     if len(args) > 1:
         raise ZenpyException("Only query can be passed as an arg!")
     elif len(args) == 1:
         params.append("query={}".format(args[0]))
     params.extend(["{}={}".format(k, v) for k, v in kwargs.items()])
     return self.endpoint + "&".join(params).lower()
Example #14
0
 def put(self, endpoint, help_centre_object_id, translation):
     if translation.locale is None:
         raise ZenpyException(
             "Locale can not be None when updating translation!")
     url = self.api._build_url(
         endpoint(help_centre_object_id, translation.locale))
     payload = self.build_payload(translation)
     return self.api._put(url, payload=payload)
Example #15
0
    def build(self, response):
        """
        Deserialize the returned objects and return either a single Zenpy object, or a ResultGenerator in
        the case of multiple results.

        :param response: the requests Response object.
        """
        response_json = response.json()

        # Special case for incremental cursor based ticket audits export.
        if get_endpoint_path(self.api,
                             response).startswith('/ticket_audits.json'):
            return TicketCursorGenerator(self,
                                         response_json,
                                         object_type="audit")

        # Special case for incremental cursor based tickets export.
        if get_endpoint_path(
                self.api,
                response).startswith('/incremental/tickets/cursor.json'):
            return TicketCursorGenerator(self,
                                         response_json,
                                         object_type="ticket")

        # Special case for Jira links.
        if get_endpoint_path(self.api,
                             response).startswith('/services/jira/links'):
            return JiraLinkGenerator(self, response_json, response)

        zenpy_objects = self.deserialize(response_json)

        # Collection of objects (eg, users/tickets)
        plural_object_type = as_plural(self.api.object_type)
        if plural_object_type in zenpy_objects:
            return ZendeskResultGenerator(
                self,
                response_json,
                response_objects=zenpy_objects[plural_object_type])

        # Here the response matches the API object_type, seems legit.
        if self.api.object_type in zenpy_objects:
            return zenpy_objects[self.api.object_type]

        # Could be anything, if we know of this object then return it.
        for zenpy_object_name in self.object_mapping.class_mapping:
            if zenpy_object_name in zenpy_objects:
                return zenpy_objects[zenpy_object_name]

        # Maybe a collection of known objects?
        for zenpy_object_name in self.object_mapping.class_mapping:
            plural_zenpy_object_name = as_plural(zenpy_object_name)
            if plural_zenpy_object_name in zenpy_objects:
                return ZendeskResultGenerator(
                    self, response_json, object_type=plural_zenpy_object_name)

        # Bummer, bail out.
        raise ZenpyException("Unknown Response: " + str(response_json))
Example #16
0
 def add_cache(self, object_type, cache_impl_name, maxsize, **kwargs):
     """
     Add a new cache for the named object type and cache implementation
     """
     if object_type not in self.users.object_manager.class_manager.class_mapping:
         raise ZenpyException("No such object type: %s" % object_type)
     cache_mapping = self._get_cache_mapping()
     cache_mapping[object_type] = ZenpyCache(cache_impl_name, maxsize,
                                             **kwargs)
Example #17
0
 def perform(self, http_method, *args, **kwargs):
     http_method = http_method.lower()
     if http_method == 'put':
         return self.put(*args, **kwargs)
     elif http_method == 'post':
         return self.post(*args, **kwargs)
     elif http_method == 'delete':
         return self.delete(*args, **kwargs)
     raise ZenpyException("{} cannot handle HTTP method: {}".format(self.__class__.__name__, http_method))
Example #18
0
 def check_type(self, zenpy_objects):
     """ Ensure the passed type matches this API's object_type. """
     expected_type = self.api._object_mapping.class_for_type(self.api.object_type)
     if not isinstance(zenpy_objects, collections.Iterable):
         zenpy_objects = [zenpy_objects]
     for zenpy_object in zenpy_objects:
         if type(zenpy_object) is not expected_type:
             raise ZenpyException(
                 "Invalid type - expected {} found {}".format(expected_type, type(zenpy_object))
             )
Example #19
0
 def build(self, response):
     response_json = response.json()
     response_objects = self.deserialize(response_json)
     if 'sla_policies' in response_objects:
         return ZendeskResultGenerator(self, response.json(), response_objects=response_objects['sla_policies'])
     elif 'sla_policy' in response_objects:
         return response_objects['sla_policy']
     elif response_objects:
         return response_objects['definitions']
     raise ZenpyException("Could not handle response: {}".format(response_json))
Example #20
0
    def __call__(self, *args, **kwargs):
        if not args:
            raise ZenpyException("You need to pass the query string as the first parameter")

        query = "query=%s" % args[0]
        result = []
        for key, value in kwargs.items():
            result.append("%s=%s" % (key, value))
        query += '&' + "&".join(result)
        return self.endpoint + query
Example #21
0
    def __call__(self, **kwargs):
        query = "start_time="
        if 'start_time' in kwargs:
            if isinstance(kwargs['start_time'], datetime):
                query += kwargs['start_time'].strftime(self.UNIX_TIME)
            else:
                query += str(kwargs['start_time'])
            return self.endpoint + query + self._format_sideload(self.sideload, seperator='&')

        raise ZenpyException("Incremental Endoint requires a start_time parameter!")
Example #22
0
 def deserialize(self, response_json):
     chats = list()
     if 'chats' in response_json:
         chat_list = response_json['chats']
     elif 'docs' in response_json:
         chat_list = response_json['docs'].values()
     else:
         raise ZenpyException("Unexpected response: {}".format(response_json))
     for chat in chat_list:
         chats.append(self.object_mapping.object_from_json('chat', chat))
     return chats
Example #23
0
    def build(self, response):
        zenpy_objects = self.deserialize(response.json())

        # JobStatus responses also include a ticket key so treat it specially.
        if 'job_status' in zenpy_objects:
            return zenpy_objects['job_status']

        # TicketAudit responses are another special case containing both
        # a ticket and audit key.
        if 'ticket' and 'audit' in zenpy_objects:
            return zenpy_objects['ticket_audit']
        raise ZenpyException("Could not process response: {}".format(response))
Example #24
0
 def format_between(self, key, values):
     if not is_iterable_but_not_string(values):
         raise ValueError("*_between requires an iterable (list, set, tuple etc)")
     elif not len(values) == 2:
         raise ZenpyException("*_between requires exactly 2 items!")
     for value in values:
         if not isinstance(value, datetime):
             raise ValueError("*_between only works with datetime objects!")
         elif value.tzinfo is not None and value.utcoffset().total_seconds() != 0:
             log.warning("search parameter '{}' requires UTC time, results likely incorrect.".format(key))
     key = key.replace('_between', '')
     dates = [v.strftime(self.ISO_8601_FORMAT) for v in values]
     return "%s>%s %s<%s" % (key, dates[0], key, dates[1])
Example #25
0
 def __call__(self, score=None, sort_order=None, start_time=None, end_time=None):
     if sort_order and sort_order not in ('asc', 'desc'):
         raise ZenpyException("sort_order must be one of (asc, desc)")
     params = dict()
     if score:
         params['score'] = score
     if sort_order:
         params['sort_order'] = sort_order
     if start_time:
         params['start_time'] = to_unix_ts(start_time)
     if end_time:
         params['end_time'] = to_unix_ts(end_time)
     return Url(self.endpoint, params=params)
Example #26
0
    def __call__(self, score=None, sort_order=None):
        if sort_order not in ('asc', 'desc'):
            raise ZenpyException("sort_order must be one of (asc, desc)")

        base_url = self.endpoint + '?'
        if score:
            result = base_url + "score={}".format(score)
        else:
            result = base_url

        if sort_order:
            result += '&sort_order={}'.format(sort_order)
        return result
Example #27
0
    def __call__(self, start_time=None):
        if start_time is None:
            raise ZenpyException(
                "Incremental Endoint requires a start_time parameter!")

        elif isinstance(start_time, datetime):
            unix_time = to_unix_ts(start_time)

        else:
            unix_time = start_time

        query = "start_time=%s" % str(unix_time)
        return self.endpoint + query + self._format_sideload(self.sideload,
                                                             seperator='&')
Example #28
0
    def upload(self, fp, token=None, target_name=None):
        """
        Upload a file to Zendesk.

        :param fp: file object, StringIO instance, content, or file path to be
                   uploaded
        :param token: upload token for uploading multiple files
        :param target_name: name of the file inside Zendesk
        :return: :class:`Upload` object containing a token and other information
                    (see https://developer.zendesk.com/rest_api/docs/core/attachments#uploading-files)
        """

        if hasattr(fp, 'read'):
            # File-like objects such as:
            #   PY3: io.StringIO, io.TextIOBase, io.BufferedIOBase
            #   PY2: file, io.StringIO, StringIO.StringIO, cStringIO.StringIO

            if not hasattr(fp, 'name') and not target_name:
                raise ZenpyException("upload requires a target file name")
            else:
                target_name = target_name or fp.name

        elif isinstance(fp, str):
            if os.path.isfile(fp):
                fp = open(fp, 'rb')
                target_name = target_name or fp.name
            elif not target_name:
                # Valid string, which is not a path, and without a target name
                raise ZenpyException("upload requires a target file name")

        elif not target_name:
            # Other serializable types accepted by requests (like dict)
            raise ZenpyException("upload requires a target file name")

        return self.post(self._get_url(self.endpoint.upload(filename=target_name, token=token)),
                         data=fp,
                         payload={})
Example #29
0
    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))
Example #30
0
    def __call__(self, start_time=None, include=None):
        if start_time is None:
            raise ZenpyException("Incremental Endpoint requires a start_time parameter!")

        elif isinstance(start_time, datetime):
            unix_time = to_unix_ts(start_time)
        else:
            unix_time = start_time

        params = dict(start_time=str(unix_time))
        if include is not None:
            if is_iterable_but_not_string(include):
                params.update(dict(include=",".join(include)))
            else:
                params.update(dict(include=include))
        return Url(self.endpoint, params=params)