0
# Infrastructure
1
2
Low-level infrastructure including HTTP clients, authentication strategies, credential providers, base classes, and exception handling. These components support the high-level APIs and can be customized for advanced use cases.
3
4
## Capabilities
5
6
### HTTP Client Infrastructure
7
8
Customizable HTTP client system supporting synchronous and asynchronous requests.
9
10
```python { .api }
11
class HttpClient:
12
"""Abstract HTTP client base class"""
13
14
def request(
15
self,
16
method: str,
17
uri: str,
18
data: dict = None,
19
headers: dict = None,
20
auth: tuple = None,
21
timeout: float = None,
22
allow_redirects: bool = None
23
) -> 'Response':
24
"""
25
Make HTTP request.
26
27
Args:
28
method (str): HTTP method ('GET', 'POST', etc.)
29
uri (str): Request URI
30
data (dict): Request body data
31
headers (dict): HTTP headers
32
auth (tuple): Authentication tuple (username, password)
33
timeout (float): Request timeout in seconds
34
allow_redirects (bool): Follow redirects
35
36
Returns:
37
Response: HTTP response object
38
"""
39
40
class TwilioHttpClient(HttpClient):
41
"""Default HTTP client using requests library"""
42
43
def __init__(
44
self,
45
pool_connections: int = 1,
46
pool_maxsize: int = 1,
47
max_retries: int = 3,
48
backoff_factor: float = 0.3,
49
proxy: dict = None,
50
ssl_context: 'SSLContext' = None,
51
timeout: float = 60.0
52
):
53
"""
54
Initialize HTTP client.
55
56
Args:
57
pool_connections (int): Number of connection pools
58
pool_maxsize (int): Max connections per pool
59
max_retries (int): Retry attempts for failed requests
60
backoff_factor (float): Backoff multiplier for retries
61
proxy (dict): Proxy configuration
62
ssl_context (SSLContext): Custom SSL context
63
timeout (float): Default timeout in seconds
64
"""
65
66
class AsyncHttpClient:
67
"""Abstract asynchronous HTTP client"""
68
69
async def request(
70
self,
71
method: str,
72
uri: str,
73
data: dict = None,
74
headers: dict = None,
75
auth: tuple = None,
76
timeout: float = None
77
) -> 'Response':
78
"""Make async HTTP request"""
79
80
class TwilioAsyncHttpClient(AsyncHttpClient):
81
"""Default async HTTP client using aiohttp"""
82
83
def __init__(
84
self,
85
timeout: float = 60.0,
86
pool_connections: int = 100,
87
pool_maxsize: int = 100,
88
ssl_context: 'SSLContext' = None
89
):
90
"""
91
Initialize async HTTP client.
92
93
Args:
94
timeout (float): Default timeout in seconds
95
pool_connections (int): Connection pool size
96
pool_maxsize (int): Maximum pool size
97
ssl_context (SSLContext): Custom SSL context
98
"""
99
100
async def request(
101
self,
102
method: str,
103
uri: str,
104
data: dict = None,
105
headers: dict = None,
106
auth: tuple = None,
107
timeout: float = None
108
) -> 'Response':
109
"""
110
Make async HTTP request.
111
112
Args:
113
method (str): HTTP method
114
uri (str): Request URI
115
data (dict): Request data
116
headers (dict): Request headers
117
auth (tuple): Authentication credentials
118
timeout (float): Request timeout
119
120
Returns:
121
Response: HTTP response object
122
"""
123
124
class Request:
125
"""HTTP request representation"""
126
127
def __init__(
128
self,
129
method: str,
130
uri: str,
131
data: dict = None,
132
headers: dict = None,
133
params: dict = None,
134
auth: tuple = None
135
):
136
"""
137
Args:
138
method (str): HTTP method
139
uri (str): Request URI
140
data (dict): Request body data
141
headers (dict): HTTP headers
142
params (dict): Query parameters
143
auth (tuple): Authentication credentials
144
"""
145
self.method = method
146
self.uri = uri
147
self.data = data
148
self.headers = headers
149
self.params = params
150
self.auth = auth
151
152
class Response:
153
"""HTTP response representation"""
154
155
def __init__(
156
self,
157
status_code: int,
158
text: str,
159
headers: dict = None
160
):
161
"""
162
Args:
163
status_code (int): HTTP status code
164
text (str): Response body text
165
headers (dict): Response headers
166
"""
167
self.status_code = status_code
168
self.text = text
169
self.headers = headers or {}
170
171
@property
172
def ok(self) -> bool:
173
"""True if status code indicates success (200-299)"""
174
return 200 <= self.status_code < 300
175
176
@property
177
def content(self) -> bytes:
178
"""Response body as bytes"""
179
return self.text.encode('utf-8')
180
```
181
182
### Authentication Strategies
183
184
Pluggable authentication system supporting multiple auth methods.
185
186
```python { .api }
187
class AuthStrategy:
188
"""Base authentication strategy"""
189
190
def get_auth_string(self) -> str:
191
"""
192
Generate authentication string.
193
194
Returns:
195
str: Authentication header value
196
"""
197
raise NotImplementedError
198
199
class TokenAuthStrategy(AuthStrategy):
200
"""Token-based authentication (Basic Auth with Account SID/Auth Token)"""
201
202
def __init__(self, username: str, password: str):
203
"""
204
Args:
205
username (str): Account SID or API Key SID
206
password (str): Auth Token or API Key Secret
207
"""
208
self.username = username
209
self.password = password
210
211
def get_auth_string(self) -> str:
212
"""Generate Basic Auth header value"""
213
214
class NoAuthStrategy(AuthStrategy):
215
"""No authentication strategy"""
216
217
def get_auth_string(self) -> str:
218
"""Return empty auth string"""
219
return ''
220
221
class AuthType:
222
"""Authentication type enumeration"""
223
BASIC = 'basic'
224
TOKEN = 'token'
225
OAUTH = 'oauth'
226
```
227
228
### Credential Providers
229
230
Alternative authentication mechanisms for advanced scenarios.
231
232
```python { .api }
233
class CredentialProvider:
234
"""Base credential provider"""
235
236
def get_headers(self) -> dict:
237
"""
238
Get authentication headers.
239
240
Returns:
241
dict: HTTP headers for authentication
242
"""
243
raise NotImplementedError
244
245
def get_username(self) -> str:
246
"""Get username for basic auth"""
247
raise NotImplementedError
248
249
def get_password(self) -> str:
250
"""Get password for basic auth"""
251
raise NotImplementedError
252
253
class ClientCredentialProvider(CredentialProvider):
254
"""OAuth 2.0 Client Credentials flow provider"""
255
256
def __init__(
257
self,
258
client_id: str,
259
client_secret: str,
260
token_endpoint: str,
261
scopes: list = None
262
):
263
"""
264
Args:
265
client_id (str): OAuth client ID
266
client_secret (str): OAuth client secret
267
token_endpoint (str): Token endpoint URL
268
scopes (list): Requested scopes
269
"""
270
271
class OrgsCredentialProvider(CredentialProvider):
272
"""Organization-based credential provider"""
273
274
def __init__(
275
self,
276
client_id: str,
277
client_secret: str,
278
account_sid: str,
279
orgs_token_endpoint: str = None
280
):
281
"""
282
Args:
283
client_id (str): Client ID
284
client_secret (str): Client secret
285
account_sid (str): Target account SID
286
orgs_token_endpoint (str): Organizations token endpoint
287
"""
288
```
289
290
### Base Resource Classes
291
292
Foundational classes for REST API resource management.
293
294
```python { .api }
295
class ClientBase:
296
"""Base client implementation"""
297
298
def __init__(
299
self,
300
username: str = None,
301
password: str = None,
302
account_sid: str = None,
303
region: str = None,
304
http_client: HttpClient = None,
305
environment: dict = None,
306
edge: str = None
307
): ...
308
309
class Domain:
310
"""Service domain base class"""
311
312
def __init__(self, twilio: ClientBase):
313
self.twilio = twilio
314
self.base_url = None
315
316
def request(
317
self,
318
method: str,
319
uri: str,
320
data: dict = None,
321
headers: dict = None
322
) -> Response:
323
"""Make authenticated request to domain"""
324
325
class Version:
326
"""API version handling"""
327
328
def __init__(self, domain: Domain, version: str = None):
329
self.domain = domain
330
self.version = version
331
332
def request(
333
self,
334
method: str,
335
uri: str,
336
data: dict = None,
337
headers: dict = None
338
) -> Response:
339
"""Make versioned API request"""
340
341
class InstanceResource:
342
"""Individual resource instance"""
343
344
def __init__(self, version: Version, payload: dict, **kwargs):
345
self._version = version
346
self._properties = payload
347
348
def fetch(self) -> 'InstanceResource':
349
"""Fetch latest resource data"""
350
351
def delete(self) -> bool:
352
"""Delete the resource"""
353
354
def update(self, **kwargs) -> 'InstanceResource':
355
"""Update the resource"""
356
357
class ListResource:
358
"""Resource collection"""
359
360
def __init__(self, version: Version):
361
self._version = version
362
363
def create(self, **kwargs) -> InstanceResource:
364
"""Create new resource instance"""
365
366
def list(self, limit: int = None, page_size: int = None) -> Iterator[InstanceResource]:
367
"""List resource instances"""
368
369
def stream(self, limit: int = None, page_size: int = None) -> Iterator[InstanceResource]:
370
"""Stream resource instances"""
371
372
class InstanceContext:
373
"""Resource context for operations"""
374
375
def __init__(self, version: Version, payload: dict, **kwargs):
376
self._version = version
377
self._properties = payload
378
379
def fetch(self) -> InstanceResource:
380
"""Fetch resource instance"""
381
382
def delete(self) -> bool:
383
"""Delete resource"""
384
385
def update(self, **kwargs) -> InstanceResource:
386
"""Update resource"""
387
388
class Page:
389
"""Paginated results container"""
390
391
def __init__(self, version: Version, response: Response):
392
self._version = version
393
self._response = response
394
395
def get_instance(self, payload: dict) -> InstanceResource:
396
"""Convert payload to resource instance"""
397
398
def get_instances(self) -> list:
399
"""Get all instances from page"""
400
401
def next_page_uri(self) -> str:
402
"""Get URI for next page"""
403
404
def previous_page_uri(self) -> str:
405
"""Get URI for previous page"""
406
```
407
408
### Exception Handling
409
410
Comprehensive exception hierarchy for error handling.
411
412
```python { .api }
413
class TwilioException(Exception):
414
"""Base exception for all Twilio errors"""
415
416
def __init__(self, message: str):
417
self.message = message
418
super().__init__(message)
419
420
class TwilioRestException(TwilioException):
421
"""REST API error with detailed information"""
422
423
def __init__(
424
self,
425
status: int,
426
uri: str,
427
message: str = None,
428
code: int = None,
429
method: str = 'GET',
430
details: dict = None
431
):
432
"""
433
Args:
434
status (int): HTTP status code
435
uri (str): Request URI that failed
436
message (str): Error message
437
code (int): Twilio error code
438
method (str): HTTP method
439
details (dict): Additional error details
440
"""
441
self.status = status
442
self.uri = uri
443
self.code = code
444
self.method = method
445
self.details = details or {}
446
self.more_info = f"https://www.twilio.com/docs/errors/{code}" if code else None
447
448
super().__init__(message or f"HTTP {status} error")
449
450
class TwilioHttpException(TwilioException):
451
"""HTTP transport layer error"""
452
453
def __init__(self, message: str):
454
super().__init__(message)
455
```
456
457
### Utility Classes
458
459
Helper classes for parameter handling and data serialization.
460
461
```python { .api }
462
class Values:
463
"""Parameter value container with special handling"""
464
465
def __init__(self, data: dict = None):
466
self._data = data or {}
467
468
def __getitem__(self, key: str):
469
return self._data.get(key)
470
471
def __setitem__(self, key: str, value):
472
if value is not None:
473
self._data[key] = value
474
475
def of(self, data: dict) -> 'Values':
476
"""Create Values instance from dict"""
477
return Values(data)
478
479
# Serialization utilities
480
def serialize_object(obj) -> str:
481
"""Serialize object to string representation"""
482
483
def serialize_list_object(obj_list: list, separator: str = ',') -> str:
484
"""Serialize list of objects to string"""
485
486
def deserialize_rfc2822_datetime(date_string: str) -> datetime:
487
"""Parse RFC2822 formatted datetime string"""
488
489
def deserialize_iso8601_datetime(date_string: str) -> datetime:
490
"""Parse ISO8601 formatted datetime string"""
491
492
def deserialize_integer(value: str) -> int:
493
"""Parse string to integer"""
494
495
def deserialize_decimal(value: str) -> Decimal:
496
"""Parse string to decimal"""
497
498
def deserialize_boolean(value: str) -> bool:
499
"""Parse string to boolean"""
500
501
def prefixed_collapsible_map(data: dict, prefix: str) -> dict:
502
"""Convert prefixed dictionary to collapsible format"""
503
504
def map_properties(data: dict) -> dict:
505
"""Map dictionary properties for API serialization"""
506
```
507
508
## Custom HTTP Client Example
509
510
```python
511
from twilio.http.http_client import HttpClient
512
from twilio.http.response import Response
513
import requests
514
515
class CustomHttpClient(HttpClient):
516
"""Custom HTTP client with additional logging"""
517
518
def __init__(self, timeout=60, retries=3):
519
self.timeout = timeout
520
self.retries = retries
521
self.session = requests.Session()
522
523
def request(self, method, uri, data=None, headers=None, auth=None, **kwargs):
524
print(f"Making {method} request to {uri}")
525
526
response = self.session.request(
527
method=method,
528
url=uri,
529
data=data,
530
headers=headers,
531
auth=auth,
532
timeout=self.timeout,
533
**kwargs
534
)
535
536
print(f"Response status: {response.status_code}")
537
538
return Response(
539
status_code=response.status_code,
540
text=response.text,
541
headers=dict(response.headers)
542
)
543
544
# Use custom client
545
from twilio.rest import Client
546
547
custom_client = CustomHttpClient(timeout=30)
548
client = Client(
549
'ACxxxxx',
550
'auth_token',
551
http_client=custom_client
552
)
553
```
554
555
## Error Handling Patterns
556
557
```python
558
from twilio.base.exceptions import TwilioRestException, TwilioException
559
560
try:
561
message = client.messages.create(
562
body="Test message",
563
from_="+15551234567",
564
to="invalid_number"
565
)
566
except TwilioRestException as e:
567
print(f"Twilio API Error:")
568
print(f" Status: {e.status}")
569
print(f" Code: {e.code}")
570
print(f" Message: {e.message}")
571
print(f" URI: {e.uri}")
572
print(f" More info: {e.more_info}")
573
574
# Handle specific error codes
575
if e.code == 21211:
576
print("Invalid 'To' phone number")
577
elif e.code == 21608:
578
print("Number not owned by account")
579
580
except TwilioException as e:
581
print(f"General Twilio error: {e.message}")
582
except Exception as e:
583
print(f"Unexpected error: {str(e)}")
584
```