File attachment support for notifications including local files, URLs, and various attachment sources with size management and caching. AppriseAttachment provides comprehensive attachment handling for notifications across different file types and sources.
Manages file attachments from multiple sources including local files, remote URLs, and data streams. Provides size management, caching, and format detection.
class AppriseAttachment:
def __init__(self, paths=None, asset=None, cache=True, location=None):
"""
Initialize attachment manager.
Parameters:
- paths (str|list, optional): File paths, URLs, or attachment objects
- asset (AppriseAsset, optional): Asset configuration
- cache (bool): Enable attachment caching
- location (str, optional): Base location for relative paths
"""
def add(self, attachments, asset=None, cache=None):
"""
Add attachment(s) to the manager.
Parameters:
- attachments (str|list): File paths, URLs, or attachment objects
- asset (AppriseAsset, optional): Asset configuration for attachments
- cache (bool, optional): Override default caching behavior
Returns:
bool: True if at least one attachment was successfully added
"""
def clear(self):
"""
Remove all attachments from the manager.
"""
def pop(self, index=-1):
"""
Remove and return attachment by index.
Parameters:
- index (int): Index of attachment to remove (-1 for last)
Returns:
AttachBase: Removed attachment object
"""
def size(self, mime_type=None):
"""
Get total size of attachments.
Parameters:
- mime_type (str, optional): Filter by MIME type
Returns:
int: Total size in bytes of matching attachments
"""
@staticmethod
def instantiate(url, asset=None, cache=None):
"""
Create attachment from URL or path.
Parameters:
- url (str): File path or URL to attachment
- asset (AppriseAsset, optional): Asset configuration
- cache (bool, optional): Enable caching for this attachment
Returns:
AttachBase: Attachment object or None if failed
"""
def sync(self):
"""
Synchronize all attachments and validate accessibility.
Returns:
bool: True if all attachments are accessible
"""
@property
def attachments(self):
"""
List of attachment objects.
Returns:
list: AttachBase objects for all loaded attachments
"""
@property
def max_file_size(self):
"""
Maximum allowed file size for attachments.
Returns:
int: Maximum file size in bytes
"""Base class for attachment handlers supporting different attachment sources and types.
class AttachBase(URLBase):
def download(self):
"""
Download attachment content.
Returns:
bytes: Attachment content data
"""
def exists(self):
"""
Check if attachment exists and is accessible.
Returns:
bool: True if attachment exists
"""
def invalidate(self):
"""
Invalidate cached attachment content.
"""
@property
def name(self):
"""
Attachment filename.
Returns:
str: Name of the attachment file
"""
@property
def path(self):
"""
Attachment path or URL.
Returns:
str: Full path or URL to attachment
"""
@property
def mimetype(self):
"""
MIME type of the attachment.
Returns:
str: MIME type (e.g., 'image/jpeg', 'application/pdf')
"""
@property
def size(self):
"""
Size of the attachment.
Returns:
int: Size in bytes
"""Singleton manager for attachment plugin discovery and loading.
class AttachmentManager:
def load_modules(self, path=None, name=None):
"""
Load attachment plugin modules.
Parameters:
- path (str, optional): Path to plugin directory
- name (str, optional): Specific plugin name to load
Returns:
bool: True if modules were loaded successfully
"""
def plugins(self):
"""
Get available attachment plugins.
Returns:
dict: Available attachment plugin classes
"""
def schemas(self):
"""
Get supported attachment URL schemas.
Returns:
list: Supported URL schemas for attachments
"""import apprise
# Create attachment from local file
attach = apprise.AppriseAttachment('/path/to/document.pdf')
# Create Apprise instance and send with attachment
apobj = apprise.Apprise()
apobj.add('mailto://user:pass@gmail.com')
apobj.notify(
body='Please find the attached document',
title='Document Delivery',
attach=attach
)import apprise
# Add multiple attachments from different sources
attach = apprise.AppriseAttachment()
# Local files
attach.add('/path/to/report.pdf')
attach.add('/path/to/images/chart.png')
# Remote URL
attach.add('https://example.com/data.xlsx')
# Multiple files with glob pattern
attach.add('/path/to/logs/*.log')
print(f"Total attachments: {len(attach.attachments)}")
print(f"Total size: {attach.size()} bytes")
# Send notification with all attachments
apobj = apprise.Apprise()
apobj.add('slack://token/channel')
apobj.notify(
body='Daily report with attachments',
title='Daily Report',
attach=attach
)import apprise
attach = apprise.AppriseAttachment()
# Add attachments with size checking
attach.add('/path/to/large-file.zip')
# Check total size before sending
max_size = 25 * 1024 * 1024 # 25MB limit
if attach.size() > max_size:
print(f"Attachments too large: {attach.size()} bytes")
# Remove largest attachment
attach.pop() # Remove last added
else:
print(f"Attachments OK: {attach.size()} bytes")
# Filter by MIME type
image_size = attach.size(mime_type='image/*')
print(f"Image attachments: {image_size} bytes")
# List attachment details
for i, attachment in enumerate(attach.attachments):
print(f"Attachment {i + 1}:")
print(f" Name: {attachment.name}")
print(f" Path: {attachment.path}")
print(f" Type: {attachment.mimetype}")
print(f" Size: {attachment.size} bytes")
print(f" Exists: {attachment.exists()}")import apprise
# Create attachment manager with caching disabled
attach = apprise.AppriseAttachment(cache=False)
# Add remote attachment with caching enabled
attach.add(
'https://example.com/report.pdf',
cache=True # Enable caching for this specific attachment
)
# Add local file (caching not applicable)
attach.add('/path/to/local-file.txt')
# Check if attachment content is cached
for attachment in attach.attachments:
if hasattr(attachment, 'invalidate'):
# Clear cache if needed
attachment.invalidate()
print(f"Cache cleared for {attachment.name}")import apprise
import tempfile
import os
# Create temporary file for attachment
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write('This is a dynamically created attachment\n')
f.write('Generated at runtime for notification\n')
temp_path = f.name
try:
# Create attachment from temporary file
attach = apprise.AppriseAttachment(temp_path)
# Send notification
apobj = apprise.Apprise()
apobj.add('discord://webhook_id/webhook_token')
success = apobj.notify(
body='Dynamic attachment example',
title='Runtime Generated Content',
attach=attach
)
if success:
print("Notification with dynamic attachment sent successfully")
finally:
# Cleanup temporary file
os.unlink(temp_path)import apprise
try:
attach = apprise.AppriseAttachment()
# Add attachment with validation
success = attach.add('/path/to/file.pdf')
if not success:
print("Failed to add attachment")
# Validate all attachments exist
for attachment in attach.attachments:
if not attachment.exists():
print(f"Warning: Attachment not found: {attachment.path}")
else:
print(f"Attachment OK: {attachment.name} ({attachment.size} bytes)")
# Check size limits
if attach.size() > attach.max_file_size:
print(f"Attachments exceed size limit: {attach.size()} > {attach.max_file_size}")
# Remove attachments until under limit
while attach.size() > attach.max_file_size and attach.attachments:
removed = attach.pop()
print(f"Removed attachment: {removed.name}")
except apprise.AttachmentException as e:
print(f"Attachment error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")import apprise
# Create attachment manager with custom location
attach = apprise.AppriseAttachment(location='/base/path')
# Add relative path attachment (resolved relative to location)
attach.add('documents/report.pdf') # Resolves to /base/path/documents/report.pdf
# Create attachment directly using instantiate method
pdf_attach = apprise.AppriseAttachment.instantiate('/path/to/document.pdf')
if pdf_attach:
print(f"Created attachment: {pdf_attach.name}")
print(f"MIME type: {pdf_attach.mimetype}")
# Add multiple attachment types
attachments = [
'/path/to/image.jpg', # Local image
'https://example.com/data.csv', # Remote CSV
'/path/to/archive.zip' # Local archive
]
for att_path in attachments:
att_obj = apprise.AppriseAttachment.instantiate(att_path)
if att_obj:
attach.add(att_obj)
print(f"Added {att_obj.mimetype}: {att_obj.name}")import apprise
# Different services support different attachment types
def send_with_appropriate_attachments():
# Email services - support most file types
email_attach = apprise.AppriseAttachment([
'/path/to/document.pdf',
'/path/to/spreadsheet.xlsx',
'/path/to/image.png'
])
apobj_email = apprise.Apprise()
apobj_email.add('mailto://user:pass@gmail.com')
apobj_email.notify(
body='Email with various attachments',
title='Multi-format Attachments',
attach=email_attach
)
# Slack/Discord - typically support images and common formats
image_attach = apprise.AppriseAttachment([
'/path/to/chart.png',
'/path/to/screenshot.jpg'
])
apobj_slack = apprise.Apprise()
apobj_slack.add('slack://token/channel')
apobj_slack.notify(
body='Image attachments for team',
title='Visual Update',
attach=image_attach
)
send_with_appropriate_attachments()