493 lines
14 KiB
Python
493 lines
14 KiB
Python
import json
|
|
from typing import Any, AsyncIterator, Dict, Iterator, Optional, Tuple
|
|
|
|
from twilio.base import values
|
|
from twilio.base.domain import Domain
|
|
from twilio.base.exceptions import TwilioRestException
|
|
from twilio.base.page import Page
|
|
from twilio.http.response import Response
|
|
|
|
|
|
class Version(object):
|
|
"""
|
|
Represents an API version.
|
|
"""
|
|
|
|
def __init__(self, domain: Domain, version: str):
|
|
self.domain = domain
|
|
self.version = version
|
|
|
|
def absolute_url(self, uri: str) -> str:
|
|
"""
|
|
Turns a relative uri into an absolute url.
|
|
"""
|
|
return self.domain.absolute_url(self.relative_uri(uri))
|
|
|
|
def relative_uri(self, uri: str) -> str:
|
|
"""
|
|
Turns a relative uri into a versioned relative uri.
|
|
"""
|
|
return "{}/{}".format(self.version.strip("/"), uri.strip("/"))
|
|
|
|
def request(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Response:
|
|
"""
|
|
Make an HTTP request.
|
|
"""
|
|
url = self.relative_uri(uri)
|
|
return self.domain.request(
|
|
method,
|
|
url,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
async def request_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Response:
|
|
"""
|
|
Make an asynchronous HTTP request
|
|
"""
|
|
url = self.relative_uri(uri)
|
|
return await self.domain.request_async(
|
|
method,
|
|
url,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
@classmethod
|
|
def exception(
|
|
cls, method: str, uri: str, response: Response, message: str
|
|
) -> TwilioRestException:
|
|
"""
|
|
Wraps an exceptional response in a `TwilioRestException`.
|
|
"""
|
|
# noinspection PyBroadException
|
|
try:
|
|
error_payload = json.loads(response.text)
|
|
if "message" in error_payload:
|
|
message = "{}: {}".format(message, error_payload["message"])
|
|
details = error_payload.get("details")
|
|
code = error_payload.get("code", response.status_code)
|
|
return TwilioRestException(
|
|
response.status_code, uri, message, code, method, details
|
|
)
|
|
except Exception:
|
|
return TwilioRestException(
|
|
response.status_code, uri, message, response.status_code, method
|
|
)
|
|
|
|
def _parse_fetch(self, method: str, uri: str, response: Response) -> Any:
|
|
"""
|
|
Parses fetch response JSON
|
|
"""
|
|
# Note that 3XX response codes are allowed for fetches.
|
|
if response.status_code < 200 or response.status_code >= 400:
|
|
raise self.exception(method, uri, response, "Unable to fetch record")
|
|
|
|
return json.loads(response.text)
|
|
|
|
def fetch(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Fetch a resource instance.
|
|
"""
|
|
response = self.request(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_fetch(method, uri, response)
|
|
|
|
async def fetch_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Asynchronously fetch a resource instance.
|
|
"""
|
|
response = await self.request_async(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_fetch(method, uri, response)
|
|
|
|
def _parse_update(self, method: str, uri: str, response: Response) -> Any:
|
|
"""
|
|
Parses update response JSON
|
|
"""
|
|
if response.status_code < 200 or response.status_code >= 300:
|
|
raise self.exception(method, uri, response, "Unable to update record")
|
|
|
|
return json.loads(response.text)
|
|
|
|
def update(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Update a resource instance.
|
|
"""
|
|
response = self.request(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_update(method, uri, response)
|
|
|
|
async def update_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Asynchronously update a resource instance.
|
|
"""
|
|
response = await self.request_async(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_update(method, uri, response)
|
|
|
|
def _parse_delete(self, method: str, uri: str, response: Response) -> bool:
|
|
"""
|
|
Parses delete response JSON
|
|
"""
|
|
if response.status_code < 200 or response.status_code >= 300:
|
|
raise self.exception(method, uri, response, "Unable to delete record")
|
|
|
|
return response.status_code == 204
|
|
|
|
def delete(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> bool:
|
|
"""
|
|
Delete a resource.
|
|
"""
|
|
response = self.request(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_delete(method, uri, response)
|
|
|
|
async def delete_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> bool:
|
|
"""
|
|
Asynchronously delete a resource.
|
|
"""
|
|
response = await self.request_async(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_delete(method, uri, response)
|
|
|
|
def read_limits(
|
|
self, limit: Optional[int] = None, page_size: Optional[int] = None
|
|
) -> Dict[str, object]:
|
|
"""
|
|
Takes a limit on the max number of records to read and a max page_size
|
|
and calculates the max number of pages to read.
|
|
|
|
:param limit: Max number of records to read.
|
|
:param page_size: Max page size.
|
|
:return A dictionary of paging limits.
|
|
"""
|
|
if limit is not None and page_size is None:
|
|
page_size = limit
|
|
|
|
return {
|
|
"limit": limit or values.unset,
|
|
"page_size": page_size or values.unset,
|
|
}
|
|
|
|
def page(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Response:
|
|
"""
|
|
Makes an HTTP request.
|
|
"""
|
|
return self.request(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
async def page_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Response:
|
|
"""
|
|
Makes an asynchronous HTTP request.
|
|
"""
|
|
return await self.request_async(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
def stream(
|
|
self,
|
|
page: Optional[Page],
|
|
limit: Optional[int] = None,
|
|
page_limit: Optional[int] = None,
|
|
) -> Iterator[Any]:
|
|
"""
|
|
Generates records one a time from a page, stopping at prescribed limits.
|
|
|
|
:param page: The page to stream.
|
|
:param limit: The max number of records to read.
|
|
:param page_limit: The max number of pages to read.
|
|
"""
|
|
current_record = 1
|
|
current_page = 1
|
|
|
|
while page is not None:
|
|
for record in page:
|
|
yield record
|
|
current_record += 1
|
|
if limit and limit is not values.unset and limit < current_record:
|
|
return
|
|
|
|
current_page += 1
|
|
if (
|
|
page_limit
|
|
and page_limit is not values.unset
|
|
and page_limit < current_page
|
|
):
|
|
return
|
|
|
|
page = page.next_page()
|
|
|
|
async def stream_async(
|
|
self,
|
|
page: Optional[Page],
|
|
limit: Optional[int] = None,
|
|
page_limit: Optional[int] = None,
|
|
) -> AsyncIterator[Any]:
|
|
"""
|
|
Generates records one a time from a page, stopping at prescribed limits.
|
|
|
|
:param page: The page to stream.
|
|
:param limit: The max number of records to read.
|
|
:param page_limit: The max number of pages to read.
|
|
"""
|
|
current_record = 1
|
|
current_page = 1
|
|
|
|
while page is not None:
|
|
for record in page:
|
|
yield record
|
|
current_record += 1
|
|
if limit and limit is not values.unset and limit < current_record:
|
|
return
|
|
|
|
current_page += 1
|
|
if (
|
|
page_limit
|
|
and page_limit is not values.unset
|
|
and page_limit < current_page
|
|
):
|
|
return
|
|
|
|
page = await page.next_page_async()
|
|
|
|
def _parse_create(self, method: str, uri: str, response: Response) -> Any:
|
|
"""
|
|
Parse create response JSON
|
|
"""
|
|
if response.status_code < 200 or response.status_code >= 300:
|
|
raise self.exception(method, uri, response, "Unable to create record")
|
|
|
|
return json.loads(response.text)
|
|
|
|
def create(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Create a resource instance.
|
|
"""
|
|
response = self.request(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_create(method, uri, response)
|
|
|
|
async def create_async(
|
|
self,
|
|
method: str,
|
|
uri: str,
|
|
params: Optional[Dict[str, object]] = None,
|
|
data: Optional[Dict[str, object]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
auth: Optional[Tuple[str, str]] = None,
|
|
timeout: Optional[float] = None,
|
|
allow_redirects: bool = False,
|
|
) -> Any:
|
|
"""
|
|
Asynchronously create a resource instance.
|
|
"""
|
|
response = await self.request_async(
|
|
method,
|
|
uri,
|
|
params=params,
|
|
data=data,
|
|
headers=headers,
|
|
auth=auth,
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
)
|
|
|
|
return self._parse_create(method, uri, response)
|