Metadata-Version: 2.4
Name: flet-storage
Version: 1.1.1
Summary: A lightweight utility for simplified client-side storage management in Flet applications.
Author-email: Andrii Bogdanovych <a@bogdanovych.org>
License: MIT
Project-URL: Repository, https://github.com/BogdanovychA/flet-storage
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: flet>=0.81.0
Dynamic: license-file

# Flet Storage

> 🌐 **Translations:** [🇺🇦 Українська](https://github.com/BogdanovychA/flet-storage/blob/main/README.uk.md)

A lightweight, asynchronous, namespaced storage utility for [Flet](https://flet.dev) applications.

`FletStorage` is a powerful wrapper around Flet's built-in `SharedPreferences`. It simplifies client-side data persistence by adding automatic JSON serialization, allowing you to store and retrieve complex Python objects without manual conversion, while keeping your data organized and isolated.

This repository also includes an agent skill for Flet Storage. See the [skill file](https://github.com/BogdanovychA/flet-storage/blob/main/.agents/skills/flet-storage/SKILL.md).

## Features

- **Automatic JSON serialization:** Store and retrieve `dict`, `list`, `int`, `bool`, `str`, and `set` directly without manual conversion.
- **Namespaced storage:** Automatically prefixes keys with `app_name` to prevent data collisions between different applications on the same device.
- **Asynchronous and parallel:** Fully asynchronous API with parallel deletion in the `clear()` method for maximum performance.
- **Robust error handling:** Clear `KeyError` and `ValueError` exceptions for predictable data management.
- **Set support:** Python sets are automatically preserved during serialization and deserialization.

## Installation
```bash
pip install flet-storage
```

## Quick Start
```python
import flet as ft
from flet_storage import FletStorage


async def main(page: ft.Page):
    # Initialize storage with a unique namespace
    storage = FletStorage("my_app")

    # Save data
    await storage.set("user", {"name": "Ivan", "age": 25})
    await storage.set("settings", {"theme": "dark", "language": "en"})

    # Retrieve data
    user = await storage.get("user")
    print(user)  # {'name': 'Ivan', 'age': 25}

    # Get with default value
    config = await storage.get_or_default("config", {"version": "1.0"})
    print(config)  # {'version': '1.0'}

    # Check if key exists
    exists = await storage.contains_key("user")
    print(exists)  # True

    # Get all keys
    keys = await storage.get_keys()
    print(keys)  # ['user', 'settings']

    # Remove a key
    await storage.remove("settings")

    # Clear entire storage
    await storage.clear()

ft.run(main)
```

## Supported Data Types

FletStorage automatically serializes and deserializes the following Python types:

| Type | Description | Example |
|------|-------------|---------|
| `dict` | Dictionaries | `{"key": "value"}` |
| `list` | Lists | `[1, 2, 3]` |
| `set` | Sets (preserved) | `{"a", "b", "c"}` |
| `str` | Strings | `"hello"` |
| `int`, `float` | Numbers | `42`, `3.14` |
| `bool` | Booleans | `True`, `False` |
| `None` | None value | `None` |

### Working with Sets

Sets are automatically preserved during storage and retrieval:
```python
# Storing a set
tags = {"python", "flet", "async"}
await storage.set("tags", tags)

# Retrieving (returns a set, not a list!)
tags = await storage.get("tags")
print(type(tags))  # <class 'set'> ✅
print(tags)  # {'python', 'flet', 'async'}

# Sets in nested structures work too
data = {
    "user": "Ivan",
    "tags": {"web", "mobile"},
    "categories": ["tech", "programming"]
}
await storage.set("profile", data)
profile = await storage.get("profile")
# profile["tags"] is a set ✅
# profile["categories"] is a list ✅
```

**Technical Note:** Sets are stored internally as `{"__type__": "set", "values": [...]}`. If you need to store a dictionary with a `"__type__"` key that equals `"set"`, it may be incorrectly interpreted as a set marker during deserialization.

## API Reference

### `__init__(app_name: str)`

Initializes the storage with a unique namespace.

**Parameters:**
- `app_name` (str): The unique namespace for the application.

**Example:**
```python
storage = FletStorage("todo_app")
```

---

### `async set(key: str, obj: object) -> bool`

Serializes an object to JSON and stores it under a namespaced key.

**Parameters:**
- `key` (str): The unique key identifier (without namespace).
- `obj` (object): Any JSON-serializable object (dict, list, set, str, int, etc.).

**Returns:**
- `bool`: `True` if the operation was successful.

**Example:**
```python
await storage.set("preferences", {"notifications": True})
await storage.set("tags", {"python", "flet"})  # Sets are supported!
```

---

### `async get(key: str) -> Any`

Retrieves and deserializes an object by its key.

**Parameters:**
- `key` (str): The key identifier to look up.

**Returns:**
- `Any`: The deserialized Python object.

**Raises:**
- `KeyError`: If the key does not exist in the storage.
- `ValueError`: If the stored data is not valid JSON.

**Example:**
```python
try:
    data = await storage.get("preferences")
except KeyError:
    print("Key not found")
```

---

### `async get_or_default(key: str, default: Any = None) -> Any`

Gets the value for the given key or returns a default value if not found.

**Parameters:**
- `key` (str): The key identifier to look up.
- `default` (Any): The value to return if the key does not exist. Defaults to `None`.

**Returns:**
- `Any`: The deserialized object if found, otherwise the default value.

**Raises:**
- `ValueError`: If the stored data is not valid JSON.

**Example:**
```python
config = await storage.get_or_default("config", {"version": "1.0"})
```

---

### `async contains_key(key: str) -> bool`

Checks if a specific key exists within the application namespace.

**Parameters:**
- `key` (str): The key identifier to check.

**Returns:**
- `bool`: `True` if the key exists, `False` otherwise.

**Example:**
```python
if await storage.contains_key("user_token"):
    token = await storage.get("user_token")
```

---

### `async remove(key: str) -> bool`

Removes a specific key and its value from the storage.

**Parameters:**
- `key` (str): The key identifier to remove.

**Returns:**
- `bool`: `True` if the operation was successful.

**Example:**
```python
await storage.remove("temp_data")
```

---

### `async get_keys() -> list[str]`

Retrieves all keys belonging to the current application namespace.

**Returns:**
- `list[str]`: A list of keys with the `app_name.` prefix removed.

**Example:**
```python
all_keys = await storage.get_keys()
print(f"Stored {len(all_keys)} keys")
```

---

### `async clear() -> None`

Deletes all keys and values associated with the current application namespace. Other namespaces in `SharedPreferences` remain untouched.

**Example:**
```python
await storage.clear()  # Removes all app data
```

---

## Usage Examples

### Saving User Settings
```python
async def save_user_settings(storage: FletStorage, settings: dict):
    await storage.set("settings", settings)
    print("Settings saved")

async def load_user_settings(storage: FletStorage) -> dict:
    return await storage.get_or_default("settings", {
        "theme": "light",
        "language": "en",
        "notifications": True
    })
```

### Managing a Todo List
```python
async def add_todo(storage: FletStorage, task: str):
    todos = await storage.get_or_default("todos", [])
    todos.append({"task": task, "completed": False})
    await storage.set("todos", todos)

async def get_all_todos(storage: FletStorage) -> list:
    return await storage.get_or_default("todos", [])

async def clear_completed_todos(storage: FletStorage):
    todos = await storage.get_or_default("todos", [])
    active_todos = [t for t in todos if not t["completed"]]
    await storage.set("todos", active_todos)
```

### Working with Tags (Sets)
```python
async def add_tag(storage: FletStorage, tag: str):
    tags = await storage.get_or_default("tags", set())
    tags.add(tag)
    await storage.set("tags", tags)

async def remove_tag(storage: FletStorage, tag: str):
    tags = await storage.get_or_default("tags", set())
    tags.discard(tag)
    await storage.set("tags", tags)

async def get_all_tags(storage: FletStorage) -> set:
    return await storage.get_or_default("tags", set())
```

### Data Caching
```python
import time

async def cache_data(storage: FletStorage, key: str, data: Any, ttl: int = 3600):
    cache_entry = {
        "data": data,
        "expires_at": time.time() + ttl
    }
    await storage.set(f"cache_{key}", cache_entry)

async def get_cached_data(storage: FletStorage, key: str) -> Any | None:
    try:
        cache_entry = await storage.get(f"cache_{key}")
        if time.time() < cache_entry["expires_at"]:
            return cache_entry["data"]
        else:
            await storage.remove(f"cache_{key}")
            return None
    except KeyError:
        return None
```

## Error Handling
```python
async def safe_get_data(storage: FletStorage, key: str):
    try:
        data = await storage.get(key)
        return data
    except KeyError:
        print(f"Key '{key}' not found")
        return None
    except ValueError as e:
        print(f"JSON error: {e}")
        return None
```

## Best Practices

1. **Use descriptive namespace names:** Choose a unique `app_name` to avoid conflicts with other applications.

2. **Handle exceptions:** Always handle `KeyError` and `ValueError` when working with `get()`.

3. **Use `get_or_default()`:** For optional data, this is more convenient than exception handling.

4. **Structure your data:** Store related data together in dictionaries for better organization.

5. **Clean up stale data:** Regularly remove unnecessary keys to keep storage clean.

6. **Use sets for unique collections:** Sets are automatically preserved and are perfect for storing unique items like tags or categories.

## Requirements

- Python 3.10+
- Flet >= 0.81.0

## License

MIT License

## Contributing

Contributions are welcome! Please submit pull requests on [GitHub](https://github.com/BogdanovychA/flet-storage).

## Support

If you encounter any issues or have questions:
- Open an issue on [GitHub](https://github.com/BogdanovychA/flet-storage/issues)

---

**Made with ❤️ for the Flet community**
