StateStorage

The StateStorage class provides persistent key-value storage that survives across Qtile config reloads and restarts. This solves the problem that Qtile does not natively persist state across config reloads.

Basic Usage

from qtile_expanded import StateStorage

# Create a storage instance with a namespace
storage = StateStorage("my_widget")

# Set values
storage.set("counter", 42)
storage["theme"] = "dark"  # Bracket notation also works

# Get values
counter = storage.get("counter", default=0)
theme = storage["theme"]

# Check if key exists
if "counter" in storage:
    print(f"Counter: {storage['counter']}")

Automatic Saving

By default, StateStorage automatically saves after each write:

storage = StateStorage("auto_save")
storage.set("key", "value")  # Automatically saved

You can disable auto-save and save manually:

storage = StateStorage("manual_save", auto_save=False)
storage.set("key", "value")
storage.save()  # Manual save

Convenience Methods

StateStorage provides several convenience methods for common operations:

# Increment a value
storage.increment("counter")  # +1
storage.increment("counter", 5)  # +5

# Decrement a value
storage.decrement("counter")  # -1
storage.decrement("counter", 2)  # -2

# Toggle a boolean
storage.toggle("enabled")  # True -> False -> True
storage.toggle("flag", default=True)  # Start with True if not set

# Update multiple values
storage.update({"key1": "value1", "key2": "value2"})

Context Manager

StateStorage can be used as a context manager for automatic saving:

storage = StateStorage("context_test", auto_save=False)

with storage:
    storage["temp"] = "value"
    # Other operations...
# Automatically saved when exiting the context

Dict-like Interface

StateStorage supports a dict-like interface:

storage = StateStorage("dict_like")

# Set using bracket notation
storage["key"] = "value"

# Get using bracket notation
value = storage["key"]

# Check if key exists
if "key" in storage:
    print("Key exists")

# Delete
del storage["key"]

# Get all keys, values, or items
keys = storage.keys()
values = storage.values()
items = storage.items()

# Get length
count = len(storage)

# Clear all
storage.clear()

Multiple Namespaces

Use different namespaces to organize state by component:

# State for widget1
widget1_state = StateStorage("widget1")
widget1_state.set("setting", "value")

# State for widget2
widget2_state = StateStorage("widget2")
widget2_state.set("setting", "different value")

# Each has its own storage
print(widget1_state.get("setting"))  # "value"
print(widget2_state.get("setting"))  # "different value"

Hook Integration

For automatic state saving on Qtile config reload and shutdown, use setup_qtile_hooks:

from qtile_expanded import StateStorage, setup_qtile_hooks

def main(q):
    setup_qtile_hooks(q)  # Auto-save on reload/shutdown

    storage = StateStorage("my_widget")
    # ... your config
    return []

Storage Location

Data is stored as JSON in:

~/.cache/qtile/qtile_expanded/{namespace}.json

You can specify a custom cache directory:

from pathlib import Path

custom_dir = Path.home() / ".my_cache"
storage = StateStorage("my_widget", cache_dir=custom_dir)

Thread Safety

StateStorage is thread-safe and uses locks for concurrent access, making it safe to use from multiple threads.

Singleton Pattern

You can use get_instance to get or create a singleton instance:

from qtile_expanded import StateStorage

# Get or create singleton instance
storage1 = StateStorage.get_instance("shared")
storage2 = StateStorage.get_instance("shared")

# Both variables reference the same instance
assert storage1 is storage2

This is useful when you want multiple components to share the same state.

API Reference

StateStorage - Persistent state storage for Qtile.

This module provides a simple key-value storage that persists across Qtile config reloads and restarts. Data is stored as JSON in the Qtile cache directory.

Features: - Automatic loading/saving of state - Thread-safe operations - Support for primitive types (str, int, float, bool, list, dict) - Namespace support for organizing state - Hook integration for automatic save on config reload

Usage:

from qtile_expanded.storage import StateStorage

# Create a storage instance storage = StateStorage(“my_app”)

# Set values storage.set(“counter”, 42) storage.set(“settings”, {“theme”: “dark”, “notifications”: True})

# Get values counter = storage.get(“counter”, default=0) settings = storage.get(“settings”, default={})

# Delete values storage.delete(“counter”)

# Save explicitly storage.save()

# Use as context manager (auto-saves) with storage:

storage.set(“temporary”, “value”)

class qtile_expanded.storage.StateStorage(namespace: str, cache_dir: str | Path | None = None, auto_save: bool = True)[source]

Bases: object

Persistent key-value storage for Qtile that survives config reloads.

Data is stored as JSON in ~/.cache/qtile/qtile_expanded/{namespace}.json

Args:

namespace: Unique identifier for this storage (e.g., “my_widget”) cache_dir: Override the Qtile cache directory (default: ~/.cache/qtile) auto_save: Whether to automatically save after each write (default: True)

Attributes:

data: The loaded state dictionary

__init__(namespace: str, cache_dir: str | Path | None = None, auto_save: bool = True)[source]

Initialize StateStorage with a namespace.

clear() None[source]

Clear all data from the storage.

decrement(key: str, amount: int = 1) int[source]

Decrement a numeric value in the storage.

Args:

key: The key to decrement amount: The amount to decrement by (default: 1)

Returns:

The new value

delete(key: str) bool[source]

Delete a value from the storage.

Args:

key: The key to delete

Returns:

True if the key existed and was deleted, False otherwise

get(key: str, default: Any | None = None) Any[source]

Get a value from the storage.

Args:

key: The key to retrieve default: Default value if key doesn’t exist

Returns:

The stored value, or default if not found

classmethod get_instance(namespace: str, **kwargs) StateStorage[source]

Get or create a StateStorage instance with the given namespace.

This ensures only one instance per namespace exists.

Args:

namespace: The storage namespace **kwargs: Additional arguments passed to StateStorage constructor

Returns:

The StateStorage instance

has(key: str) bool[source]

Check if a key exists in the storage.

increment(key: str, amount: int = 1) int[source]

Increment a numeric value in the storage.

Args:

key: The key to increment amount: The amount to increment by (default: 1)

Returns:

The new value

items() list[tuple[str, Any]][source]

Get all key-value pairs in the storage.

keys() list[str][source]

Get all keys in the storage.

save() None[source]

Save state to the JSON file.

classmethod save_all() None[source]

Save all registered StateStorage instances.

set(key: str, value: Any) None[source]

Set a value in the storage.

Args:

key: The key to store the value under value: The value to store (must be JSON-serializable)

toggle(key: str, default: bool = False) bool[source]

Toggle a boolean value in the storage.

Args:

key: The key to toggle default: Default value if key doesn’t exist

Returns:

The new value

update(other: dict[str, Any]) None[source]

Update the storage with multiple key-value pairs.

Args:

other: Dictionary of key-value pairs to update

values() list[Any][source]

Get all values in the storage.

qtile_expanded.storage.setup_qtile_hooks(qtile) None[source]

Set up Qtile hooks to automatically save state before reload/shutdown.

This function should be called from your Qtile config:

from qtile_expanded.storage import setup_qtile_hooks

def main(q):

setup_qtile_hooks(q) # … rest of your config

Args:

qtile: The Qtile instance (usually ‘q’ in config.py)