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)
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()
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) self.cache.mapping[object_type] = ZenpyCache(cache_impl_name, maxsize, **kwargs)
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._get_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)
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))
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))
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 Exception("Invalid type - expected %(expected_class)s" % locals())
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)) )
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
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))
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!")
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
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
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))
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)
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
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])
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='&')
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={})
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)
def __call__(self, **kwargs): if len(kwargs) > 1: raise ZenpyException("Only expect a single keyword to the ChatEndpoint") endpoint_path = self.endpoint if 'ids' in kwargs: endpoint_path = "{}?ids={}".format(self.endpoint, ','.join(kwargs['ids'])) else: for key, value in kwargs.items(): if key == 'email': endpoint_path = '{}/email/{}'.format(self.endpoint, value) elif self.endpoint == 'departments' and key == 'name': endpoint_path = '{}/name/{}'.format(self.endpoint, value) else: endpoint_path = "{}/{}".format(self.endpoint, value) break return endpoint_path
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 __call__(self, start_time=None): if not start_time: raise ZenpyException("Incremental Endoint requires a start_time parameter!") if isinstance(start_time, datetime): if is_timezone_aware(start_time): start_time = start_time.astimezone(tzutc()) else: log.warning( "Non timezone-aware datetime object passed to IncrementalEndpoint. " "The Zendesk API expects UTC time, if this is not the case results will be incorrect!" ) unix_time = time.mktime(start_time.timetuple()) else: unix_time = start_time query = "start_time=%s" % str(unix_time) return self.endpoint + query + self._format_sideload(self.sideload, seperator='&')
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!")
def __call__(self, start_time=None, **kwargs): 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 = kwargs params.update(dict(start_time=str(unix_time))) if 'fields' in kwargs: if is_iterable_but_not_string(kwargs['fields']): f = ",".join(kwargs['fields']) else: f = kwargs['fields'] else: f = "*" params.update(dict(fields="chats(" + f + ")")) return Url(self.endpoint, params=params)
def __call__(self, *args): if not args or len(args) < 2: raise ZenpyException( "This endpoint requires at least two arguments!") return Url(self.endpoint.format(*args))
def delete(self, items): raise ZenpyException("You cannot delete requests!")
def delete(self, items): raise ZenpyException( "You cannot delete objets using the ticket_import endpoint!")
def update(self, items): raise ZenpyException( "You cannot update objects using ticket_import endpoint!")
def __call__(self, *args, **kwargs): raise ZenpyException("You must pass ticket objects to this endpoint!")