Skip to content

Commit b77e1ad

Browse files
committed
Branch for the v2 code.
1 parent 98f3fde commit b77e1ad

File tree

9 files changed

+1642
-699
lines changed

9 files changed

+1642
-699
lines changed

intercom/__init__.py

Lines changed: 220 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,224 @@
1-
# coding=utf-8
2-
#
3-
# Copyright 2012 keyes.ie
4-
#
5-
# License: http://jkeyes.mit-license.org/
6-
#
7-
""" Intercom API wrapper. """
8-
9-
import functools
1+
from datetime import datetime
2+
import numbers
3+
4+
class ArgumentError(Exception):
5+
pass
6+
7+
8+
class FlatStore(dict):
9+
10+
def __init__(self, *args, **kwargs):
11+
self.update(*args, **kwargs)
12+
13+
def __setitem__(self, key, value):
14+
# print "SETTING ITEM [%s]=[%s]" % (key, value)
15+
if not (
16+
isinstance(value, numbers.Real) or
17+
isinstance(value, basestring)
18+
):
19+
raise ValueError(
20+
"custom data only allows string and real number values")
21+
if not isinstance(key, basestring):
22+
raise ValueError("custom data only allows string keys")
23+
super(FlatStore, self).__setitem__(key, value)
24+
25+
def update(self, *args, **kwargs):
26+
if args:
27+
if len(args) > 1:
28+
raise TypeError("update expected at most 1 arguments, "
29+
"got %d" % len(args))
30+
other = dict(args[0])
31+
for key in other:
32+
self[key] = other[key]
33+
for key in kwargs:
34+
self[key] = kwargs[key]
35+
36+
def setdefault(self, key, value=None):
37+
if key not in self:
38+
self[key] = value
39+
return self[key]
40+
41+
42+
class SetterProperty(object):
43+
44+
def __init__(self, func, doc=None):
45+
self.func = func
46+
self.__doc__ = doc if doc is not None else func.__doc__
47+
48+
def __set__(self, obj, value):
49+
return self.func(obj, value)
50+
51+
__version__ = '2.0-alpha'
52+
53+
import copy
54+
import json
55+
import random
56+
import re
57+
import requests
1058
import time
1159

12-
from datetime import datetime
1360

