Skip to content

3.0.0

Choose a tag to compare

@david-lev david-lev released this 22 Aug 13:57
· 434 commits to master since this release
d099323

PyWa v3.0.0 is here!

Update with pip: pip3 install -U pywa

This is one of the biggest updates to PyWa ever, bringing calls, user preferences, a fully redesigned template system, new update types, and more.

Important

Migration to v3 introduces breaking changes. Please review carefully before upgrading.

✨ Highlights

  • Templates: completely redesigned β€” more flexible, reusable, and powerful.
  • Calls: full support for making, receiving, and managing calls.
  • User Preferences: listen for marketing opt-in/out events.
  • System Updates: PhoneNumberChange and IdentityChange now first-class updates.
  • Listeners: can now handle all update types, not just user messages.
  • Client Improvements:
    • New methods: delete_media, update_display_name, get_business_account, and more.
    • upload_media now returns a Media object.
    • send_* methods enforce keyword-only context arguments.
  • Types: SuccessResult replaces bool for extensibility.
  • Security: XSS vulnerability fixed.
  • Docs: new PyWa logo! πŸš€ (by @nyatitilkesh)

πŸ”₯ Breaking changes

  • Old template system removed β†’ use the new API.
  • system messages removed from Message β†’ use PhoneNumberChange / IdentityChange.
  • upload_media returns Media instead of a string.
  • send_* methods require keyword-only context args.
  • Methods like mark_as_read, indicate_typing now return SuccessResult.
  • FlowRequestDecryptedMedia replaces tuple return.

See full details and migration steps in the migration guide.

❀️ Thanks

  • A huge thanks to my wife for always providing endless support and patience.
  • Special thanks to my brother @yehuda-lev for his help, code reviews, testing, and for supporting users in the community groups πŸ™Œ.

πŸ“š Resources

πŸ‘ New Contributors

πŸ’‘ Examples

  • Create and send template messages

See Templates for more details and examples.

from pywa import WhatsApp, types
from pywa.types.templates import *

wa = WhatsApp(..., business_account_id=123456)

# Create a template
wa.create_template(
    template=Template(
        name="buy_new_iphone_x",
        category=TemplateCategory.MARKETING,
        language=TemplateLanguage.ENGLISH_US,
        parameter_format=ParamFormat.NAMED,
        components=[
            ht := HeaderText("The New iPhone {{iphone_num}} is here!", iphone_num=15),
            bt := BodyText("Buy now and use the code {{code}} to get {{per}}% off!", code="WA_IPHONE_15", per=15),
            FooterText(text="Powered by PyWa"),
            Buttons(
                buttons=[
                    url := URLButton(text="Buy Now", url="https://example.com/shop/{{1}}", example="iphone15"),
                    PhoneNumberButton(text="Call Us", phone_number="1234567890"),
                    qrb1 := QuickReplyButton(text="Unsubscribe from marketing messages"),
                    qrb2 := QuickReplyButton(text="Unsubscribe from all messages"),
                ]
            ),

        ]
    ),
)

# Send the template message
wa.send_template(
    to="9876543210",
    name="buy_new_iphone_x",
    language=TemplateLanguage.ENGLISH_US,
    params=[
        ht.params(iphone_num=30),
        bt.params(code="WA_IPHONE_30", per=30),
        url.params(url_variable="iphone30", index=0),
        qrb1.params(callback_data="unsubscribe_from_marketing_messages", index=1),
        qrb2.params(callback_data="unsubscribe_from_all_messages", index=2),
    ]
)

# Manage templates
templates = wa.get_templates(statuses=[TemplateStatus.APPROVED])
print(templates.total_count, templates.message_template_limit)
for template in templates:
    print(template)
    template.update(...)
    template.delete()
    template.duplicate(...)
    template.compare(...)
    template.send(to=...)

# Handle changes
@wa.on_template_status_update
def on_template_status_update(_: WhatsApp, status: types.TemplateStatusUpdate):
    print("Template status update:", status.template_name, status.template_language, status.new_status)
    
@wa.on_template_category_update
def on_template_category_update(_: WhatsApp, category: types.TemplateCategoryUpdate):
    print("Template category update:", category.template_name, category.previous_category, category.new_category)
    
@wa.on_template_quality_update
def on_template_quality_update(_: WhatsApp, quality: types.TemplateQualityUpdate):
    print("Template quality update:", quality.template_name, quality.previous_quality_score, quality.new_quality_score)
    
@wa.on_template_components_update
def on_template_components_update(_: WhatsApp, components: types.TemplateComponentsUpdate):
    print("Template components update:", components.template_name, components.template_title, components.template_element)

# Inline listeners
wa.create_template(template=Template(...)).wait_until_approved()
  • Listen to phone number change and identity change
from pywa import WhatsApp, types

wa = WhatsApp(...)

@wa.on_phone_number_change
def on_phone_number_change(_: WhatsApp, update: types.PhoneNumberChange):
    repository.update_phone_number(old=update.old_wa_id, new=update.new_wa_id) # update user wa_id in your db

@wa.on_identity_change
def on_identity_change(_: WhatsApp, update: types.IdentityChange):
    repository.log_out_user(wa_id=update.sender)  # secure the user account
  • Calling
from pywa import WhatsApp, types, filters

@wa.on_call_connect(filters.incoming_call)
def on_incoming_call(_: WhatsApp, call: types.CallConnect):
    print(f"Incoming call from {call.from_user.name}: {call.session}")
    call.pre_accept(...)
    call.accept(...)
    call.terminate(...)
    call.reject(...)

@wa.on_call_terminate
def on_call_terminate(_: WhatsApp, call: types.CallTerminate):
    print("Call terminated:", call.duration, call.start_time, call.end_time, call.status)
    
@wa.on_call_status
def on_call_status(_: WhatsApp, call: types.CallStatus):
    print("Call status update:", call.status)

@wa.on_call_permission_update
def on_call_permission_update(_: WhatsApp, perm: types.CallPermissionUpdate):
    print("Call permission update:", perm.response, perm.response_source, perm.expiration_timestamp)
    if not perm.is_expired:
        perm.call(...)
  • User marketing preferences
from pywa import WhatsApp, types

@wa.on_user_marketing_preferences
def on_user_marketing_preferences(_: WhatsApp, prefs: types.UserMarketingPreferences):
    print(prefs.detail)
    if prefs:
        repository.opt_in(prefs.sender)
    else:
        repository.opt_out(prefs.sender)

Full Changelog: 2.11.0...3.0.0