1import json 2from math import ceil 3 4from twilio.base import values 5from twilio.base.exceptions import TwilioRestException 6 7 8class Version(object): 9 """ 10 Represents an API version. 11 """ 12 13 def __init__(self, domain): 14 """ 15 :param Domain domain: 16 :return: 17 """ 18 self.domain = domain 19 self.version = None 20 21 def absolute_url(self, uri): 22 """ 23 Turns a relative uri into an absolute url. 24 """ 25 return self.domain.absolute_url(self.relative_uri(uri)) 26 27 def relative_uri(self, uri): 28 """ 29 Turns a relative uri into a versioned relative uri. 30 """ 31 return '{}/{}'.format(self.version.strip('/'), uri.strip('/')) 32 33 def request(self, method, uri, params=None, data=None, headers=None, 34 auth=None, timeout=None, allow_redirects=False): 35 """ 36 Make an HTTP request. 37 """ 38 url = self.relative_uri(uri) 39 return self.domain.request( 40 method, 41 url, 42 params=params, 43 data=data, 44 headers=headers, 45 auth=auth, 46 timeout=timeout, 47 allow_redirects=allow_redirects 48 ) 49 50 @classmethod 51 def exception(cls, method, uri, response, message): 52 """ 53 Wraps an exceptional response in a `TwilioRestException`. 54 """ 55 # noinspection PyBroadException 56 try: 57 error_payload = json.loads(response.text) 58 if 'message' in error_payload: 59 message = '{}: {}'.format(message, error_payload['message']) 60 details = error_payload.get('details') 61 code = error_payload.get('code', response.status_code) 62 return TwilioRestException(response.status_code, uri, message, code, method, details) 63 except Exception: 64 return TwilioRestException(response.status_code, uri, message, response.status_code, 65 method) 66 67 def fetch(self, method, uri, params=None, data=None, headers=None, auth=None, timeout=None, 68 allow_redirects=False): 69 """ 70 Fetch a resource instance. 71 """ 72 response = self.request( 73 method, 74 uri, 75 params=params, 76 data=data, 77 headers=headers, 78 auth=auth, 79 timeout=timeout, 80 allow_redirects=allow_redirects, 81 ) 82 83 # Note that 3XX response codes are allowed for fetches. 84 if response.status_code < 200 or response.status_code >= 400: 85 raise self.exception(method, uri, response, 'Unable to fetch record') 86 87 return json.loads(response.text) 88 89 def update(self, method, uri, params=None, data=None, headers=None, auth=None, timeout=None, 90 allow_redirects=False): 91 """ 92 Update a resource instance. 93 """ 94 response = self.request( 95 method, 96 uri, 97 params=params, 98 data=data, 99 headers=headers, 100 auth=auth, 101 timeout=timeout, 102 allow_redirects=allow_redirects, 103 ) 104 105 if response.status_code < 200 or response.status_code >= 300: 106 raise self.exception(method, uri, response, 'Unable to update record') 107 108 return json.loads(response.text) 109 110 def delete(self, method, uri, params=None, data=None, headers=None, auth=None, timeout=None, 111 allow_redirects=False): 112 """ 113 Delete a resource. 114 """ 115 response = self.request( 116 method, 117 uri, 118 params=params, 119 data=data, 120 headers=headers, 121 auth=auth, 122 timeout=timeout, 123 allow_redirects=allow_redirects, 124 ) 125 126 if response.status_code < 200 or response.status_code >= 300: 127 raise self.exception(method, uri, response, 'Unable to delete record') 128 129 return response.status_code == 204 130 131 def read_limits(self, limit=None, page_size=None): 132 """ 133 Takes a limit on the max number of records to read and a max page_size 134 and calculates the max number of pages to read. 135 136 :param int limit: Max number of records to read. 137 :param int page_size: Max page size. 138 :return dict: A dictionary of paging limits. 139 """ 140 if limit is not None and page_size is None: 141 page_size = limit 142 143 return { 144 'limit': limit or values.unset, 145 'page_size': page_size or values.unset, 146 } 147 148 def page(self, method, uri, params=None, data=None, headers=None, auth=None, timeout=None, 149 allow_redirects=False): 150 """ 151 Makes an HTTP request. 152 """ 153 return self.request( 154 method, 155 uri, 156 params=params, 157 data=data, 158 headers=headers, 159 auth=auth, 160 timeout=timeout, 161 allow_redirects=allow_redirects, 162 ) 163 164 def stream(self, page, limit=None, page_limit=None): 165 """ 166 Generates records one a time from a page, stopping at prescribed limits. 167 168 :param Page page: The page to stream. 169 :param int limit: The max number of records to read. 170 :param int page_limit: The max number of pages to read. 171 """ 172 current_record = 1 173 current_page = 1 174 175 while page is not None: 176 for record in page: 177 yield record 178 current_record += 1 179 if limit and limit is not values.unset and limit < current_record: 180 return 181 182 current_page += 1 183 if page_limit and page_limit is not values.unset and page_limit < current_page: 184 return 185 186 page = page.next_page() 187 188 def create(self, method, uri, params=None, data=None, headers=None, auth=None, timeout=None, 189 allow_redirects=False): 190 """ 191 Create a resource instance. 192 """ 193 response = self.request( 194 method, 195 uri, 196 params=params, 197 data=data, 198 headers=headers, 199 auth=auth, 200 timeout=timeout, 201 allow_redirects=allow_redirects, 202 ) 203 204 if response.status_code < 200 or response.status_code >= 300: 205 raise self.exception(method, uri, response, 'Unable to create record') 206 207 return json.loads(response.text) 208