Aller au contenu

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

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.

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 PersistentStoreMode
from 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.AUTO
ModeBehaviourWhen to Use
MEMORYIn-memory only, no disk usageEphemeral or restricted environments; This is the default for all plugins unless overriden with the storage_mode variable during it’s class initialzation
AUTODisk when available, otherwise memoryFlushes to disk only when the plugin is destroyed (at end) Recommended default
FLUSHFlush to disk aggressivelyTrades 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.

Each plugin receives a ready-to-use store via self.store.

# Inside a NotifyBase plugin
store = self.store

The store behaves like a dictionary, but with expiry, persistence, and safety semantics layered on top.

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.

# Store a value
self.store['server_version'] = '1.8.2'
# Retrieve a value
version = self.store.get('server_version')
if version:
...
  • Values may be any JSON-serialisable type
  • Retrieval returns None if missing or expired
  • Disk persistence is automatic when enabled

A stored value:

  • Exists in memory until expired or cleared
  • May remain on disk after expiry
  • Is treated as invalid once expired
  • Evaluates as False when expired
if 'key' in self.store:
# Only true if key exists AND has not expired

Expiry is always enforced, even if the file still exists on disk.

Persistent storage also supports direct file access for plugin-specific data.

# Write raw data
self.store.write(b'my binary content')
# Read it back
content = self.store.read()
  • Content is returned as bytes
  • Files may be compressed by default
  • Useful for certificates, tokens, or blobs
# Remove specific keys
self.store.clear('key1', 'key2')
# Remove everything
self.store.clear()
self.store.delete('key')
self.store.delete('key1', 'key2')
self.store.prune()

Expired entries are removed from memory and flagged for disk cleanup.

These operations are optional and primarily useful for long-running services.

# Remove expired persistent files
PersistentStore.disk_prune(path)
# Scan for namespaces
PersistentStore.disk_scan(path)

These functions operate outside of a plugin instance and should be used cautiously.

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.

  • 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

The following core plugins already leverage:

  • Matrix: Uses persistent storage to cache Matrix session and discovery metadata, including the access_token, home_server, and user_id obtained during login or registration. It also stores a transaction_id to prevent duplicate message handling, caches room alias resolution results (mapping aliases to resolved room_id and home_server), and persists .well-known discovery 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 from address 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_in value (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.