A major part of my work is creating spreadsheets. With Python, I can make these over and over again much quicker than manually doing the edits in Excel. Some of the scripts can take some time to run. I decided to add notifications to let me know the script is finished. And more importantly, adding notifications when the script has issues.
Doing a search on PyPI provides a long list of packages available. After looking at quite a few, I tried narrowing it down based on when was the last update, how active the developers were and how well the documentation was. Three packages stood out.
For the most part each package handles the same toasts and have well laid out documentation. Below I'll show a few common toasts between the three packages and also show a couple of features that each package can shine with.
Installing
Using pip is a common method of installation.
pip install win11toast
pip install Windows-Toasts
pip install winsdk-toast
You only need to include the package you decide to go with. 😀
Win11toast
This has good documentation and is kept updated.
from win11toast import toast
Jupyter Notebook
For Win11toast to work with Jupyter Notebook it takes a little change. Instead of using toast
you'll need to import and use toast_async
instead of toast
.
from win11toast import toast_async
await toast_async('Hello Python', 'Click to open url', on_click='https://www.python.org')
Basic
A quick notification will include a title and message. Everything else will be set to defaults. If the message is long, it'll wrap around.
toast('Hello Python🐍',
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum accusantium porro numquam aspernatur voluptates cum, odio in, animi nihil cupiditate molestias laborum. Consequatur exercitationem modi vitae. In voluptates quia obcaecati!')
Adding an Icon
Icons can be added by specifying the absolute location.
toast('Hello Python🐍',
'An Icon is displayed',
icon=str(Path('Smile_24px.ico').resolve()))
The Smile_24px.ico
file is in the same directory as the Python file. I'm using Path
to select a relative location of a file and convert it to absolute. Path
will also need to be imported.
Playing a Windows Sound
Most of these packages use the standard Windows Sounds. For Win11toast
you'll use the Window's name of the sound. Microsoft - Element Audio Docs lists out the sounds and you can pick any of them.
toast('Hello', 'Hello from Python', audio='ms-winsoundevent:Notification.Looping.Alarm')
For those times you don't want even the default sound, silent
can be set to true
.
toast('Hello Python🐍', audio={'silent': 'true'})
Playing a sound file
Win11toast
has the extra ability to play a sound file either from the local drive or from a URL.
toast('Hello Python🐍',
'Play a sound file',
audio=r'c:\Users\Admin\Downloads\MUS_Mysterious_Stranger_A_0001.mp3'
Using Windows Speech
Windows has Speech built directly in. It's been there for quite a few versions. Win11toast has an attribute that lets you pass text for Windows to say out loud.
toast('Hello Python🐍', 'Did you hear it?', dialogue='Let Windows do the talking.')
It's common today to turn off the notification's sounds and Windows Speech. While I really love this feature, I won't rely on it.
Click on the notification
Besides passing along information, another very common use of the notification is to let the user trigger a new step. By setting the on_click
attribute to a string with a URL, Windows will attempt to open it in the default browser.
toast('Hello Python🐍',
'Click toast to open url',
on_click='https://www.python.org')
Adding a Button
Sometimes having a button, just looks more friendly. Adding a dismiss button is quick.
toast('Hello', 'Hello from Python', button='Dismiss')
To get the button to do something else just takes a dictionary of what we want.
toast('Hello', 'Hello from Python',
button={'activationType': 'protocol', 'arguments': 'https://google.com', 'content': 'Open Google'})
In this example we'll have two buttons. The first with the label of Play
will open the file in the argument in the default application. The second button will open the URL in the default application. Since that is a location on the drive, it should open the File Explorer.
buttons = [
{'activationType': 'protocol', 'arguments': 'C:\Windows\Media\Alarm01.wav', 'content': 'Play'},
{'activationType': 'protocol', 'arguments': 'file:///C:/Windows/Media', 'content': 'Open Folder'}
]
toast('Music Player', 'Download Finished', buttons=buttons)
Dropdown Selection
The selection
argument will create a list of options to select.
toast('Hello', 'Which do you like?',
selection=['Apple', 'Banana', 'Grape'],
button='Submit',
on_click=lambda args: print('clicked!', args))
When I selected Banana
the args comes back as {'arguments': 'http:', 'user_input': {'selection': 'Banana'}}
.
Windows-Toasts
Windows-Toasts
more separate steps to build and set off a toast notification.
This package worked quite while inside Jupyter Lab and Jupyter Notebook. If you need it to run in Jupyter Notebook this would be the package for easiest starting.
import windows_toasts
Basic
As shown in even the basic toast, it takes a few steps to get the basic toast out. I think the advantage would be in more complex toasts.
# Create a Windows Toast Object
wintoaster = windows_toasts.WindowsToaster('Hello Python🐍')
newToast = windows_toasts.Toast()
# Add text to the Toast
newToast.text_fields = ['Hello, World!']
wintoaster.show_toast(newToast)
Clickable toast
With this Clickable Toast we're adding an action to occur if the toast is clicked. Clicking the x
or letting the toast expire will not execute the action.
The example is using a lambda expression but could be a function as well.
wintoaster = windows_toasts.WindowsToaster('Hello Python🐍')
newToast = windows_toasts.Toast()
newToast.text_fields = ['Hello, World!']
# Add an action to clicking the Toast
newToast.on_activated = lambda _: print('Toast clicked!')
wintoaster.show_toast(newToast)
Adding an Image
Adding an image uses the ToastDisplayImage
function.
from pathlib import Path
toaster = windows_toasts.WindowsToaster('Windows-Toasts')
newToast = windows_toasts.Toast()
newToast.text_fields = ['Look an image']
# str or PathLike
newToast.AddImage(ToastDisplayImage.fromPath(str(Path('Coffe-To-Go_24px.png').resolve())))
toaster.show_toast(newToast)
Playing a Windows Sound
There is a set list of sounds that are accessible for Windows Toast. The list is available in the documentation
toaster = WindowsToaster('Windows-Toasts')
newToast = windows_toasts.Toast()
newToast.text_fields = ['Ding ding! Ding ding! Ding ding!']
newToast.audio = ToastAudio(AudioSource.IM, looping=True)
toaster.show_toast(newToast)
Buttons on the notification
Here we are adding 2 buttons and a on_activated
attribute that contains a function to be called and the response is passed in. Instead of a lambda any function could be used. When either button is clicked the same function is executed. Using the reponse.arguments
we can access the string value passed to ToastButton
.
toaster = InteractableWindowsToaster('Hello Python🐍')
newToast = windows_toasts.Toast(['Hello, how are you?'])
# Add two actions (buttons)
newToast.AddAction(ToastButton(content='Decent', arguments='decent'))
newToast.AddAction(ToastButton('Not so good', 'bad'))
newToast.on_activated = lambda response: print(response.arguments)
# Send it
toaster.show_toast(newToast)
The value in response.arguments
will be the value of arguments
in the AddAction
method call. This will let you know which button was clicked.
Launch a Website
Opening website is very common so there is a bit of a shortcut.
toaster = WindowsToaster('Rick Astley')
newToast = windows_toasts.Toast()
newToast.text_fields = ['Hello there! You just won a thousand dollars! Click me to claim it!']
newToast.launch_action = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
# Send it
toaster.show_toast(newToast)
Scheduled Notification
If you need to schedule a notification at a specific time, Windows-Toast
has the option. Because it's passing the notification directly to Windows, the Python script does not need to be running. I tried it with a Launch_action
and it still was able to continue the action. I don't know how much action it can delay. If you find a limit (or get it to do a lot of actions) comment below.
from datetime import datetime, timedelta
toaster = windows_toasts.WindowsToaster('Python')
displayTime = datetime.now() + timedelta(seconds=20)
newToast = windows_toasts.Toast([f'This will pop up at {displayTime}'])
newToast.text_fields = ['Hello there! You just won a thousand dollars! Click me to claim it!']
newToast.launch_action = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
toaster.schedule_toast(newToast, displayTime)
Winsdk_toast
Winsdk_toast
doesn't have the best of documentation. A lot of how I learned to use this is from studying the code. But I do like the methods that are used.
Unlike the others, this package didn't work in Jupyter Notebook.
from winsdk_toast import Notifier, Toast
from winsdk_toast.event import EventArgsActivated, EventArgsDismissed, EventArgsFailed
Basic
For each .add_text()
used a new line of text will be added.
# Step1. Create Notifier with applicationId
notifier = Notifier("Hello Python🐍")
# Step2. Create Toast which contains the message to be shown
toast = Toast()
toast.add_text("Simple toast, just notifies.")
# Step3. Show the Toast
notifier.show(toast)
Adding an image
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text("Add Icon")
# Add image to the Toast
toast.add_image(str(Path("Smile_24px.ico").resolve()))
notifier.show(toast)
Playing a Windows Sound
Most of these packages use the standard Windows Sounds. For winsdk-toasts
you'll use the Window's name of the sound. Microsoft - Element Audio Docs lists out the sounds and you can pick any of them.
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text("Add Sound")
# Add sound to the Toast
toast.set_audio(src="ms-winsoundevent:Notification.Looping.Alarm")
notifier.show(toast)
Passing silent='true'
will silence the notification.
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text("Silent")
toast.set_audio(silent='true')
notifier.show(toast)
Button on Notification
The .add_action()
method will add a button to the Toast.
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text("The script is finished.")
# Add button to the Toast
toast.add_action('OK')
notifier.show(toast)
Adding events
Here's the part where this package really is different than the other two.
There are three possible endings to a notification:
- activated - It was clicked on
- dismissed - the x was clicked, or it expired.
- failed - Something went wrong, and the toast didn't work correctly
Each of these possibilities can have a method that will run. These functions are specified in the notifier.show()
call. Only the functions that you want to use need to be overridden.
# This will run if the button is clicked.
def handle_activated(event_args_activated: EventArgsActivated):
print('inputs:', event_args_activated.inputs)
print('argument:', event_args_activated.argument)
# This will run if the `x` is clicked or the notification times out.
def handle_dismissed(event_args_dismissed: EventArgsDismissed):
print('dismissed')
print('reason:', event_args_dismissed.reason)
# This will run if the notification failed/errored
def handle_failed(event_args_failed: EventArgsFailed):
print('failed')
print('error_code:', event_args_failed.error_code)
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text('What up?', hint_align='center', hint_style='caption')
# Add input Box
toast.add_input(type_='text', id_='input_name')
toast.add_action('Close')
# The functions and the attributes do not have to match.
notifier.show(
toast,
handle_activated=handle_activated,
handle_dismissed=handle_dismissed,
handle_failed=handle_failed
)
Also we add here is the .add_input()
. This adds an input box the user can enter data. Inside the handle_activated
will be passed a dictionary that will contain {'input_name': '/userinput/'}
.
input_name
- theid_
from the.add_input()
statement/userinput/
- whatever the user entered in the input field
Multiple fields can be added to the Toast.
While working learning how this package worked, I was able to show multiple buttons. But I wasn't able to identify which button was clicked. The next section goes over a Drop Down Selection. This is a very nice way to have the user select different options.
Drop down selection
Adding multiple input fields is great, but sometimes we want the user to choose from a preselected set of answers. That's where .add_selection()
comes to play.
def notifyActiveHandler(event_args_activated: EventArgsActivated):
print('activated')
print('inputs:', event_args_activated.inputs)
print('argument:', event_args_activated.argument)
def handle_dismissed(event_args_dismissed: EventArgsDismissed):
print('dismissed')
print('reason:', event_args_dismissed.reason)
notifier = Notifier("Hello Python🐍")
toast = Toast()
toast.add_text('Which topping do you want?', hint_align='center', hint_style='caption')
toast.add_input('selection', id_='topping')
# Add Selection items for the input box
toast.add_selection(content='Apple', id_='apple')
toast.add_selection(content='Chocolate', id_='chocolate')
toast.add_selection(content='Sugar', id_='sugar')
toast.add_selection(content='Whipped Cream', id_='whipped_cream')
toast.add_action('Pick!')
# Step3. Show the Toast
notifier.show(toast,
handle_activated=notifyActiveHandler,
handle_dismissed=handle_dismissed
)
Creed
While getting advice concerning this article, I was asked about Linux and macOS. I did find the Python package Creed. It only handles a basic toast but it checks the OS and provides the proper toast for that OS.
For Windows it's using win10toast
which is an old version of win11toast
. For MacOS and Linux it makes a call directly to the OS to create the Toast.
from creed import Notif
Notif(title="Python", message="Hello, World", duration=20).Toast()
Conclusion
This is by no means an exhaustive look at each of these packages. I tried to pick situations that might be useful. I've decided to go with win11toast
. I found it a simpler syntax and gave all the features that I needed.
If you are favoring a different package or found a feature I should have listed, please comment below.
Resources
These are some of the links I studied for this post.