π Migration#
Migration from 1.x to 2.x#
New features#
Listeners: Listeners are a new way to handle incoming user updates (messages, callbacks, etc.). They are more flexible, faster, and easier to use than handlers.
Filters: Filters are now objects that can be combined using logical operators. They are more powerful and flexible than the previous filter system.
Handlers: Now you can register handlers with decorators without the need to use the
add_handlermethod.FlowCompletion: A new method
.get_media(types.Image, key="img")allows you to construct a media object and perform actions like.download()on it.FlowRequest: Decrypt media directly from FlowRequest using
.decrypt_media(key, index).Client: The client can run without a token but wonβt allow API operations (only webhook listening).
SentMessage: The
SentMessageobject returned bysend_message,send_image, etc., contains the message ID and allows to act on the sent message with methods likereply_x,wait_for_xetc.Flows: Create conditionals for
Ifby using pythonβs operators like==,!=,>,<,>=,<=etc.
Breaking changes#
Async Separation: In the sync version of pywa, no async callbacks or filters are allowed.
Returning
SentMessageObject: Functions likesend_message,send_image, etc., no longer return a string (message ID). Instead, they return aSentMessageobject, which contains the ID and allows further actions.Filter System Redesign: Filters are now objects rather than simple callables. You can combine filters using logical operators like
&,|, and~.Reordered Init Parameters: The order of the parameters in the
WhatsAppclass has been changed and some parameters have been removed.Handler Factory Changes: Factories in handlers are now limited to
CallbackDatasubclasses (not any callable). Only one class is allowed, not multiple.Removal of Deprecated Arguments: Deprecated arguments like
keyboard(usebuttonsinstead) andbody(usecaption) have been removed fromsend_message,send_image,send_video, andsend_document.Server: The function signature
webhook_update_handlerthat used to pass updates manually to the server has been changed.Deprecated Argument Removal: Deprecated arguments such as
keyboardandbodyhave been removed.Client: The
continue_handlingparam is now set toFalseby default. so if update is handled, it will not be passed to the next handler unless you set it toTrueor callupdate.continue_handling()in the handler.Flows: The
.data_keyand.from_refof the flowjson components renamed to.ref.
Migration steps#
If you are using the sync version of pywa, and you have async callbacks or filters, you need to remove them or switch to the async version:
# Old code
from pywa import WhatsApp, types
wa = WhatsApp(...)
@wa.on_message
async def on_message(_: WhatsApp, msg: types.Message):
msg.reply("Hello, World!")
# New code
from pywa_async import WhatsApp, types
wa = WhatsApp(...)
@wa.on_message
async def on_message(_: WhatsApp, msg: types.Message):
await msg.reply("Hello, World!")
If you use the message ID returned by functions like
send_message,send_image, etc (e.g to store it in a database), you need to update your code to use the.idattribute of theSentMessageobject:
# Old code
message_id = wa.send_message("Hello, World!")
db.store_message_id(message_id)
# New code
sent_message = wa.send_message("Hello, World!")
db.store_message_id(sent_message.id)
If you are using filters, you need to update your code to use the new filter system:
# Old code
from pywa import WhatsApp, filters, types
wa = WhatsApp(...)
@wa.on_message(filters.text, lambda _, m: m.text.isdigit())
def on_message(_: WhatsApp, msg: types.Message):
msg.reply("Hello, World!")
@wa.on_message(filters.any_(filters.text.is_command, filters.text.command("start")))
def on_command(_, __): ...
# New code
from pywa import WhatsApp, filters, types
wa = WhatsApp(...)
@wa.on_message(filters.text & filters.new(lambda _, m: m.text.isdigit()))
def on_message(_: WhatsApp, msg: types.Message):
msg.reply("Hello, World!")
@wa.on_message(filters.is_command | filters.command("start"))
def on_command(_, __): ...
If you are using lots of handlers, you may want to switch to listeners:
# Old code
from pywa import WhatsApp, types, filters
@wa.on_message(filters.command("start"))
def on_start(_: WhatsApp, m: types.Message):
m.reply("How old are you?")
@wa.on_message(filters.text & filters.new(lambda _, m: m.text.isdigit()))
def on_age(_: WhatsApp, m: types.Message):
m.reply(f"You are {m.text} years old")
m.reply("What is your name?")
@wa.on_message(filters.text & filters.new(lambda _, m: m.text.isalpha()))
def on_name(_: WhatsApp, m: types.Message):
m.reply(f"Hello {m.text}")
# New code
from pywa import WhatsApp, types, filters
wa = WhatsApp(...)
@wa.on_message(filters.command("start"))
def on_start(_: WhatsApp, m: types.Message):
age = m.reply("How old are you?").wait_for_reply(
filters=filters.text & filters.new(lambda _, m: m.text.isdigit()),
)
m.reply(f"You are {age.text} years old")
name = m.reply("What is your name?").wait_for_reply(
filters=filters.text & filters.new(lambda _, m: m.text.isalpha()),
)
m.reply(f"Hello {name.text}")
If You are writing the handlers in separate modules and then using
add_handlerto register the callback wrapped with handler objects, you can now use decorators to register handlers:
# Old code
# module1.py
from pywa import WhatsApp, types
def on_start(_: WhatsApp, m: types.Message):
m.reply("How old are you?")
def on_age(_: WhatsApp, m: types.Message):
m.reply(f"You are {m.text} years old")
m.reply("What is your name?")
# module2.py
from pywa import WhatsApp, handlers, filters
wa = WhatsApp(...)
wa.add_handlers(handlers.MessageHandler(on_start, filters.command("start")))
wa.add_handlers(handlers.MessageHandler(on_age, filters.text & filters.new(lambda _, m: m.text.isdigit())))
# New code
# module1.py
from pywa import WhatsApp, types, filters
@WhatsApp.on_message(filters.command("start")) # we using the class here, not the instance!
def on_start(_: WhatsApp, m: types.Message):
m.reply("How old are you?")
@WhatsApp.on_message(filters.text & filters.new(lambda _, m: m.text.isdigit()))
def on_age(_: WhatsApp, m: types.Message):
m.reply(f"You are {m.text} years old")
m.reply("What is your name?")
# module2.py
from pywa import WhatsApp
from . import module1
wa = WhatsApp(..., handlers_modules=[module1])
If you want to keep the continuation of the update to the next handler, you need to set the
continue_handlingattribute of the update object toTrue:
from pywa import WhatsApp
wa = WhatsApp(..., continue_handling=True)
If you are using the
webhook_update_handlerfunction to pass updates manually to the server, you need to update the function signature:
# Old code
from pywa import WhatsApp, utils
wa = WhatsApp(..., server=None)
def some_web_framework_handler(req):
res, status = wa.webhook_update_handler(
update=req.json(),
raw_body=req.read(),
hmac_header=req.headers.get(utils.HUB_SIG)
)
return res, status
# New code
from pywa import WhatsApp, utils
wa = WhatsApp(..., server=None)
def some_web_framework_handler(req):
res, status = wa.webhook_update_handler(
update=req.read(),
hmac_header=req.headers.get(utils.HUB_SIG),
)
return res, status
If you are using the
.data_keyand.from_refof the flowjson components, you need to update your code to use the.refattribute:
# Old code
from pywa.types.flows import *
FlowJSON(
screens=[
Screen(
data=[
name := ScreenData(key="name", example="David")
],
layout=Layout(
children=[
date := DatePicker(
on_select_action=Action(
payload={"date": FormRef("date"), "name": DataKey("name")},
),
),
Footer(
...,
on_click_action=Action(
payload={
"date": date.form_ref,
"name": name.data_key
},
),
),
],
),
)
],
)
# New code
FlowJSON(
screens=[
Screen(
data=[
name := ScreenData(key="name", example="David")
],
layout=Layout(
children=[
date := DatePicker(
on_select_action=Action(
payload={"date": ComponentRef("date"), "name": ScreenDataRef("name")},
),
),
Footer(
...,
on_click_action=Action(
payload={
"date": date.ref,
"name": name.ref
},
),
),
],
),
)
],
)