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
1058import 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