Skip to content

Storage

aaalllexxx edited this page Jun 29, 2026 · 1 revision

Storage

ENPAF ships a small persistent store backed by SQLite. It works identically in dev and on-device — the only difference is the file location (see Where the data lives). There are two layers:

  1. A key/value store (app.storage) for simple values.
  2. Collections (app.storage.collection(name)) for lists of documents.

Both serialize/deserialize Python types (str, int, float, bool, None, dict, list) for you.

Key/value store

app.storage.set("theme", "dark")
app.storage.get("theme")              # "dark"
app.storage.get("missing", "light")   # default -> "light"

app.storage.set("profile", {"name": "Alex", "tags": ["a", "b"]})
app.storage.get("profile")            # dict round-trips exactly

app.storage.exists("theme")           # True
app.storage.delete("theme")           # True (False if it didn't exist)

app.storage.keys()                    # ["profile", ...]
app.storage.keys("user:%")            # SQL LIKE pattern
app.storage.all()                     # {key: value, ...}
app.storage.clear()                   # wipe all keys
Method Returns Description
set(key, value) Insert or update.
get(key, default=None) value Fetch, with default.
delete(key) bool Whether the key existed.
exists(key) bool Presence check.
keys(pattern=None) list[str] All keys, optional LIKE filter.
all() dict Everything.
clear() Remove all keys.

From JavaScript

await enpaf.storage.set("theme", "dark");
const theme = await enpaf.storage.get("theme");
await enpaf.storage.delete("theme");

Collections

A collection is a simple document store (think a tiny NoSQL table). Each document gets an auto-increment _id and a _created_at timestamp.

notes = app.storage.collection("notes")

note_id = notes.add({"text": "Buy milk", "done": False})   # -> int id
notes.all()              # [{"text": ..., "done": ..., "_id": 1, "_created_at": ...}]
notes.find({"done": False})        # list of matching docs
notes.find_one({"_id": note_id})   # first match or None
notes.update(note_id, {"text": "Buy milk", "done": True})   # True/False
notes.delete(note_id)              # True/False
notes.count()                      # int
notes.clear()                      # remove all docs in THIS collection
Method Returns Description
add(data) int Insert a document, returns its id.
all() list[dict] All documents (each with _id, _created_at).
find(query) list[dict] Documents where every query field matches.
find_one(query) dict / None First match.
update(id, data) bool Replace a document by id.
delete(id) bool Delete by id.
count() int Number of documents.
clear() Remove all documents in the collection.

Collections are isolated by name — clearing one never affects another.

Example: a notes API

@app.bridge_handler("save_note")
def save_note(params):
    text = params.get("text", "").strip()
    if not text:
        return {"success": False, "error": "Empty note"}
    return {"success": True, "id": app.storage.collection("notes").add({"text": text})}

@app.bridge_handler("get_notes")
def get_notes(params):
    return {"notes": app.storage.collection("notes").all()}

@app.bridge_handler("delete_note")
def delete_note(params):
    app.storage.collection("notes").delete(int(params["id"]))
    return {"success": True}

Where the data lives

  • Dev: data/enpaf_data.db inside your project.
  • On-device: the app's private files directory, via the ENPAF_DATA_DIR environment variable that the Android runtime sets before importing your app. This matters: the app's source files are packaged read-only inside the APK, so writing the DB next to them would crash. ENPAF redirects writes to a writable location automatically.

You can override the location by setting ENPAF_DATA_DIR yourself.

Notes & limitations

  • Connections are thread-local and use SQLite WAL mode, so reads/writes from the bridge thread and background threads are safe.
  • find/find_one do exact field-equality matching in Python (not SQL queries), which is fine for modest data sizes. For large datasets or complex queries, bundle a real ORM via python_requirements and use it from your handlers.

Clone this wiki locally