Initial commit: Email alerts application

This commit is contained in:
Iyeoluwa Akinrinola
2025-07-25 11:31:36 +01:00
commit adfb625ae9
6322 changed files with 2882826 additions and 0 deletions
@@ -0,0 +1,234 @@
import os
import platform
from typing import Dict, List, MutableMapping, Optional, Tuple
from urllib.parse import urlparse, urlunparse
from twilio import __version__
from twilio.base.exceptions import TwilioException
from twilio.http import HttpClient
from twilio.http.http_client import TwilioHttpClient
from twilio.http.response import Response
class ClientBase(object):
"""A client for accessing the Twilio API."""
def __init__(
self,
username: Optional[str] = None,
password: Optional[str] = None,
account_sid: Optional[str] = None,
region: Optional[str] = None,
http_client: Optional[HttpClient] = None,
environment: Optional[MutableMapping[str, str]] = None,
edge: Optional[str] = None,
user_agent_extensions: Optional[List[str]] = None,
):
"""
Initializes the Twilio Client
:param username: Username to authenticate with
:param password: Password to authenticate with
:param account_sid: Account SID, defaults to Username
:param region: Twilio Region to make requests to, defaults to 'us1' if an edge is provided
:param http_client: HttpClient, defaults to TwilioHttpClient
:param environment: Environment to look for auth details, defaults to os.environ
:param edge: Twilio Edge to make requests to, defaults to None
:param user_agent_extensions: Additions to the user agent string
"""
environment = environment or os.environ
self.username = username or environment.get("TWILIO_ACCOUNT_SID")
""" :type : str """
self.password = password or environment.get("TWILIO_AUTH_TOKEN")
""" :type : str """
self.edge = edge or environment.get("TWILIO_EDGE")
""" :type : str """
self.region = region or environment.get("TWILIO_REGION")
""" :type : str """
self.user_agent_extensions = user_agent_extensions or []
""" :type : list[str] """
if not self.username or not self.password:
raise TwilioException("Credentials are required to create a TwilioClient")
self.account_sid = account_sid or self.username
""" :type : str """
self.auth = (self.username, self.password)
""" :type : tuple(str, str) """
self.http_client: HttpClient = http_client or TwilioHttpClient()
""" :type : HttpClient """
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:
"""
Makes a request to the Twilio API using the configured http client
Authentication information is automatically added if none is provided
:param method: HTTP Method
:param uri: Fully qualified url
:param params: Query string parameters
:param data: POST body data
:param headers: HTTP Headers
:param auth: Authentication
:param timeout: Timeout in seconds
:param allow_redirects: Should the client follow redirects
:returns: Response from the Twilio API
"""
auth = self.get_auth(auth)
headers = self.get_headers(method, headers)
uri = self.get_hostname(uri)
return self.http_client.request(
method,
uri,
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:
"""
Asynchronously makes a request to the Twilio API using the configured http client
The configured http client must be an asynchronous http client
Authentication information is automatically added if none is provided
:param method: HTTP Method
:param uri: Fully qualified url
:param params: Query string parameters
:param data: POST body data
:param headers: HTTP Headers
:param auth: Authentication
:param timeout: Timeout in seconds
:param allow_redirects: Should the client follow redirects
:returns: Response from the Twilio API
"""
if not self.http_client.is_async:
raise RuntimeError(
"http_client must be asynchronous to support async API requests"
)
auth = self.get_auth(auth)
headers = self.get_headers(method, headers)
uri = self.get_hostname(uri)
return await self.http_client.request(
method,
uri,
params=params,
data=data,
headers=headers,
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
)
def get_auth(self, auth: Optional[Tuple[str, str]]) -> Tuple[str, str]:
"""
Get the request authentication object
:param auth: Authentication (username, password)
:returns: The authentication object
"""
return auth or self.auth
def get_headers(
self, method: str, headers: Optional[Dict[str, str]]
) -> Dict[str, str]:
"""
Get the request headers including user-agent, extensions, encoding, content-type, MIME type
:param method: HTTP method
:param headers: HTTP headers
:returns: HTTP headers
"""
headers = headers or {}
# Set User-Agent
pkg_version = __version__
os_name = platform.system()
os_arch = platform.machine()
python_version = platform.python_version()
headers["User-Agent"] = "twilio-python/{} ({} {}) Python/{}".format(
pkg_version,
os_name,
os_arch,
python_version,
)
# Extensions
for extension in self.user_agent_extensions:
headers["User-Agent"] += " {}".format(extension)
headers["X-Twilio-Client"] = "python-{}".format(__version__)
# Types, encodings, etc.
headers["Accept-Charset"] = "utf-8"
if method == "POST" and "Content-Type" not in headers:
headers["Content-Type"] = "application/x-www-form-urlencoded"
if "Accept" not in headers:
headers["Accept"] = "application/json"
return headers
def get_hostname(self, uri: str) -> str:
"""
Determines the proper hostname given edge and region preferences
via client configuration or uri.
:param uri: Fully qualified url
:returns: The final uri used to make the request
"""
if not self.edge and not self.region:
return uri
parsed_url = urlparse(uri)
pieces = parsed_url.netloc.split(".")
prefix = pieces[0]
suffix = ".".join(pieces[-2:])
region = None
edge = None
if len(pieces) == 4:
# product.region.twilio.com
region = pieces[1]
elif len(pieces) == 5:
# product.edge.region.twilio.com
edge = pieces[1]
region = pieces[2]
edge = self.edge or edge
region = self.region or region or (edge and "us1")
parsed_url = parsed_url._replace(
netloc=".".join([part for part in [prefix, edge, region, suffix] if part])
)
return str(urlunparse(parsed_url))
def __repr__(self) -> str:
"""
Provide a friendly representation
:returns: Machine friendly representation
"""
return "<Twilio {}>".format(self.account_sid)
@@ -0,0 +1,75 @@
import datetime
from decimal import BasicContext, Decimal
from email.utils import parsedate
from typing import Optional, Union
ISO8601_DATE_FORMAT = "%Y-%m-%d"
ISO8601_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
def iso8601_date(s: str) -> Union[datetime.date, str]:
"""
Parses an ISO 8601 date string and returns a UTC date object or the string
if the parsing failed.
:param s: ISO 8601-formatted date string (2015-01-25)
:return:
"""
try:
return (
datetime.datetime.strptime(s, ISO8601_DATE_FORMAT)
.replace(tzinfo=datetime.timezone.utc)
.date()
)
except (TypeError, ValueError):
return s
def iso8601_datetime(
s: str,
) -> Union[datetime.datetime, str]:
"""
Parses an ISO 8601 datetime string and returns a UTC datetime object,
or the string if parsing failed.
:param s: ISO 8601-formatted datetime string (2015-01-25T12:34:56Z)
"""
try:
return datetime.datetime.strptime(s, ISO8601_DATETIME_FORMAT).replace(
tzinfo=datetime.timezone.utc
)
except (TypeError, ValueError):
return s
def rfc2822_datetime(s: str) -> Optional[datetime.datetime]:
"""
Parses an RFC 2822 date string and returns a UTC datetime object,
or the string if parsing failed.
:param s: RFC 2822-formatted string date
:return: datetime or str
"""
date_tuple = parsedate(s)
if date_tuple is None:
return None
return datetime.datetime(*date_tuple[:6]).replace(tzinfo=datetime.timezone.utc)
def decimal(d: Optional[str]) -> Union[Decimal, str]:
"""
Parses a decimal string into a Decimal
:param d: decimal string
"""
if not d:
return d
return Decimal(d, BasicContext)
def integer(i: str) -> Union[int, str]:
"""
Parses an integer string into an int
:param i: integer string
:return: int
"""
try:
return int(i)
except (TypeError, ValueError):
return i
@@ -0,0 +1,93 @@
from typing import Dict, Optional, Tuple
from twilio.http.response import Response
from twilio.rest import Client
class Domain(object):
"""
This represents at Twilio API subdomain.
Like, `api.twilio.com` or `lookups.twilio.com'.
"""
def __init__(self, twilio: Client, base_url: str):
self.twilio = twilio
self.base_url = base_url
def absolute_url(self, uri: str) -> str:
"""
Converts a relative `uri` to an absolute url.
:param string uri: The relative uri to make absolute.
:return: An absolute url (based off this domain)
"""
return "{}/{}".format(self.base_url.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:
"""
Makes an HTTP request to this domain.
:param method: The HTTP method.
:param uri: The HTTP uri.
:param params: Query parameters.
:param data: The request body.
:param headers: The HTTP headers.
:param auth: Basic auth tuple of (username, password)
:param timeout: The request timeout.
:param allow_redirects: True if the client should follow HTTP
redirects.
"""
url = self.absolute_url(uri)
return self.twilio.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:
"""
Makes an asynchronous HTTP request to this domain.
:param method: The HTTP method.
:param uri: The HTTP uri.
:param params: Query parameters.
:param data: The request body.
:param headers: The HTTP headers.
:param auth: Basic auth tuple of (username, password)
:param timeout: The request timeout.
:param allow_redirects: True if the client should follow HTTP
redirects.
"""
url = self.absolute_url(uri)
return await self.twilio.request_async(
method,
url,
params=params,
data=data,
headers=headers,
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
)
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
import sys
from typing import Optional
class TwilioException(Exception):
pass
class TwilioRestException(TwilioException):
"""A generic 400 or 500 level exception from the Twilio API
:param int status: the HTTP status that was returned for the exception
:param str uri: The URI that caused the exception
:param str msg: A human-readable message for the error
:param int|None code: A Twilio-specific error code for the error. This is
not available for all errors.
:param method: The HTTP method used to make the request
:param details: Additional error details returned for the exception
"""
def __init__(
self,
status: int,
uri: str,
msg: str = "",
code: Optional[int] = None,
method: str = "GET",
details: Optional[object] = None,
):
self.uri = uri
self.status = status
self.msg = msg
self.code = code
self.method = method
self.details = details
def __str__(self) -> str:
"""Try to pretty-print the exception, if this is going on screen."""
def red(words: str) -> str:
return "\033[31m\033[49m%s\033[0m" % words
def white(words: str) -> str:
return "\033[37m\033[49m%s\033[0m" % words
def blue(words: str) -> str:
return "\033[34m\033[49m%s\033[0m" % words
def teal(words: str) -> str:
return "\033[36m\033[49m%s\033[0m" % words
def get_uri(code: int) -> str:
return "https://www.twilio.com/docs/errors/{0}".format(code)
# If it makes sense to print a human readable error message, try to
# do it. The one problem is that someone might catch this error and
# try to display the message from it to an end user.
if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
msg = (
"\n{red_error} {request_was}\n\n{http_line}"
"\n\n{twilio_returned}\n\n{message}\n".format(
red_error=red("HTTP Error"),
request_was=white("Your request was:"),
http_line=teal("%s %s" % (self.method, self.uri)),
twilio_returned=white("Twilio returned the following information:"),
message=blue(str(self.msg)),
)
)
if self.code:
msg = "".join(
[
msg,
"\n{more_info}\n\n{uri}\n\n".format(
more_info=white("More information may be available here:"),
uri=blue(get_uri(self.code)),
),
]
)
return msg
else:
return "HTTP {0} error: {1}".format(self.status, self.msg)
@@ -0,0 +1,6 @@
from twilio.base.version import Version
class InstanceContext(object):
def __init__(self, version: Version):
self._version = version
@@ -0,0 +1,6 @@
from twilio.base.version import Version
class InstanceResource(object):
def __init__(self, version: Version):
self._version = version
@@ -0,0 +1,6 @@
from twilio.base.version import Version
class ListResource(object):
def __init__(self, version: Version):
self._version = version
@@ -0,0 +1,47 @@
import warnings
import functools
class ObsoleteException(Exception):
"""Base class for warnings about obsolete features."""
def obsolete_client(func):
"""This is a decorator which can be used to mark Client classes as
obsolete. It will result in an error being emitted when the class is
instantiated."""
@functools.wraps(func)
def new_func(*args, **kwargs):
raise ObsoleteException(
"{} has been removed from this version of the library. "
"Please refer to current documentation for guidance.".format(func.__name__)
)
return new_func
def deprecated_method(new_func=None):
"""
This is a decorator which can be used to mark deprecated methods.
It will report in a DeprecationWarning being emitted to stderr when the deprecated method is used.
"""
def deprecated_method_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
msg = "Function method .{}() is deprecated".format(func.__name__)
msg += (
" in favor of .{}()".format(new_func)
if isinstance(new_func, str)
else ""
)
warnings.warn(msg, DeprecationWarning)
return func(*args, **kwargs)
return wrapper
if callable(new_func):
return deprecated_method_wrapper(new_func)
return deprecated_method_wrapper
@@ -0,0 +1,171 @@
import json
from typing import Any, Dict, Optional
from twilio.base.exceptions import TwilioException
from twilio.http.response import Response
class Page(object):
"""
Represents a page of records in a collection.
A `Page` lets you iterate over its records and fetch the next and previous
pages in the collection.
"""
META_KEYS = {
"end",
"first_page_uri",
"next_page_uri",
"last_page_uri",
"page",
"page_size",
"previous_page_uri",
"total",
"num_pages",
"start",
"uri",
}
def __init__(self, version, response: Response, solution={}):
payload = self.process_response(response)
self._version = version
self._payload = payload
self._solution = solution
self._records = iter(self.load_page(payload))
def __iter__(self):
"""
A `Page` is a valid iterator.
"""
return self
def __next__(self):
return self.next()
def next(self):
"""
Returns the next record in the `Page`.
"""
return self.get_instance(next(self._records))
@classmethod
def process_response(cls, response: Response) -> Any:
"""
Load a JSON response.
:param response: The HTTP response.
:return The JSON-loaded content.
"""
if response.status_code != 200:
raise TwilioException("Unable to fetch page", response)
return json.loads(response.text)
def load_page(self, payload: Dict[str, Any]):
"""
Parses the collection of records out of a list payload.
:param payload: The JSON-loaded content.
:return list: The list of records.
"""
if "meta" in payload and "key" in payload["meta"]:
return payload[payload["meta"]["key"]]
else:
keys = set(payload.keys())
key = keys - self.META_KEYS
if len(key) == 1:
return payload[key.pop()]
raise TwilioException("Page Records can not be deserialized")
@property
def previous_page_url(self) -> Optional[str]:
"""
:return str: Returns a link to the previous_page_url or None if doesn't exist.
"""
if "meta" in self._payload and "previous_page_url" in self._payload["meta"]:
return self._payload["meta"]["previous_page_url"]
elif (
"previous_page_uri" in self._payload and self._payload["previous_page_uri"]
):
return self._version.domain.absolute_url(self._payload["previous_page_uri"])
return None
@property
def next_page_url(self) -> Optional[str]:
"""
:return str: Returns a link to the next_page_url or None if doesn't exist.
"""
if "meta" in self._payload and "next_page_url" in self._payload["meta"]:
return self._payload["meta"]["next_page_url"]
elif "next_page_uri" in self._payload and self._payload["next_page_uri"]:
return self._version.domain.absolute_url(self._payload["next_page_uri"])
return None
def get_instance(self, payload: Dict[str, Any]) -> Any:
"""
:param dict payload: A JSON-loaded representation of an instance record.
:return: A rich, resource-dependent object.
"""
raise TwilioException(
"Page.get_instance() must be implemented in the derived class"
)
def next_page(self) -> Optional["Page"]:
"""
Return the `Page` after this one.
:return The next page.
"""
if not self.next_page_url:
return None
response = self._version.domain.twilio.request("GET", self.next_page_url)
cls = type(self)
return cls(self._version, response, self._solution)
async def next_page_async(self) -> Optional["Page"]:
"""
Asynchronously return the `Page` after this one.
:return The next page.
"""
if not self.next_page_url:
return None
response = await self._version.domain.twilio.request_async(
"GET", self.next_page_url
)
cls = type(self)
return cls(self._version, response, self._solution)
def previous_page(self) -> Optional["Page"]:
"""
Return the `Page` before this one.
:return The previous page.
"""
if not self.previous_page_url:
return None
response = self._version.domain.twilio.request("GET", self.previous_page_url)
cls = type(self)
return cls(self._version, response, self._solution)
async def previous_page_async(self) -> Optional["Page"]:
"""
Asynchronously return the `Page` before this one.
:return The previous page.
"""
if not self.previous_page_url:
return None
response = await self._version.domain.twilio.request_async(
"GET", self.previous_page_url
)
cls = type(self)
return cls(self._version, response, self._solution)
def __repr__(self) -> str:
return "<Page>"
@@ -0,0 +1,80 @@
import datetime
import json
from twilio.base import values
def iso8601_date(d):
"""
Return a string representation of a date that the Twilio API understands
Format is YYYY-MM-DD. Returns None if d is not a string, datetime, or date
"""
if d == values.unset:
return d
elif isinstance(d, datetime.datetime):
return str(d.date())
elif isinstance(d, datetime.date):
return str(d)
elif isinstance(d, str):
return d
def iso8601_datetime(d):
"""
Return a string representation of a date that the Twilio API understands
Format is YYYY-MM-DD. Returns None if d is not a string, datetime, or date
"""
if d == values.unset:
return d
elif isinstance(d, datetime.datetime) or isinstance(d, datetime.date):
return d.strftime("%Y-%m-%dT%H:%M:%SZ")
elif isinstance(d, str):
return d
def prefixed_collapsible_map(m, prefix):
"""
Return a dict of params corresponding to those in m with the added prefix
"""
if m == values.unset:
return {}
def flatten_dict(d, result=None, prv_keys=None):
if result is None:
result = {}
if prv_keys is None:
prv_keys = []
for k, v in d.items():
if isinstance(v, dict):
flatten_dict(v, result, prv_keys + [k])
else:
result[".".join(prv_keys + [k])] = v
return result
if isinstance(m, dict):
flattened = flatten_dict(m)
return {"{}.{}".format(prefix, k): v for k, v in flattened.items()}
return {}
def object(obj):
"""
Return a jsonified string represenation of obj if obj is jsonifiable else
return obj untouched
"""
if isinstance(obj, dict) or isinstance(obj, list):
return json.dumps(obj)
return obj
def map(lst, serialize_func):
"""
Applies serialize_func to every element in lst
"""
if not isinstance(lst, list):
return lst
return [serialize_func(e) for e in lst]
@@ -0,0 +1,13 @@
from typing import Dict
unset = object()
def of(d: Dict[str, object]) -> Dict[str, object]:
"""
Remove unset values from a dict.
:param d: A dict to strip.
:return A dict with unset values removed.
"""
return {k: v for k, v in d.items() if v != unset}
@@ -0,0 +1,492 @@
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)