61+
RELATED_DOCS_TEXT = "See https://github.com/jkeyes/python-intercom for usage examples."
62+
COMPATIBILITY_WARNING_TEXT = "It looks like you are upgrading from an older version of python-intercom. Please note that this new version (%s) is not backwards compatible." % (__version__)
63+
COMPATIBILITY_WORKAROUND_TEXT = "To get rid of this error please set Intercom.app_api_key and don't set Intercom.api_key."
64+
CONFIGURATION_REQUIRED_TEXT = "You must set both Intercom.app_id and Intercom.app_api_key to use this client."
65+
66+
67+
68+
class _Config(object):
69+
app_id = None
70+
app_api_key = None
71+
hostname = "api.intercom.io"
72+
protocol = "https"
73+
endpoints = None
74+
current_endpoint = None
75+
target_base_url = None
76+
timeout = 10
77+
endpoint_randomized_at = None
78+
79+
class Intercom(object):
80+
_config = _Config()
81+
_class_register = {}
82+
83+
@classmethod
84+
def send_request_to_path(cls, method, path, params=None):
85+
""" Construct an API request, send it to the API, and parse the
86+
response. """
87+
req_params = {}
88+
if '://' in path:
89+
url = path
90+
else:
91+
url = cls.current_endpoint + path
92+
93+
headers = {
94+
'User-Agent': 'python-intercom/' + __version__,
95+
'Accept': 'application/json'
96+
}
97+
if method in ('POST', 'PUT', 'DELETE'):
98+
headers['content-type'] = 'application/json'
99+
req_params['data'] = json.dumps(params)
100+
elif method == 'GET':
101+
req_params['params'] = params
102+
req_params['headers'] = headers
103+
104+
resp = requests.request(
105+
method, url, timeout=cls._config.timeout,
106+
auth=(Intercom.app_id, Intercom.app_api_key), **req_params)
107+
108+
if resp.content:
109+
return json.loads(resp.content)
110+
111+
@classmethod
112+
def get(cls, path, **params):
113+
return cls.send_request_to_path('GET', path, params)
114+
115+
@classmethod
116+
def post(cls, path, **params):
117+
return cls.send_request_to_path('POST', path, params)
118+
119+
@classmethod
120+
def put(cls, path, **params):
121+
return cls.send_request_to_path('PUT', path, params)
122+
123+
@classmethod
124+
def delete(cls, path, **params):
125+
print "DELETE %s" % (path)
126+
return cls.send_request_to_path('DELETE', path, params)
127+
128+
class __metaclass__(type):
129+
130+
@property
131+
def app_id(cls):
132+
return cls._config.app_id
133+
134+
@app_id.setter
135+
def app_id(cls, value):
136+
cls._config.app_id = value
137+
138+
@property
139+
def app_api_key(cls):
140+
return cls._config.app_api_key
141+
142+
@app_api_key.setter
143+
def app_api_key(cls, value):
144+
cls._config.app_api_key = value
145+
146+
@property
147+
def _random_endpoint(cls):
148+
if cls.endpoints:
149+
endpoints = copy.copy(cls.endpoints)
150+
random.shuffle(endpoints)
151+
return endpoints[0]
152+
153+
@property
154+
def _alternative_random_endpoint(cls):
155+
endpoints = copy.copy(cls.endpoints)
156+
if cls.current_endpoint in endpoints:
157+
endpoints.remove(cls.current_endpoint)
158+
random.shuffle(endpoints)
159+
if endpoints:
160+
return endpoints[0]
161+
162+
@property
163+
def _target_base_url(cls):
164+
if None in [cls.app_id, cls.app_api_key]:
165+
raise ArgumentError('%s %s' % (CONFIGURATION_REQUIRED_TEXT, RELATED_DOCS_TEXT))
166+
if cls._config.target_base_url is None:
167+
basic_auth_part = '%s:%s@' % (cls.app_id, cls.app_api_key)
168+
if cls.current_endpoint:
169+
cls._config.target_base_url = re.sub(
170+
r'(https?:\/\/)(.*)',
171+
'\g<1>%s\g<2>' % (basic_auth_part),
172+
cls.current_endpoint)
173+
return cls._config.target_base_url
174+
175+
176+
@property
177+
def hostname(cls):
178+
return cls._config.hostname
179+
180+
@hostname.setter
181+
def hostname(cls, value):
182+
cls._config.hostname = value
183+
cls.current_endpoint = None
184+
cls.endpoints = None
185+
186+
@property
187+
def protocol(cls):
188+
return cls._config.protocol
189+
190+
@protocol.setter
191+
def protocol(cls, value):
192+
cls._config.protocol = value
193+
cls.current_endpoint = None
194+
cls.endpoints = None
195+
196+
@property
197+
def current_endpoint(cls):
198+
now = time.mktime(datetime.utcnow().timetuple())
199+
expired = cls._config.endpoint_randomized_at < (now - (60 * 5))
200+
if cls._config.endpoint_randomized_at is None or expired:
201+
cls._config.endpoint_randomized_at = now
202+
cls.current_endpoint = cls._random_endpoint
203+
return cls._config.current_endpoint
204+
205+
@current_endpoint.setter
206+
def current_endpoint(cls, value):
207+
cls._config.current_endpoint = value
208+
cls._config.target_base_url = None
209+
210+
@property
211+
def endpoints(cls):
212+
if not cls._config.endpoints:
213+
return ['%s://%s' % (cls.protocol, cls.hostname)]
214+
else:
215+
return cls._config.endpoints
216+
217+
@endpoints.setter
218+
def endpoints(cls, value):
219+
cls._config.endpoints = value
220+
cls.current_endpoint = cls._random_endpoint
14221

15-
def from_timestamp_property(func_to_decorate):
16-
""" A decorator for properties to convert the property value from a
17-
timestamp to a datetime. """
18-
@functools.wraps(func_to_decorate)
19-
def wrapper(instance):
20-
""" Closure that converts from timestamp to datetime. """
21-
value = func_to_decorate(instance)
22-
if value:
23-
return datetime.fromtimestamp(value)
24-
return wrapper
25-
26-
27-
def to_timestamp_property(func_to_decorate):
28-
""" A decorator for properties to convert the property value from a
29-
datetime to a timestamp. """
30-
@functools.wraps(func_to_decorate)
31-
def wrapper(instance, value):
32-
""" Closure that converts from datetime to timestamp. """
33-
if value:
34-
value = time.mktime(value.timetuple())
35-
func_to_decorate(instance, value)
36-
return wrapper
37-
38-
from .intercom import AuthenticationError
39-
from .intercom import BadGatewayError
40-
from .intercom import Intercom
41-
from .intercom import ResourceNotFound
42-
from .intercom import ServerError
43-
from .intercom import ServiceUnavailableError
44-
45-
from .impression import Impression
46-
from .message_thread import MessageThread
47-
from .note import Note
48-
from .user import User
49-
from .tag import Tag
50-
51-
__all__ = (
52-
AuthenticationError, BadGatewayError, Intercom, ResourceNotFound,
53-
ServerError, ServiceUnavailableError, Impression, MessageThread,
54-
Note, User, Tag
55-
)
222+
@SetterProperty
223+
def endpoint(cls, value):
224+
cls.endpoints = [value]

0 commit comments

Comments
 (0)