whatsapp-cloud-api-py 0.1.0__py3-none-any.whl → 0.1.1__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.
@@ -0,0 +1,623 @@
1
+ Metadata-Version: 2.4
2
+ Name: whatsapp-cloud-api-py
3
+ Version: 0.1.1
4
+ Summary: Async Python SDK for WhatsApp Business Cloud API with Pydantic V2
5
+ Project-URL: Homepage, https://github.com/HeiCg/whatsapp-cloud-api-py
6
+ Project-URL: Repository, https://github.com/HeiCg/whatsapp-cloud-api-py
7
+ Project-URL: Issues, https://github.com/HeiCg/whatsapp-cloud-api-py/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: httpx[http2]>=0.27
12
+ Requires-Dist: pydantic>=2.7
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Requires-Dist: pyventus>=0.7.2; extra == 'dev'
17
+ Requires-Dist: respx>=0.22; extra == 'dev'
18
+ Requires-Dist: ruff>=0.8; extra == 'dev'
19
+ Provides-Extra: events
20
+ Requires-Dist: pyventus>=0.7.2; extra == 'events'
21
+ Provides-Extra: server
22
+ Requires-Dist: cryptography>=43.0; extra == 'server'
23
+ Provides-Extra: webhooks
24
+ Requires-Dist: starlette>=0.37; extra == 'webhooks'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # whatsapp-cloud-api-py
28
+
29
+ Community-built async Python SDK for the WhatsApp Business Cloud API.
30
+
31
+ > **Note:** This is an **independent Python implementation** — not a port or fork. It was inspired by the excellent [`@kapso/whatsapp-cloud-api`](https://github.com/gokapso/whatsapp-cloud-api-js) (TypeScript), but written from scratch in Python with its own architecture, design choices, and API surface.
32
+
33
+ Built with **httpx** (HTTP/2 + connection pooling), **Pydantic V2** (Rust-powered validation), and optional **pyventus** event-driven webhooks.
34
+
35
+ ## Features
36
+
37
+ - **Fully async** — all I/O uses `async`/`await` with httpx
38
+ - **HTTP/2** — connection pooling and multiplexing out of the box
39
+ - **Pydantic V2** — fast, type-safe input/response models with Rust-powered validation
40
+ - **27 message types** — text, image, video, audio, document, sticker, location, contacts, reaction, template, interactive (buttons, list, flow, CTA URL, catalog), mark as read
41
+ - **Media operations** — upload, get metadata, download, delete (with auto-retry on auth failures)
42
+ - **Template management** — list, create, delete message templates
43
+ - **Phone number management** — registration, verification, business profile
44
+ - **WhatsApp Flows** — create and deploy (auto-publish)
45
+ - **Webhook handling** — HMAC-SHA256 signature verification + payload normalization
46
+ - **Event-driven webhooks** — optional pyventus integration with 18 typed events
47
+ - **Error categorization** — 14 error categories with retry hints (but no forced auto-retry)
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ uv add whatsapp-cloud-api-py
53
+ ```
54
+
55
+ With extras:
56
+
57
+ ```bash
58
+ # Event-driven webhooks (pyventus)
59
+ uv add "whatsapp-cloud-api-py[events]"
60
+
61
+ # All extras
62
+ uv add "whatsapp-cloud-api-py[events,webhooks,server]"
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ ```python
68
+ import asyncio
69
+ from whatsapp_cloud_api import WhatsAppClient, TextMessage
70
+
71
+ async def main():
72
+ async with WhatsAppClient(access_token="YOUR_TOKEN") as client:
73
+ response = await client.messages.send_text(TextMessage(
74
+ phone_number_id="PHONE_NUMBER_ID",
75
+ to="5511999999999",
76
+ body="Hello from Python!",
77
+ ))
78
+ print(response.messages[0].id)
79
+
80
+ asyncio.run(main())
81
+ ```
82
+
83
+ ## Sending Messages
84
+
85
+ All message types return a `SendMessageResponse` with `contacts` and `messages` fields.
86
+
87
+ ### Text
88
+
89
+ ```python
90
+ from whatsapp_cloud_api import TextMessage
91
+
92
+ await client.messages.send_text(TextMessage(
93
+ phone_number_id="PHONE_ID",
94
+ to="5511999999999",
95
+ body="Hello!",
96
+ preview_url=True, # enable link previews
97
+ ))
98
+ ```
99
+
100
+ ### Image
101
+
102
+ ```python
103
+ from whatsapp_cloud_api import ImageMessage
104
+ from whatsapp_cloud_api.resources.messages import MediaById, MediaByLink
105
+
106
+ # By media ID (from upload)
107
+ await client.messages.send_image(ImageMessage(
108
+ phone_number_id="PHONE_ID",
109
+ to="5511999999999",
110
+ image=MediaById(id="MEDIA_ID", caption="Check this out"),
111
+ ))
112
+
113
+ # By URL
114
+ await client.messages.send_image(ImageMessage(
115
+ phone_number_id="PHONE_ID",
116
+ to="5511999999999",
117
+ image=MediaByLink(link="https://example.com/photo.jpg"),
118
+ ))
119
+ ```
120
+
121
+ ### Audio / Video / Document / Sticker
122
+
123
+ ```python
124
+ from whatsapp_cloud_api import AudioMessage, VideoMessage, DocumentMessage, StickerMessage
125
+ from whatsapp_cloud_api.resources.messages import (
126
+ AudioPayloadByLink, MediaByLink, DocumentPayloadByLink, StickerByLink,
127
+ )
128
+
129
+ await client.messages.send_audio(AudioMessage(
130
+ phone_number_id="PHONE_ID", to="5511999999999",
131
+ audio=AudioPayloadByLink(link="https://example.com/audio.mp3"),
132
+ ))
133
+
134
+ await client.messages.send_video(VideoMessage(
135
+ phone_number_id="PHONE_ID", to="5511999999999",
136
+ video=MediaByLink(link="https://example.com/video.mp4", caption="Watch this"),
137
+ ))
138
+
139
+ await client.messages.send_document(DocumentMessage(
140
+ phone_number_id="PHONE_ID", to="5511999999999",
141
+ document=DocumentPayloadByLink(
142
+ link="https://example.com/file.pdf",
143
+ filename="report.pdf",
144
+ caption="Monthly report",
145
+ ),
146
+ ))
147
+
148
+ await client.messages.send_sticker(StickerMessage(
149
+ phone_number_id="PHONE_ID", to="5511999999999",
150
+ sticker=StickerByLink(link="https://example.com/sticker.webp"),
151
+ ))
152
+ ```
153
+
154
+ ### Location
155
+
156
+ ```python
157
+ from whatsapp_cloud_api import LocationMessage
158
+ from whatsapp_cloud_api.resources.messages import LocationPayload
159
+
160
+ await client.messages.send_location(LocationMessage(
161
+ phone_number_id="PHONE_ID",
162
+ to="5511999999999",
163
+ location=LocationPayload(
164
+ latitude=-23.5505,
165
+ longitude=-46.6333,
166
+ name="Sao Paulo",
167
+ address="Av. Paulista, 1000",
168
+ ),
169
+ ))
170
+ ```
171
+
172
+ ### Contacts
173
+
174
+ ```python
175
+ from whatsapp_cloud_api import ContactsMessage
176
+ from whatsapp_cloud_api.resources.messages import Contact, ContactName, ContactPhone
177
+
178
+ await client.messages.send_contacts(ContactsMessage(
179
+ phone_number_id="PHONE_ID",
180
+ to="5511999999999",
181
+ contacts=[Contact(
182
+ name=ContactName(formatted_name="Maria Silva", first_name="Maria"),
183
+ phones=[ContactPhone(phone="+5511988887777", type="MOBILE")],
184
+ )],
185
+ ))
186
+ ```
187
+
188
+ ### Reaction
189
+
190
+ ```python
191
+ from whatsapp_cloud_api import ReactionMessage
192
+ from whatsapp_cloud_api.resources.messages import ReactionPayload
193
+
194
+ await client.messages.send_reaction(ReactionMessage(
195
+ phone_number_id="PHONE_ID",
196
+ to="5511999999999",
197
+ reaction=ReactionPayload(message_id="wamid.xxx", emoji="👍"),
198
+ ))
199
+ ```
200
+
201
+ ### Template
202
+
203
+ ```python
204
+ from whatsapp_cloud_api import TemplateMessage
205
+ from whatsapp_cloud_api.resources.messages import TemplatePayload, TemplateLanguage
206
+
207
+ await client.messages.send_template(TemplateMessage(
208
+ phone_number_id="PHONE_ID",
209
+ to="5511999999999",
210
+ template=TemplatePayload(
211
+ name="hello_world",
212
+ language=TemplateLanguage(code="en_US"),
213
+ ),
214
+ ))
215
+ ```
216
+
217
+ ### Interactive Buttons
218
+
219
+ ```python
220
+ from whatsapp_cloud_api import InteractiveButtonsMessage
221
+ from whatsapp_cloud_api.resources.messages import InteractiveButton
222
+
223
+ await client.messages.send_interactive_buttons(InteractiveButtonsMessage(
224
+ phone_number_id="PHONE_ID",
225
+ to="5511999999999",
226
+ body_text="Choose an option:",
227
+ buttons=[
228
+ InteractiveButton(id="opt_1", title="Option 1"),
229
+ InteractiveButton(id="opt_2", title="Option 2"),
230
+ InteractiveButton(id="opt_3", title="Option 3"),
231
+ ],
232
+ ))
233
+ ```
234
+
235
+ ### Interactive List
236
+
237
+ ```python
238
+ from whatsapp_cloud_api import InteractiveListMessage
239
+ from whatsapp_cloud_api.resources.messages import ListSection, ListRow
240
+
241
+ await client.messages.send_interactive_list(InteractiveListMessage(
242
+ phone_number_id="PHONE_ID",
243
+ to="5511999999999",
244
+ body_text="Pick a product:",
245
+ button_text="View options",
246
+ sections=[ListSection(
247
+ title="Products",
248
+ rows=[
249
+ ListRow(id="p1", title="Product A", description="$10.00"),
250
+ ListRow(id="p2", title="Product B", description="$20.00"),
251
+ ],
252
+ )],
253
+ ))
254
+ ```
255
+
256
+ ### Interactive Flow
257
+
258
+ ```python
259
+ from whatsapp_cloud_api import InteractiveFlowMessage
260
+ from whatsapp_cloud_api.resources.messages import FlowParameters
261
+
262
+ await client.messages.send_interactive_flow(InteractiveFlowMessage(
263
+ phone_number_id="PHONE_ID",
264
+ to="5511999999999",
265
+ body_text="Complete the form:",
266
+ parameters=FlowParameters(
267
+ flow_id="FLOW_ID",
268
+ flow_cta="Open Form",
269
+ flow_action="navigate",
270
+ ),
271
+ ))
272
+ ```
273
+
274
+ ### Interactive CTA URL
275
+
276
+ ```python
277
+ from whatsapp_cloud_api import InteractiveCtaUrlMessage
278
+ from whatsapp_cloud_api.resources.messages import CtaUrlParameters
279
+
280
+ await client.messages.send_interactive_cta_url(InteractiveCtaUrlMessage(
281
+ phone_number_id="PHONE_ID",
282
+ to="5511999999999",
283
+ body_text="Visit our website",
284
+ parameters=CtaUrlParameters(display_text="Open", url="https://example.com"),
285
+ ))
286
+ ```
287
+
288
+ ### Mark as Read
289
+
290
+ ```python
291
+ from whatsapp_cloud_api import MarkReadInput
292
+
293
+ await client.messages.mark_read(MarkReadInput(
294
+ phone_number_id="PHONE_ID",
295
+ message_id="wamid.xxx",
296
+ ))
297
+ ```
298
+
299
+ ## Media
300
+
301
+ ```python
302
+ from whatsapp_cloud_api.resources.media import MediaUploadInput
303
+
304
+ # Upload
305
+ result = await client.media.upload(MediaUploadInput(
306
+ phone_number_id="PHONE_ID",
307
+ type="image",
308
+ file=open("photo.jpg", "rb").read(),
309
+ filename="photo.jpg",
310
+ mime_type="image/jpeg",
311
+ ))
312
+ print(result.id) # media ID to use in messages
313
+
314
+ # Get metadata
315
+ meta = await client.media.get("MEDIA_ID")
316
+ print(meta.url, meta.mime_type)
317
+
318
+ # Download
319
+ data = await client.media.download("MEDIA_ID")
320
+
321
+ # Delete
322
+ await client.media.delete("MEDIA_ID")
323
+ ```
324
+
325
+ ## Templates
326
+
327
+ ```python
328
+ from whatsapp_cloud_api.resources.templates import (
329
+ TemplateListInput, TemplateCreateInput, TemplateDeleteInput,
330
+ )
331
+
332
+ # List
333
+ templates = await client.templates.list(TemplateListInput(
334
+ business_account_id="WABA_ID",
335
+ ))
336
+
337
+ # Create
338
+ result = await client.templates.create(TemplateCreateInput(
339
+ business_account_id="WABA_ID",
340
+ name="order_confirmation",
341
+ language="pt_BR",
342
+ category="UTILITY",
343
+ components=[
344
+ {"type": "BODY", "text": "Pedido {{1}} confirmado!"},
345
+ ],
346
+ ))
347
+
348
+ # Delete
349
+ await client.templates.delete(TemplateDeleteInput(
350
+ business_account_id="WABA_ID",
351
+ name="order_confirmation",
352
+ ))
353
+ ```
354
+
355
+ ## Phone Numbers
356
+
357
+ ```python
358
+ from whatsapp_cloud_api.resources.phone_numbers import (
359
+ RequestCodeInput, VerifyCodeInput, RegisterInput, UpdateBusinessProfileInput,
360
+ )
361
+
362
+ # Request verification code
363
+ await client.phone_numbers.request_code(RequestCodeInput(
364
+ phone_number_id="PHONE_ID", code_method="SMS", language="pt_BR",
365
+ ))
366
+
367
+ # Verify
368
+ await client.phone_numbers.verify_code(VerifyCodeInput(
369
+ phone_number_id="PHONE_ID", code="123456",
370
+ ))
371
+
372
+ # Register
373
+ await client.phone_numbers.register(RegisterInput(
374
+ phone_number_id="PHONE_ID", pin="123456",
375
+ ))
376
+
377
+ # Business profile
378
+ profile = await client.phone_numbers.business_profile.get("PHONE_ID")
379
+
380
+ await client.phone_numbers.business_profile.update(UpdateBusinessProfileInput(
381
+ phone_number_id="PHONE_ID",
382
+ about="We sell things",
383
+ description="Best store in town",
384
+ websites=["https://example.com"],
385
+ ))
386
+ ```
387
+
388
+ ## Webhooks
389
+
390
+ ### Signature Verification
391
+
392
+ ```python
393
+ from whatsapp_cloud_api import verify_signature
394
+
395
+ is_valid = verify_signature(
396
+ app_secret="YOUR_META_APP_SECRET",
397
+ raw_body=request_body_bytes,
398
+ signature_header=request.headers.get("x-hub-signature-256"),
399
+ )
400
+ ```
401
+
402
+ ### Payload Normalization
403
+
404
+ ```python
405
+ from whatsapp_cloud_api import normalize_webhook
406
+
407
+ webhook = normalize_webhook(payload)
408
+
409
+ print(webhook.phone_number_id)
410
+ print(webhook.messages) # list[WebhookMessage]
411
+ print(webhook.statuses) # list[MessageStatusUpdate]
412
+ print(webhook.contacts) # list[dict]
413
+ ```
414
+
415
+ ## Event-Driven Webhooks (pyventus)
416
+
417
+ Install with `uv add "whatsapp-cloud-api-py[events]"`.
418
+
419
+ Instead of manually parsing webhook payloads with `if/elif` chains, use typed event handlers:
420
+
421
+ ```python
422
+ from whatsapp_cloud_api import normalize_webhook, verify_signature
423
+ from whatsapp_cloud_api.events import (
424
+ dispatch_webhook,
425
+ TextReceived,
426
+ ImageReceived,
427
+ ButtonReply,
428
+ ListReply,
429
+ FlowResponse,
430
+ LocationReceived,
431
+ ReactionReceived,
432
+ OrderReceived,
433
+ MessageDelivered,
434
+ MessageRead,
435
+ MessageFailed,
436
+ )
437
+ from pyventus.events import EventLinker, AsyncIOEventEmitter
438
+
439
+
440
+ @EventLinker.on(TextReceived)
441
+ async def handle_text(event: TextReceived):
442
+ print(f"Text from {event.from_number}: {event.body}")
443
+
444
+
445
+ @EventLinker.on(ImageReceived)
446
+ async def handle_image(event: ImageReceived):
447
+ media_bytes = await client.media.download(event.image_id)
448
+ # process image...
449
+
450
+
451
+ @EventLinker.on(ButtonReply)
452
+ async def handle_button(event: ButtonReply):
453
+ print(f"Button pressed: {event.button_id} ({event.button_title})")
454
+
455
+
456
+ @EventLinker.on(MessageFailed)
457
+ async def handle_failure(event: MessageFailed):
458
+ logger.error(f"Message {event.message_id} failed: {event.errors}")
459
+
460
+
461
+ # Dispatch
462
+ webhook = normalize_webhook(raw_payload)
463
+ emitter = AsyncIOEventEmitter()
464
+ dispatch_webhook(webhook, emitter)
465
+ ```
466
+
467
+ ### FastAPI Integration
468
+
469
+ ```python
470
+ from fastapi import FastAPI, Request, Depends, HTTPException
471
+ from pyventus.events import EventLinker, FastAPIEventEmitter
472
+ from whatsapp_cloud_api import WhatsAppClient, normalize_webhook, verify_signature
473
+ from whatsapp_cloud_api.events import dispatch_webhook, TextReceived
474
+
475
+ app = FastAPI()
476
+ client = WhatsAppClient(access_token="YOUR_TOKEN")
477
+ APP_SECRET = "YOUR_META_APP_SECRET"
478
+
479
+
480
+ @EventLinker.on(TextReceived)
481
+ async def echo(event: TextReceived):
482
+ from whatsapp_cloud_api import TextMessage
483
+ await client.messages.send_text(TextMessage(
484
+ phone_number_id=event.phone_number_id,
485
+ to=event.from_number,
486
+ body=f"You said: {event.body}",
487
+ ))
488
+
489
+
490
+ @app.post("/webhook")
491
+ async def webhook(request: Request, emitter=Depends(FastAPIEventEmitter())):
492
+ body = await request.body()
493
+ if not verify_signature(
494
+ app_secret=APP_SECRET,
495
+ raw_body=body,
496
+ signature_header=request.headers.get("x-hub-signature-256"),
497
+ ):
498
+ raise HTTPException(status_code=403)
499
+
500
+ data = normalize_webhook(await request.json())
501
+ dispatch_webhook(data, emitter)
502
+ return {"status": "ok"}
503
+
504
+
505
+ @app.get("/webhook")
506
+ async def verify_webhook(mode: str = "", token: str = "", challenge: str = ""):
507
+ if mode == "subscribe" and token == "YOUR_VERIFY_TOKEN":
508
+ return int(challenge)
509
+ raise HTTPException(status_code=403)
510
+ ```
511
+
512
+ The `FastAPIEventEmitter` runs handlers via Starlette's `BackgroundTasks`, so the endpoint returns immediately while events are processed in the background.
513
+
514
+ ### Available Events
515
+
516
+ | Event | Trigger | Key Fields |
517
+ |---|---|---|
518
+ | `TextReceived` | Text message | `body`, `from_number` |
519
+ | `ImageReceived` | Image message | `image_id`, `mime_type`, `caption` |
520
+ | `VideoReceived` | Video message | `video_id`, `mime_type`, `caption` |
521
+ | `AudioReceived` | Audio/voice note | `audio_id`, `mime_type`, `voice` |
522
+ | `DocumentReceived` | Document | `document_id`, `filename`, `caption` |
523
+ | `StickerReceived` | Sticker | `sticker_id`, `animated` |
524
+ | `LocationReceived` | Location | `latitude`, `longitude`, `name` |
525
+ | `ContactsReceived` | Contact card(s) | `contacts` |
526
+ | `ReactionReceived` | Reaction emoji | `emoji`, `reacted_message_id` |
527
+ | `ButtonReply` | Interactive button | `button_id`, `button_title` |
528
+ | `ListReply` | Interactive list | `list_id`, `list_title` |
529
+ | `FlowResponse` | WhatsApp Flow | `response_json`, `flow_token` |
530
+ | `OrderReceived` | Product order | `catalog_id`, `product_items` |
531
+ | `MessageSent` | Status: sent | `message_id`, `recipient_id` |
532
+ | `MessageDelivered` | Status: delivered | `message_id`, `recipient_id` |
533
+ | `MessageRead` | Status: read | `message_id`, `recipient_id` |
534
+ | `MessageFailed` | Status: failed | `message_id`, `errors` |
535
+ | `UnknownMessageReceived` | Unmapped type | `raw_type`, `raw_data` |
536
+
537
+ All events inherit from `WhatsAppEvent` and include `phone_number_id`. Message events also include `message_id`, `timestamp`, `from_number`, and `context`.
538
+
539
+ ## Error Handling
540
+
541
+ ```python
542
+ from whatsapp_cloud_api import GraphApiError
543
+
544
+ try:
545
+ await client.messages.send_text(msg)
546
+ except GraphApiError as e:
547
+ print(e.category) # "throttling", "authorization", "parameter", ...
548
+ print(e.retry.action) # "retry", "retry_after", "fix_and_retry", "do_not_retry", "refresh_token"
549
+ print(e.retry.retry_after_ms) # milliseconds to wait (for rate limits)
550
+
551
+ if e.is_rate_limit():
552
+ await asyncio.sleep(e.retry.retry_after_ms / 1000)
553
+ # retry...
554
+
555
+ if e.requires_token_refresh():
556
+ # refresh your access token
557
+ pass
558
+ ```
559
+
560
+ ## Client Configuration
561
+
562
+ ```python
563
+ from whatsapp_cloud_api import WhatsAppClient
564
+
565
+ # Default: graph.facebook.com, v23.0, HTTP/2, 30s timeout
566
+ client = WhatsAppClient(access_token="TOKEN")
567
+
568
+ # Custom configuration
569
+ client = WhatsAppClient(
570
+ access_token="TOKEN",
571
+ base_url="https://graph.facebook.com",
572
+ graph_version="v23.0",
573
+ timeout=60.0,
574
+ )
575
+
576
+ # Bring your own httpx client
577
+ import httpx
578
+ custom_http = httpx.AsyncClient(http2=True, timeout=60.0)
579
+ client = WhatsAppClient(access_token="TOKEN", http_client=custom_http)
580
+
581
+ # Always use as async context manager
582
+ async with WhatsAppClient(access_token="TOKEN") as client:
583
+ await client.messages.send_text(...)
584
+ ```
585
+
586
+ ## Project Structure
587
+
588
+ ```
589
+ src/whatsapp_cloud_api/
590
+ __init__.py # Public API
591
+ client.py # Async HTTP client (httpx, HTTP/2)
592
+ types.py # Pydantic response models
593
+ errors/
594
+ graph_api_error.py # GraphApiError + from_response()
595
+ categorize.py # Error code -> category mapping
596
+ retry.py # RetryHint (action + delay)
597
+ resources/
598
+ messages/
599
+ models.py # Pydantic models for all message types
600
+ resource.py # MessagesResource (20+ send methods)
601
+ templates/
602
+ models.py # Template CRUD input models
603
+ resource.py # TemplatesResource
604
+ media.py # Upload, download, get, delete
605
+ phone_numbers.py # Registration, verification, profile
606
+ flows.py # Flow management + deploy
607
+ webhooks/
608
+ verify.py # HMAC-SHA256 signature verification
609
+ normalize.py # Webhook payload normalization
610
+ events/
611
+ events.py # Dataclass events (18 types)
612
+ dispatcher.py # NormalizedWebhook -> pyventus events
613
+ utils/
614
+ case.py # snake_case <-> camelCase (cached)
615
+ ```
616
+
617
+ ## Acknowledgments
618
+
619
+ This project was inspired by [`@kapso/whatsapp-cloud-api`](https://github.com/gokapso/whatsapp-cloud-api-js), a TypeScript client for the same API. While the two projects cover similar ground, this Python SDK was written independently with its own architecture and design decisions.
620
+
621
+ ## License
622
+
623
+ MIT
@@ -23,7 +23,7 @@ whatsapp_cloud_api/utils/case.py,sha256=Vu2LH15ZuCKqfXZdRn1fPk-ctmQgSaqC3_wz4kwD
23
23
  whatsapp_cloud_api/webhooks/__init__.py,sha256=wKf4IE-exZXQ_xUwhkf4J54e7-EGOkvoASmp2Uv5sM0,131
24
24
  whatsapp_cloud_api/webhooks/normalize.py,sha256=8tTAmGRvzZ15AmuWXBphOlmoomM86e3SfVpKJKqATmY,2633
25
25
  whatsapp_cloud_api/webhooks/verify.py,sha256=DAA2vHtm2vB9AthDZrER6b3jZTU31CaDVUnnvRvOKg0,1033
26
- whatsapp_cloud_api_py-0.1.0.dist-info/METADATA,sha256=Jrz3AAPyMpaupejrrRG_VNhf1MsTLuwV0tXKIjvVcjk,962
27
- whatsapp_cloud_api_py-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
- whatsapp_cloud_api_py-0.1.0.dist-info/licenses/LICENSE,sha256=x8Z_8RMIgoi1dz7Wl4v_0OdmLI-EHdc3kJtE4PbaFT4,1062
29
- whatsapp_cloud_api_py-0.1.0.dist-info/RECORD,,
26
+ whatsapp_cloud_api_py-0.1.1.dist-info/METADATA,sha256=O6IM8STdUUtF5NHSXzYpj36A5hczOrs6sCXNqd15DbA,18857
27
+ whatsapp_cloud_api_py-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
+ whatsapp_cloud_api_py-0.1.1.dist-info/licenses/LICENSE,sha256=x8Z_8RMIgoi1dz7Wl4v_0OdmLI-EHdc3kJtE4PbaFT4,1062
29
+ whatsapp_cloud_api_py-0.1.1.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: whatsapp-cloud-api-py
3
- Version: 0.1.0
4
- Summary: Async Python SDK for WhatsApp Business Cloud API with Pydantic V2
5
- Project-URL: Homepage, https://github.com/HeiCg/whatsapp-cloud-api-py
6
- Project-URL: Repository, https://github.com/HeiCg/whatsapp-cloud-api-py
7
- Project-URL: Issues, https://github.com/HeiCg/whatsapp-cloud-api-py/issues
8
- License-Expression: MIT
9
- License-File: LICENSE
10
- Requires-Python: >=3.11
11
- Requires-Dist: httpx[http2]>=0.27
12
- Requires-Dist: pydantic>=2.7
13
- Provides-Extra: dev
14
- Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
15
- Requires-Dist: pytest>=8.0; extra == 'dev'
16
- Requires-Dist: pyventus>=0.7.2; extra == 'dev'
17
- Requires-Dist: respx>=0.22; extra == 'dev'
18
- Requires-Dist: ruff>=0.8; extra == 'dev'
19
- Provides-Extra: events
20
- Requires-Dist: pyventus>=0.7.2; extra == 'events'
21
- Provides-Extra: server
22
- Requires-Dist: cryptography>=43.0; extra == 'server'
23
- Provides-Extra: webhooks
24
- Requires-Dist: starlette>=0.37; extra == 'webhooks'