Releases: david-lev/pywa
2.0.2
What's Changed
Update with pip:
pip3 install -U pywa
Important
Breaking Changes! Please see the Migration Guide for instructions on updating from earlier versions.
- [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.
- [sent_message]: The
SentMessage
object returned bysend_message
,send_image
, etc., contains the message ID and allows to act on the sent message with methods likereply_x
,wait_for_x
etc. - [filters]: Filters are now objects that can be combined using logical operators. They are more powerful and flexible than the previous filter system.
- [handlers] allow to register callbacks without WhatsApp instance
- [flows] allow pythonic conditionals in
If
component - [flows]: A new method
FlowCompletion.get_media(types.Image, key="img")
allows you to construct a media object and perform actions like.download()
on it. - [flows]: Decrypt media directly from FlowRequest using
.decrypt_media(key, index)
. - [flows] rename
.data_key
and.from_ref
to.ref
- [client]: The client can run without a token but won’t allow API operations (only webhook listening).
- [server] rely on update hash instead of update id to avid duplicate updates
- [callback] allow to override callback data id
- [utils] bump graph-api version to
21.0
# my_handlers.py
from pywa import WhatsApp, filters, types, listeners
# You can register callback without th instance!
@WhatsApp.on_message(filters.text & filters.matches("store")) # combine filters with & operator
def store(_: WhatsApp, m: types.Message):
try:
age = m.reply("How old are you?", buttons=[types.Button("Cancel", "cancel")]).wait_for_reply( # wait for a reply
filters=filters.text & filters.new(lambda _, m: m.text.isdigit()), # create a new filter
cancelers=filters.callback_button & filters.matches("cancel"), # cancel the listener if the user clicks the cancel button
timeout=10, # set a timeout
)
m.reply(f"Your age is {age.text}")
except listeners.ListenerCanceled:
m.reply("You cancelled the store")
except listeners.ListenerTimeout:
m.reply("You took too long to reply")
# main.py
from pywa import WhatsApp
from . import my_handlers
wa = WhatsApp(..., handlers_modules=[my_handlers])
# run the server
from pywa.types.flows import *
FlowJSON(
version=pywa.Version.FLOW_JSON,
screens=[
Screen(
id="WELCOME",
title="Welcome",
data=[is_admin := ScreenData(key="is_admin", example=True)],
layout=Layout(
children=[
animal := TextInput(
name="animal",
label="Animal",
helper_text="Type: cat",
),
If(
condition=is_admin.ref & (animal.ref == "cat"), # we can use python operators
then=[TextHeading(text="It is a cat")],
else_=[TextHeading(text="It is not a cat")],
),
]
),
)
],
)
Full Changelog: 1.26.0...2.0.2
2.0.0-rc.2
What's Changed
Update with pip:
pip3 install -U pywa==2.0.0-rc.2
Caution
Make sure to read the migration guide before updating to this version!
Important
Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration
Note
Updated documentation to the new version is available here
[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_handlers
method.
[flows] allow pythonic conditionals in If
component.
[flows]: A new method FlowCompletion.get_media(types.Image, key="img")
allows you to construct a media object and perform actions like .download()
on it.
[flows]: 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).
[sent_message]: The SentMessage
object returned by send_message
, send_image
, etc., contains the message ID and allows to act on the sent message with methods like reply_x
, wait_for_x
etc.
[callback] allow to override callback data id
[docs] docs updates
# my_handlers.py
from pywa import WhatsApp, filters, types, listeners
# You can register callback without th instance!
@WhatsApp.on_message(filters.text & filters.matches("store")) # combine filters with & operator
def store(_: WhatsApp, m: types.Message):
try:
age = m.reply("How old are you?", buttons=[types.Button("Cancel", "cancel")]).wait_for_reply( # wait for a reply
filters=filters.text & filters.new(lambda _, m: m.text.isdigit()), # create a new filter
cancelers=filters.callback_button & filters.matches("cancel"), # cancel the listener if the user clicks the cancel button
timeout=10, # set a timeout
)
m.reply(f"Your age is {age.text}")
except listeners.ListenerCanceled:
m.reply("You cancelled the store")
except listeners.ListenerTimeout:
m.reply("You took too long to reply")
# main.py
from pywa import WhatsApp
from . import my_handlers
wa = WhatsApp(..., handlers_modules=[my_handlers])
# run the server
from pywa.types.flows import *
FlowJSON(
version=pywa.Version.FLOW_JSON,
screens=[
Screen(
id="WELCOME",
title="Welcome",
data=[is_admin := ScreenData(key="is_admin", example=True)],
layout=Layout(
children=[
animal := TextInput(
name="animal",
label="Animal",
helper_text="Type: cat",
),
If(
condition=is_admin.ref & (animal.ref == "cat"), # we can use python operators
then=[TextHeading(text="It is a cat")],
else_=[TextHeading(text="It is not a cat")],
),
]
),
)
],
)
Full Changelog: 1.26.0...2.0.0-rc.2
2.0.0-rc.1
What's Changed
Update with pip:
pip3 install -U pywa==2.0.0-rc.1
Caution
Make sure to read the migration guide before updating to this version!
Important
Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration
Note
Updated documentation to the new version is available here
[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_handlers
method.
[flows]: A new method FlowCompletion.get_media(types.Image, key="img")
allows you to construct a media object and perform actions like .download()
on it.
[flows]: 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).
[sent_message]: The SentMessage
object returned by send_message
, send_image
, etc., contains the message ID and allows to act on the sent message with methods like reply_x
, wait_for_x
etc.
[callback] allow to override callback data id
[docs] docs updates
# my_handlers.py
from pywa import WhatsApp, filters, types, listeners
# You can register callback without th instance!
@WhatsApp.on_message(filters.text & filters.matches("store")) # combine filters with & operator
def store(_: WhatsApp, m: types.Message):
try:
age = m.reply("How old are you?", buttons=[types.Button("Cancel", "cancel")]).wait_for_reply( # wait for a reply
filters=filters.text & filters.new(lambda _, m: m.text.isdigit()), # create a new filter
cancelers=filters.callback_button & filters.matches("cancel"), # cancel the listener if the user clicks the cancel button
timeout=10, # set a timeout
)
m.reply(f"Your age is {age.text}")
except listeners.ListenerCanceled:
m.reply("You cancelled the store")
except listeners.ListenerTimeout:
m.reply("You took too long to reply")
# main.py
from pywa import WhatsApp
from . import my_handlers
wa = WhatsApp(..., handlers_modules=[my_handlers])
# run the server
Full Changelog: 1.26.0...2.0.0-rc.1
2.0.0-beta.1
What's Changed
Update with pip:
pip3 install -U pywa==2.0.0-beta.1
Caution
- Make sure to read the migration guide before updating to this version!
- This is a beta version, use it with caution!
Important
- Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration
[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_handler
method.
[flows]: A new method FlowCompletion.get_media(types.Image, key="img")
allows you to construct a media object and perform actions like .download()
on it.
[flows]: 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).
[sent_message]: The SentMessage
object returned by send_message
, send_image
, etc., contains the message ID and allows to act on the sent message with methods like reply_x
, wait_for_x
etc.
# my_handlers.py
from pywa import WhatsApp, filters, types, listeners
# You can register callback without th instance!
@WhatsApp.on_message(filters.text & filters.matches("store")) # combine filters with & operator
def store(_: WhatsApp, m: types.Message):
try:
age = m.reply("How old are you?", buttons=[types.Button("Cancel", "cancel")]).wait_for_reply( # wait for a reply
filters=filters.text & filters.new(lambda _, m: m.text.isdigit()), # create a new filter
cancelers=filters.callback_button & filters.matches("cancel"), # cancel the listener if the user clicks the cancel button
timeout=10, # set a timeout
)
m.reply(f"Your age is {age.text}")
except listeners.ListenerCanceled:
m.reply("You cancelled the store")
except listeners.ListenerTimeout:
m.reply("You took too long to reply")
# main.py
from pywa import WhatsApp
from . import my_handlers
wa = WhatsApp(..., handlers_modules=[my_handlers])
# run the server
Full Changelog: 1.26.0...2.0.0-beta.1
1.26.0
What's Changed
Update with pip:
pip3 install -U pywa
- [flows] adding support of
RichText
- [flows] adding support of
markdown
inTextBody
andTextCaption
- [flows] adding
sensitive
attr to Screen, allowing to hide specific fields from the response summary - [client] adding
reply_to_message
arg tosend_location
,request_location
,send_sticker
, andsend_audio
- [message] adding
reply_location_request
flow = FlowJSON(
screens=[
screen_1 := Screen(
layout=Layout(
children=[
RichText(
text=[
"# Heading 1",
"## Heading 2",
"Let’s make a **bold** statement",
"Let's make this text *italic*",
"Let's make this text ~~Strikethrough~~",
"This text is ~~***really important***~~",
"This is a [pywa docs](https://pywa.readthedocs.io/)",
"This is a ordered list:",
"1. Item 1",
"2. Item 2",
"This is a unordered list:",
"- Item 1",
"- Item 2",
"![image](data:image/png;base64,<base64 content>)",
"| Tables | Are | Cool |",
"| ------------- | ------------- | ----- |",
"| col 3 is | right-aligned | $1600 |",
"| col 2 is | centered | $12 |",
],
),
]
),
)
]
Full Changelog: 1.25.0...1.26.0
1.25.0
What's Changed
Update with pip:
pip3 install -U pywa
- [handlers] adding priority
- [client] adding QR methods (create update get and delete)
- [client] adding
get_flow_metrics
method - [flows] adding to
DataSource
support for images/colors - [flows] support
datetime
objs inDatePicker
- [flows] support
Screen
when using.data_key_of(...)
and.form_ref_of(...)
- [flows] update flow json with indent and without ensuring ascii
- [flows] adding
health_status
toFlowDetails
from pywa import WhatsApp, types
wa = WhatsApp(...)
@wa.on_message(filters.text, priority=2)
def one(_: WhatsApp, m: types.Message):
...
@wa.on_message(filters.text, priority=3) # will be called before `one`
def two(_: WhatsApp, m: types.Message):
...
wa.create_qr_code(prefilled_message="Hi, I am interested in your product", image_type="SVG")
wa.get_flow_metrics(
flow_id=...,
metric_name=types.FlowMetricName.ENDPOINT_REQUEST_COUNT,
granularity=types.FlowMetricGranularity.DAY,
since=datetime.date(2024, 8, 1),
)
flow = FlowJSON(
screens=[
screen_1 := Screen(
data=[name := ScreenData(key="name", example="David")], # data key in screen_1
layout=...
),
screen_2 := Screen(
layout=Layout(
children=[
TextHeading(text=name.data_key_of(screen_1)), # reference to data key in screen_1
date := DatePicker(
name="date",
label="When?",
min_date=datetime.date(2024, 8, 1), # date obj in DatePicker
max_date=datetime.date(2024, 8, 30),
)
]
),
)
]
)
Full Changelog: 1.24.0...1.25.0
1.24.0
What's Changed
Update with pip:
pip3 install -U pywa
- [server] validating
X-Hub-Signature-256
header - [requirements] removing
requests
- [server] default callback url registration delay to 3 sec
wa = WhatsApp(
validate_updates=True, # Default: True
app_secret="122hifewyhf9we372d93676",
...
)
Full Changelog: 1.23.0...1.24.0
1.23.0
What's Changed
Update with pip:
pip3 install -U pywa
- [client] allowing to manage multiple numbers from the same client (Partner solutions)
- [flows] adding
.respond()
shortcut forFlowRequest
- [flows] allowing body in
FlowResponseError
subclasses
from pywa import WhatsApp, types, filters
wa = WhatsApp(token="xyzxyz", ...) # `phone_id` is now optional
wa.send_message(
sender=..., # now you can specify sender
to=...,
text="Hello from PyWa!"
)
@wa.on_message(filters.text.command("start"))
def on_start(_: WhatsApp, msg: types.Message):
if msg.recipient == ...: # or check the recipient
...
elif msg.recipient == ...:
...
msg.reply(...) # .reply() is automatically replaying to the sender
from pywa import WhatsApp, types
wa = WhatsApp(...)
@wa.on_flow_request("/my-flow")
def my_flow_handler(_: WhatsApp, req: types.FlowRequest) -> types.FlowResponse:
return req.respond( # instead of instantiating FlowResponse with req.version
screen="SIGN_UP",
data={}
)
Full Changelog: 1.22.0...1.23.0
1.22.0
What's Changed
Update with pip:
pip3 install -U pywa
- [handlers] introducing new way to help split flow endpoint logic to multiple handlers (see example)
- [client] adding
add_flow_request_handler
method to registerFlowRequestHandler
s - [flows] pop
flow_token
fromFlowCompletion
.response - [docs] update examples
from pywa import WhatsApp, types
wa = WhatsApp(...)
@wa.on_flow_request("/my-flow")
def my_flow_handler(_: WhatsApp, flow: types.FlowRequest) -> types.FlowResponse | None:
...
@my_flow_handler.on(action=types.FlowRequestActionType.INIT)
def on_init(_: WhatsApp, request: types.FlowRequest) -> types.FlowResponse:
...
@my_flow_handler.on(action=types.FlowRequestActionType.DATA_EXCHANGE, screen="SIGN_UP")
def on_signup(_: WhatsApp, request: types.FlowRequest) -> types.FlowResponse:
...
@my_flow_handler.on(
action=types.FlowRequestActionType.DATA_EXCHANGE,
screen="LOGIN",
data_filter=lambda _, data: not user_repository.exists(data["email"]),
)
def if_not_registered(_: WhatsApp, request: types.FlowRequest) -> types.FlowResponse | None:
...
Full Changelog: 1.21.0...1.22.0
1.21.0
What's Changed
Update with pip:
pip3 install -U pywa
Caution
- [server] This version drops support for non-asynchronous WSGI applications (Flask without
asgiref
) - [flows]
version
is now required inFlowJSON
- [flows] added new components
PhotoPicker
,DocumentPicker
,If
andSwitch
- [flows] added
.data_key_of
and.form_ref_of
to refer from other screens - [flows] added
description
toCheckboxGroup
and toRadioButtonsGroup
- [utils] adding
flow_request_media_decryptor
function to decrypt medias from flow requests - [client] allow updating flow application id with
update_flow_metadata
- [server] remove event loop
- [docs] update examples
- [version] bump
FLOW_JSON
version to4.0
from pywa import WhatsApp, utils, types
from pywa.types.flows import *
wa = WhatsApp(...)
my_flow = FlowJSON(
version="4.0",
screens=[
Screen(
id="UPLOAD_DRIVER_LICENSE",
title="Upload Driver License",
layout=Layout(
children=[
doc := DocumentPicker(
name="driver_license",
label="Driver License",
min_uploaded_documents=1,
max_uploaded_documents=1,
allowed_mime_types=["application/pdf", "image/jpeg"],
),
Footer(
label="Next",
enabled=True,
on_click_action=Action(
name=FlowActionType.DATA_EXCHANGE,
payload={"doc": doc.form_ref_of("UPLOAD_DRIVER_LICENSE")},
),
),
]
)
)
]
)
@wa.on_flow_request("/flow")
def on_flow_request(_, flow: types.FlowRequest):
media_id, filename, media_bytes = utils.flow_request_media_decryptor_sync(flow.data["doc"])
with open(filename, "wb") as f:
f.write(media_bytes)
...
Full Changelog: 1.20.2...1.21.0