[templates] parse media headers returned without an example - #231
Conversation
|
Hi, thanks for the report. |
`_BaseMediaHeaderComponent.from_dict` reads `data["example"]["header_handle"][0]`
unconditionally, but WhatsApp does not always return the sample media when a
template is read back. The sample templates Meta provisions on a new WABA come
back as bare `{"type": "HEADER", "format": "IMAGE"}`, so every one of them raises
`KeyError: 'example'`, which `_parse_component` logs at ERROR with a traceback
before falling back to the raw dict.
Treat that exact shape — no `example` key at all — as what it is, absent on read,
so the component is still parsed into its type. `example` may therefore be `None`,
which the annotations and docstrings now say. An `example` that is present but not
the documented shape still raises, so a real API change is reported rather than
silently parsed into a component with no media.
A header with no example has nothing to upload, so it is skipped when uploading
a template's media instead of being handed to `detect_media_source(None)`;
submitting such a template still fails in `to_dict` with the existing
"media example not uploaded yet" error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f725564 to
7c5ef56
Compare
|
Thanks for looking! The reason you saw no error is the key to the bug: a template you created yourself will never trigger it. When you upload the sample media, WhatsApp echoes I verified that asymmetry against a real WABA — uploaded a sample image via the Resumable Upload API, created a template with an IMAGE header, then read it back: {"type": "HEADER", "format": "IMAGE",
"example": {"header_handle": ["https://scontent.whatsapp.net/v/t61.29466-34/663658189_...png?..."]}}Same while It breaks on templates whose sample media the business never sent — most commonly the sample templates Meta provisions on a new WABA (the "Jasper's Market" set). Those come back bare: {"type": "HEADER", "format": "IMAGE"}Repro without any API callThe payload above is all it takes, so this needs no WABA: from pywa.types.templates import _parse_component
_parse_component({"type": "HEADER", "format": "IMAGE"})
# ERROR pywa.types.templates Failed to parse component: {'type': 'HEADER', 'format': 'IMAGE'}.
# Defaulting to raw dictionary representation. Please update pywa or report this issue.
# KeyError: 'example'Reproduces on 3.8.0, 3.9.0 and 4.4.0. Repro through the client
{"type": "CAROUSEL", "cards": [
{"components": [{"type": "HEADER", "format": "IMAGE"},
{"type": "BODY", "text": "Simple and Healthy Sheet Pan Dinner to Feed the Whole Family"},
{"type": "BUTTONS", "buttons": [{"type": "URL", "text": "Get this recipe", "url": "https://developers.facebook.com/docs/..."}]}]},
{"components": [{"type": "HEADER", "format": "IMAGE"},
{"type": "BODY", "text": "3 Plant-Powered Salad Bowls to Fuel Your Week"},
{"type": "BUTTONS", "buttons": [{"type": "URL", "text": "Get this recipe", "url": "https://developers.facebook.com/docs/..."}]}]}]}If a fresh test WABA of yours has Update just pushedI narrowed the fix to address review feedback on our side: only a completely absent Happy to reshape any of this if you'd prefer a different approach — e.g. logging at |
Can you explain how can I reduce the bug? Btw, can you edit the code Line 1555 in 0bcf2f1 print(data) before the parsing and share the response here?I want to see how the response looks like.Thank you! |
The problem
_BaseMediaHeaderComponent.from_dictreadsdata["example"]["header_handle"][0]unconditionally:But WhatsApp does not always return the sample media when a template is read back. The sample templates Meta provisions on a new WABA come back with a bare header:
{"type": "HEADER", "format": "IMAGE"}So
_parse_componenthitsKeyError: 'example', logs it at ERROR with a full traceback, and falls back to the raw dict:Reproduces on 3.8.0, 3.9.0 and 4.4.0.
Where I hit it
A WhatsApp Business Account still carrying Meta's "Jasper's Market" onboarding samples. Listing its templates parses three such headers —
jaspers_market_image_cta_v1plus two media-carousel cards — so eachget_templates()call emitted three ERROR lines with tracebacks. Nothing downstream broke (the dict fallback is fine), but the log noise tripped our production container-error alert.Verified against a real WABA that this is specific to templates whose sample media WhatsApp did not receive from the business: a template I created myself with an uploaded
header_handlegetsexample.header_handleechoed back as ascontent.whatsapp.netURL, both whilePENDINGand onceAPPROVED. So approval is not what strips it.The change
from_dicttreats a missing header handle as what it is — absent on read — and still returns the typed component.examplemay therefore beNone, which the annotations and docstrings now say.detect_media_source(None). Submitting such a template still fails into_dictwith the existing"... media example not uploaded yet."error.A component carrying a malformed example still goes down the normal path, so a genuine parse regression is still reported.
Tests
Three cases added to
tests/test_templates.py: a bare header for each of IMAGE/VIDEO/DOCUMENT parses with no log output, a header with an example still carries its handle, and an example-less header cannot be submitted.uv run pytest— 554 passed.uv run ruff check .anduv run ty checkclean.