truagents 0.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- truagents/__init__.py +18 -0
- truagents/__version__.py +1 -0
- truagents/auth.py +293 -0
- truagents/client.py +304 -0
- truagents/errors.py +193 -0
- truagents/generated/__init__.py +8 -0
- truagents/generated/api/__init__.py +1 -0
- truagents/generated/api/email_unsubscribe/__init__.py +1 -0
- truagents/generated/api/email_unsubscribe/list_email_unsubscribes.py +290 -0
- truagents/generated/api/email_unsubscribe/push_email_unsubscribes.py +236 -0
- truagents/generated/api/o_auth/__init__.py +1 -0
- truagents/generated/api/o_auth/issue_o_auth_token.py +241 -0
- truagents/generated/api/sms_unsubscribe/__init__.py +1 -0
- truagents/generated/api/sms_unsubscribe/list_sms_unsubscribes.py +290 -0
- truagents/generated/api/sms_unsubscribe/push_sms_unsubscribes.py +232 -0
- truagents/generated/api/voice_unsubscribe/__init__.py +1 -0
- truagents/generated/api/voice_unsubscribe/list_phone_unsubscribes.py +298 -0
- truagents/generated/api/voice_unsubscribe/push_phone_unsubscribes.py +232 -0
- truagents/generated/client.py +282 -0
- truagents/generated/errors.py +16 -0
- truagents/generated/models/__init__.py +65 -0
- truagents/generated/models/email_skipped_item.py +83 -0
- truagents/generated/models/email_skipped_item_reason.py +11 -0
- truagents/generated/models/email_source_enum.py +11 -0
- truagents/generated/models/email_unsubscribe_batch_request.py +93 -0
- truagents/generated/models/email_unsubscribe_batch_response.py +119 -0
- truagents/generated/models/email_unsubscribe_item.py +50 -0
- truagents/generated/models/email_unsubscribe_list_response.py +113 -0
- truagents/generated/models/email_unsubscribe_record.py +98 -0
- truagents/generated/models/email_unsubscribe_updated_entry.py +79 -0
- truagents/generated/models/o_auth_client_credentials_request.py +87 -0
- truagents/generated/models/o_auth_client_credentials_request_grant_type.py +8 -0
- truagents/generated/models/o_auth_error_response.py +83 -0
- truagents/generated/models/o_auth_error_response_error.py +11 -0
- truagents/generated/models/o_auth_refresh_token_request.py +75 -0
- truagents/generated/models/o_auth_refresh_token_request_grant_type.py +8 -0
- truagents/generated/models/o_auth_token_response.py +97 -0
- truagents/generated/models/o_auth_token_response_token_type.py +8 -0
- truagents/generated/models/phone_skipped_item.py +83 -0
- truagents/generated/models/phone_skipped_item_reason.py +11 -0
- truagents/generated/models/phone_source_enum.py +11 -0
- truagents/generated/models/phone_unsubscribe_batch_request.py +92 -0
- truagents/generated/models/phone_unsubscribe_batch_response.py +118 -0
- truagents/generated/models/phone_unsubscribe_item.py +51 -0
- truagents/generated/models/phone_unsubscribe_list_response.py +111 -0
- truagents/generated/models/phone_unsubscribe_record.py +99 -0
- truagents/generated/models/phone_unsubscribe_updated_entry.py +79 -0
- truagents/generated/models/rest_error_response.py +74 -0
- truagents/generated/models/unauthorized_organization_error.py +90 -0
- truagents/generated/models/unauthorized_organization_error_error.py +8 -0
- truagents/generated/types.py +54 -0
- truagents/observability.py +70 -0
- truagents/retry.py +64 -0
- truagents-0.0.0.dev0.dist-info/METADATA +171 -0
- truagents-0.0.0.dev0.dist-info/RECORD +56 -0
- truagents-0.0.0.dev0.dist-info/WHEEL +4 -0
truagents/errors.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Typed exception hierarchy for the TruAgents SDK.
|
|
2
|
+
|
|
3
|
+
Two branches under a common `TruAgentsError` base:
|
|
4
|
+
|
|
5
|
+
- `AuthError` — RFC 6749-style OAuth failures raised by TokenManager.
|
|
6
|
+
- `APIError` — HTTP failures raised by resource methods on the client.
|
|
7
|
+
|
|
8
|
+
`NetworkError` wraps transport-layer exceptions (`httpx.TransportError` etc.).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
import time
|
|
15
|
+
from datetime import UTC
|
|
16
|
+
from email.utils import parsedate_to_datetime
|
|
17
|
+
from typing import Any, Literal
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TruAgentsError(Exception):
|
|
23
|
+
"""Base class for all SDK errors.
|
|
24
|
+
|
|
25
|
+
Subclasses that add typed constructor arguments beyond the base class
|
|
26
|
+
must override `__reduce__` so `pickle.dumps` -> `pickle.loads` reconstructs
|
|
27
|
+
via those arguments. Without `__reduce__`, `Exception.args` carries only
|
|
28
|
+
the formatted message and unpickling calls the subclass with a single
|
|
29
|
+
positional string, dropping the typed fields (or raising `TypeError` for
|
|
30
|
+
multi-arg constructors). See `AuthError.__reduce__`, `APIError.__reduce__`,
|
|
31
|
+
`RateLimited.__reduce__`, and `NetworkError.__reduce__` for the pattern.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
code: str = "TRUAGENTS_ERROR"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AuthError(TruAgentsError):
|
|
38
|
+
"""OAuth-token-endpoint failure. Mirrors RFC 6749 error responses."""
|
|
39
|
+
|
|
40
|
+
code = "AUTH_ERROR"
|
|
41
|
+
|
|
42
|
+
def __init__(self, http_status: int, error: str, error_description: str) -> None:
|
|
43
|
+
self.http_status = http_status
|
|
44
|
+
self.error = error
|
|
45
|
+
self.error_description = error_description
|
|
46
|
+
super().__init__(f"HTTP {http_status} error={error} error_description={error_description}")
|
|
47
|
+
|
|
48
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, ...]]:
|
|
49
|
+
return (type(self), (self.http_status, self.error, self.error_description))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InvalidClient(AuthError):
|
|
53
|
+
code = "INVALID_CLIENT"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InvalidGrant(AuthError):
|
|
57
|
+
code = "INVALID_GRANT"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TokenExpired(AuthError):
|
|
61
|
+
code = "TOKEN_EXPIRED"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class APIError(TruAgentsError):
|
|
65
|
+
"""Non-2xx response from a resource endpoint."""
|
|
66
|
+
|
|
67
|
+
code = "API_ERROR"
|
|
68
|
+
|
|
69
|
+
def __init__(self, http_status: int, body: str) -> None:
|
|
70
|
+
self.http_status = http_status
|
|
71
|
+
self.body = body
|
|
72
|
+
super().__init__(f"HTTP {http_status} body={body}")
|
|
73
|
+
|
|
74
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, ...]]:
|
|
75
|
+
return (type(self), (self.http_status, self.body))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RateLimited(APIError):
|
|
79
|
+
code = "RATE_LIMITED"
|
|
80
|
+
|
|
81
|
+
def __init__(self, http_status: int, body: str, retry_after: float) -> None:
|
|
82
|
+
self.retry_after = retry_after
|
|
83
|
+
super().__init__(http_status, body)
|
|
84
|
+
|
|
85
|
+
def __str__(self) -> str:
|
|
86
|
+
return f"HTTP {self.http_status} body={self.body} retry_after={self.retry_after}s"
|
|
87
|
+
|
|
88
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, ...]]:
|
|
89
|
+
return (type(self), (self.http_status, self.body, self.retry_after))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class NotFound(APIError):
|
|
93
|
+
code = "NOT_FOUND"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class InvalidRequest(APIError):
|
|
97
|
+
code = "INVALID_REQUEST"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ServerError(APIError):
|
|
101
|
+
code = "SERVER_ERROR"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NetworkError(TruAgentsError):
|
|
105
|
+
"""Raised when the transport layer fails to complete a request.
|
|
106
|
+
|
|
107
|
+
`cause` carries the underlying `httpx` transport exception. That exception
|
|
108
|
+
may reference the originating request including its POST body (which for
|
|
109
|
+
the token endpoint contains `client_secret`). See `SECURITY.md` §
|
|
110
|
+
"Serialization of exceptions" before serializing a `NetworkError`.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
code = "NETWORK_ERROR"
|
|
114
|
+
|
|
115
|
+
def __init__(self, cause: Exception) -> None:
|
|
116
|
+
self.cause = cause
|
|
117
|
+
super().__init__(f"Network error: {cause}")
|
|
118
|
+
|
|
119
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, ...]]:
|
|
120
|
+
return (type(self), (self.cause,))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _parse_retry_after(header_value: str | None) -> float:
|
|
124
|
+
"""Parse the `Retry-After` header. Returns 0.0 when absent or unparseable.
|
|
125
|
+
|
|
126
|
+
Guards against `nan` / `inf` — a malformed 429 with `Retry-After: nan`
|
|
127
|
+
would otherwise flow into `time.sleep(nan)` and raise `ValueError` from
|
|
128
|
+
inside the retry loop as an untyped exception.
|
|
129
|
+
"""
|
|
130
|
+
if not header_value:
|
|
131
|
+
return 0.0
|
|
132
|
+
try:
|
|
133
|
+
seconds = float(header_value)
|
|
134
|
+
except ValueError:
|
|
135
|
+
pass
|
|
136
|
+
else:
|
|
137
|
+
if not math.isfinite(seconds):
|
|
138
|
+
return 0.0
|
|
139
|
+
return seconds
|
|
140
|
+
try:
|
|
141
|
+
target = parsedate_to_datetime(header_value)
|
|
142
|
+
except (TypeError, ValueError):
|
|
143
|
+
return 0.0
|
|
144
|
+
if target is None:
|
|
145
|
+
return 0.0
|
|
146
|
+
now = time.time()
|
|
147
|
+
target_ts = target.astimezone(UTC).timestamp()
|
|
148
|
+
return max(target_ts - now, 0.0)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _decode_body(response: httpx.Response) -> str:
|
|
152
|
+
try:
|
|
153
|
+
return response.text
|
|
154
|
+
except UnicodeDecodeError:
|
|
155
|
+
return response.content.decode(errors="replace")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _extract_oauth_error_fields(response: httpx.Response) -> tuple[str, str]:
|
|
159
|
+
try:
|
|
160
|
+
body = response.json()
|
|
161
|
+
except ValueError:
|
|
162
|
+
return "unknown_error", ""
|
|
163
|
+
if not isinstance(body, dict):
|
|
164
|
+
return "unknown_error", ""
|
|
165
|
+
return (
|
|
166
|
+
str(body.get("error", "unknown_error")),
|
|
167
|
+
str(body.get("error_description", "")),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def classify_http_error(response: httpx.Response, kind: Literal["oauth", "api"]) -> TruAgentsError:
|
|
172
|
+
"""Map an HTTP response to the appropriate SDK exception subclass."""
|
|
173
|
+
status = response.status_code
|
|
174
|
+
if kind == "oauth":
|
|
175
|
+
error, description = _extract_oauth_error_fields(response)
|
|
176
|
+
if status == 400 and error == "invalid_grant":
|
|
177
|
+
return InvalidGrant(status, error, description)
|
|
178
|
+
if status == 401:
|
|
179
|
+
return InvalidClient(status, error, description)
|
|
180
|
+
return AuthError(status, error, description)
|
|
181
|
+
|
|
182
|
+
body = _decode_body(response)
|
|
183
|
+
if status == 400:
|
|
184
|
+
return InvalidRequest(status, body)
|
|
185
|
+
if status == 401:
|
|
186
|
+
return TokenExpired(status, "token_expired", body)
|
|
187
|
+
if status == 404:
|
|
188
|
+
return NotFound(status, body)
|
|
189
|
+
if status == 429:
|
|
190
|
+
return RateLimited(status, body, _parse_retry_after(response.headers.get("Retry-After")))
|
|
191
|
+
if 500 <= status < 600:
|
|
192
|
+
return ServerError(status, body)
|
|
193
|
+
return APIError(status, body)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Contains methods for accessing the API"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Contains endpoint functions for accessing the API"""
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ... import errors
|
|
8
|
+
from ...client import AuthenticatedClient, Client
|
|
9
|
+
from ...models.email_unsubscribe_list_response import EmailUnsubscribeListResponse
|
|
10
|
+
from ...models.rest_error_response import RestErrorResponse
|
|
11
|
+
from ...models.unauthorized_organization_error import UnauthorizedOrganizationError
|
|
12
|
+
from ...types import UNSET, Response, Unset
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_kwargs(
|
|
16
|
+
*,
|
|
17
|
+
org_slug: str | Unset = UNSET,
|
|
18
|
+
since: datetime.datetime | Unset = UNSET,
|
|
19
|
+
until: datetime.datetime | Unset = UNSET,
|
|
20
|
+
cursor: str | Unset = UNSET,
|
|
21
|
+
limit: int | Unset = 100,
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
|
|
24
|
+
params: dict[str, Any] = {}
|
|
25
|
+
|
|
26
|
+
params["org_slug"] = org_slug
|
|
27
|
+
|
|
28
|
+
json_since: str | Unset = UNSET
|
|
29
|
+
if not isinstance(since, Unset):
|
|
30
|
+
json_since = since.isoformat()
|
|
31
|
+
params["since"] = json_since
|
|
32
|
+
|
|
33
|
+
json_until: str | Unset = UNSET
|
|
34
|
+
if not isinstance(until, Unset):
|
|
35
|
+
json_until = until.isoformat()
|
|
36
|
+
params["until"] = json_until
|
|
37
|
+
|
|
38
|
+
params["cursor"] = cursor
|
|
39
|
+
|
|
40
|
+
params["limit"] = limit
|
|
41
|
+
|
|
42
|
+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
|
|
43
|
+
|
|
44
|
+
_kwargs: dict[str, Any] = {
|
|
45
|
+
"method": "get",
|
|
46
|
+
"url": "/api/v1/unsubscribe/email",
|
|
47
|
+
"params": params,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return _kwargs
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_response(
|
|
54
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
55
|
+
) -> (
|
|
56
|
+
EmailUnsubscribeListResponse
|
|
57
|
+
| RestErrorResponse
|
|
58
|
+
| UnauthorizedOrganizationError
|
|
59
|
+
| None
|
|
60
|
+
):
|
|
61
|
+
if response.status_code == 200:
|
|
62
|
+
response_200 = EmailUnsubscribeListResponse.from_dict(response.json())
|
|
63
|
+
|
|
64
|
+
return response_200
|
|
65
|
+
|
|
66
|
+
if response.status_code == 400:
|
|
67
|
+
response_400 = RestErrorResponse.from_dict(response.json())
|
|
68
|
+
|
|
69
|
+
return response_400
|
|
70
|
+
|
|
71
|
+
if response.status_code == 401:
|
|
72
|
+
response_401 = RestErrorResponse.from_dict(response.json())
|
|
73
|
+
|
|
74
|
+
return response_401
|
|
75
|
+
|
|
76
|
+
if response.status_code == 403:
|
|
77
|
+
response_403 = UnauthorizedOrganizationError.from_dict(response.json())
|
|
78
|
+
|
|
79
|
+
return response_403
|
|
80
|
+
|
|
81
|
+
if response.status_code == 429:
|
|
82
|
+
response_429 = RestErrorResponse.from_dict(response.json())
|
|
83
|
+
|
|
84
|
+
return response_429
|
|
85
|
+
|
|
86
|
+
if client.raise_on_unexpected_status:
|
|
87
|
+
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
88
|
+
else:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_response(
|
|
93
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
94
|
+
) -> Response[
|
|
95
|
+
EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError
|
|
96
|
+
]:
|
|
97
|
+
return Response(
|
|
98
|
+
status_code=HTTPStatus(response.status_code),
|
|
99
|
+
content=response.content,
|
|
100
|
+
headers=response.headers,
|
|
101
|
+
parsed=_parse_response(client=client, response=response),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def sync_detailed(
|
|
106
|
+
*,
|
|
107
|
+
client: AuthenticatedClient | Client,
|
|
108
|
+
org_slug: str | Unset = UNSET,
|
|
109
|
+
since: datetime.datetime | Unset = UNSET,
|
|
110
|
+
until: datetime.datetime | Unset = UNSET,
|
|
111
|
+
cursor: str | Unset = UNSET,
|
|
112
|
+
limit: int | Unset = 100,
|
|
113
|
+
) -> Response[
|
|
114
|
+
EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError
|
|
115
|
+
]:
|
|
116
|
+
"""List email opt-out / opt-in records
|
|
117
|
+
|
|
118
|
+
Returns the current state of email unsubscribe records for the organization addressed by `org_slug`
|
|
119
|
+
(or the `client_id`'s default organization when omitted), ordered by `updated_at` descending. Treat
|
|
120
|
+
the endpoint as a change stream: `since` / `until` bound the pagination window and `cursor` walks
|
|
121
|
+
toward older records. Rate limited to 60 requests per minute per `client_id`.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
org_slug (str | Unset):
|
|
125
|
+
since (datetime.datetime | Unset):
|
|
126
|
+
until (datetime.datetime | Unset):
|
|
127
|
+
cursor (str | Unset):
|
|
128
|
+
limit (int | Unset): Default: 100.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
132
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Response[EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError]
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
kwargs = _get_kwargs(
|
|
139
|
+
org_slug=org_slug,
|
|
140
|
+
since=since,
|
|
141
|
+
until=until,
|
|
142
|
+
cursor=cursor,
|
|
143
|
+
limit=limit,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
response = client.get_httpx_client().request(
|
|
147
|
+
**kwargs,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return _build_response(client=client, response=response)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def sync(
|
|
154
|
+
*,
|
|
155
|
+
client: AuthenticatedClient | Client,
|
|
156
|
+
org_slug: str | Unset = UNSET,
|
|
157
|
+
since: datetime.datetime | Unset = UNSET,
|
|
158
|
+
until: datetime.datetime | Unset = UNSET,
|
|
159
|
+
cursor: str | Unset = UNSET,
|
|
160
|
+
limit: int | Unset = 100,
|
|
161
|
+
) -> (
|
|
162
|
+
EmailUnsubscribeListResponse
|
|
163
|
+
| RestErrorResponse
|
|
164
|
+
| UnauthorizedOrganizationError
|
|
165
|
+
| None
|
|
166
|
+
):
|
|
167
|
+
"""List email opt-out / opt-in records
|
|
168
|
+
|
|
169
|
+
Returns the current state of email unsubscribe records for the organization addressed by `org_slug`
|
|
170
|
+
(or the `client_id`'s default organization when omitted), ordered by `updated_at` descending. Treat
|
|
171
|
+
the endpoint as a change stream: `since` / `until` bound the pagination window and `cursor` walks
|
|
172
|
+
toward older records. Rate limited to 60 requests per minute per `client_id`.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
org_slug (str | Unset):
|
|
176
|
+
since (datetime.datetime | Unset):
|
|
177
|
+
until (datetime.datetime | Unset):
|
|
178
|
+
cursor (str | Unset):
|
|
179
|
+
limit (int | Unset): Default: 100.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
183
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
return sync_detailed(
|
|
190
|
+
client=client,
|
|
191
|
+
org_slug=org_slug,
|
|
192
|
+
since=since,
|
|
193
|
+
until=until,
|
|
194
|
+
cursor=cursor,
|
|
195
|
+
limit=limit,
|
|
196
|
+
).parsed
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def asyncio_detailed(
|
|
200
|
+
*,
|
|
201
|
+
client: AuthenticatedClient | Client,
|
|
202
|
+
org_slug: str | Unset = UNSET,
|
|
203
|
+
since: datetime.datetime | Unset = UNSET,
|
|
204
|
+
until: datetime.datetime | Unset = UNSET,
|
|
205
|
+
cursor: str | Unset = UNSET,
|
|
206
|
+
limit: int | Unset = 100,
|
|
207
|
+
) -> Response[
|
|
208
|
+
EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError
|
|
209
|
+
]:
|
|
210
|
+
"""List email opt-out / opt-in records
|
|
211
|
+
|
|
212
|
+
Returns the current state of email unsubscribe records for the organization addressed by `org_slug`
|
|
213
|
+
(or the `client_id`'s default organization when omitted), ordered by `updated_at` descending. Treat
|
|
214
|
+
the endpoint as a change stream: `since` / `until` bound the pagination window and `cursor` walks
|
|
215
|
+
toward older records. Rate limited to 60 requests per minute per `client_id`.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
org_slug (str | Unset):
|
|
219
|
+
since (datetime.datetime | Unset):
|
|
220
|
+
until (datetime.datetime | Unset):
|
|
221
|
+
cursor (str | Unset):
|
|
222
|
+
limit (int | Unset): Default: 100.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
226
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Response[EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError]
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
kwargs = _get_kwargs(
|
|
233
|
+
org_slug=org_slug,
|
|
234
|
+
since=since,
|
|
235
|
+
until=until,
|
|
236
|
+
cursor=cursor,
|
|
237
|
+
limit=limit,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
response = await client.get_async_httpx_client().request(**kwargs)
|
|
241
|
+
|
|
242
|
+
return _build_response(client=client, response=response)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def asyncio(
|
|
246
|
+
*,
|
|
247
|
+
client: AuthenticatedClient | Client,
|
|
248
|
+
org_slug: str | Unset = UNSET,
|
|
249
|
+
since: datetime.datetime | Unset = UNSET,
|
|
250
|
+
until: datetime.datetime | Unset = UNSET,
|
|
251
|
+
cursor: str | Unset = UNSET,
|
|
252
|
+
limit: int | Unset = 100,
|
|
253
|
+
) -> (
|
|
254
|
+
EmailUnsubscribeListResponse
|
|
255
|
+
| RestErrorResponse
|
|
256
|
+
| UnauthorizedOrganizationError
|
|
257
|
+
| None
|
|
258
|
+
):
|
|
259
|
+
"""List email opt-out / opt-in records
|
|
260
|
+
|
|
261
|
+
Returns the current state of email unsubscribe records for the organization addressed by `org_slug`
|
|
262
|
+
(or the `client_id`'s default organization when omitted), ordered by `updated_at` descending. Treat
|
|
263
|
+
the endpoint as a change stream: `since` / `until` bound the pagination window and `cursor` walks
|
|
264
|
+
toward older records. Rate limited to 60 requests per minute per `client_id`.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
org_slug (str | Unset):
|
|
268
|
+
since (datetime.datetime | Unset):
|
|
269
|
+
until (datetime.datetime | Unset):
|
|
270
|
+
cursor (str | Unset):
|
|
271
|
+
limit (int | Unset): Default: 100.
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
275
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
EmailUnsubscribeListResponse | RestErrorResponse | UnauthorizedOrganizationError
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
await asyncio_detailed(
|
|
283
|
+
client=client,
|
|
284
|
+
org_slug=org_slug,
|
|
285
|
+
since=since,
|
|
286
|
+
until=until,
|
|
287
|
+
cursor=cursor,
|
|
288
|
+
limit=limit,
|
|
289
|
+
)
|
|
290
|
+
).parsed
|