wazzapi 0.2.0__tar.gz → 0.4.0__tar.gz

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 (38) hide show
  1. {wazzapi-0.2.0 → wazzapi-0.4.0}/PKG-INFO +20 -4
  2. {wazzapi-0.2.0 → wazzapi-0.4.0}/README.md +17 -1
  3. wazzapi-0.4.0/examples/list_devices.py +15 -0
  4. {wazzapi-0.2.0 → wazzapi-0.4.0}/pyproject.toml +5 -5
  5. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/smoke_test.py +1 -1
  6. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_client.py +1 -0
  7. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_resources.py +123 -0
  8. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/__init__.py +1 -1
  9. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/client.py +33 -1
  10. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/__init__.py +12 -1
  11. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/contacts.py +23 -0
  12. wazzapi-0.4.0/wazzapi/models/devices.py +42 -0
  13. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/messages.py +3 -0
  14. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/__init__.py +8 -1
  15. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/contacts.py +36 -0
  16. wazzapi-0.4.0/wazzapi/resources/devices.py +40 -0
  17. {wazzapi-0.2.0 → wazzapi-0.4.0}/.gitignore +0 -0
  18. {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/create_template.py +0 -0
  19. {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/list_contacts.py +0 -0
  20. {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/preview_template.py +0 -0
  21. {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/send_message.py +0 -0
  22. {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/verify_webhook.py +0 -0
  23. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_groups.py +0 -0
  24. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_media.py +0 -0
  25. {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_webhooks.py +0 -0
  26. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/errors.py +0 -0
  27. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/media.py +0 -0
  28. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/base.py +0 -0
  29. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/groups.py +0 -0
  30. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/media.py +0 -0
  31. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/templates.py +0 -0
  32. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/webhooks.py +0 -0
  33. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/py.typed +0 -0
  34. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/base.py +0 -0
  35. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/groups.py +0 -0
  36. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/messages.py +0 -0
  37. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/templates.py +0 -0
  38. {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/webhooks.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wazzapi
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Typed Python SDK for WazzAPI
5
- Project-URL: Homepage, https://github.com/wazzapihq/wazzapi-py
6
5
  Project-URL: API, https://api.wazzapi.com
6
+ Project-URL: Homepage, https://github.com/wazzapihq/wazzapi-py
7
7
  Keywords: api,sdk,wazzapi,webhook,whatsapp
8
8
  Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Intended Audience :: Developers
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
- Requires-Dist: cryptography<45.0,>=44.0
20
+ Requires-Dist: cryptography<50.0,>=46.0
21
21
  Requires-Dist: httpx<1.0,>=0.28
22
22
  Requires-Dist: pydantic<3.0,>=2.11
23
23
  Description-Content-Type: text/markdown
@@ -26,11 +26,12 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  Official Python SDK for WazzAPI.
28
28
 
29
- Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
29
+ Use it to send messages, inspect WhatsApp devices, manage contacts and templates, and verify incoming webhooks with a simple typed client.
30
30
 
31
31
  ## What you can do with this SDK
32
32
 
33
33
  - send direct WhatsApp messages
34
+ - list and inspect WhatsApp devices
34
35
  - list, create, update, and delete contacts
35
36
  - manage message templates and preview rendered content
36
37
  - verify and parse incoming WazzAPI webhooks
@@ -118,6 +119,19 @@ with WazzapiClient(api_key="your-api-key") as client:
118
119
 
119
120
  for contact in response.contacts:
120
121
  print(contact.model_dump())
122
+
123
+ ### List devices
124
+
125
+ ```python
126
+ from wazzapi import WazzapiClient
127
+
128
+ with WazzapiClient(api_key="your-api-key") as client:
129
+ response = client.devices.list(limit=20, status="connected")
130
+
131
+ for device in response.devices:
132
+ print(device.name, device.session_name, device.status)
133
+ ```
134
+
121
135
  ```
122
136
 
123
137
  ### Create a template
@@ -182,6 +196,7 @@ Supported webhook event families:
182
196
 
183
197
  Ready-to-run examples live in `examples/`:
184
198
 
199
+ - `examples/list_devices.py`
185
200
  - `examples/list_contacts.py`
186
201
  - `examples/send_message.py`
187
202
  - `examples/create_template.py`
@@ -192,6 +207,7 @@ Run them with:
192
207
 
193
208
  ```bash
194
209
  uv run python examples/list_contacts.py
210
+ uv run python examples/list_devices.py
195
211
  uv run python examples/send_message.py
196
212
  uv run python examples/create_template.py
197
213
  uv run python examples/preview_template.py
@@ -2,11 +2,12 @@
2
2
 
3
3
  Official Python SDK for WazzAPI.
4
4
 
5
- Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
5
+ Use it to send messages, inspect WhatsApp devices, manage contacts and templates, and verify incoming webhooks with a simple typed client.
6
6
 
7
7
  ## What you can do with this SDK
8
8
 
9
9
  - send direct WhatsApp messages
10
+ - list and inspect WhatsApp devices
10
11
  - list, create, update, and delete contacts
11
12
  - manage message templates and preview rendered content
12
13
  - verify and parse incoming WazzAPI webhooks
@@ -94,6 +95,19 @@ with WazzapiClient(api_key="your-api-key") as client:
94
95
 
95
96
  for contact in response.contacts:
96
97
  print(contact.model_dump())
98
+
99
+ ### List devices
100
+
101
+ ```python
102
+ from wazzapi import WazzapiClient
103
+
104
+ with WazzapiClient(api_key="your-api-key") as client:
105
+ response = client.devices.list(limit=20, status="connected")
106
+
107
+ for device in response.devices:
108
+ print(device.name, device.session_name, device.status)
109
+ ```
110
+
97
111
  ```
98
112
 
99
113
  ### Create a template
@@ -158,6 +172,7 @@ Supported webhook event families:
158
172
 
159
173
  Ready-to-run examples live in `examples/`:
160
174
 
175
+ - `examples/list_devices.py`
161
176
  - `examples/list_contacts.py`
162
177
  - `examples/send_message.py`
163
178
  - `examples/create_template.py`
@@ -168,6 +183,7 @@ Run them with:
168
183
 
169
184
  ```bash
170
185
  uv run python examples/list_contacts.py
186
+ uv run python examples/list_devices.py
171
187
  uv run python examples/send_message.py
172
188
  uv run python examples/create_template.py
173
189
  uv run python examples/preview_template.py
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from wazzapi import WazzapiClient
6
+
7
+ api_key = os.environ["WAZZAPI_API_KEY"]
8
+
9
+ with WazzapiClient(api_key=api_key) as client:
10
+ response = client.devices.list(limit=20, status="connected")
11
+
12
+ for device in response.devices:
13
+ print(device.model_dump())
14
+
15
+ print({"total": response.total, "limit": response.limit, "offset": response.offset})
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wazzapi"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "Typed Python SDK for WazzAPI"
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  requires-python = ">=3.10"
@@ -19,19 +19,19 @@ classifiers = [
19
19
  "Typing :: Typed",
20
20
  ]
21
21
  dependencies = [
22
- "cryptography>=44.0,<45.0",
22
+ "cryptography>=46.0,<50.0",
23
23
  "httpx>=0.28,<1.0",
24
24
  "pydantic>=2.11,<3.0",
25
25
  ]
26
26
 
27
27
  [project.urls]
28
- Homepage = "https://github.com/wazzapihq/wazzapi-py"
29
28
  API = "https://api.wazzapi.com"
29
+ Homepage = "https://github.com/wazzapihq/wazzapi-py"
30
30
 
31
31
  [dependency-groups]
32
32
  dev = [
33
- "pytest>=8.3,<9.0",
34
- "pytest-asyncio>=0.25,<1.0",
33
+ "pytest>=9.0,<10.0",
34
+ "pytest-asyncio>=1.0,<2.0",
35
35
  "twine>=6.1,<7.0",
36
36
  ]
37
37
 
@@ -10,7 +10,7 @@ from wazzapi import (
10
10
 
11
11
 
12
12
  def main() -> None:
13
- assert __version__ == "0.2.0"
13
+ assert __version__ == "0.4.0"
14
14
 
15
15
  client = WazzapiClient(api_key="smoke-test-token")
16
16
  try:
@@ -15,6 +15,7 @@ def test_client_formats_bearer_auth_header() -> None:
15
15
  assert client.http.headers["Authorization"] == "Bearer plain-token"
16
16
  assert client.http.headers["Accept"] == "application/json"
17
17
  assert client.http.headers["Content-Type"] == "application/json"
18
+ assert client.devices is not None
18
19
  finally:
19
20
  client.close()
20
21
 
@@ -12,6 +12,18 @@ from wazzapi import WazzapiClient, models
12
12
  @pytest.mark.parametrize(
13
13
  ("call_factory", "expected_method", "expected_path", "expected_response_type", "response_json"),
14
14
  [
15
+ (
16
+ lambda client: client.devices.list(limit=25, status="connected", search="main"),
17
+ "GET",
18
+ "/api/v1/devices",
19
+ models.DeviceListResponse,
20
+ {
21
+ "devices": [],
22
+ "total": 0,
23
+ "limit": 25,
24
+ "offset": 0,
25
+ },
26
+ ),
15
27
  (
16
28
  lambda client: client.contacts.list(limit=25, search="alice"),
17
29
  "GET",
@@ -134,6 +146,50 @@ def test_delete_endpoints_return_none() -> None:
134
146
  assert seen_paths == ["/api/v1/contacts/contact_1", "/api/v1/templates/template_1"]
135
147
 
136
148
 
149
+ def test_get_device_returns_typed_response() -> None:
150
+ seen: dict[str, object] = {}
151
+
152
+ def handler(request: httpx.Request) -> httpx.Response:
153
+ seen["method"] = request.method
154
+ seen["path"] = request.url.path
155
+ return httpx.Response(
156
+ 200,
157
+ json={
158
+ "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
159
+ "name": "Primary Device",
160
+ "session_name": "main",
161
+ "status": "connected",
162
+ "is_primary": True,
163
+ "auto_warmer_enabled": True,
164
+ "total_messages_sent": 123,
165
+ "total_messages_received": 45,
166
+ "contacts_synced_count": 900,
167
+ "created_at": "2026-05-01T00:00:00Z",
168
+ "updated_at": "2026-05-02T00:00:00Z",
169
+ "timezone": "Asia/Jakarta",
170
+ "connection_attempts": 2,
171
+ "daily_message_limit": 1000,
172
+ "message_delay_ms": 500,
173
+ "phone_number": "6281234567890",
174
+ "last_connected_at": "2026-05-02T00:00:00Z",
175
+ "last_health_check_at": "2026-05-02T01:00:00Z",
176
+ "last_contact_sync_at": "2026-05-02T02:00:00Z",
177
+ "last_contact_sync_status": "completed",
178
+ "backend_id": "7a5d6343-1dd1-429a-8d83-25c9bf06c878",
179
+ },
180
+ )
181
+
182
+ transport = httpx.MockTransport(handler)
183
+ with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
184
+ result = client.devices.get("3c90c3cc-0d44-4b50-8888-8dd25736052a")
185
+
186
+ assert seen["method"] == "GET"
187
+ assert seen["path"] == "/api/v1/devices/3c90c3cc-0d44-4b50-8888-8dd25736052a"
188
+ assert isinstance(result, models.DeviceResponse)
189
+ assert result.session_name == "main"
190
+ assert result.timezone == "Asia/Jakarta"
191
+
192
+
137
193
  def test_sync_status_returns_typed_list() -> None:
138
194
  def handler(_: httpx.Request) -> httpx.Response:
139
195
  return httpx.Response(
@@ -157,3 +213,70 @@ def test_sync_status_returns_typed_list() -> None:
157
213
  assert len(result) == 1
158
214
  assert isinstance(result[0], models.ContactSyncStatusResponse)
159
215
  assert result[0].account_id == "wa_123"
216
+
217
+
218
+ def test_get_profile_picture_returns_metadata() -> None:
219
+ seen: dict[str, object] = {}
220
+
221
+ def handler(request: httpx.Request) -> httpx.Response:
222
+ seen["method"] = request.method
223
+ seen["path"] = request.url.path
224
+ seen["query"] = dict(request.url.params)
225
+ return httpx.Response(
226
+ 200,
227
+ json={
228
+ "contact_id": "contact_1",
229
+ "phone_number": "+6281234567890",
230
+ "url": "https://cdn.example.com/pic.jpg",
231
+ "picture_id": "pic_1",
232
+ "picture_type": "image",
233
+ "preview": True,
234
+ "whatsapp_account_id": "wa_1",
235
+ "cached": False,
236
+ },
237
+ )
238
+
239
+ transport = httpx.MockTransport(handler)
240
+ with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
241
+ result = client.contacts.get_profile_picture(
242
+ "contact_1",
243
+ whatsapp_account_id="wa_1",
244
+ preview=True,
245
+ )
246
+
247
+ assert seen["method"] == "GET"
248
+ assert seen["path"] == "/api/v1/contacts/contact_1/profile-picture"
249
+ assert seen["query"] == {"whatsapp_account_id": "wa_1", "preview": "true"}
250
+ assert isinstance(result, models.ContactProfilePictureResponse)
251
+ assert result.url == "https://cdn.example.com/pic.jpg"
252
+ assert result.preview is True
253
+
254
+
255
+ def test_get_profile_picture_image_returns_bytes() -> None:
256
+ seen: dict[str, object] = {}
257
+ image_bytes = b"\xff\xd8\xff\xe0fake-jpeg"
258
+
259
+ def handler(request: httpx.Request) -> httpx.Response:
260
+ seen["method"] = request.method
261
+ seen["path"] = request.url.path
262
+ seen["query"] = dict(request.url.params)
263
+ return httpx.Response(
264
+ 200,
265
+ content=image_bytes,
266
+ headers={"content-type": "image/jpeg"},
267
+ )
268
+
269
+ transport = httpx.MockTransport(handler)
270
+ with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
271
+ result = client.contacts.get_profile_picture_image(
272
+ "contact_1",
273
+ proxy=True,
274
+ preview=False,
275
+ )
276
+
277
+ assert seen["method"] == "GET"
278
+ assert seen["path"] == "/api/v1/contacts/contact_1/profile-picture/image"
279
+ assert seen["query"] == {"proxy": "true", "preview": "false"}
280
+ assert isinstance(result, models.ContactProfilePictureImageResult)
281
+ assert result.content == image_bytes
282
+ assert result.content_type == "image/jpeg"
@@ -16,7 +16,7 @@ from .webhooks import (
16
16
  verify_webhook_signature,
17
17
  )
18
18
 
19
- __version__ = "0.2.0"
19
+ __version__ = "0.4.0"
20
20
 
21
21
  __all__ = [
22
22
  "EVENT_HEADER",
@@ -7,7 +7,7 @@ import httpx
7
7
  from pydantic import BaseModel, TypeAdapter
8
8
 
9
9
  from .errors import WazzapiAPIError
10
- from .resources import ContactsResource, GroupsResource, MessagesResource, TemplatesResource
10
+ from .resources import ContactsResource, DevicesResource, GroupsResource, MessagesResource, TemplatesResource
11
11
 
12
12
  DEFAULT_BASE_URL = "https://api.wazzapi.com"
13
13
 
@@ -39,6 +39,7 @@ class WazzapiClient:
39
39
  )
40
40
 
41
41
  self.contacts = ContactsResource(self)
42
+ self.devices = DevicesResource(self)
42
43
  self.groups = GroupsResource(self)
43
44
  self.messages = MessagesResource(self)
44
45
  self.templates = TemplatesResource(self)
@@ -106,6 +107,37 @@ class WazzapiClient:
106
107
  payload = response.json()
107
108
  return TypeAdapter(response_model).validate_python(payload)
108
109
 
110
+ def _request_bytes(
111
+ self,
112
+ method: str,
113
+ path: str,
114
+ *,
115
+ params: Mapping[str, Any] | None = None,
116
+ response_model: Any | None = None,
117
+ ) -> Any:
118
+ """Issue a request and return binary content (follows redirects)."""
119
+
120
+ response = self._client.request(
121
+ method,
122
+ path,
123
+ params=self._filter_none(params),
124
+ follow_redirects=True,
125
+ )
126
+
127
+ if response.status_code >= 400:
128
+ raise WazzapiAPIError.from_response(response)
129
+
130
+ payload = {
131
+ "content": response.content,
132
+ "content_type": response.headers.get("content-type"),
133
+ "final_url": str(response.url) if response.url else None,
134
+ }
135
+
136
+ if response_model is None:
137
+ return payload
138
+
139
+ return TypeAdapter(response_model).validate_python(payload)
140
+
109
141
  @staticmethod
110
142
  def _filter_none(data: Mapping[str, Any] | None) -> dict[str, Any] | None:
111
143
  if data is None:
@@ -1,6 +1,8 @@
1
1
  from .base import WazzapiModel
2
2
  from .contacts import *
3
3
  from .contacts import __all__ as contacts_all
4
+ from .devices import *
5
+ from .devices import __all__ as devices_all
4
6
  from .groups import *
5
7
  from .groups import __all__ as groups_all
6
8
  from .media import *
@@ -12,4 +14,13 @@ from .templates import __all__ as templates_all
12
14
  from .webhooks import *
13
15
  from .webhooks import __all__ as webhooks_all
14
16
 
15
- __all__ = ["WazzapiModel", *contacts_all, *groups_all, *media_all, *messages_all, *templates_all, *webhooks_all]
17
+ __all__ = [
18
+ "WazzapiModel",
19
+ *contacts_all,
20
+ *devices_all,
21
+ *groups_all,
22
+ *media_all,
23
+ *messages_all,
24
+ *templates_all,
25
+ *webhooks_all,
26
+ ]
@@ -173,6 +173,27 @@ class ContactUpdateRequest(WazzapiModel):
173
173
  is_opted_out: bool | None = None
174
174
 
175
175
 
176
+ class ContactProfilePictureResponse(WazzapiModel):
177
+ """Ephemeral WhatsApp profile picture metadata for a contact."""
178
+
179
+ contact_id: str
180
+ phone_number: str
181
+ url: str
182
+ picture_id: str | None = None
183
+ picture_type: str | None = None
184
+ preview: bool
185
+ whatsapp_account_id: str
186
+ cached: bool = False
187
+
188
+
189
+ class ContactProfilePictureImageResult(WazzapiModel):
190
+ """Raw image bytes returned by the profile-picture image endpoint."""
191
+
192
+ content: bytes
193
+ content_type: str | None = None
194
+ final_url: str | None = None
195
+
196
+
176
197
  class RemoveFromGroupRequest(WazzapiModel):
177
198
  contact_ids: list[str]
178
199
 
@@ -193,6 +214,8 @@ __all__ = [
193
214
  "ContactGroupUpdateRequest",
194
215
  "ContactItem",
195
216
  "ContactListResponse",
217
+ "ContactProfilePictureImageResult",
218
+ "ContactProfilePictureResponse",
196
219
  "ContactResponse",
197
220
  "ContactSyncHistoryItem",
198
221
  "ContactSyncHistoryResponse",
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ from .base import WazzapiModel
6
+
7
+
8
+ class DeviceItem(WazzapiModel):
9
+ id: str
10
+ name: str
11
+ session_name: str
12
+ status: str
13
+ is_primary: bool
14
+ auto_warmer_enabled: bool
15
+ total_messages_sent: int
16
+ total_messages_received: int
17
+ contacts_synced_count: int
18
+ created_at: datetime
19
+ updated_at: datetime
20
+ phone_number: str | None = None
21
+ last_connected_at: datetime | None = None
22
+ last_health_check_at: datetime | None = None
23
+ last_contact_sync_at: datetime | None = None
24
+ last_contact_sync_status: str | None = None
25
+
26
+
27
+ class DeviceListResponse(WazzapiModel):
28
+ devices: list[DeviceItem]
29
+ total: int
30
+ limit: int
31
+ offset: int
32
+
33
+
34
+ class DeviceResponse(DeviceItem):
35
+ timezone: str
36
+ connection_attempts: int
37
+ daily_message_limit: int
38
+ message_delay_ms: int
39
+ backend_id: str | None = None
40
+
41
+
42
+ __all__ = ["DeviceItem", "DeviceListResponse", "DeviceResponse"]
@@ -136,6 +136,9 @@ class SendMessageRequest(WazzapiModel):
136
136
  message_type: str = "text"
137
137
  media_type: str | None = None
138
138
  media_url: str | None = None
139
+ media_base64: str | None = None
140
+ filename: str | None = None
141
+ mimetype: str | None = None
139
142
  caption: str | None = None
140
143
  latitude: float | None = None
141
144
  longitude: float | None = None
@@ -1,6 +1,13 @@
1
1
  from .contacts import ContactsResource
2
+ from .devices import DevicesResource
2
3
  from .groups import GroupsResource
3
4
  from .messages import MessagesResource
4
5
  from .templates import TemplatesResource
5
6
 
6
- __all__ = ["ContactsResource", "GroupsResource", "MessagesResource", "TemplatesResource"]
7
+ __all__ = [
8
+ "ContactsResource",
9
+ "DevicesResource",
10
+ "GroupsResource",
11
+ "MessagesResource",
12
+ "TemplatesResource",
13
+ ]
@@ -218,5 +218,41 @@ class ContactsResource(BaseResource):
218
218
  self._client._request("DELETE", f"/api/v1/contacts/{contact_id}")
219
219
  return None
220
220
 
221
+ def get_profile_picture(
222
+ self,
223
+ contact_id: str,
224
+ *,
225
+ whatsapp_account_id: str | None = None,
226
+ preview: bool | None = None,
227
+ ) -> models.ContactProfilePictureResponse:
228
+ return self._client._request(
229
+ "GET",
230
+ f"/api/v1/contacts/{contact_id}/profile-picture",
231
+ params={
232
+ "whatsapp_account_id": whatsapp_account_id,
233
+ "preview": preview,
234
+ },
235
+ response_model=models.ContactProfilePictureResponse,
236
+ )
237
+
238
+ def get_profile_picture_image(
239
+ self,
240
+ contact_id: str,
241
+ *,
242
+ whatsapp_account_id: str | None = None,
243
+ preview: bool | None = None,
244
+ proxy: bool | None = None,
245
+ ) -> models.ContactProfilePictureImageResult:
246
+ return self._client._request_bytes(
247
+ "GET",
248
+ f"/api/v1/contacts/{contact_id}/profile-picture/image",
249
+ params={
250
+ "whatsapp_account_id": whatsapp_account_id,
251
+ "preview": preview,
252
+ "proxy": proxy,
253
+ },
254
+ response_model=models.ContactProfilePictureImageResult,
255
+ )
256
+
221
257
 
222
258
  __all__ = ["ContactsResource"]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from .. import models
4
+ from .base import BaseResource
5
+
6
+
7
+ class DevicesResource(BaseResource):
8
+ def list(
9
+ self,
10
+ *,
11
+ limit: int | None = None,
12
+ offset: int | None = None,
13
+ status: str | None = None,
14
+ search: str | None = None,
15
+ sort_by: str | None = None,
16
+ sort_order: str | None = None,
17
+ ) -> models.DeviceListResponse:
18
+ return self._client._request(
19
+ "GET",
20
+ "/api/v1/devices",
21
+ params={
22
+ "limit": limit,
23
+ "offset": offset,
24
+ "status": status,
25
+ "search": search,
26
+ "sort_by": sort_by,
27
+ "sort_order": sort_order,
28
+ },
29
+ response_model=models.DeviceListResponse,
30
+ )
31
+
32
+ def get(self, device_id: str) -> models.DeviceResponse:
33
+ return self._client._request(
34
+ "GET",
35
+ f"/api/v1/devices/{device_id}",
36
+ response_model=models.DeviceResponse,
37
+ )
38
+
39
+
40
+ __all__ = ["DevicesResource"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes