Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions intercom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# -*- coding: utf-8 -*-

from datetime import datetime
from .errors import ArgumentError
from .errors import HttpError # noqa
from .errors import (ArgumentError, AuthenticationError, # noqa
BadGatewayError, BadRequestError, HttpError, IntercomError,
MultipleMatchingUsersError, RateLimitExceeded, ResourceNotFound,
ServerError, ServiceUnavailableError, UnexpectedError)
from .lib.setter_property import SetterProperty
from .request import Request
from .admin import Admin
from .company import Company
from .conversation import Conversation
from .event import Event
from .message import Message
from .note import Note
from .notification import Notification
from .user import User
from .segment import Segment
from .subscription import Subscription
from .tag import Tag
from .admin import Admin # noqa
from .company import Company # noqa
from .conversation import Conversation # noqa
from .event import Event # noqa
from .message import Message # noqa
from .note import Note # noqa
from .notification import Notification # noqa
from .user import User # noqa
from .segment import Segment # noqa
from .subscription import Subscription # noqa
from .tag import Tag # noqa

import copy
import random
Expand All @@ -24,11 +26,6 @@

__version__ = '2.0.alpha'

__all__ = (
Admin, Company, Conversation, Event, Message, Note, Notification,
Segment, Subscription, Tag, User
)


RELATED_DOCS_TEXT = "See https://github.com/jkeyes/python-intercom \
for usage examples."
Expand Down
56 changes: 56 additions & 0 deletions intercom/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,59 @@ class ArgumentError(ValueError):

class HttpError(Exception):
pass


class IntercomError(Exception):

def __init__(self, message=None, context=None):
super(IntercomError, self).__init__(message)
self.context = context


class ResourceNotFound(IntercomError):
pass


class AuthenticationError(IntercomError):
pass


class ServerError(IntercomError):
pass


class BadGatewayError(IntercomError):
pass


class ServiceUnavailableError(IntercomError):
pass


class BadRequestError(IntercomError):
pass


class RateLimitExceeded(IntercomError):
pass


class MultipleMatchingUsersError(IntercomError):
pass


class UnexpectedError(IntercomError):
pass


error_codes = {
'unauthorized': AuthenticationError,
'forbidden': AuthenticationError,
'bad_request': BadRequestError,
'missing_parameter': BadRequestError,
'parameter_invalid': BadRequestError,
'not_found': ResourceNotFound,
'rate_limit_exceeded': RateLimitExceeded,
'service_unavailable': ServiceUnavailableError,
'conflict': MultipleMatchingUsersError,
}
67 changes: 65 additions & 2 deletions intercom/request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

from .errors import HttpError # noqa
from . import errors

import json
import requests
Expand Down Expand Up @@ -32,8 +32,71 @@ def send_request_to_path(cls, method, url, auth, params=None):
method, url, timeout=cls.timeout,
auth=auth, **req_params)

cls.raise_errors_on_failure(resp)

if resp.content:
return json.loads(resp.content)
return cls.parse_body(resp)

@classmethod
def parse_body(cls, resp):
try:
body = json.loads(resp.content)
except ValueError:
cls.raise_errors_on_failure(resp)
if body.get('type') == 'error.list':
cls.raise_application_errors_on_failure(body, resp.status_code)
return body

@classmethod
def raise_errors_on_failure(cls, resp):
if resp.status_code == 404:
raise errors.ResourceNotFound('Resource Not Found')
elif resp.status_code == 401:
raise errors.AuthenticationError('Unauthorized')
elif resp.status_code == 403:
raise errors.AuthenticationError('Forbidden')
elif resp.status_code == 500:
raise errors.ServerError('Server Error')
elif resp.status_code == 502:
raise errors.BadGatewayError('Bad Gateway Error')
elif resp.status_code == 503:
raise errors.ServiceUnavailableError('Service Unavailable')

@classmethod
def raise_application_errors_on_failure(cls, error_list_details, http_code): # noqa
# Currently, we don't support multiple errors
error_details = error_list_details['errors'][0]
error_code = error_details.get('type')
if error_code is None:
error_code = error_details.get('code')
error_context = {
'http_code': http_code,
'application_error_code': error_code
}
error_class = errors.error_codes.get(error_code)
if error_class is None:
# unexpected error
if error_code:
message = cls.message_for_unexpected_error_with_type(
error_details, http_code)
else:
message = cls.message_for_unexpected_error_without_type(
error_details, http_code)
error_class = errors.UnexpectedError
else:
message = error_details['message']
raise error_class(message, error_context)

