63

Using python I am wanting to post a message to the OSX Notification Center. What library do I need to use? should i write a program in objective-c and then call that program from python?


update

How do I access the features of notification center for 10.9 such as the buttons and the text field?

kyle k
  • 5,134
  • 10
  • 31
  • 45
  • I answered a similar question [here](https://stackoverflow.com/questions/62234033/how-create-local-notification-on-macos-catalina-pyobjc/62248246#62248246). The code uses pyobjc, but has a few issues – themthem Jun 10 '20 at 06:20

9 Answers9

166

All the other answers here require third party libraries; this one doesn't require anything. It just uses an apple script to create the notification:

import os

def notify(title, text):
    os.system("""
              osascript -e 'display notification "{}" with title "{}"'
              """.format(text, title))

notify("Title", "Heres an alert")

Note that this example does not escape quotes, double quotes, or other special characters, so these characters will not work correctly in the text or title of the notification.

Update: This should work with any strings, no need to escape anything. It works by passing the raw strings as args to the apple script instead of trying to embed them in the text of the apple script program.

import subprocess

CMD = '''
on run argv
  display notification (item 2 of argv) with title (item 1 of argv)
end run
'''

def notify(title, text):
  subprocess.call(['osascript', '-e', CMD, title, text])

# Example uses:
notify("Title", "Heres an alert")
notify(r'Weird\/|"!@#$%^&*()\ntitle', r'!@#$%^&*()"')
Christopher Shroba
  • 7,006
  • 8
  • 40
  • 68
  • 3
    that's great! do you know how to keep the notification there for longer, add a sound or an image? – Andrea Mar 17 '17 at 11:43
  • 7
    @AndreaDiBiagio sure! Look here (http://apple.co/2mXi1Sc ) for how to play a sound with the notification, and here (http://apple.stackexchange.com/q/106904/ ) for how to change the icon. Adding the sound is easy; adding the image is more difficult... you'd probably have to dynamically create the applescript file and bundle and set the image there. Good luck! – Christopher Shroba Mar 17 '17 at 15:52
  • 2
    how increase the time for long stay ? – Aaditya Ura Sep 24 '17 at 10:19
  • For people asking about how to have more powerful notifications (longer duration / custom icon / play sound), have a look at [Growl](http://growl.info/). I'm not sure it can do all those things, but it is definitely more powerful than macOS's built-in notifications. (Disclaimer: I've never used it) – Christopher Shroba Oct 18 '17 at 15:33
  • 3
    You can also display a permanent notification [by changing a setting](https://apple.stackexchange.com/a/214598/100222), or display a popup warning by replacing `notification` with `alert` or `dialog` in the above script. – SomeDude Aug 14 '19 at 17:24
  • 1
    this is a very clever method of using applications that are already present in Mac, cheers to this answer. – tisaconundrum Dec 07 '20 at 01:05
71

You should install terminal-notifier first with Ruby for example:

$ [sudo] gem install terminal-notifier

And then you can use this code:

import os

# The notifier function
def notify(title, subtitle, message):
    t = '-title {!r}'.format(title)
    s = '-subtitle {!r}'.format(subtitle)
    m = '-message {!r}'.format(message)
    os.system('terminal-notifier {}'.format(' '.join([m, t, s])))

# Calling the function
notify(title    = 'A Real Notification',
       subtitle = 'with python',
       message  = 'Hello, this is me, notifying you!')

And there you go:

enter image description here

Cœur
  • 37,241
  • 25
  • 195
  • 267
Peter Varo
  • 11,726
  • 7
  • 55
  • 77
  • terminal-notifier is installed within /Library/Ruby location. When packaging python script with py2app, generated app has not the feature of terminal-notifier, may be due to py2app is not able to find terminal-notifier location. Any suggestion for this issue – imp Oct 25 '13 at 09:17
  • 4
    There's a Python project called [pync](https://pypi.python.org/pypi/pync) which wraps terminal-notifier. Perhaps you could update the answer to mention it? – David Z Jan 07 '16 at 06:09
  • 7
    For an easy solution with no additional dependencies to install, you can use AppleScript to post the notification. See [my answer](http://stackoverflow.com/a/41318195/2874789) for how! (Sorry to hijack the comments here, just want to make sure people know that it's possible without installing anything :) ) – Christopher Shroba Feb 05 '17 at 00:58
  • Can we increase width or height or this popup? – Aaditya Ura Sep 24 '17 at 07:38
20

copy from: https://gist.github.com/baliw/4020619

following works for me.

import Foundation
import objc
import AppKit
import sys

NSUserNotification = objc.lookUpClass('NSUserNotification')
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')

def notify(title, subtitle, info_text, delay=0, sound=False, userInfo={}):
    notification = NSUserNotification.alloc().init()
    notification.setTitle_(title)
    notification.setSubtitle_(subtitle)
    notification.setInformativeText_(info_text)
    notification.setUserInfo_(userInfo)
    if sound:
        notification.setSoundName_("NSUserNotificationDefaultSoundName")
    notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(delay, Foundation.NSDate.date()))
    NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)


notify("Test message", "Subtitle", "This message should appear instantly, with a sound", sound=True)
sys.stdout.write("Notification sent...\n")
sesame
  • 815
  • 1
  • 10
  • 18
  • 2
    Is there a way to custom the icon showing in notification? The message doesn't wrap, any workaround? – Meow Oct 12 '14 at 06:30
  • 1
    Is it possible to set a callback, such as clicking the notification opens a URL? – themthem Jun 06 '20 at 04:26
9

For a Python only implementation, I've modified the code that someone posted as part of another related question, and is working well for me:

import mmap, os, re, sys
from PyObjCTools import AppHelper
import Foundation
import objc
import AppKit
import time
from threading import Timer

from datetime import datetime, date

# objc.setVerbose(1)

class MountainLionNotification(Foundation.NSObject):
    # Based on http://stackoverflow.com/questions/12202983/working-with-mountain-lions-notification-center-using-pyobjc

    def init(self):
        self = super(MountainLionNotification, self).init()
        if self is None: return None

        # Get objc references to the classes we need.
        self.NSUserNotification = objc.lookUpClass('NSUserNotification')
        self.NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')

        return self

    def clearNotifications(self):
        """Clear any displayed alerts we have posted. Requires Mavericks."""

        NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
        NSUserNotificationCenter.defaultUserNotificationCenter().removeAllDeliveredNotifications()

    def notify(self, title, subtitle, text, url):
        """Create a user notification and display it."""

        notification = self.NSUserNotification.alloc().init()
        notification.setTitle_(str(title))
        notification.setSubtitle_(str(subtitle))
        notification.setInformativeText_(str(text))
        notification.setSoundName_("NSUserNotificationDefaultSoundName")
        notification.setHasActionButton_(True)
        notification.setActionButtonTitle_("View")
        notification.setUserInfo_({"action":"open_url", "value":url})

        self.NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
        self.NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)

        # Note that the notification center saves a *copy* of our object.
        return notification

    # We'll get this if the user clicked on the notification.
    def userNotificationCenter_didActivateNotification_(self, center, notification):
        """Handler a user clicking on one of our posted notifications."""

        userInfo = notification.userInfo()
        if userInfo["action"] == "open_url":
            import subprocess
            # Open the log file with TextEdit.
            subprocess.Popen(['open', "-e", userInfo["value"]])

You could likely clean up the import statements to remove some unneeded imports.

Simon
  • 174
  • 1
  • 3
  • I know this answer is old, but I'm getting an error at `class MountainLionNotification(Foundation.NSObject):`: `objc.BadPrototypeError: Objective-C expects 1 arguments, Python argument has 5 arguments for `. Do you have any idea how to fix it? – themthem Jun 06 '20 at 04:28
  • Change the definition of the notify method to include four underscores, like this: `def notify____(self, title, subtitle, text, url):` – Bobby Zandavi Apr 15 '22 at 22:28
7

Another choice is a python lib named pync, maybe it's a better choice. pync is a simple Python wrapper around the terminal-notifier command-line tool, which allows you to send User Notifications to the Notification Center on Mac OS X 10.10, or higher.

Installation:

pip install pync

Examples:

from pync import Notifier

Notifier.notify('Hello World')
Notifier.notify('Hello World', title='Python')
Notifier.notify('Hello World', group=os.getpid())
Notifier.notify('Hello World', activate='com.apple.Safari')
Notifier.notify('Hello World', open='http://github.com/')
Notifier.notify('Hello World', execute='say "OMG"')

Notifier.remove(os.getpid())

Notifier.list(os.getpid())
Song Bi
  • 729
  • 1
  • 7
  • 11
4

Try ntfy if you also want the script to be able to communicate with you over other devices.

Installation

[sudo] pip install ntfy 

where pip refers to the Package Installer of the target Python version

For Python3 installation:

[sudo] pip3 install ntfy    

Usage

I use this simple function for notifications regarding command executions and download completions:

def notification(title, message):
    """Notifies the logged in user about the download completion."""

    import os
    cmd = 'ntfy -t {0} send {1}'.format(title, message)
    os.system(cmd)

notification("Download Complete", "Mr.RobotS01E05.mkv saved at /path")

Advantages of ntfy

  1. This tool is quite handy as it logs all the notifications directly to the Notification Center rather than referring to other third-party application.

  2. Multiple Backend supports: This tool can connect to you over any device through services such as PushBullet, SimplePush, Slack, Telegram etc. Check the entire list of supported backend services here.

Kshitij Saraogi
  • 6,821
  • 8
  • 41
  • 71
4

Here is a way (You need the Foundation module):

from Foundation import NSUserNotification
from Foundation import NSUserNotificationCenter
from Foundation import NSUserNotificationDefaultSoundName


class Notification():
    def notify(self, _title, _message, _sound = False):
        self._title = _title
        self._message = _message
        self._sound = _sound

        self.notification = NSUserNotification.alloc().init()
        self.notification.setTitle_(self._title)
        self.notification.setInformativeText_(self._message)
        if self._sound == True:
            self.notification.setSoundName_(NSUserNotificationDefaultSoundName)

        center = NSUserNotificationCenter.defaultUserNotificationCenter()
        center.deliverNotification_(self.notification)

N = Notification()
N.notify(_title="SOME", _message="Something", _sound=True)

This works only for MAC. Hope you enjoy!

  • For anyone who reads this, I made a Python library named `simple-notifications` based on this approach. Hope it's still relevant. – Mia Aug 08 '19 at 07:31
3

I recommend using the new library macos-notifcations. It is a Python library to make it as easy as possible to create interactable notifications.

A simple example:

from mac_notifications import client

client.create_notification(
    title="Meeting starts now!",
    subtitle="Team Standup",
    icon="/Users/jorrick/zoom.png",
    action_button_str="Join zoom meeting",
    action_button_callback=partial(join_zoom_meeting, conf_number=zoom_conf_number)
)

Features

  • Easy python interface. It's as simple as 'client.create_notification(title="Meeting starts now!", subtitle="Team Standup")'
  • Ability to add action buttons with callbacks!
  • Ability to reply to notifications!
  • ⌚ Delayed notifications.
  • Just a single dependency on pyobjc.

Disclaimer: I am the creator of macos-notifications.

Jorrick Sleijster
  • 935
  • 1
  • 9
  • 22
0

Many answers above suggest forking foreach notification.

If you may have many notifications, and you'd like to reuse one child process, then may I suggest:

from subprocess import Popen, PIPE, DEVNULL
import atexit


class MacOS_Notifications:
    """ one child process to rule all notifications """

    def __init__(self):
        subprocess_argv = ['osascript', '-l', 'JavaScript', '-i']
        io_redirections = {"stdin": PIPE, "stdout": DEVNULL}

        self.subproc = Popen(subprocess_argv, **io_redirections)
        atexit.register(self.__del__)  # cleanup: kill child

        self.__send_to_subproc(f'''
            const app = Application.currentApplication();
            app.includeStandardAdditions = true;
        ''')

    def __send_to_subproc(self, script):
        self.subproc.stdin.write(script.encode())
        self.subproc.stdin.flush()

    def __del__(self):
        self.subproc.stdin.close()
        self.subproc.wait()

    def display(self, message, title, subtitle=""):
        self.__send_to_subproc(f'''
            app.displayNotification("{message}", {{
                withTitle: "{title}",
                subtitle: "{subtitle}",
            }});
        ''')

Use it like this:

notifications = MacOS_Notifications()  # fork is done here

# later...

notifications.display(title="Hello", message="msg 1")  # this is cheap

notifications.display(title="Hello", message="msg 2")  # this is cheap