Python library for cross-platform desktop notifications
—
Classes for managing icons, sounds, and file attachments with support for both file paths, URIs, and system-named resources across different platforms.
Icon resource class supporting file paths, URIs, and system-named icons with automatic format conversion and platform compatibility.
@dataclass(frozen=True)
class Icon(Resource):
path: Path | None = None
"""Local file system path to icon image file"""
uri: str | None = None
"""URI reference to icon resource (file://, http://, etc.)"""
name: str | None = None
"""System icon name (platform-specific named icons)"""
def as_uri(self) -> str:
"""
Convert resource to URI string format.
Returns:
str: URI representation of the icon resource
Raises:
AttributeError: If no path, URI, or name is provided
"""
def as_path(self) -> Path:
"""
Convert resource to Path object.
Note: URI scheme information is lost during conversion.
Returns:
Path: Path representation of the icon resource
Raises:
AttributeError: If no path or URI is provided
"""
def as_name(self) -> str:
"""
Get system icon name.
Returns:
str: System icon name
Raises:
AttributeError: If no name is provided
"""
def is_named(self) -> bool:
"""
Check if icon was initialized with system name.
Returns:
bool: True if using named system icon
"""
def is_file(self) -> bool:
"""
Check if icon was initialized with file path or URI.
Returns:
bool: True if using file-based icon
"""Sound resource class for notification audio with same interface as Icon, supporting file-based and system-named sounds.
@dataclass(frozen=True)
class Sound(Resource):
path: Path | None = None
"""Local file system path to sound file"""
uri: str | None = None
"""URI reference to sound resource"""
name: str | None = None
"""System sound name (e.g., 'default', 'Tink', 'Blow')"""
def as_uri(self) -> str:
"""Convert sound resource to URI string format"""
def as_path(self) -> Path:
"""Convert sound resource to Path object"""
def as_name(self) -> str:
"""Get system sound name"""
def is_named(self) -> bool:
"""Check if using named system sound"""
def is_file(self) -> bool:
"""Check if using file-based sound"""File attachment class for notification previews, limited to file paths and URIs (no system-named attachments).
@dataclass(frozen=True)
class Attachment(FileResource):
path: Path | None = None
"""Local file system path to attachment file"""
uri: str | None = None
"""URI reference to attachment file"""
def as_uri(self) -> str:
"""
Convert attachment to URI string format.
Returns:
str: URI representation of the attachment
"""
def as_path(self) -> Path:
"""
Convert attachment to Path object.
Returns:
Path: Path representation of the attachment
"""Base classes providing common resource management functionality.
@dataclass(frozen=True)
class FileResource:
"""Base class for file-based resources (path or URI only)"""
path: Path | None = None
"""Local file system path"""
uri: str | None = None
"""URI reference to resource"""
def __post_init__(self) -> None:
"""Validates that only one field is set and at least one is provided"""
def as_uri(self) -> str:
"""Convert to URI string format"""
def as_path(self) -> Path:
"""Convert to Path object"""
@dataclass(frozen=True)
class Resource(FileResource):
"""Base class for resources supporting both files and system names"""
name: str | None = None
"""System resource name"""
def as_name(self) -> str:
"""Get system resource name"""
def is_named(self) -> bool:
"""Check if using system name"""
def is_file(self) -> bool:
"""Check if using file path or URI"""Pre-configured resource constants for common use cases.
DEFAULT_ICON: Icon
"""Default Python icon included with the package"""
DEFAULT_SOUND: Sound
"""Default system notification sound (platform-appropriate)"""from desktop_notifier import Icon, DesktopNotifier
from pathlib import Path
# Icon from local file path
app_icon = Icon(path=Path("/path/to/app-icon.png"))
# Icon from URI (local or remote)
web_icon = Icon(uri="https://example.com/icon.png")
file_uri_icon = Icon(uri="file:///absolute/path/to/icon.png")
# Use with notifier
notifier = DesktopNotifier(app_name="My App", app_icon=app_icon)
# Or per-notification
await notifier.send(
title="Custom Icon",
message="This notification has a custom icon",
icon=Icon(path=Path("/path/to/notification-icon.png"))
)from desktop_notifier import Icon
# Common system icons (platform-dependent availability)
info_icon = Icon(name="info")
warning_icon = Icon(name="warning")
error_icon = Icon(name="error")
mail_icon = Icon(name="mail-unread")
# Check what type of icon
print(f"Using named icon: {info_icon.is_named()}") # True
print(f"Using file icon: {info_icon.is_file()}") # False
# Linux-specific system icons
linux_icons = [
Icon(name="dialog-information"),
Icon(name="dialog-warning"),
Icon(name="dialog-error"),
Icon(name="emblem-important"),
Icon(name="call-start"),
Icon(name="call-stop")
]from desktop_notifier import Sound, DEFAULT_SOUND
# System named sounds
default_sound = DEFAULT_SOUND # Sound(name="default")
custom_system_sound = Sound(name="Tink") # macOS system sound
alert_sound = Sound(name="Blow") # Another macOS sound
# File-based sounds
custom_sound = Sound(path=Path("/path/to/notification.wav"))
remote_sound = Sound(uri="https://example.com/chime.mp3")
# Use with notifications
await notifier.send(
title="Sound Test",
message="This plays a custom sound",
sound=custom_sound
)
# Platform-specific sound names
if platform.system() == "Darwin": # macOS
sounds = [
Sound(name="Basso"),
Sound(name="Blow"),
Sound(name="Bottle"),
Sound(name="Frog"),
Sound(name="Funk"),
Sound(name="Glass"),
Sound(name="Hero"),
Sound(name="Morse"),
Sound(name="Ping"),
Sound(name="Pop"),
Sound(name="Purr"),
Sound(name="Sosumi"),
Sound(name="Submarine"),
Sound(name="Tink")
]
elif platform.system() == "Windows":
sounds = [
Sound(name="ms-winsoundevent:Notification.Default"),
Sound(name="ms-winsoundevent:Notification.IM"),
Sound(name="ms-winsoundevent:Notification.Mail"),
Sound(name="ms-winsoundevent:Notification.Reminder"),
Sound(name="ms-winsoundevent:Notification.SMS")
]from desktop_notifier import Attachment
# Image attachments for preview
image_attachment = Attachment(path=Path("/photos/vacation.jpg"))
screenshot = Attachment(uri="file:///tmp/screenshot.png")
# Document attachments
pdf_doc = Attachment(path=Path("/documents/report.pdf"))
text_file = Attachment(uri="file:///logs/error.log")
# Use with notifications
await notifier.send(
title="Photo Shared",
message="New vacation photos are ready",
attachment=image_attachment,
on_clicked=lambda: print("User wants to view photos")
)
# Platform support varies for attachment types:
# - macOS: Most image formats, PDFs, some video formats
# - Windows: Images (PNG, JPG, GIF), some video formats
# - Linux: Depends on notification daemon implementationfrom desktop_notifier import Icon, Sound, Attachment
from pathlib import Path
# Resource conversion utilities
icon = Icon(path=Path("/app/icon.png"))
# Convert between formats
icon_uri = icon.as_uri() # "file:///app/icon.png"
icon_path = icon.as_path() # Path("/app/icon.png")
# Type checking
print(f"Is file-based: {icon.is_file()}") # True
print(f"Is named: {icon.is_named()}") # False
# System named resource
system_icon = Icon(name="dialog-information")
print(f"System name: {system_icon.as_name()}") # "dialog-information"
# Validation - only one field can be set
try:
invalid_icon = Icon(path=Path("/icon.png"), name="warning")
# Raises RuntimeError: "Only a single field can be set"
except RuntimeError as e:
print(f"Validation error: {e}")
try:
empty_icon = Icon()
# Raises RuntimeError: "Either of ['path', 'uri', 'name'] must be set"
except RuntimeError as e:
print(f"Validation error: {e}")import platform
from desktop_notifier import Icon, Sound
from pathlib import Path
def get_platform_appropriate_icon(icon_type: str) -> Icon:
"""Select best icon based on platform capabilities"""
if platform.system() == "Linux":
# Use freedesktop.org standard icon names
icon_map = {
"info": "dialog-information",
"warning": "dialog-warning",
"error": "dialog-error"
}
return Icon(name=icon_map.get(icon_type, "dialog-information"))
elif platform.system() in ["Darwin", "Windows"]:
# Use custom app icons on macOS/Windows
icon_path = Path(f"assets/icons/{icon_type}.png")
if icon_path.exists():
return Icon(path=icon_path)
else:
return Icon(name=icon_type) # Fallback to system
else:
# Fallback for other platforms
return Icon(name=icon_type)
def get_platform_appropriate_sound(sound_type: str) -> Sound:
"""Select best sound based on platform"""
if platform.system() == "Darwin":
sound_map = {
"success": "Tink",
"warning": "Basso",
"error": "Sosumi",
"message": "Ping"
}
return Sound(name=sound_map.get(sound_type, "default"))
elif platform.system() == "Windows":
sound_map = {
"success": "ms-winsoundevent:Notification.Default",
"message": "ms-winsoundevent:Notification.IM"
}
return Sound(name=sound_map.get(sound_type, "default"))
else:
return Sound(name="default")
# Usage
info_icon = get_platform_appropriate_icon("info")
success_sound = get_platform_appropriate_sound("success")
await notifier.send(
title="Platform Optimized",
message="This uses the best icon and sound for your OS",
icon=info_icon,
sound=success_sound
)Install with Tessl CLI
npx tessl i tessl/pypi-desktop-notifier