@classmethod
def message_for_unexpected_error_with_type(cls, error_details, http_code): # noqa
error_type = error_details['type']
message = error_details['message']
return "The error of type '%s' is not recognized. It occurred with the message: %s and http_code: '%s'. Please contact Intercom with these details." % (error_type, message, http_code) # noqa

@classmethod
def message_for_unexpected_error_without_type(cls, error_details, http_code): # noqa
message = error_details['message']
return "An unexpected error occured. It occurred with the message: %s and http_code: '%s'. Please contact Intercom with these details." % (message, http_code) # noqa


class ResourceEncoder(json.JSONEncoder):
Expand Down
189 changes: 189 additions & 0 deletions tests/unit/request_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-

import httpretty
import intercom
import json
import re
from describe import expect
from intercom import Intercom
from intercom import UnexpectedError

get = httpretty.GET
post = httpretty.POST
r = re.compile


class DescribeRequest:

@httpretty.activate
def it_raises_resource_not_found(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=404)
with expect.to_raise_error(intercom.ResourceNotFound):
Intercom.get('/notes')

@httpretty.activate
def it_raises_authentication_error_unauthorized(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=401)
with expect.to_raise_error(intercom.AuthenticationError):
Intercom.get('/notes')

@httpretty.activate
def it_raises_authentication_error_forbidden(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=403)
with expect.to_raise_error(intercom.AuthenticationError):
Intercom.get('/notes')

@httpretty.activate
def it_raises_server_error(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=500)
with expect.to_raise_error(intercom.ServerError):
Intercom.get('/notes')

@httpretty.activate
def it_raises_bad_gateway_error(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=502)
with expect.to_raise_error(intercom.BadGatewayError):
Intercom.get('/notes')

@httpretty.activate
def it_raises_service_unavailable_error(self):
httpretty.register_uri(
get, r(r'/notes$'), body='', status=503)
with expect.to_raise_error(intercom.ServiceUnavailableError):
Intercom.get('/notes')

@httpretty.activate
def it_raises_an_unexpected_typed_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'hopper',
'message': 'The first compiler.'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
try:
Intercom.get('/users')
except (UnexpectedError) as err:
assert "The error of type 'hopper' is not recognized" in err.message # noqa
expect(err.context['http_code']) == 200
expect(err.context['application_error_code']) == 'hopper'

@httpretty.activate
def it_raises_an_unexpected_untyped_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'message': 'UNIVAC'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
try:
Intercom.get('/users')
except (UnexpectedError) as err:
assert "An unexpected error occured." in err.message
expect(err.context['application_error_code']) is None

@httpretty.activate
def it_raises_a_bad_request_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': None,
'message': 'email is required'
}
]
}

for code in ['missing_parameter', 'parameter_invalid', 'bad_request']:
payload['errors'][0]['type'] = code
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.BadRequestError):
Intercom.get('/users')

@httpretty.activate
def it_raises_an_authentication_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'unauthorized',
'message': 'Your name\'s not down.'
}
]
}
for code in ['unauthorized', 'forbidden']:
payload['errors'][0]['type'] = code
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.AuthenticationError):
Intercom.get('/users')

@httpretty.activate
def it_raises_resource_not_found_by_type(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'not_found',
'message': 'Waaaaally?'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.ResourceNotFound):
Intercom.get('/users')

@httpretty.activate
def it_raises_rate_limit_exceeded(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'rate_limit_exceeded',
'message': 'Fair use please.'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.RateLimitExceeded):
Intercom.get('/users')

@httpretty.activate
def it_raises_a_service_unavailable_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'service_unavailable',
'message': 'Zzzzz.'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.ServiceUnavailableError):
Intercom.get('/users')

@httpretty.activate
def it_raises_a_multiple_matching_users_error(self):
payload = {
'type': 'error.list',
'errors': [
{
'type': 'conflict',
'message': 'Two many cooks.'
}
]
}
httpretty.register_uri(get, r("/users"), body=json.dumps(payload))
with expect.to_raise_error(intercom.MultipleMatchingUsersError):
Intercom.get('/users')
Loading