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