module documentation

API for emitting macOS notifications.

See Also
configureNotifications.
Class Action An action is just an async method that takes its self (an instance of a notification class encapsulating the ID & data), and reacts to the specified action.
Class NotificationConfig The application-wide configuration for a notification.
Class NotificationTranslator Translate notifications from the notification ID and some user data,
Class Notifier A Notifier can deliver notifications.
Class response Undocumented
Class TextAction A TextAction is just like an Action, but it takes some text.
Function configureNotifications Configure notifications for the current application.

Configure notifications for the current application.

This is an asynchronous (using Twisted's Deferred) context manager, run with `with` statement, which works like this:

    async with configureNotifications() as cfg:
        notifier = cfg.add(MyNotificationData, MyNotificationLoader())

Each add invocation adds a category of notifications you can send, and returns an object (a Notifier) that can send that category of notification.

At the end of the async with block, the notification configuration is finalized, its state is sent to macOS, and the categories of notification your application can send is frozen for the rest of the lifetime of your process; the Notifier objects returned from add are now active nad can be used. Note that you may only call configureNotifications once in your entire process, so you will need to pass those notifiers elsewhere!

Each call to add requires 2 arguments: a notification-data class which stores the sent notification's ID and any other ancillary data transmitted along with it, and an object that can load and store that first class, when notification responses from the operating system convey data that was previously scheduled as a notification. In our example above, they can be as simple as this:

    class MyNotificationData:
        id: str

    class MyNotificationLoader:
        def fromNotification(
            self, notificationID: str, userData: dict[str, object]
        ) -> MyNotificationData:
            return MyNotificationData(notificationID)
        def toNotification(
            self,
            notification: MyNotificationData,
        ) -> tuple[str, dict[str, object]]:
            return (notification.id, {})

Then, when you want to send a notification, you can do:

    await notifier.notifyAt(
        aware(datetime.now(TZ) + timedelta(seconds=5), TZ),
        MyNotificationData("my.notification.id.1"),
        "Title Here",
        "Subtitle Here",
    )

And that will show the user a notification.

The MyNotificationData class might seem simplistic to the point of uselessness, and in this oversimplified case, it is! However, if you are sending notifications to a user, you really need to be able to respond to notifications from a user, and that's where your notification data class as well as response comes in. To respond to a notification when the user clicks on it, you can add a method like so:

    class MyNotificationData:
        id: str

        @response(identifier="response-action-1", title="Action 1")
        async def responseAction1(self) -> None:
            await answer("User pressed 'Action 1' button")

        @response.default()
        async def userClicked(self) -> None:
            await answer("User clicked the notification.")

When sent with Notifier.notifyAt, your MyNotificationData class will be serialized and deserialized with MyNotificationLoader.toNotification (converting your Python class into a macOS notification, to send along to the OS) and MyNotificationLoader.fromNotification (converting the data sent along with the user's response back into a MyNotificationData).

Note
If your app schedules a notification, then quits, when the user responds (clicks on it, uses a button, dismisses it, etc) then the OS will re-launch your application and send the notification data back in, which is why all the serialization and deserialization is required. Your process may have exited and thus the original notification will no longer be around. However, if you are just running as a Python script, piggybacking on the 'Python Launcher' app bundle, macOS will not be able to re-launch your app. Notifications going back to the same process seem to work okay, but note that as documented, macOS really requires your application to have its own bundle and its own unique CFBundleIdentifier in order to avoid any weird behavior.