omdaa-api-client 1.0.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.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: omdaa-api-client
3
+ Version: 1.0.0
4
+ Summary: Official Python client for Omdaa WhatsApp Business API
5
+ Author: Omdaa
6
+ License: MIT
7
+ Project-URL: Homepage, https://omdaa.com
8
+ Project-URL: Documentation, https://omdaanet/docs/sdk
9
+ Keywords: omdaa,whatsapp,api,client,sdk
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.28.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
16
+
17
+ # omdaa-api-client (Python)
18
+
19
+ Official Python client for the [Omdaa](https://omdaa.com) WhatsApp Business API.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install omdaa-api-client
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ from omdaa import OmdaaClient, OmdaaError
31
+
32
+ client = OmdaaClient("your-api-key")
33
+ # Optional: OmdaaClient("your-api-key", base_url="https://omdaa.com/api/v1")
34
+
35
+ try:
36
+ sessions = client.sessions.list()
37
+ result = client.messages.send_text({
38
+ "sessionId": "default",
39
+ "to": "201234567890",
40
+ "message": "Hello from Omdaa Python!",
41
+ })
42
+ client.webhooks.set({"url": "https://yoursite.com/webhook", "enabled": True})
43
+ except OmdaaError as e:
44
+ print(e.status_code, str(e))
45
+ ```
46
+
47
+ ## API Resources
48
+
49
+ Same as the Node.js SDK: `messages`, `sessions`, `webhooks`, `templates`, `scheduled`, `bulk`, `contacts`, `groups`, `chats`, `business`, `storage`, `labels`, `integrations`, `queue`, `interactive`, `api_keys`, `profile`, `dashboard`, `plans`, `billing`, `referral`, `notifications`, `metrics`, `backup`, `security`, `audit`, `organizations`, `settings`, `channels`, `privacy`, `support`, `proxy`, `calls`, `auth`, `users`, `email`, `ai`.
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.9+
54
+ - requests >= 2.28.0
55
+
56
+ ## Development & tests
57
+
58
+ ```bash
59
+ pip install -e ".[dev]"
60
+ python -m pytest tests -v
61
+ # or: python -m unittest discover -s tests -v
62
+ ```
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,50 @@
1
+ # omdaa-api-client (Python)
2
+
3
+ Official Python client for the [Omdaa](https://omdaa.com) WhatsApp Business API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install omdaa-api-client
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from omdaa import OmdaaClient, OmdaaError
15
+
16
+ client = OmdaaClient("your-api-key")
17
+ # Optional: OmdaaClient("your-api-key", base_url="https://omdaa.com/api/v1")
18
+
19
+ try:
20
+ sessions = client.sessions.list()
21
+ result = client.messages.send_text({
22
+ "sessionId": "default",
23
+ "to": "201234567890",
24
+ "message": "Hello from Omdaa Python!",
25
+ })
26
+ client.webhooks.set({"url": "https://yoursite.com/webhook", "enabled": True})
27
+ except OmdaaError as e:
28
+ print(e.status_code, str(e))
29
+ ```
30
+
31
+ ## API Resources
32
+
33
+ Same as the Node.js SDK: `messages`, `sessions`, `webhooks`, `templates`, `scheduled`, `bulk`, `contacts`, `groups`, `chats`, `business`, `storage`, `labels`, `integrations`, `queue`, `interactive`, `api_keys`, `profile`, `dashboard`, `plans`, `billing`, `referral`, `notifications`, `metrics`, `backup`, `security`, `audit`, `organizations`, `settings`, `channels`, `privacy`, `support`, `proxy`, `calls`, `auth`, `users`, `email`, `ai`.
34
+
35
+ ## Requirements
36
+
37
+ - Python 3.9+
38
+ - requests >= 2.28.0
39
+
40
+ ## Development & tests
41
+
42
+ ```bash
43
+ pip install -e ".[dev]"
44
+ python -m pytest tests -v
45
+ # or: python -m unittest discover -s tests -v
46
+ ```
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "omdaa-api-client"
7
+ version = "1.0.0"
8
+ description = "Official Python client for Omdaa WhatsApp Business API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Omdaa" }]
13
+ keywords = ["omdaa", "whatsapp", "api", "client", "sdk"]
14
+ dependencies = [
15
+ "requests>=2.28.0",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = ["pytest>=7.0", "pytest-cov>=4.0"]
20
+
21
+ [project.urls]
22
+ Homepage = "https://omdaa.com"
23
+ Documentation = "https://omdaanet/docs/sdk"
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
27
+ pythonpath = ["src"]
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
31
+
32
+ [tool.setuptools.package-dir]
33
+ "" = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Omdaa API Client - Official Python SDK for Omdaa WhatsApp Business API.
3
+ """
4
+
5
+ from omdaa.client import OmdaaClient
6
+ from omdaa.exceptions import OmdaaError
7
+
8
+ __all__ = ["OmdaaClient", "OmdaaError"]
9
+ __version__ = "1.0.0"
@@ -0,0 +1,157 @@
1
+ """
2
+ HTTP client for Omdaa API with Bearer auth.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ import requests
8
+ from omdaa.exceptions import OmdaaError
9
+
10
+ DEFAULT_BASE_URL = "https://omdaa.com/api/v1"
11
+
12
+
13
+ class Client:
14
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
15
+ self.api_key = api_key
16
+ self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
17
+ self._session = requests.Session()
18
+ self._session.headers.update({
19
+ "Authorization": f"Bearer {api_key}",
20
+ "Content-Type": "application/json",
21
+ "Accept": "application/json",
22
+ })
23
+
24
+ def request(
25
+ self,
26
+ method: str,
27
+ path: str,
28
+ body: Optional[dict] = None,
29
+ params: Optional[dict] = None,
30
+ ) -> dict:
31
+ url = f"{self.base_url}{path}" if path.startswith("/") else f"{self.base_url}/{path}"
32
+ kwargs = {}
33
+ if params:
34
+ kwargs["params"] = {k: v for k, v in params.items() if v is not None}
35
+ if body and method != "GET":
36
+ kwargs["json"] = body
37
+ try:
38
+ r = self._session.request(method, url, **kwargs)
39
+ data = r.json() if r.content else {}
40
+ if not r.ok:
41
+ raise OmdaaError(
42
+ data.get("message", r.text or r.reason),
43
+ status_code=r.status_code,
44
+ body=data if isinstance(data, dict) else None,
45
+ )
46
+ return data
47
+ except OmdaaError:
48
+ raise
49
+ except requests.RequestException as e:
50
+ raise OmdaaError(str(e)) from e
51
+
52
+ def get(self, path: str, params: dict | None = None) -> dict:
53
+ return self.request("GET", path, params=params)
54
+
55
+ def post(self, path: str, body: Optional[dict] = None) -> dict:
56
+ return self.request("POST", path, body=body or {})
57
+
58
+ def put(self, path: str, body: Optional[dict] = None) -> dict:
59
+ return self.request("PUT", path, body=body or {})
60
+
61
+ def delete(self, path: str, body: Optional[dict] = None) -> dict:
62
+ return self.request("DELETE", path, body=body)
63
+
64
+
65
+ class OmdaaClient:
66
+ """Main client with all API resources. Use with API Key (Bearer auth)."""
67
+
68
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
69
+ self._client = Client(api_key, base_url)
70
+ from omdaa.resources.resources import (
71
+ MessagesResource,
72
+ SessionsResource,
73
+ WebhooksResource,
74
+ TemplatesResource,
75
+ ScheduledResource,
76
+ BulkResource,
77
+ ContactsResource,
78
+ GroupsResource,
79
+ ChatsResource,
80
+ BusinessResource,
81
+ StorageResource,
82
+ LabelsResource,
83
+ IntegrationsResource,
84
+ QueueResource,
85
+ InteractiveResource,
86
+ ApiKeysResource,
87
+ ProfileResource,
88
+ DashboardResource,
89
+ PlansResource,
90
+ BillingResource,
91
+ ReferralResource,
92
+ NotificationsResource,
93
+ MetricsResource,
94
+ BackupResource,
95
+ SecurityResource,
96
+ AuditResource,
97
+ OrganizationsResource,
98
+ SettingsResource,
99
+ ChannelsResource,
100
+ PrivacyResource,
101
+ SupportResource,
102
+ ProxyResource,
103
+ CallsResource,
104
+ AuthResource,
105
+ UsersResource,
106
+ EmailResource,
107
+ AiResource,
108
+ )
109
+ self.messages = MessagesResource(self)
110
+ self.sessions = SessionsResource(self)
111
+ self.webhooks = WebhooksResource(self)
112
+ self.templates = TemplatesResource(self)
113
+ self.scheduled = ScheduledResource(self)
114
+ self.bulk = BulkResource(self)
115
+ self.contacts = ContactsResource(self)
116
+ self.groups = GroupsResource(self)
117
+ self.chats = ChatsResource(self)
118
+ self.business = BusinessResource(self)
119
+ self.storage = StorageResource(self)
120
+ self.labels = LabelsResource(self)
121
+ self.integrations = IntegrationsResource(self)
122
+ self.queue = QueueResource(self)
123
+ self.interactive = InteractiveResource(self)
124
+ self.api_keys = ApiKeysResource(self)
125
+ self.profile = ProfileResource(self)
126
+ self.dashboard = DashboardResource(self)
127
+ self.plans = PlansResource(self)
128
+ self.billing = BillingResource(self)
129
+ self.referral = ReferralResource(self)
130
+ self.notifications = NotificationsResource(self)
131
+ self.metrics = MetricsResource(self)
132
+ self.backup = BackupResource(self)
133
+ self.security = SecurityResource(self)
134
+ self.audit = AuditResource(self)
135
+ self.organizations = OrganizationsResource(self)
136
+ self.settings = SettingsResource(self)
137
+ self.channels = ChannelsResource(self)
138
+ self.privacy = PrivacyResource(self)
139
+ self.support = SupportResource(self)
140
+ self.proxy = ProxyResource(self)
141
+ self.calls = CallsResource(self)
142
+ self.auth = AuthResource(self)
143
+ self.users = UsersResource(self)
144
+ self.email = EmailResource(self)
145
+ self.ai = AiResource(self)
146
+
147
+ def get(self, path: str, params: Optional[dict] = None) -> dict:
148
+ return self._client.get(path, params=params)
149
+
150
+ def post(self, path: str, body: Optional[dict] = None) -> dict:
151
+ return self._client.post(path, body=body)
152
+
153
+ def put(self, path: str, body: Optional[dict] = None) -> dict:
154
+ return self._client.put(path, body=body)
155
+
156
+ def delete(self, path: str, body: Optional[dict] = None) -> dict:
157
+ return self._client.delete(path, body=body)
@@ -0,0 +1,8 @@
1
+ from typing import Optional
2
+
3
+
4
+ class OmdaaError(Exception):
5
+ def __init__(self, message: str, status_code: Optional[int] = None, body: Optional[dict] = None):
6
+ super().__init__(message)
7
+ self.status_code = status_code
8
+ self.body = body
@@ -0,0 +1,79 @@
1
+ from omdaa.resources.resources import (
2
+ MessagesResource,
3
+ SessionsResource,
4
+ WebhooksResource,
5
+ TemplatesResource,
6
+ ScheduledResource,
7
+ BulkResource,
8
+ ContactsResource,
9
+ GroupsResource,
10
+ ChatsResource,
11
+ BusinessResource,
12
+ StorageResource,
13
+ LabelsResource,
14
+ IntegrationsResource,
15
+ QueueResource,
16
+ InteractiveResource,
17
+ ApiKeysResource,
18
+ ProfileResource,
19
+ DashboardResource,
20
+ PlansResource,
21
+ BillingResource,
22
+ ReferralResource,
23
+ NotificationsResource,
24
+ MetricsResource,
25
+ BackupResource,
26
+ SecurityResource,
27
+ AuditResource,
28
+ OrganizationsResource,
29
+ SettingsResource,
30
+ ChannelsResource,
31
+ PrivacyResource,
32
+ SupportResource,
33
+ ProxyResource,
34
+ CallsResource,
35
+ AuthResource,
36
+ UsersResource,
37
+ EmailResource,
38
+ AiResource,
39
+ )
40
+
41
+ __all__ = [
42
+ "MessagesResource",
43
+ "SessionsResource",
44
+ "WebhooksResource",
45
+ "TemplatesResource",
46
+ "ScheduledResource",
47
+ "BulkResource",
48
+ "ContactsResource",
49
+ "GroupsResource",
50
+ "ChatsResource",
51
+ "BusinessResource",
52
+ "StorageResource",
53
+ "LabelsResource",
54
+ "IntegrationsResource",
55
+ "QueueResource",
56
+ "InteractiveResource",
57
+ "ApiKeysResource",
58
+ "ProfileResource",
59
+ "DashboardResource",
60
+ "PlansResource",
61
+ "BillingResource",
62
+ "ReferralResource",
63
+ "NotificationsResource",
64
+ "MetricsResource",
65
+ "BackupResource",
66
+ "SecurityResource",
67
+ "AuditResource",
68
+ "OrganizationsResource",
69
+ "SettingsResource",
70
+ "ChannelsResource",
71
+ "PrivacyResource",
72
+ "SupportResource",
73
+ "ProxyResource",
74
+ "CallsResource",
75
+ "AuthResource",
76
+ "UsersResource",
77
+ "EmailResource",
78
+ "AiResource",
79
+ ]
@@ -0,0 +1,617 @@
1
+ # All API resources - mirror omdaa-js SDK paths and methods.
2
+ from urllib.parse import quote
3
+
4
+
5
+ class MessagesResource:
6
+ def __init__(self, client):
7
+ self._client = client
8
+
9
+ def send_text(self, params):
10
+ body = {
11
+ "sessionId": params.get("sessionId"),
12
+ "to": params.get("to"),
13
+ "message": params.get("message") or params.get("text"),
14
+ "quotedMessageId": params.get("quotedMessageId"),
15
+ "quoted": params.get("quoted"),
16
+ }
17
+ return self._client.post("/messages/send-text", body)
18
+
19
+ def send_image(self, params):
20
+ return self._client.post("/messages/send-image", params)
21
+
22
+ def get_received(self, params=None):
23
+ return self._client.get("/messages/received", params=params)
24
+
25
+ def get_sent(self, params=None):
26
+ return self._client.get("/messages/sent", params=params)
27
+
28
+ def get_stats(self, params=None):
29
+ return self._client.get("/messages/stats", params=params)
30
+
31
+ def get_by_chat(self, remote_jid, params=None):
32
+ return self._client.get(f"/messages/chat/{quote(remote_jid)}", params=params)
33
+
34
+ def search(self, params=None):
35
+ return self._client.get("/messages/search", params=params)
36
+
37
+ def get_recent_chats(self, params=None):
38
+ return self._client.get("/messages/recent-chats", params=params)
39
+
40
+ def get_chats(self, params=None):
41
+ return self._client.get("/messages/chats", params=params)
42
+
43
+ def delete_message(self, message_id):
44
+ return self._client.delete(f"/messages/{quote(message_id)}")
45
+
46
+ def forward(self, body):
47
+ return self._client.post("/messages/forward", body)
48
+
49
+ def forward_multiple(self, body):
50
+ return self._client.post("/messages/forward-multiple", body)
51
+
52
+ def delete_multiple(self, body):
53
+ return self._client.delete("/messages/multiple", body)
54
+
55
+ def schedule(self, body):
56
+ return self._client.post("/messages/schedule", body)
57
+
58
+ def send_location(self, body):
59
+ return self._client.post("/messages/send-location", body)
60
+
61
+ def send_contact(self, body):
62
+ return self._client.post("/messages/send-contact", body)
63
+
64
+ def send_voice(self, body):
65
+ return self._client.post("/messages/send-voice", body)
66
+
67
+ def send_audio(self, body):
68
+ return self._client.post("/messages/send-audio", body)
69
+
70
+ def send_reaction(self, body):
71
+ return self._client.post("/messages/reaction", body)
72
+
73
+ def remove_reaction(self, body):
74
+ return self._client.delete("/messages/reaction", body)
75
+
76
+ def send_read_receipt(self, body):
77
+ return self._client.post("/messages/read-receipt", body)
78
+
79
+ def mark_chat_read(self, body):
80
+ return self._client.post("/messages/mark-chat-read", body)
81
+
82
+ def get_read_status(self, message_id):
83
+ return self._client.get(f"/messages/read-status/{quote(message_id)}")
84
+
85
+ def update_read_receipts_privacy(self, body):
86
+ return self._client.put("/messages/privacy/read-receipts", body)
87
+
88
+ def send_typing(self, body):
89
+ return self._client.post("/messages/typing", body)
90
+
91
+ def send_recording(self, body):
92
+ return self._client.post("/messages/recording", body)
93
+
94
+ def send_file(self, body):
95
+ return self._client.post("/messages/send-file", body)
96
+
97
+ def send_media(self, body):
98
+ return self._client.post("/messages/send-media", body)
99
+
100
+ def send_poll(self, body):
101
+ return self._client.post("/messages/send-poll", body)
102
+
103
+ def send_list(self, body):
104
+ return self._client.post("/messages/send-list", body)
105
+
106
+ def send_buttons(self, body):
107
+ return self._client.post("/messages/send-buttons", body)
108
+
109
+ def send_status(self, body):
110
+ return self._client.post("/messages/send-status", body)
111
+
112
+ def send_media_advanced(self, body):
113
+ return self._client.post("/messages/send-media-advanced", body)
114
+
115
+ def send_ptv(self, body):
116
+ return self._client.post("/messages/send-ptv", body)
117
+
118
+ def send_sticker(self, body):
119
+ return self._client.post("/messages/send-sticker", body)
120
+
121
+ def get_poll_results(self, poll_id, params=None):
122
+ return self._client.get(f"/messages/poll/{quote(poll_id)}/results", params=params)
123
+
124
+
125
+ class SessionsResource:
126
+ def __init__(self, client):
127
+ self._client = client
128
+
129
+ def list(self):
130
+ return self._client.get("/sessions")
131
+
132
+ def create(self, params=None):
133
+ return self._client.post("/sessions/create", params or {})
134
+
135
+ def get(self, session_id):
136
+ return self._client.get(f"/sessions/{quote(session_id)}")
137
+
138
+ def get_qr(self, session_id):
139
+ return self._client.get(f"/sessions/{quote(session_id)}/qr")
140
+
141
+ def disconnect(self, session_id):
142
+ return self._client.delete(f"/sessions/{quote(session_id)}/logout")
143
+
144
+ def delete(self, session_id):
145
+ return self._client.delete(f"/sessions/delete/{quote(session_id)}")
146
+
147
+
148
+ class WebhooksResource:
149
+ def __init__(self, client):
150
+ self._client = client
151
+
152
+ def get(self):
153
+ return self._client.get("/webhooks")
154
+
155
+ def set(self, params):
156
+ return self._client.post("/webhooks/set", params)
157
+
158
+ def remove(self):
159
+ return self._client.delete("/webhooks/remove")
160
+
161
+ def test(self):
162
+ return self._client.get("/webhooks/test")
163
+
164
+ def get_events(self, params=None):
165
+ return self._client.get("/webhooks/events", params=params)
166
+
167
+ def get_stats(self):
168
+ return self._client.get("/webhooks/stats")
169
+
170
+
171
+ class TemplatesResource:
172
+ def __init__(self, client):
173
+ self._client = client
174
+
175
+ def list(self, params=None):
176
+ return self._client.get("/templates", params=params)
177
+
178
+ def get(self, template_id):
179
+ return self._client.get(f"/templates/{quote(template_id)}")
180
+
181
+ def create(self, body):
182
+ return self._client.post("/templates", body)
183
+
184
+ def delete(self, template_id):
185
+ return self._client.delete(f"/templates/{quote(template_id)}")
186
+
187
+
188
+ class ScheduledResource:
189
+ def __init__(self, client):
190
+ self._client = client
191
+
192
+ def list(self, params=None):
193
+ return self._client.get("/scheduled", params=params)
194
+
195
+ def get(self, scheduled_id):
196
+ return self._client.get(f"/scheduled/{quote(scheduled_id)}")
197
+
198
+ def cancel(self, scheduled_id):
199
+ return self._client.delete(f"/scheduled/{quote(scheduled_id)}")
200
+
201
+
202
+ class BulkResource:
203
+ def __init__(self, client):
204
+ self._client = client
205
+
206
+ def list(self, params=None):
207
+ return self._client.get("/bulk", params=params)
208
+
209
+ def get(self, bulk_id):
210
+ return self._client.get(f"/bulk/{quote(bulk_id)}")
211
+
212
+ def create(self, body):
213
+ return self._client.post("/bulk", body)
214
+
215
+ def cancel(self, bulk_id):
216
+ return self._client.delete(f"/bulk/{quote(bulk_id)}")
217
+
218
+
219
+ class ContactsResource:
220
+ def __init__(self, client):
221
+ self._client = client
222
+
223
+ def list(self, params=None):
224
+ return self._client.get("/contacts", params=params)
225
+
226
+ def get(self, contact_id):
227
+ return self._client.get(f"/contacts/{quote(contact_id)}")
228
+
229
+ def create(self, body):
230
+ return self._client.post("/contacts", body)
231
+
232
+ def update(self, contact_id, body):
233
+ return self._client.put(f"/contacts/{quote(contact_id)}", body)
234
+
235
+ def delete(self, contact_id):
236
+ return self._client.delete(f"/contacts/{quote(contact_id)}")
237
+
238
+
239
+ class GroupsResource:
240
+ def __init__(self, client):
241
+ self._client = client
242
+
243
+ def list(self, params=None):
244
+ return self._client.get("/groups", params=params)
245
+
246
+ def get(self, group_id):
247
+ return self._client.get(f"/groups/{quote(group_id)}")
248
+
249
+ def create(self, body):
250
+ return self._client.post("/groups", body)
251
+
252
+ def update(self, group_id, body):
253
+ return self._client.put(f"/groups/{quote(group_id)}", body)
254
+
255
+ def leave(self, group_id):
256
+ return self._client.post(f"/groups/{quote(group_id)}/leave", {})
257
+
258
+
259
+ class ChatsResource:
260
+ def __init__(self, client):
261
+ self._client = client
262
+
263
+ def list(self, params=None):
264
+ return self._client.get("/chats", params=params)
265
+
266
+ def get(self, chat_id):
267
+ return self._client.get(f"/chats/{quote(chat_id)}")
268
+
269
+ def archive(self, chat_id, body=None):
270
+ return self._client.post(f"/chats/{quote(chat_id)}/archive", body or {})
271
+
272
+ def unarchive(self, chat_id):
273
+ return self._client.post(f"/chats/{quote(chat_id)}/unarchive", {})
274
+
275
+
276
+ class BusinessResource:
277
+ def __init__(self, client):
278
+ self._client = client
279
+
280
+ def get(self, params=None):
281
+ return self._client.get("/business", params=params)
282
+
283
+ def update(self, body):
284
+ return self._client.put("/business", body)
285
+
286
+
287
+ class StorageResource:
288
+ def __init__(self, client):
289
+ self._client = client
290
+
291
+ def get_usage(self, params=None):
292
+ return self._client.get("/storage/usage", params=params)
293
+
294
+ def list_files(self, params=None):
295
+ return self._client.get("/storage/files", params=params)
296
+
297
+
298
+ class LabelsResource:
299
+ def __init__(self, client):
300
+ self._client = client
301
+
302
+ def list(self, params=None):
303
+ return self._client.get("/labels", params=params)
304
+
305
+ def get(self, label_id):
306
+ return self._client.get(f"/labels/{quote(label_id)}")
307
+
308
+ def create(self, body):
309
+ return self._client.post("/labels", body)
310
+
311
+ def delete(self, label_id):
312
+ return self._client.delete(f"/labels/{quote(label_id)}")
313
+
314
+
315
+ class IntegrationsResource:
316
+ def __init__(self, client):
317
+ self._client = client
318
+
319
+ def list(self, params=None):
320
+ return self._client.get("/integrations", params=params)
321
+
322
+ def get(self, integration_id):
323
+ return self._client.get(f"/integrations/{quote(integration_id)}")
324
+
325
+ def connect(self, body):
326
+ return self._client.post("/integrations/connect", body)
327
+
328
+ def disconnect(self, integration_id):
329
+ return self._client.delete(f"/integrations/{quote(integration_id)}")
330
+
331
+
332
+ class QueueResource:
333
+ def __init__(self, client):
334
+ self._client = client
335
+
336
+ def list(self, params=None):
337
+ return self._client.get("/queue", params=params)
338
+
339
+ def get(self, job_id):
340
+ return self._client.get(f"/queue/{quote(job_id)}")
341
+
342
+ def cancel(self, job_id):
343
+ return self._client.delete(f"/queue/{quote(job_id)}")
344
+
345
+
346
+ class InteractiveResource:
347
+ def __init__(self, client):
348
+ self._client = client
349
+
350
+ def send_list(self, body):
351
+ return self._client.post("/interactive/list", body)
352
+
353
+ def send_buttons(self, body):
354
+ return self._client.post("/interactive/buttons", body)
355
+
356
+
357
+ class ApiKeysResource:
358
+ def __init__(self, client):
359
+ self._client = client
360
+
361
+ def list(self, params=None):
362
+ return self._client.get("/api-keys", params=params)
363
+
364
+ def create(self, body):
365
+ return self._client.post("/api-keys", body)
366
+
367
+ def revoke(self, key_id):
368
+ return self._client.delete(f"/api-keys/{quote(key_id)}")
369
+
370
+
371
+ class ProfileResource:
372
+ def __init__(self, client):
373
+ self._client = client
374
+
375
+ def get(self):
376
+ return self._client.get("/profile")
377
+
378
+ def update(self, body):
379
+ return self._client.put("/profile", body)
380
+
381
+
382
+ class DashboardResource:
383
+ def __init__(self, client):
384
+ self._client = client
385
+
386
+ def get(self, params=None):
387
+ return self._client.get("/dashboard", params=params)
388
+
389
+
390
+ class PlansResource:
391
+ def __init__(self, client):
392
+ self._client = client
393
+
394
+ def list(self, params=None):
395
+ return self._client.get("/plans", params=params)
396
+
397
+ def get(self, plan_id):
398
+ return self._client.get(f"/plans/{quote(plan_id)}")
399
+
400
+
401
+ class BillingResource:
402
+ def __init__(self, client):
403
+ self._client = client
404
+
405
+ def get(self):
406
+ return self._client.get("/billing")
407
+
408
+ def get_invoices(self, params=None):
409
+ return self._client.get("/billing/invoices", params=params)
410
+
411
+
412
+ class ReferralResource:
413
+ def __init__(self, client):
414
+ self._client = client
415
+
416
+ def get(self):
417
+ return self._client.get("/referral")
418
+
419
+ def get_links(self, params=None):
420
+ return self._client.get("/referral/links", params=params)
421
+
422
+
423
+ class NotificationsResource:
424
+ def __init__(self, client):
425
+ self._client = client
426
+
427
+ def list(self, params=None):
428
+ return self._client.get("/notifications", params=params)
429
+
430
+ def mark_read(self, notification_id):
431
+ return self._client.post(f"/notifications/{quote(notification_id)}/read", {})
432
+
433
+
434
+ class MetricsResource:
435
+ def __init__(self, client):
436
+ self._client = client
437
+
438
+ def get(self, params=None):
439
+ return self._client.get("/metrics", params=params)
440
+
441
+
442
+ class BackupResource:
443
+ def __init__(self, client):
444
+ self._client = client
445
+
446
+ def create(self, body=None):
447
+ return self._client.post("/backup", body or {})
448
+
449
+ def list(self, params=None):
450
+ return self._client.get("/backup", params=params)
451
+
452
+ def restore(self, backup_id):
453
+ return self._client.post(f"/backup/{quote(backup_id)}/restore", {})
454
+
455
+
456
+ class SecurityResource:
457
+ def __init__(self, client):
458
+ self._client = client
459
+
460
+ def get_settings(self):
461
+ return self._client.get("/security/settings")
462
+
463
+ def update_settings(self, body):
464
+ return self._client.put("/security/settings", body)
465
+
466
+
467
+ class AuditResource:
468
+ def __init__(self, client):
469
+ self._client = client
470
+
471
+ def list(self, params=None):
472
+ return self._client.get("/audit", params=params)
473
+
474
+ def get(self, audit_id):
475
+ return self._client.get(f"/audit/{quote(audit_id)}")
476
+
477
+
478
+ class OrganizationsResource:
479
+ def __init__(self, client):
480
+ self._client = client
481
+
482
+ def list(self, params=None):
483
+ return self._client.get("/organizations", params=params)
484
+
485
+ def get(self, org_id):
486
+ return self._client.get(f"/organizations/{quote(org_id)}")
487
+
488
+ def create(self, body):
489
+ return self._client.post("/organizations", body)
490
+
491
+ def update(self, org_id, body):
492
+ return self._client.put(f"/organizations/{quote(org_id)}", body)
493
+
494
+
495
+ class SettingsResource:
496
+ def __init__(self, client):
497
+ self._client = client
498
+
499
+ def get(self):
500
+ return self._client.get("/settings")
501
+
502
+ def update(self, body):
503
+ return self._client.put("/settings", body)
504
+
505
+
506
+ class ChannelsResource:
507
+ def __init__(self, client):
508
+ self._client = client
509
+
510
+ def list(self, params=None):
511
+ return self._client.get("/channels", params=params)
512
+
513
+ def get(self, channel_id):
514
+ return self._client.get(f"/channels/{quote(channel_id)}")
515
+
516
+
517
+ class PrivacyResource:
518
+ def __init__(self, client):
519
+ self._client = client
520
+
521
+ def get(self):
522
+ return self._client.get("/privacy")
523
+
524
+ def update(self, body):
525
+ return self._client.put("/privacy", body)
526
+
527
+
528
+ class SupportResource:
529
+ def __init__(self, client):
530
+ self._client = client
531
+
532
+ def tickets(self, params=None):
533
+ return self._client.get("/support/tickets", params=params)
534
+
535
+ def create_ticket(self, body):
536
+ return self._client.post("/support/tickets", body)
537
+
538
+
539
+ class ProxyResource:
540
+ def __init__(self, client):
541
+ self._client = client
542
+
543
+ def get(self):
544
+ return self._client.get("/proxy")
545
+
546
+ def update(self, body):
547
+ return self._client.put("/proxy", body)
548
+
549
+
550
+ class CallsResource:
551
+ def __init__(self, client):
552
+ self._client = client
553
+
554
+ def list(self, params=None):
555
+ return self._client.get("/calls", params=params)
556
+
557
+ def get(self, call_id):
558
+ return self._client.get(f"/calls/{quote(call_id)}")
559
+
560
+
561
+ class AuthResource:
562
+ def __init__(self, client):
563
+ self._client = client
564
+
565
+ def login(self, body):
566
+ return self._client.post("/auth/login", body)
567
+
568
+ def logout(self):
569
+ return self._client.post("/auth/logout", {})
570
+
571
+ def refresh(self, body=None):
572
+ return self._client.post("/auth/refresh", body or {})
573
+
574
+ def me(self):
575
+ return self._client.get("/auth/me")
576
+
577
+
578
+ class UsersResource:
579
+ def __init__(self, client):
580
+ self._client = client
581
+
582
+ def list(self, params=None):
583
+ return self._client.get("/users", params=params)
584
+
585
+ def get(self, user_id):
586
+ return self._client.get(f"/users/{quote(user_id)}")
587
+
588
+ def create(self, body):
589
+ return self._client.post("/users", body)
590
+
591
+ def update(self, user_id, body):
592
+ return self._client.put(f"/users/{quote(user_id)}", body)
593
+
594
+ def delete(self, user_id):
595
+ return self._client.delete(f"/users/{quote(user_id)}")
596
+
597
+
598
+ class EmailResource:
599
+ def __init__(self, client):
600
+ self._client = client
601
+
602
+ def send(self, body):
603
+ return self._client.post("/email/send", body)
604
+
605
+ def get_settings(self):
606
+ return self._client.get("/email/settings")
607
+
608
+
609
+ class AiResource:
610
+ def __init__(self, client):
611
+ self._client = client
612
+
613
+ def chat(self, body):
614
+ return self._client.post("/ai/chat", body)
615
+
616
+ def completions(self, body):
617
+ return self._client.post("/ai/completions", body)
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: omdaa-api-client
3
+ Version: 1.0.0
4
+ Summary: Official Python client for Omdaa WhatsApp Business API
5
+ Author: Omdaa
6
+ License: MIT
7
+ Project-URL: Homepage, https://omdaa.com
8
+ Project-URL: Documentation, https://omdaanet/docs/sdk
9
+ Keywords: omdaa,whatsapp,api,client,sdk
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.28.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
16
+
17
+ # omdaa-api-client (Python)
18
+
19
+ Official Python client for the [Omdaa](https://omdaa.com) WhatsApp Business API.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install omdaa-api-client
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ from omdaa import OmdaaClient, OmdaaError
31
+
32
+ client = OmdaaClient("your-api-key")
33
+ # Optional: OmdaaClient("your-api-key", base_url="https://omdaa.com/api/v1")
34
+
35
+ try:
36
+ sessions = client.sessions.list()
37
+ result = client.messages.send_text({
38
+ "sessionId": "default",
39
+ "to": "201234567890",
40
+ "message": "Hello from Omdaa Python!",
41
+ })
42
+ client.webhooks.set({"url": "https://yoursite.com/webhook", "enabled": True})
43
+ except OmdaaError as e:
44
+ print(e.status_code, str(e))
45
+ ```
46
+
47
+ ## API Resources
48
+
49
+ Same as the Node.js SDK: `messages`, `sessions`, `webhooks`, `templates`, `scheduled`, `bulk`, `contacts`, `groups`, `chats`, `business`, `storage`, `labels`, `integrations`, `queue`, `interactive`, `api_keys`, `profile`, `dashboard`, `plans`, `billing`, `referral`, `notifications`, `metrics`, `backup`, `security`, `audit`, `organizations`, `settings`, `channels`, `privacy`, `support`, `proxy`, `calls`, `auth`, `users`, `email`, `ai`.
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.9+
54
+ - requests >= 2.28.0
55
+
56
+ ## Development & tests
57
+
58
+ ```bash
59
+ pip install -e ".[dev]"
60
+ python -m pytest tests -v
61
+ # or: python -m unittest discover -s tests -v
62
+ ```
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/omdaa/__init__.py
4
+ src/omdaa/client.py
5
+ src/omdaa/exceptions.py
6
+ src/omdaa/resources/__init__.py
7
+ src/omdaa/resources/resources.py
8
+ src/omdaa_api_client.egg-info/PKG-INFO
9
+ src/omdaa_api_client.egg-info/SOURCES.txt
10
+ src/omdaa_api_client.egg-info/dependency_links.txt
11
+ src/omdaa_api_client.egg-info/requires.txt
12
+ src/omdaa_api_client.egg-info/top_level.txt
13
+ tests/test_client.py
@@ -0,0 +1,5 @@
1
+ requests>=2.28.0
2
+
3
+ [dev]
4
+ pytest>=7.0
5
+ pytest-cov>=4.0
@@ -0,0 +1,97 @@
1
+ """Tests for OmdaaClient and Client."""
2
+ import sys
3
+ from pathlib import Path
4
+ from unittest import main, TestCase
5
+ from unittest.mock import patch
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
8
+
9
+ from omdaa import OmdaaClient, OmdaaError
10
+ from omdaa.client import Client
11
+
12
+
13
+ class TestOmdaaError(TestCase):
14
+ def test_omdaa_error_has_status_code_and_body(self):
15
+ e = OmdaaError("Bad request", status_code=400, body={"code": "invalid"})
16
+ self.assertEqual(str(e), "Bad request")
17
+ self.assertEqual(e.status_code, 400)
18
+ self.assertEqual(e.body, {"code": "invalid"})
19
+
20
+
21
+ class TestOmdaaClientResources(TestCase):
22
+ def test_omdaa_client_creates_all_resources(self):
23
+ with patch.object(Client, "request", return_value={"success": True}):
24
+ client = OmdaaClient("test-key")
25
+ self.assertIsNotNone(client.messages)
26
+ self.assertIsNotNone(client.sessions)
27
+ self.assertIsNotNone(client.webhooks)
28
+ self.assertIsNotNone(client.templates)
29
+ self.assertIsNotNone(client.scheduled)
30
+ self.assertIsNotNone(client.bulk)
31
+ self.assertIsNotNone(client.contacts)
32
+ self.assertIsNotNone(client.groups)
33
+ self.assertIsNotNone(client.chats)
34
+ self.assertIsNotNone(client.business)
35
+ self.assertIsNotNone(client.storage)
36
+ self.assertIsNotNone(client.labels)
37
+ self.assertIsNotNone(client.integrations)
38
+ self.assertIsNotNone(client.queue)
39
+ self.assertIsNotNone(client.interactive)
40
+ self.assertIsNotNone(client.api_keys)
41
+ self.assertIsNotNone(client.profile)
42
+ self.assertIsNotNone(client.dashboard)
43
+ self.assertIsNotNone(client.plans)
44
+ self.assertIsNotNone(client.billing)
45
+ self.assertIsNotNone(client.referral)
46
+ self.assertIsNotNone(client.notifications)
47
+ self.assertIsNotNone(client.metrics)
48
+ self.assertIsNotNone(client.backup)
49
+ self.assertIsNotNone(client.security)
50
+ self.assertIsNotNone(client.audit)
51
+ self.assertIsNotNone(client.organizations)
52
+ self.assertIsNotNone(client.settings)
53
+ self.assertIsNotNone(client.channels)
54
+ self.assertIsNotNone(client.privacy)
55
+ self.assertIsNotNone(client.support)
56
+ self.assertIsNotNone(client.proxy)
57
+ self.assertIsNotNone(client.calls)
58
+ self.assertIsNotNone(client.auth)
59
+ self.assertIsNotNone(client.users)
60
+ self.assertIsNotNone(client.email)
61
+ self.assertIsNotNone(client.ai)
62
+
63
+
64
+ class TestMessagesSendText(TestCase):
65
+ def test_messages_send_text_calls_post(self):
66
+ with patch.object(Client, "request", return_value={"success": True, "messageId": "msg-1"}) as m:
67
+ client = OmdaaClient("test-key")
68
+ out = client.messages.send_text({
69
+ "sessionId": "default",
70
+ "to": "201234567890",
71
+ "message": "Hello",
72
+ })
73
+ self.assertEqual(out["messageId"], "msg-1")
74
+ m.assert_called_once()
75
+ call = m.call_args
76
+ args, kwargs = call[0], call[1]
77
+ self.assertIn("POST", args)
78
+ self.assertIn("/messages/send-text", args)
79
+ self.assertEqual(kwargs["body"]["sessionId"], "default")
80
+ self.assertEqual(call[1]["body"]["to"], "201234567890")
81
+ self.assertEqual(call[1]["body"]["message"], "Hello")
82
+
83
+
84
+ class TestSessionsList(TestCase):
85
+ def test_sessions_list_calls_get(self):
86
+ with patch.object(Client, "request", return_value={"sessions": []}) as m:
87
+ client = OmdaaClient("test-key")
88
+ out = client.sessions.list()
89
+ self.assertIn("sessions", out)
90
+ m.assert_called_once()
91
+ args = m.call_args[0]
92
+ self.assertIn("GET", args)
93
+ self.assertIn("/sessions", args)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()