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.
Files changed (56) hide show
  1. truagents/__init__.py +18 -0
  2. truagents/__version__.py +1 -0
  3. truagents/auth.py +293 -0
  4. truagents/client.py +304 -0
  5. truagents/errors.py +193 -0
  6. truagents/generated/__init__.py +8 -0
  7. truagents/generated/api/__init__.py +1 -0
  8. truagents/generated/api/email_unsubscribe/__init__.py +1 -0
  9. truagents/generated/api/email_unsubscribe/list_email_unsubscribes.py +290 -0
  10. truagents/generated/api/email_unsubscribe/push_email_unsubscribes.py +236 -0
  11. truagents/generated/api/o_auth/__init__.py +1 -0
  12. truagents/generated/api/o_auth/issue_o_auth_token.py +241 -0
  13. truagents/generated/api/sms_unsubscribe/__init__.py +1 -0
  14. truagents/generated/api/sms_unsubscribe/list_sms_unsubscribes.py +290 -0
  15. truagents/generated/api/sms_unsubscribe/push_sms_unsubscribes.py +232 -0
  16. truagents/generated/api/voice_unsubscribe/__init__.py +1 -0
  17. truagents/generated/api/voice_unsubscribe/list_phone_unsubscribes.py +298 -0
  18. truagents/generated/api/voice_unsubscribe/push_phone_unsubscribes.py +232 -0
  19. truagents/generated/client.py +282 -0
  20. truagents/generated/errors.py +16 -0
  21. truagents/generated/models/__init__.py +65 -0
  22. truagents/generated/models/email_skipped_item.py +83 -0
  23. truagents/generated/models/email_skipped_item_reason.py +11 -0
  24. truagents/generated/models/email_source_enum.py +11 -0
  25. truagents/generated/models/email_unsubscribe_batch_request.py +93 -0
  26. truagents/generated/models/email_unsubscribe_batch_response.py +119 -0
  27. truagents/generated/models/email_unsubscribe_item.py +50 -0
  28. truagents/generated/models/email_unsubscribe_list_response.py +113 -0
  29. truagents/generated/models/email_unsubscribe_record.py +98 -0
  30. truagents/generated/models/email_unsubscribe_updated_entry.py +79 -0
  31. truagents/generated/models/o_auth_client_credentials_request.py +87 -0
  32. truagents/generated/models/o_auth_client_credentials_request_grant_type.py +8 -0
  33. truagents/generated/models/o_auth_error_response.py +83 -0
  34. truagents/generated/models/o_auth_error_response_error.py +11 -0
  35. truagents/generated/models/o_auth_refresh_token_request.py +75 -0
  36. truagents/generated/models/o_auth_refresh_token_request_grant_type.py +8 -0
  37. truagents/generated/models/o_auth_token_response.py +97 -0
  38. truagents/generated/models/o_auth_token_response_token_type.py +8 -0
  39. truagents/generated/models/phone_skipped_item.py +83 -0
  40. truagents/generated/models/phone_skipped_item_reason.py +11 -0
  41. truagents/generated/models/phone_source_enum.py +11 -0
  42. truagents/generated/models/phone_unsubscribe_batch_request.py +92 -0
  43. truagents/generated/models/phone_unsubscribe_batch_response.py +118 -0
  44. truagents/generated/models/phone_unsubscribe_item.py +51 -0
  45. truagents/generated/models/phone_unsubscribe_list_response.py +111 -0
  46. truagents/generated/models/phone_unsubscribe_record.py +99 -0
  47. truagents/generated/models/phone_unsubscribe_updated_entry.py +79 -0
  48. truagents/generated/models/rest_error_response.py +74 -0
  49. truagents/generated/models/unauthorized_organization_error.py +90 -0
  50. truagents/generated/models/unauthorized_organization_error_error.py +8 -0
  51. truagents/generated/types.py +54 -0
  52. truagents/observability.py +70 -0
  53. truagents/retry.py +64 -0
  54. truagents-0.0.0.dev0.dist-info/METADATA +171 -0
  55. truagents-0.0.0.dev0.dist-info/RECORD +56 -0
  56. truagents-0.0.0.dev0.dist-info/WHEEL +4 -0
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any, TypeVar
5
+
6
+ from attrs import define as _attrs_define
7
+ from attrs import field as _attrs_field
8
+
9
+ from ..models.o_auth_token_response_token_type import OAuthTokenResponseTokenType
10
+
11
+ T = TypeVar("T", bound="OAuthTokenResponse")
12
+
13
+
14
+ @_attrs_define
15
+ class OAuthTokenResponse:
16
+ """
17
+ Attributes:
18
+ access_token (str): Opaque bearer token (prefix `tru_at_`). Send on every API request as `Authorization: Bearer
19
+ <access_token>`. Example: tru_at_<access_token>.
20
+ token_type (OAuthTokenResponseTokenType):
21
+ expires_in (int): Seconds until the access token expires. Example: 900.
22
+ refresh_token (str): Opaque refresh token (prefix `tru_rt_`). Single-use; store atomically. Example:
23
+ tru_rt_<refresh_token>.
24
+ refresh_expires_in (int): Seconds until the refresh token expires. Example: 2592000.
25
+ """
26
+
27
+ access_token: str
28
+ token_type: OAuthTokenResponseTokenType
29
+ expires_in: int
30
+ refresh_token: str
31
+ refresh_expires_in: int
32
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
33
+
34
+ def to_dict(self) -> dict[str, Any]:
35
+ access_token = self.access_token
36
+
37
+ token_type = self.token_type.value
38
+
39
+ expires_in = self.expires_in
40
+
41
+ refresh_token = self.refresh_token
42
+
43
+ refresh_expires_in = self.refresh_expires_in
44
+
45
+ field_dict: dict[str, Any] = {}
46
+ field_dict.update(self.additional_properties)
47
+ field_dict.update(
48
+ {
49
+ "access_token": access_token,
50
+ "token_type": token_type,
51
+ "expires_in": expires_in,
52
+ "refresh_token": refresh_token,
53
+ "refresh_expires_in": refresh_expires_in,
54
+ }
55
+ )
56
+
57
+ return field_dict
58
+
59
+ @classmethod
60
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
61
+ d = dict(src_dict)
62
+ access_token = d.pop("access_token")
63
+
64
+ token_type = OAuthTokenResponseTokenType(d.pop("token_type"))
65
+
66
+ expires_in = d.pop("expires_in")
67
+
68
+ refresh_token = d.pop("refresh_token")
69
+
70
+ refresh_expires_in = d.pop("refresh_expires_in")
71
+
72
+ o_auth_token_response = cls(
73
+ access_token=access_token,
74
+ token_type=token_type,
75
+ expires_in=expires_in,
76
+ refresh_token=refresh_token,
77
+ refresh_expires_in=refresh_expires_in,
78
+ )
79
+
80
+ o_auth_token_response.additional_properties = d
81
+ return o_auth_token_response
82
+
83
+ @property
84
+ def additional_keys(self) -> list[str]:
85
+ return list(self.additional_properties.keys())
86
+
87
+ def __getitem__(self, key: str) -> Any:
88
+ return self.additional_properties[key]
89
+
90
+ def __setitem__(self, key: str, value: Any) -> None:
91
+ self.additional_properties[key] = value
92
+
93
+ def __delitem__(self, key: str) -> None:
94
+ del self.additional_properties[key]
95
+
96
+ def __contains__(self, key: str) -> bool:
97
+ return key in self.additional_properties
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class OAuthTokenResponseTokenType(str, Enum):
5
+ BEARER = "Bearer"
6
+
7
+ def __str__(self) -> str:
8
+ return str(self.value)
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any, TypeVar
5
+
6
+ from attrs import define as _attrs_define
7
+ from attrs import field as _attrs_field
8
+
9
+ from ..models.phone_skipped_item_reason import PhoneSkippedItemReason
10
+ from ..types import UNSET, Unset
11
+
12
+ T = TypeVar("T", bound="PhoneSkippedItem")
13
+
14
+
15
+ @_attrs_define
16
+ class PhoneSkippedItem:
17
+ """An input item rejected by per-item validation. Echoes the original item fields plus a `reason`.
18
+
19
+ Attributes:
20
+ reason (PhoneSkippedItemReason):
21
+ phone (str | Unset): Echoed from input when present.
22
+ unsubscribed (bool | Unset): Echoed from input when present.
23
+ """
24
+
25
+ reason: PhoneSkippedItemReason
26
+ phone: str | Unset = UNSET
27
+ unsubscribed: bool | Unset = UNSET
28
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
29
+
30
+ def to_dict(self) -> dict[str, Any]:
31
+ reason = self.reason.value
32
+
33
+ phone = self.phone
34
+
35
+ unsubscribed = self.unsubscribed
36
+
37
+ field_dict: dict[str, Any] = {}
38
+ field_dict.update(self.additional_properties)
39
+ field_dict.update(
40
+ {
41
+ "reason": reason,
42
+ }
43
+ )
44
+ if phone is not UNSET:
45
+ field_dict["phone"] = phone
46
+ if unsubscribed is not UNSET:
47
+ field_dict["unsubscribed"] = unsubscribed
48
+
49
+ return field_dict
50
+
51
+ @classmethod
52
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
53
+ d = dict(src_dict)
54
+ reason = PhoneSkippedItemReason(d.pop("reason"))
55
+
56
+ phone = d.pop("phone", UNSET)
57
+
58
+ unsubscribed = d.pop("unsubscribed", UNSET)
59
+
60
+ phone_skipped_item = cls(
61
+ reason=reason,
62
+ phone=phone,
63
+ unsubscribed=unsubscribed,
64
+ )
65
+
66
+ phone_skipped_item.additional_properties = d
67
+ return phone_skipped_item
68
+
69
+ @property
70
+ def additional_keys(self) -> list[str]:
71
+ return list(self.additional_properties.keys())
72
+
73
+ def __getitem__(self, key: str) -> Any:
74
+ return self.additional_properties[key]
75
+
76
+ def __setitem__(self, key: str, value: Any) -> None:
77
+ self.additional_properties[key] = value
78
+
79
+ def __delitem__(self, key: str) -> None:
80
+ del self.additional_properties[key]
81
+
82
+ def __contains__(self, key: str) -> bool:
83
+ return key in self.additional_properties
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+
4
+ class PhoneSkippedItemReason(str, Enum):
5
+ DUPLICATE_IDENTIFIER = "duplicate identifier"
6
+ INVALID_PHONE_FORMAT = "invalid phone format"
7
+ MISSING_IDENTIFIER = "missing identifier"
8
+ MISSING_UNSUBSCRIBED = "missing unsubscribed"
9
+
10
+ def __str__(self) -> str:
11
+ return str(self.value)
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+
4
+ class PhoneSourceEnum(str, Enum):
5
+ ADMIN = "admin"
6
+ EXTERNAL_API = "external_api"
7
+ IMPORT = "import"
8
+ TWILIO = "twilio"
9
+
10
+ def __str__(self) -> str:
11
+ return str(self.value)
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import TYPE_CHECKING, Any, TypeVar
5
+
6
+ from attrs import define as _attrs_define
7
+ from attrs import field as _attrs_field
8
+
9
+ from ..types import UNSET, Unset
10
+
11
+ if TYPE_CHECKING:
12
+ from ..models.phone_unsubscribe_item import PhoneUnsubscribeItem
13
+
14
+
15
+ T = TypeVar("T", bound="PhoneUnsubscribeBatchRequest")
16
+
17
+
18
+ @_attrs_define
19
+ class PhoneUnsubscribeBatchRequest:
20
+ """
21
+ Example:
22
+ {'org_slug': 'acme-corp', 'items': [{'phone': '+15551234567', 'unsubscribed': True}]}
23
+
24
+ Attributes:
25
+ items (list[PhoneUnsubscribeItem]): Up to 10,000 items per request. Items are processed in array order; partial
26
+ failures do not roll back accepted items.
27
+ org_slug (str | Unset): Organization to write to. Must be in your `client_id`'s authorized set. Omit to use the
28
+ key's default organization. One batch targets exactly one organization — to write to several organizations, send
29
+ one request per organization. Example: acme-corp.
30
+ """
31
+
32
+ items: list[PhoneUnsubscribeItem]
33
+ org_slug: str | Unset = UNSET
34
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
35
+
36
+ def to_dict(self) -> dict[str, Any]:
37
+ items = []
38
+ for items_item_data in self.items:
39
+ items_item = items_item_data.to_dict()
40
+ items.append(items_item)
41
+
42
+ org_slug = self.org_slug
43
+
44
+ field_dict: dict[str, Any] = {}
45
+ field_dict.update(self.additional_properties)
46
+ field_dict.update(
47
+ {
48
+ "items": items,
49
+ }
50
+ )
51
+ if org_slug is not UNSET:
52
+ field_dict["org_slug"] = org_slug
53
+
54
+ return field_dict
55
+
56
+ @classmethod
57
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
58
+ from ..models.phone_unsubscribe_item import PhoneUnsubscribeItem
59
+
60
+ d = dict(src_dict)
61
+ items = []
62
+ _items = d.pop("items")
63
+ for items_item_data in _items:
64
+ items_item = PhoneUnsubscribeItem.from_dict(items_item_data)
65
+
66
+ items.append(items_item)
67
+
68
+ org_slug = d.pop("org_slug", UNSET)
69
+
70
+ phone_unsubscribe_batch_request = cls(
71
+ items=items,
72
+ org_slug=org_slug,
73
+ )
74
+
75
+ phone_unsubscribe_batch_request.additional_properties = d
76
+ return phone_unsubscribe_batch_request
77
+
78
+ @property
79
+ def additional_keys(self) -> list[str]:
80
+ return list(self.additional_properties.keys())
81
+
82
+ def __getitem__(self, key: str) -> Any:
83
+ return self.additional_properties[key]
84
+
85
+ def __setitem__(self, key: str, value: Any) -> None:
86
+ self.additional_properties[key] = value
87
+
88
+ def __delitem__(self, key: str) -> None:
89
+ del self.additional_properties[key]
90
+
91
+ def __contains__(self, key: str) -> bool:
92
+ return key in self.additional_properties
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import TYPE_CHECKING, Any, TypeVar
5
+
6
+ from attrs import define as _attrs_define
7
+ from attrs import field as _attrs_field
8
+
9
+ if TYPE_CHECKING:
10
+ from ..models.phone_skipped_item import PhoneSkippedItem
11
+ from ..models.phone_unsubscribe_updated_entry import PhoneUnsubscribeUpdatedEntry
12
+
13
+
14
+ T = TypeVar("T", bound="PhoneUnsubscribeBatchResponse")
15
+
16
+
17
+ @_attrs_define
18
+ class PhoneUnsubscribeBatchResponse:
19
+ """
20
+ Example:
21
+ {'org_slug': 'acme-corp', 'processed': 1, 'updated': [{'phone': '+15551234567', 'unsubscribed': True,
22
+ 'updated_at': '2026-04-15T10:30:00Z'}], 'skipped_items': []}
23
+
24
+ Attributes:
25
+ org_slug (str): Organization the batch was applied to — the value you supplied, or your key's default
26
+ organization when you omitted it. Example: acme-corp.
27
+ processed (int): Count of items accepted into `updated` — equal to `items.length − skipped_items.length`.
28
+ Idempotent writes (item already at requested state) are counted.
29
+ updated (list[PhoneUnsubscribeUpdatedEntry]): One entry per accepted input item, in input order.
30
+ skipped_items (list[PhoneSkippedItem]): Items rejected by per-item validation; each carries the original field
31
+ echoes plus a `reason`.
32
+ """
33
+
34
+ org_slug: str
35
+ processed: int
36
+ updated: list[PhoneUnsubscribeUpdatedEntry]
37
+ skipped_items: list[PhoneSkippedItem]
38
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
39
+
40
+ def to_dict(self) -> dict[str, Any]:
41
+ org_slug = self.org_slug
42
+
43
+ processed = self.processed
44
+
45
+ updated = []
46
+ for updated_item_data in self.updated:
47
+ updated_item = updated_item_data.to_dict()
48
+ updated.append(updated_item)
49
+
50
+ skipped_items = []
51
+ for skipped_items_item_data in self.skipped_items:
52
+ skipped_items_item = skipped_items_item_data.to_dict()
53
+ skipped_items.append(skipped_items_item)
54
+
55
+ field_dict: dict[str, Any] = {}
56
+ field_dict.update(self.additional_properties)
57
+ field_dict.update(
58
+ {
59
+ "org_slug": org_slug,
60
+ "processed": processed,
61
+ "updated": updated,
62
+ "skipped_items": skipped_items,
63
+ }
64
+ )
65
+
66
+ return field_dict
67
+
68
+ @classmethod
69
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
70
+ from ..models.phone_skipped_item import PhoneSkippedItem
71
+ from ..models.phone_unsubscribe_updated_entry import (
72
+ PhoneUnsubscribeUpdatedEntry,
73
+ )
74
+
75
+ d = dict(src_dict)
76
+ org_slug = d.pop("org_slug")
77
+
78
+ processed = d.pop("processed")
79
+
80
+ updated = []
81
+ _updated = d.pop("updated")
82
+ for updated_item_data in _updated:
83
+ updated_item = PhoneUnsubscribeUpdatedEntry.from_dict(updated_item_data)
84
+
85
+ updated.append(updated_item)
86
+
87
+ skipped_items = []
88
+ _skipped_items = d.pop("skipped_items")
89
+ for skipped_items_item_data in _skipped_items:
90
+ skipped_items_item = PhoneSkippedItem.from_dict(skipped_items_item_data)
91
+
92
+ skipped_items.append(skipped_items_item)
93
+
94
+ phone_unsubscribe_batch_response = cls(
95
+ org_slug=org_slug,
96
+ processed=processed,
97
+ updated=updated,
98
+ skipped_items=skipped_items,
99
+ )
100
+
101
+ phone_unsubscribe_batch_response.additional_properties = d
102
+ return phone_unsubscribe_batch_response
103
+
104
+ @property
105
+ def additional_keys(self) -> list[str]:
106
+ return list(self.additional_properties.keys())
107
+
108
+ def __getitem__(self, key: str) -> Any:
109
+ return self.additional_properties[key]
110
+
111
+ def __setitem__(self, key: str, value: Any) -> None:
112
+ self.additional_properties[key] = value
113
+
114
+ def __delitem__(self, key: str) -> None:
115
+ del self.additional_properties[key]
116
+
117
+ def __contains__(self, key: str) -> bool:
118
+ return key in self.additional_properties
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any, TypeVar
5
+
6
+ from attrs import define as _attrs_define
7
+
8
+ T = TypeVar("T", bound="PhoneUnsubscribeItem")
9
+
10
+
11
+ @_attrs_define
12
+ class PhoneUnsubscribeItem:
13
+ """
14
+ Attributes:
15
+ phone (str): Phone number in E.164 format. Non-E.164 input is rejected per-item with `reason: "invalid phone
16
+ format"`. Example: +15551234567.
17
+ unsubscribed (bool):
18
+ """
19
+
20
+ phone: str
21
+ unsubscribed: bool
22
+
23
+ def to_dict(self) -> dict[str, Any]:
24
+ phone = self.phone
25
+
26
+ unsubscribed = self.unsubscribed
27
+
28
+ field_dict: dict[str, Any] = {}
29
+
30
+ field_dict.update(
31
+ {
32
+ "phone": phone,
33
+ "unsubscribed": unsubscribed,
34
+ }
35
+ )
36
+
37
+ return field_dict
38
+
39
+ @classmethod
40
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
41
+ d = dict(src_dict)
42
+ phone = d.pop("phone")
43
+
44
+ unsubscribed = d.pop("unsubscribed")
45
+
46
+ phone_unsubscribe_item = cls(
47
+ phone=phone,
48
+ unsubscribed=unsubscribed,
49
+ )
50
+
51
+ return phone_unsubscribe_item
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
5
+
6
+ from attrs import define as _attrs_define
7
+ from attrs import field as _attrs_field
8
+
9
+ if TYPE_CHECKING:
10
+ from ..models.phone_unsubscribe_record import PhoneUnsubscribeRecord
11
+
12
+
13
+ T = TypeVar("T", bound="PhoneUnsubscribeListResponse")
14
+
15
+
16
+ @_attrs_define
17
+ class PhoneUnsubscribeListResponse:
18
+ """
19
+ Example:
20
+ {'org_slug': 'acme-corp', 'data': [{'phone': '+15551234567', 'unsubscribed': True, 'source': 'twilio',
21
+ 'updated_at': '2026-04-12T16:22:01Z'}], 'next_cursor': None, 'has_more': False}
22
+
23
+ Attributes:
24
+ org_slug (str): Organization this page belongs to — the value you supplied, the one carried by `cursor`, or your
25
+ key's default organization when neither was provided. Example: acme-corp.
26
+ data (list[PhoneUnsubscribeRecord]):
27
+ next_cursor (None | str): Pass to the `cursor` query parameter to fetch the next page. `null` when no more
28
+ results are available. See the `Cursor` parameter for the encoding contract.
29
+ has_more (bool):
30
+ """
31
+
32
+ org_slug: str
33
+ data: list[PhoneUnsubscribeRecord]
34
+ next_cursor: None | str
35
+ has_more: bool
36
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
37
+
38
+ def to_dict(self) -> dict[str, Any]:
39
+ org_slug = self.org_slug
40
+
41
+ data = []
42
+ for data_item_data in self.data:
43
+ data_item = data_item_data.to_dict()
44
+ data.append(data_item)
45
+
46
+ next_cursor: None | str
47
+ next_cursor = self.next_cursor
48
+
49
+ has_more = self.has_more
50
+
51
+ field_dict: dict[str, Any] = {}
52
+ field_dict.update(self.additional_properties)
53
+ field_dict.update(
54
+ {
55
+ "org_slug": org_slug,
56
+ "data": data,
57
+ "next_cursor": next_cursor,
58
+ "has_more": has_more,
59
+ }
60
+ )
61
+
62
+ return field_dict
63
+
64
+ @classmethod
65
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
66
+ from ..models.phone_unsubscribe_record import PhoneUnsubscribeRecord
67
+
68
+ d = dict(src_dict)
69
+ org_slug = d.pop("org_slug")
70
+
71
+ data = []
72
+ _data = d.pop("data")
73
+ for data_item_data in _data:
74
+ data_item = PhoneUnsubscribeRecord.from_dict(data_item_data)
75
+
76
+ data.append(data_item)
77
+
78
+ def _parse_next_cursor(data: object) -> None | str:
79
+ if data is None:
80
+ return data
81
+ return cast(None | str, data)
82
+
83
+ next_cursor = _parse_next_cursor(d.pop("next_cursor"))
84
+
85
+ has_more = d.pop("has_more")
86
+
87
+ phone_unsubscribe_list_response = cls(
88
+ org_slug=org_slug,
89
+ data=data,
90
+ next_cursor=next_cursor,
91
+ has_more=has_more,
92
+ )
93
+
94
+ phone_unsubscribe_list_response.additional_properties = d
95
+ return phone_unsubscribe_list_response
96
+
97
+ @property
98
+ def additional_keys(self) -> list[str]:
99
+ return list(self.additional_properties.keys())
100
+
101
+ def __getitem__(self, key: str) -> Any:
102
+ return self.additional_properties[key]
103
+
104
+ def __setitem__(self, key: str, value: Any) -> None:
105
+ self.additional_properties[key] = value
106
+
107
+ def __delitem__(self, key: str) -> None:
108
+ del self.additional_properties[key]
109
+
110
+ def __contains__(self, key: str) -> bool:
111
+ return key in self.additional_properties
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from collections.abc import Mapping
5
+ from typing import Any, TypeVar
6
+
7
+ from attrs import define as _attrs_define
8
+ from attrs import field as _attrs_field
9
+
10
+ from ..models.phone_source_enum import PhoneSourceEnum
11
+
12
+ T = TypeVar("T", bound="PhoneUnsubscribeRecord")
13
+
14
+
15
+ @_attrs_define
16
+ class PhoneUnsubscribeRecord:
17
+ """Used by both `/api/v1/unsubscribe/sms` and `/api/v1/unsubscribe/phone` — the channel is implicit in the URL.
18
+
19
+ Example:
20
+ {'phone': '+15551234567', 'unsubscribed': True, 'source': 'twilio', 'updated_at': '2026-04-12T16:22:01Z'}
21
+
22
+ Attributes:
23
+ phone (str): Phone number in E.164 format. Example: +15551234567.
24
+ unsubscribed (bool): `true` = opted out (TruAgents will not send on this channel).
25
+ source (PhoneSourceEnum): The category of writer responsible for the row's most recent state change on the SMS
26
+ or voice channel.
27
+
28
+ - `external_api` — pushed by the partner via this API.
29
+ - `twilio` — derived from a Twilio STOP/START message (also visible on `/phone` when an administrator has
30
+ unified the SMS and voice scopes).
31
+ - `admin` — manually changed by a TruAgents administrator.
32
+ - `import` — created during initial backfill from legacy per-contact flags.
33
+ updated_at (datetime.datetime): ISO 8601 timestamp of the most recent state change.
34
+ """
35
+
36
+ phone: str
37
+ unsubscribed: bool
38
+ source: PhoneSourceEnum
39
+ updated_at: datetime.datetime
40
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
41
+
42
+ def to_dict(self) -> dict[str, Any]:
43
+ phone = self.phone
44
+
45
+ unsubscribed = self.unsubscribed
46
+
47
+ source = self.source.value
48
+
49
+ updated_at = self.updated_at.isoformat()
50
+
51
+ field_dict: dict[str, Any] = {}
52
+ field_dict.update(self.additional_properties)
53
+ field_dict.update(
54
+ {
55
+ "phone": phone,
56
+ "unsubscribed": unsubscribed,
57
+ "source": source,
58
+ "updated_at": updated_at,
59
+ }
60
+ )
61
+
62
+ return field_dict
63
+
64
+ @classmethod
65
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
66
+ d = dict(src_dict)
67
+ phone = d.pop("phone")
68
+
69
+ unsubscribed = d.pop("unsubscribed")
70
+
71
+ source = PhoneSourceEnum(d.pop("source"))
72
+
73
+ updated_at = datetime.datetime.fromisoformat(d.pop("updated_at"))
74
+
75
+ phone_unsubscribe_record = cls(
76
+ phone=phone,
77
+ unsubscribed=unsubscribed,
78
+ source=source,
79
+ updated_at=updated_at,
80
+ )
81
+
82
+ phone_unsubscribe_record.additional_properties = d
83
+ return phone_unsubscribe_record
84
+
85
+ @property
86
+ def additional_keys(self) -> list[str]:
87
+ return list(self.additional_properties.keys())
88
+
89
+ def __getitem__(self, key: str) -> Any:
90
+ return self.additional_properties[key]
91
+
92
+ def __setitem__(self, key: str, value: Any) -> None:
93
+ self.additional_properties[key] = value
94
+
95
+ def __delitem__(self, key: str) -> None:
96
+ del self.additional_properties[key]
97
+
98
+ def __contains__(self, key: str) -> bool:
99
+ return key in self.additional_properties