wazzapi 0.2.0__tar.gz → 0.3.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.
- {wazzapi-0.2.0 → wazzapi-0.3.0}/PKG-INFO +19 -3
- {wazzapi-0.2.0 → wazzapi-0.3.0}/README.md +17 -1
- wazzapi-0.3.0/examples/list_devices.py +15 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/pyproject.toml +2 -2
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/smoke_test.py +1 -1
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/test_client.py +1 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/test_resources.py +56 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/__init__.py +1 -1
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/client.py +2 -1
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/__init__.py +12 -1
- wazzapi-0.3.0/wazzapi/models/devices.py +42 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/__init__.py +8 -1
- wazzapi-0.3.0/wazzapi/resources/devices.py +40 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/.gitignore +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/examples/create_template.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/examples/list_contacts.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/examples/preview_template.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/examples/send_message.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/examples/verify_webhook.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/test_groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/test_media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/tests/test_webhooks.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/errors.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/base.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/contacts.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/messages.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/templates.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/models/webhooks.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/py.typed +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/base.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/contacts.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/messages.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/resources/templates.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.3.0}/wazzapi/webhooks.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wazzapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -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.
|
|
3
|
+
version = "0.3.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"
|
|
@@ -25,8 +25,8 @@ dependencies = [
|
|
|
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 = [
|
|
@@ -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(
|
|
@@ -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)
|
|
@@ -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__ = [
|
|
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
|
+
]
|
|
@@ -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"]
|
|
@@ -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__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ContactsResource",
|
|
9
|
+
"DevicesResource",
|
|
10
|
+
"GroupsResource",
|
|
11
|
+
"MessagesResource",
|
|
12
|
+
"TemplatesResource",
|
|
13
|
+
]
|
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|