Persistent Storage
Ce contenu n’est pas encore disponible dans votre langue.
Persistent storage allows Apprise plugins to safely retain small amounts of state between executions.
This feature is intended for plugin authors, and exists to prevent unnecessary or repeated requests to upstream services when the information can be reused locally.
Persistent storage is opportunistic and best-effort. If disk storage is unavailable or unsafe, Apprise automatically degrades to memory-only operation without failing notifications.
Persistent storage is:
- A lightweight key/value store
- Namespaced per plugin
- Expiry-aware
- Crash-resilient
- Safe in both short-lived and long-running processes
Persistent storage is not:
- A general database
- A user-facing cache
- A replacement for application state
- A guaranteed long-term persistence layer
It is intentionally scoped to small, frequently reused metadata, such as:
- Capability discovery results
- Negotiated API endpoints
- Remote identifiers
- Authentication or feature flags
- Cached service metadata
How Persistent Storage Is Enabled
Section titled “How Persistent Storage Is Enabled”Persistent storage is controlled by the Apprise Asset Configuration, not by the plugin directly.
The AppriseAsset object defines:
- Where persistent data may be written
- Whether disk persistence is allowed
- How Apprise behaves if storage is unavailable
If no storage path is available, Apprise transparently falls back to memory-only storage.
Plugins do not need to handle this fallback themselves.
Plugin opt-in (storage_mode)
Section titled “Plugin opt-in (storage_mode)”Persistent storage is always available through self.store, but disk persistence is opt-in per plugin.
By default, plugins treat the store as memory-only. To allow Apprise to reuse cached state across executions (when the active AppriseAsset permits it), set a class-level storage_mode on your plugin:
from apprise.common import PersistentStoreModefrom apprise.plugins import NotifyBase
class MyPlugin(NotifyBase): # Allow persistent storage to use disk when available. # Without this, the store behaves as memory-only for the plugin. storage_mode = PersistentStoreMode.AUTOPersistent Storage Modes
Section titled “Persistent Storage Modes”| Mode | Behaviour | When to Use |
|---|---|---|
MEMORY | In-memory only, no disk usage | Ephemeral or restricted environments; This is the default for all plugins unless overriden with the storage_mode variable during it’s class initialzation |
AUTO | Disk when available, otherwise memory | Flushes to disk only when the plugin is destroyed (at end) Recommended default |
FLUSH | Flush to disk aggressively | Trades higher I/O for more durability. Long-running daemons with critical metadata |
AUTO is the safest and most portable choice across containers, CI, and system services.
Accessing Persistent Storage in a Plugin
Section titled “Accessing Persistent Storage in a Plugin”Each plugin receives a ready-to-use store via self.store.
# Inside a NotifyBase pluginstore = self.storeThe store behaves like a dictionary, but with expiry, persistence, and safety semantics layered on top.
Cache Identity and url_identifier
Section titled “Cache Identity and url_identifier”Persistent storage is scoped using a plugin’s URL identity. The identity is derived from what your plugin returns in url_identifier.
When you think of the Universal URL Syntax, you are ONLY building a url_identifier using the service:// and credentials, for the best results, e.g:
service://credentials/direction/?parameter=value| || || || @property || url_identifier() |It should not include:
- recipient or target routing (anything that belongs in the URL path for delivery)
- direction-like components used only to choose a destination
- optional query-string settings that do not change the upstream identity
This allows you to run many notifications targeting different recipients while still sharing cached state for the same upstream server, such as OAuth tokens, discovery results, and resolved identifiers.
Key / Value Usage Patterns
Section titled “Key / Value Usage Patterns”Setting and Reading Values
Section titled “Setting and Reading Values”# Store a valueself.store['server_version'] = '1.8.2'
# Retrieve a valueversion = self.store.get('server_version')if version: ...- Values may be any JSON-serialisable type
- Retrieval returns
Noneif missing or expired - Disk persistence is automatic when enabled
Expiring Cached Values
Section titled “Expiring Cached Values”# Cache for 1 hourself.store.set( 'capabilities', {'markdown': True, 'html': False}, expires=3600,)Expiry may be specified as:
- Seconds from now (
intorfloat) - A
datetime True(expire immediately)NoneorFalse(never expires)
Expired entries automatically evaluate as invalid.
Memory-Only Entries
Section titled “Memory-Only Entries”# Cache for runtime onlyself.store.set( 'session_token', token, persistent=False,)- Stored in memory only
- Never written to disk
- Useful for request-scoped or sensitive values
Understanding Expiry Semantics
Section titled “Understanding Expiry Semantics”A stored value:
- Exists in memory until expired or cleared
- May remain on disk after expiry
- Is treated as invalid once expired
- Evaluates as
Falsewhen expired
if 'key' in self.store: # Only true if key exists AND has not expiredExpiry is always enforced, even if the file still exists on disk.
File-Based Storage (Advanced)
Section titled “File-Based Storage (Advanced)”Persistent storage also supports direct file access for plugin-specific data.
# Write raw dataself.store.write(b'my binary content')
# Read it backcontent = self.store.read()- Content is returned as
bytes - Files may be compressed by default
- Useful for certificates, tokens, or blobs
# Write uncompressed contentself.store.write( 'plain text', key='custom-key', compress=False,)
content = self.store.read('custom-key', compress=False)Each key maps to its own persistent file.
with self.store.open('key', 'wb') as fp: fp.write(b'data')
with self.store.open('key', 'rb') as fp: content = fp.read()This mirrors standard Python file semantics while remaining namespace-safe.
Cache Maintenance and Cleanup
Section titled “Cache Maintenance and Cleanup”Clearing Entries
Section titled “Clearing Entries”# Remove specific keysself.store.clear('key1', 'key2')
# Remove everythingself.store.clear()Deleting Persistent Files
Section titled “Deleting Persistent Files”self.store.delete('key')self.store.delete('key1', 'key2')Pruning Expired Entries
Section titled “Pruning Expired Entries”self.store.prune()Expired entries are removed from memory and flagged for disk cleanup.
Disk-Level Operations (Advanced)
Section titled “Disk-Level Operations (Advanced)”These operations are optional and primarily useful for long-running services.
# Remove expired persistent filesPersistentStore.disk_prune(path)
# Scan for namespacesPersistentStore.disk_scan(path)These functions operate outside of a plugin instance and should be used cautiously.
Automatic Safety Guarantees
Section titled “Automatic Safety Guarantees”Persistent storage is designed to never block notifications.
If disk access fails due to:
- Permissions
- Read-only filesystems
- Corruption
- Container restrictions
Apprise automatically degrades to MEMORY mode and continues operating.
No plugin-side error handling is required.
Best Practices for Plugin Authors
Section titled “Best Practices for Plugin Authors”- Treat persistent storage as an optimisation, not a dependency
- Store small values only
- Always handle missing or expired data gracefully
- Prefer expiry over manual invalidation
- Use clear, plugin-specific keys
- Assume storage may be unavailable at runtime
Plugins That Use Persistent Storage
Section titled “Plugins That Use Persistent Storage”The following core plugins already leverage:
-
Matrix: Uses persistent storage to cache Matrix session and discovery metadata, including the
access_token,home_server, anduser_idobtained during login or registration. It also stores atransaction_idto prevent duplicate message handling, caches room alias resolution results (mapping aliases to resolvedroom_idandhome_server), and persists.well-knowndiscovery results. This avoids repeated authentication, discovery, and room resolution calls across executions. -
Nextcloud: Uses persistent storage to cache recipient discovery results. Group name resolution and “all users” lookups are cached to avoid repeated API calls when the same groups are referenced frequently. Cache lifetime is controlled via the plugin’s group discovery cache configuration.
-
Office365: Uses persistent storage to cache resolved sender identity metadata. When the configured sender is not a fully qualified email address, the plugin resolves the correct
fromaddress and display name via Microsoft Graph and stores the result so subsequent notifications do not need to repeat the lookup. -
Opsgenie: Uses persistent storage to cache Opsgenie alert request identifiers returned when alerts are created. These identifiers are indexed under a stable, derived key and retained for an extended period, allowing follow-up actions such as acknowledge, close, note, or delete to operate on previously created alerts without requiring the user to supply the alert ID again.
-
SendPulse: Uses persistent storage to cache OAuth access tokens. Tokens are stored with an expiry derived from the upstream
expires_invalue (with a safety buffer), allowing subsequent notifications to reuse the token and avoid unnecessary re-authentication requests. -
Telegram: Uses persistent storage to cache a detected bot owner user ID when automatic owner detection is enabled. Once discovered, the owner ID is reused for future notifications so detection does not need to be repeated on subsequent runs.
Feel free to use these examples to help shape your own design or improve on a plugin that exists already that could really benifit from the Persistent Store.