- 1. Введение
- 2. Mac OS
- 3. Linux
- 4. Windows
- 5. Делаем скрипт кросс-платформенным
- 6. Вывод
Введение
Мы часто используем
print
что-бы проследить за состоянием скрипта, или когда занимаемся отладкой. Вместо того, что-бы постоянно смотреть в окно терминала и ждать какой-то вывод, мы можем исползовать всплывающие уведомления как способ для просмотра вывода и параллельно с этим заниматься чем-то еще.
Сделать это совсем не сложно — мы будем использовать встроенные комманды нашей операционной системы для вывода уведомлений, просто запуская их через Python. Несколько строчек кода, и никаких сторонних модулей.
Mac OS
Следующая команда запускает AppleScript (встроенный скриптовый язык компании Apple) для уведомлений.
osascript -e ‘display notification «Your message goes here» with title «Title»‘
Если вы запустите эту комманду то увидите следующий результат:
Подробнее о самом скрипте:
Итак, если вы используете Mac OS, тогда вы можете выполнить следующий Python код:
import os title = "Готово" message = "Файл скачан" command = f''' osascript -e 'display notification "{message}" with title "{title}"' ''' os.system(command)Запустив этот скрипт, вы увидите схожий результат как и выше.
Linux
Linux предлагает еще более простой вариант:
notify-send «Your message» «Title»
Аналогично тому, как мы выводили уведомление на Mac OS через os.system, так же можно поступить в Linux системе.
Windows
В Windows нет такой команды которая запускала бы уведомления как в Linux и Mac OS, но это все равно можно сделать с помощью библиотеки
win10toast
, сначала её нужно установить:
pip install win10toast
и теперь пример ее использования:import win10toast toaster = win10toast.ToastNotifier() toaster.show_toast("Заголовок", "Описание уведомления")Метод
show_toast
принимает и другие аргументы, но подробнее о них вы можете напрямую почитать — просто вызвав
help
передав ей объект функции о которой нужно узнать подробнее.Делаем скрипт кросс-платформенным
Что-бы узнать какая система стоит на вашем компьютере используется команда
system()
из встроенной библиотеки
platform
. Для Mac OS эта команда возвращает строку «Darwin», для Linux «Linux», для Windows — «Windows», теперь, зная все это, можем поместить весь код в функцию
push
. Вот что получится:import platform, os def push(title, message): plt = platform.system() if plt == "Darwin": command = ''' osascript -e 'display notification "{message}" with title "{title}"' ''' elif plt == "Linux": command = f''' notify-send "{title}" "{message}" ''' elif plt == "Windows": win10toast.ToastNotifier().show_toast(title, message) return else: return os.system(command)Вывод
В статье было показано как вывести всплывающие уведомления на Python’е, определенно это был не единственный способ. Существует достаточное количество библиотек которые выполняют подобные цели.
Project description
Python Notifications
Python3 module for sending notifications.
The list of available backends:
- Platform (
pynotifier.backends.platform.Backend
):- macOS
- Linux
- Windows
- Email (
pynotifier.backends.smtp.Backend
)
Platform notifications requirements
Windows:
WinToaster
— Python module
Linux:
libnotify-bin
CLI tool (manual installation is required). For Ubuntu run:
sudo apt-get install libnotify-bin
Installation
Install using pip:
pip install py-notifier
Example
import ssl import smtplib from pynotifier import NotificationClient, Notification from pynotifier.backends import platform, smtp smtp_server = smtplib.SMTP_SSL('smtp.gmail.com', 465, context=ssl.create_default_context()) c = NotificationClient() c.register_backend(platform.Backend()) c.register_backend(smtp.Backend(server=smtp_server, name='My Organization', email='sender@organization.com', password='super_password')) filenames = [ 'path/to/file1.json', 'path/to/file2.txt', # ... ] attachments = [] for filename in filenames: attachments.append(smtp.Attachment(open(filename, 'rb'), filename)) smtp_config = smtp.NotificationConfig(emails=['receiver_1@email.com', 'receiver_2@email.com'], attachments=attachments) notification = Notification(title='Hello', message='World', smtp=smtp_config) c.notify_all(notification)
License
The project is licensed under the terms of the MIT License,
see the LICENSE file for more information.
Download files
Download the file for your platform. If you’re not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file py-notifier-0.5.0.tar.gz
.
File metadata
-
Download URL:
py-notifier-0.5.0.tar.gz - Upload date:
- Size: 7.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.16
File hashes
Hashes for py-notifier-0.5.0.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 88445d25a20f901f4821cc7a17bf8f873c11c2316288083afb1277bd1a8d6112 |
|
MD5 | d26af3e2c5c6ff660e1ed4d340d853e3 |
|
BLAKE2b-256 | 95431c61910687afd4e896b504d61dcb7eecb7c2816d5f154237853fafc143eb |
See more details on using hashes here.
File details
Details for the file py_notifier-0.5.0-py3-none-any.whl
.
File metadata
-
Download URL:
py_notifier-0.5.0-py3-none-any.whl - Upload date:
- Size: 10.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.16
File hashes
Hashes for py_notifier-0.5.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cb75a4ae1ed7839b5794dc94c0dd5b38e791c32f53041e99c64f71d9a1dfb8d3 |
|
MD5 | 0d1716fbdd041882a5f4769efa207c04 |
|
BLAKE2b-256 | cb93922e37bfd08533315863b9afb2184377c540a8b2545b0ded1b459c4ffbdd |
See more details on using hashes here.
Всем доброго времени суток. Давайте поговорим сегодня о простой, но далеко не самой очевидной вещи, которая не лежит не виду, а когда с ней сталкиваешься, приходиться некоторое время гуглить информацию. О всплывающих сообщениях. Ведь порою в скриптах нужно отображать информацию не только в консоли. А если приложение имеет еще и пользовательский интерфейс, то сообщения, конечно же, лучше выводить без ее использования.
Что потребуется?
Тут все зависит от операционной системы и способа, которым вы выберете выводить сообщение. Если операционная система Linux, то здесь может не потребоваться ничего, так как будет использоваться функция операционной системы. А можно установить специальную библиотеку notify2. Установка происходит через терминал с помощью команды pip:
pip install notify2
А дальше импортируем в скрипт.
import notify2
Всплывающие сообщения в Linux
Давайте посмотрим, какие сообщения можно вывести с помощью данной библиотеки, и с какими небольшими «подводными камнями» я столкнулся при ее использовании.
Python:
icon_path = "/usr/share/icons/gnome/24x24/emotes/face-laugh.png"
notify2.init(text)
mess = notify2.Notification(text, icon=icon_path)
mess.set_urgency(notify2.URGENCY_NORMAL)
mess.show()
Первая переменная здесь – это просто путь до иконки, который будет выводиться в сообщении. Дальше инициализируем соединение d-bus. Это механизм, с помощью которого процессы взаимодействуют друг с другом и который используют программы в Linux. А потому, нужно подключиться через d-bus к процессу, который управляет системным интерфейсом и передать ему такие параметры, как значок, заголовок и текст сообщения.
Далее создаем объект уведомлений, в который передаем сообщение и его иконку. И, если в этом есть необходимость, можно установить уровень срочности сообщения, у которого их целых три: URGENCY_LOW, URGENCY_CRITICAL, URGENCY_NORMAL. Ну и с помощью метода show() показываем сообщение.
Вот в принципе и все. Казалось бы, какие тут могут быть «камни»? Но, я попытался запустить данный код в Kali. И о чудо, я получил ошибку, которая гласила, что d-bus не установлен в системе. Оказывается, бывает и такое. Тогда я сделал обработку ошибки и вывел сообщение уже более простым способом:
Python:
command = f'''notify-send {text}'''
system(command)
где command – это собственно команда и сообщение. А system эту команду исполняет. В итоге, получилась небольшая функция, которую я назвал linux_notify(text), на входе принимающая текст сообщения:
Python:
def linux_notify(text):
try:
import notify2
icon_path = "/usr/share/icons/gnome/24x24/emotes/face-laugh.png"
notify2.init(text)
mess = notify2.Notification(text, icon=icon_path)
mess.set_urgency(notify2.URGENCY_NORMAL)
mess.show()
except ModuleNotFoundError:
command = f'''notify-send {text}'''
system(command)
Всплывающие сообщение в Windows
Ну и после этого я подумал, а не поискать ли мне простые способы отображения всплывающих сообщений и в Windows. Подумал, поискал и вот, что нашел. Для начала — win10toast. Устанавливаем в терминале. Тут все стандартно:
pip install win10toast
Затем импортируем в скрипт:
from win10toast import ToastNotifier
и уже можно с ней работать.
Python:
ToastNotifier().show_toast("Демо сообщение", text, icon_path=r"C:\Windows\WinSxS\amd64_microsoft-windows-dxp-"
r"deviceexperience_31bf3856ad364e35_10.0.19041.746"
r"_none_251e769058968366\sync.ico",
duration=3, threaded=True)
Итак. Первый параметр, это заголовок сообщения. Второй, само сообщение. Третий – путь к иконке. Я нашел в недрах винды что-то более-менее подходящее. Не факт, что есть на других машинах, но в теории должно быть, так то. Параметр duration отвечает за количество секунд, которые будет отображаться сообщение. Ну и threaded. Забавный параметр, который обязательно включить, если вы сделали пользовательский интерфейс в PyQt. Иначе, после вывода сообщения окно программы у вас будет просто закрываться. Насколько я понял, это потоковый параметр, который ждет завершения работы уведомления. Это был первый способ.
Второй способ немного проще. Устанавливаем библиотеку plyer:
pip install plyer
и импортируем ее в скрипт:
from plyer import notification
А затем пишем простой код:
Python:
notification.notify(message=text, app_icon=r"C:\Windows\WinSxS\amd64_microsoft-windows-dxp-deviceexperience_"
r"31bf3856ad364e35_10.0.19041.746_none_251e769058968366\sync.ico")
И все. Сообщение готово. Первый параметр тут текст сообщения. Второй – путь к иконке. И все работает. Причем в любых приложениях на винде. Хоть консольных, хоть с пользовательским интерфейсом.
Такие вот способы, которые более-менее позволяют вывести сообщение в Linux и Windows. Без сомнения, есть и другие, более сложные способы. Но, чтобы не лезть в дебри, а просто вывести сообщение, этих способов будет достаточно. И я оформил все в небольшой код. Который, в общем-то, не обязателен, но так все же приятнее и легче он читается.
Python:
# pip install notify2
# pip install win10toast
# pip install plyer
from platform import system as psystem
import time
from os import system
def linux_notify(text):
try:
import notify2
icon_path = "/usr/share/icons/gnome/24x24/emotes/face-laugh.png"
notify2.init(text)
mess = notify2.Notification(text, icon=icon_path)
mess.set_urgency(notify2.URGENCY_NORMAL)
mess.show()
except ModuleNotFoundError:
command = f'''notify-send {text}'''
system(command)
def windows_notify(text):
from plyer import notification
from win10toast import ToastNotifier
ToastNotifier().show_toast("Демо сообщение", text, icon_path=r"C:\Windows\WinSxS\amd64_microsoft-windows-dxp-"
r"deviceexperience_31bf3856ad364e35_10.0.19041.746"
r"_none_251e769058968366\sync.ico",
duration=3, threaded=True)
time.sleep(4)
notification.notify(message=text, app_icon=r"C:\Windows\WinSxS\amd64_microsoft-windows-dxp-deviceexperience_"
r"31bf3856ad364e35_10.0.19041.746_none_251e769058968366\sync.ico")
if __name__ == "__main__":
message = input('[+] Введите текст сообщение: ')
if psystem() == "Linux":
linux_notify(message)
elif psystem() == "Windows":
windows_notify(message)
А на этом все. Какой из библиотек пользоваться, решать без сомнения вам.
Спасибо за внимание. Надеюсь, что данная информация будет кому-нибудь полезна
notify.py
Cross platform desktop notifications for Python scripts and applications.
Docs
You can read the docs on this Git’s Wiki, or here
Supported Platforms.
- Windows 10/11
- macOS 10 >=10.10
- Linux (libnotify)
No dependencies are required other than loguru & jeepney (Only for linux/DBUS).
Install
Usage
Send Simple Notification
from notifypy import Notify notification = Notify() notification.title = "Cool Title" notification.message = "Even cooler message." notification.send()
Send Notification With Icon
from notifypy import Notify notification = Notify() notification.title = "Cool Title" notification.message = "Even cooler message." notification.icon = "path/to/icon.png" notification.send()
Send Notification With Sound
from notifypy import Notify notification = Notify() notification.title = "Cool Title" notification.message = "Even cooler message." notification.audio = "path/to/audio/file.wav" notification.send()
Sending Notifications without blocking
from notifypy import Notify notification = Notify() notification.send(block=False)
Sending with Default Notification Titles/Messages/Icons
from notifypy import Notify notification = Notify( default_notification_title="Function Message", default_application_name="Great Application", default_notification_icon="path/to/icon.png", default_notification_audio="path/to/sound.wav" ) def your_function(): # stuff happening here. notification.message = "Function Result" notification.send()
CLI
A CLI is available when you install notify-py
notifypy --title --message --applicationName --iconPath --soundPath
You may need to add python3 -m
to the beginning.
Important Caveats
-
As it stands (May 18, 2020), this is simply a notification service. There is no support for embedding custom actions (buttons, dialogs) regardless of platform. Other then telling you if the shell command was sent, there is also no confirmation on user action on the notification.
-
macOS does not support custom icons on the fly.. You will need to bundle a customized version of the notifier embedded with your custom icon.
Windows Specific.
- No support for balloon tips (pre Win10).. This will be changed in the future.
Contributors
- Leterax
- jnoortheen
- dynobo
- Xou
Inspiration and Special Thanks
-
https://github.com/go-toast/toast — Ported their Windows 10 toast notification to Python.
-
Vítor Galvão for https://github.com/vitorgalvao/notificator
-
https://notificationsounds.com/notification-sounds/done-for-you-612 example_notification_sound.wav
-
https://github.com/mikaelbr/node-notifier
Contributing
Contributions are welcome!
Please base your changes on the latest development branch and open a PR to that branch. PR will not be accepted to the master branch. Tests are ran against all platforms.
Setting Up Environment
- Install Poetry
poetry install
- Add patches/new features/bug fiexes
- Run tests
poetry run pytest tests/*
- Run lints
poetry run pylint --errors-only notifypy/
- Run Black Formatting
poetry run black notifypy
- Open PR to
dev
branch. - Add your name to contributors list if you wish!
Зачем еще одно руководство?
Когда передо мной поставили задачу сделать черновой вариант push notifications, быстрый поиск показал, что на хабре уже есть много статей по настройке push notifications. Вот наиболее, на мой взгляд, годные:
Как работает JS: веб push-уведомления
Web PUSH Notifications быстро и просто924/
Service Workers. Web Push и где они обитают
Это все прекрасно, но лично мне очень не хватало простого и понятного руководства, которое позволило бы сразу, практически методом копипаста сделать так, чтобы все сразу заработало. Ну и кроме того среди руководств нет адаптированного под бек на питоне.
Настройка уведомлений в итоге заняла три дня и мне кажется, что это несколько многовато. Надеюсь, моя статья поможет кому-то настроить push notifications за три часа вместо трех дней.
Проект, на котором я работаю, реализован на Django и описывать ход работы я буду применительно к этому фреймворку, но желающие легко адаптируют его к Flask или чему-либо еще.
Итак, погнали.
Получаем ключи
Без ключей нас, естественно, никуда не пустят, поэтому начнем с них. Для генерации ключей я использовала py_vapid. Он устанавливается автоматически вместе с pywebpush, который нам все равно понадобится позже, поэтому мы, чтобы два раза не вставать, начнем с pywebpush:
> bin/pip install pywebpush
<Здесь вывод результата установки>
> bin/vapid --applicationServerKey
No private_key.pem file found.
Do you want me to create one for you? (Y/n)Y
Generating private_key.pem
Generating public_key.pem
Application Server Key = <Мой Server Key>
Полученное значение Application Server Key добавляем в файл settings.py.
NOTIFICATION_KEY = <Мой Server Key>
Кроме того, нам нужно будет передать NOTIFICATION_KEY в контекст. Проще всего для этого написать свой context_processor.
Делаем Service worker
Service worker, кто не знает — это специальный файл, работающий в фоновом режиме. Нам он нужен для отображения наших уведомлений.
Самый простой код service worker будет выглядеть так:
self.addEventListener('push', function(event) {
var message = JSON.parse(event.data.text()); //
event.waitUntil(
self.registration.showNotification(message.title, {
body: message.body,
})
);
});
А теперь нам нужно зарегистрировать наш service worker. Процесс, в принципе, описан здесь. Поэтому кратко:
function checkWorkerAndPushManager () {
if (!('serviceWorker' in navigator)) {
console.log('Workers are not supported.');
return;
}
if (!('PushManager' in window)) {
console.log('Push notifications are not supported.');
return;
}
}
function registerWorker () {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/static/js/sw.js').then(function (registration) {
console.log('ServiceWorker registration successful');
}, function (err) {
console.log('ServiceWorker registration failed: ', err);
return;
});
});
return true;
}
var supported = checkWorkerAndPushManager();
if (supported){
var worker = registerWorker ();
}
Отлично, можно проверить работу нашего service worker. Для этого зайдите в Developer Tools в браузере, убедитесь, что в консоли появилось сообщение об успешной регистрации вокера и перейдите на вкладку Application и слева выберите Service Worker.
Если уведомление не появляется — проверьте, что у вас в браузере разрешены уведомления.
Ну вот, мы уже умеем оправлять уведомления сами себе. Круто, едем дальше.
Получаем разрешение пользователя на показ уведомлений
После того, как вокер зарегистрирован, попросим у пользователя разрешение показывать уведомления.
function requestPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
// Поддержка устаревшей версии с функцией обратного вызова.
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
console.log(permissionResult);
throw new Error('Permission not granted.');
}
});
return true;
}
К этому коду и комментариев никаких не нужно, он просто работает.
Подписываемся и сохраняем подписку
Подписка выполняется тоже на фронте. Здесь можно найти код подписки, но нет используемой функции urlBase64ToUint8Array, поэтому я код вместе с ней.
NOTIFICATION_KEY = '{{ NOTIFICATION_KEY }};
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/')
;
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
function subscribeUserToPush(key) {
return navigator.serviceWorker.register('/static/path/to/js/sw.js')
.then(function(registration) {
var subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(key),
};
return registration.pushManager.subscribe(subscribeOptions)
})
.then(function(pushSubscription) {
sendSubscriptionToBackEnd(pushSubscription);
});
}
(Используемая здесь urlBase64ToUint8Array, видимо, из разряда костылей и велосипедов, но попытка перекодировать данные с помощью btoa не привела к успеху, уж не знаю, почему. Может кто подскажет).
Далее полученные данные отправляем на сервер. У меня это реализовано так:
function sendSubscriptionToBackEnd(subscription) {
$.post(
SAVE_REGISTRATION_URL,
{
'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val(),
//Естественно, в темплейте есть тег {% csrf_token %}.
'registration_data': JSON.stringify(subscription)
}
);
}
Ну и дальше сохраняем это на сервере. Можно прямо строкой. Да, не вздумайте делать связь user-subscription типа «один к одному». Вроде бы очевидно, но вдруг кому вздумается.
У меня для сохранения используется вот такая простая модель, она дальше будет использоваться, поэтому я ее приведу:
class UserSubscription(models.Model):
subscription = models.CharField(max_length=500)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='subscriptions')
Последний шаг. Отправляем сообщение с помощью pywebpush
Здесь все по туториалу, никаких особенных тонкостей. Ну, разве что сообщение делаем такой структуры, чтобы наш service worker мог его разобрать.
import json
from pywebpush import webpush, WebPushException
from django.conf import settings
from .models import UserSubscription
def push_notification(user_id):
user_subscriptions = UserSubscription.objects.filter(user_id=notification.user_id)
for subscription in user_subscriptions:
data = json.dumps({
'title': 'Hello',
'body': 'there',
})
try:
webpush(
subscription_info=json.loads(subscription.subscription),
data=data,
vapid_private_key='./private_key.pem',
vapid_claims={
'sub': 'mailto:{}'.format(settings.ADMIN_EMAIL),
}
)
notification.sent = True
notification.save()
except WebPushException as ex:
print('I\'m sorry, Dave, but I can\'t do that: {}'.format(repr(ex)))
print(ex)
# Mozilla returns additional information in the body of the response.
if ex.response and ex.response.json():
extra = ex.response.json()
print('Remote service replied with a {}:{}, {}',
extra.code,
extra.errno,
extra.message
)
Собственно, уже можно вызывать push_notification из django shell и пытаться запустить.
В этом коде хорошо бы еще сделать перехват ответа со статусом 410. Такой ответ говорит, что подписка аннулирована, и такие подписки хорошо бы удалять из БД.
В заключение
На самом деле есть еще одна замечательная библиотека django-webpush. Возможно, тем, кто работает с Django, стоит начинать именно с нее.
P.S. Всех с Днем программиста!