Brainstorm initial API and modify db to be more FUSE friendly
This commit is contained in:
parent
0a444a55c4
commit
96c9e62354
3 changed files with 183 additions and 105 deletions
199
trovedb/db.py
199
trovedb/db.py
|
|
@ -3,58 +3,49 @@ Python API for accessing underlying SQlite3 File Database
|
||||||
|
|
||||||
A Sqlite3 database provides the basic underlying interface to the underlying
|
A Sqlite3 database provides the basic underlying interface to the underlying
|
||||||
notes interface. The low level database is an extremely simple model using
|
notes interface. The low level database is an extremely simple model using
|
||||||
similar ideas to get storage. However, UUID are utilized for persistent object
|
similar ideas to git storage.
|
||||||
references.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
import uuid as _uuid
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from .trove import NODE_ROOT_ID
|
||||||
|
|
||||||
_SCHEMA = """
|
_SCHEMA = """
|
||||||
CREATE TABLE IF NOT EXISTS objects (
|
CREATE TABLE IF NOT EXISTS objects (
|
||||||
uuid TEXT PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
type TEXT NOT NULL CHECK(type IN ('blob', 'tree')),
|
type TEXT NOT NULL CHECK(type IN ('blob', 'tree')),
|
||||||
data BLOB,
|
data BLOB,
|
||||||
created TEXT NOT NULL,
|
|
||||||
modified TEXT NOT NULL
|
modified TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS metadata (
|
CREATE TABLE IF NOT EXISTS metadata (
|
||||||
uuid TEXT NOT NULL REFERENCES objects(uuid) ON DELETE CASCADE,
|
id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||||
key TEXT NOT NULL,
|
key TEXT NOT NULL,
|
||||||
value BLOB,
|
value BLOB,
|
||||||
PRIMARY KEY (uuid, key)
|
PRIMARY KEY (id, key)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS labels
|
CREATE TABLE IF NOT EXISTS labels
|
||||||
(
|
(
|
||||||
label
|
label TEXT PRIMARY KEY,
|
||||||
TEXT
|
id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE
|
||||||
PRIMARY
|
);
|
||||||
KEY,
|
"""
|
||||||
uuid
|
|
||||||
TEXT
|
|
||||||
NOT
|
|
||||||
NULL
|
|
||||||
REFERENCES
|
|
||||||
objects
|
|
||||||
(
|
|
||||||
uuid
|
|
||||||
) ON DELETE CASCADE
|
|
||||||
); \
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _now() -> str:
|
def _now() -> str:
|
||||||
return datetime.now(timezone.utc).isoformat()
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
class TroveDB:
|
def _initialize_db(con: sqlite3.Connection):
|
||||||
|
con.executescript(_SCHEMA)
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
|
||||||
|
class Sqlite3Trove:
|
||||||
def __init__(self, con: sqlite3.Connection):
|
def __init__(self, con: sqlite3.Connection):
|
||||||
self._con = con
|
self._con = con
|
||||||
self._con.execute("PRAGMA foreign_keys = ON")
|
self._con.execute("PRAGMA foreign_keys = ON")
|
||||||
|
|
@ -65,24 +56,22 @@ class TroveDB:
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init(cls, path: str | Path) -> "TroveDB":
|
def open(cls, path: str | Path, create: bool = False) -> "Sqlite3Trove":
|
||||||
"""Create a new Trove database at path. Fails if file already exists."""
|
|
||||||
p = Path(path)
|
|
||||||
if p.exists():
|
|
||||||
raise FileExistsError(f"Database already exists: {p}")
|
|
||||||
con = sqlite3.connect(str(p))
|
|
||||||
con.executescript(_SCHEMA)
|
|
||||||
con.commit()
|
|
||||||
return cls(con)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def open(cls, path: str | Path) -> "TroveDB":
|
|
||||||
"""Open an existing Trove database."""
|
"""Open an existing Trove database."""
|
||||||
p = Path(path)
|
p = Path(path)
|
||||||
|
initialize = False
|
||||||
if not p.exists():
|
if not p.exists():
|
||||||
|
if not create:
|
||||||
raise FileNotFoundError(f"Database not found: {p}")
|
raise FileNotFoundError(f"Database not found: {p}")
|
||||||
|
initialize = True
|
||||||
con = sqlite3.connect(str(p))
|
con = sqlite3.connect(str(p))
|
||||||
return cls(con)
|
if initialize:
|
||||||
|
con.executescript(_SCHEMA)
|
||||||
|
con.commit()
|
||||||
|
obj = cls(con)
|
||||||
|
if initialize:
|
||||||
|
obj.write_blob(b"", NODE_ROOT_ID)
|
||||||
|
return obj
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._con.close()
|
self._con.close()
|
||||||
|
|
@ -97,77 +86,84 @@ class TroveDB:
|
||||||
# CRUD operations
|
# CRUD operations
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def read_blob(self, uuid: str) -> bytes | None:
|
def read_object(self, object_id: int) -> bytes | None:
|
||||||
"""Return raw data for a blob object, or None if not found."""
|
"""Return raw data for a blob object, or None if not found."""
|
||||||
row = self._con.execute(
|
row = self._con.execute(
|
||||||
"SELECT data, type FROM objects WHERE uuid = ?", (uuid,)
|
"SELECT data, type FROM objects WHERE id = ?", (object_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
if row["type"] != "blob":
|
|
||||||
raise TypeError(f"Object {uuid} is type '{row['type']}', not 'blob'")
|
|
||||||
return bytes(row["data"]) if row["data"] is not None else b""
|
return bytes(row["data"]) if row["data"] is not None else b""
|
||||||
|
|
||||||
def read_metadata(self, uuid: str, key: str) -> bytes | None:
|
def read_metadata(self, object_id: int, key: str) -> bytes | None:
|
||||||
"""Return raw metadata value for (uuid, key), or None if not found."""
|
"""Return raw metadata value for (uuid, key), or None if not found."""
|
||||||
row = self._con.execute(
|
row = self._con.execute(
|
||||||
"SELECT value FROM metadata WHERE uuid = ? AND key = ?", (uuid, key)
|
"SELECT value FROM metadata WHERE id = ? AND key = ?", (object_id, key)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
return bytes(row["value"]) if row["value"] is not None else b""
|
return bytes(row["value"]) if row["value"] is not None else b""
|
||||||
|
|
||||||
def write_blob(self, data: bytes, existing_uuid: str | None = None) -> str:
|
def _write_object(self, data: bytes, dtype: str, object_id: int | None = None) -> int:
|
||||||
"""
|
"""
|
||||||
Insert or replace a blob. Returns the uuid.
|
Insert or replace an object. Returns the id.
|
||||||
Pass existing_uuid to update an existing object.
|
If object_id is None, creates a new object with auto-assigned ID.
|
||||||
|
If object_id is provided, updates or creates the object with that ID.
|
||||||
"""
|
"""
|
||||||
now = _now()
|
modified = _now()
|
||||||
uid = existing_uuid or str(_uuid.uuid4())
|
if object_id is None:
|
||||||
if existing_uuid:
|
cur = self._con.execute(
|
||||||
self._con.execute(
|
"INSERT INTO objects (type, data, modified) VALUES (?, ?, ?)",
|
||||||
"UPDATE objects SET data = ?, modified = ? WHERE uuid = ? AND type = 'blob'",
|
(dtype, data, modified)
|
||||||
(data, now, uid),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._con.execute(
|
|
||||||
"INSERT INTO objects (uuid, type, data, created, modified) VALUES (?, 'blob', ?, ?, ?)",
|
|
||||||
(uid, data, now, now),
|
|
||||||
)
|
)
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
return uid
|
return cur.lastrowid
|
||||||
|
else:
|
||||||
|
self._con.execute(
|
||||||
|
"INSERT OR REPLACE INTO objects (id, type, data, modified) VALUES (?, ?, ?, ?)",
|
||||||
|
(object_id, dtype, data, modified)
|
||||||
|
)
|
||||||
|
self._con.commit()
|
||||||
|
return object_id
|
||||||
|
|
||||||
def delete_blob(self, uuid: str) -> bool:
|
def write_blob(self, data: bytes, existing_id: int | None = None) -> int:
|
||||||
|
"""
|
||||||
|
Insert or replace a blob. Returns the id.
|
||||||
|
Pass existing_id to update an existing object.
|
||||||
|
"""
|
||||||
|
return self._write_object(data, "blob", existing_id)
|
||||||
|
|
||||||
|
def delete_object(self, object_id: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Delete a blob and all its metadata rows.
|
Delete a blob and all its metadata rows.
|
||||||
Returns True if an object was deleted, False if uuid not found.
|
Returns True if an object was deleted, False if id not found.
|
||||||
Foreign key cascade handles the metadata rows.
|
Foreign key cascade handles the metadata rows.
|
||||||
"""
|
"""
|
||||||
cur = self._con.execute(
|
cur = self._con.execute(
|
||||||
"DELETE FROM objects WHERE uuid = ?", (uuid,)
|
"DELETE FROM objects WHERE id = ?", (object_id,)
|
||||||
)
|
)
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
return cur.rowcount > 0
|
return cur.rowcount > 0
|
||||||
|
|
||||||
def get_label(self, label: str) -> str | None:
|
def get_label(self, label: str) -> int | None:
|
||||||
"""
|
"""
|
||||||
Return the UUID associated with a label, or None if not found.
|
Return the ID associated with a label, or None if not found.
|
||||||
"""
|
"""
|
||||||
row = self._con.execute(
|
row = self._con.execute(
|
||||||
"SELECT uuid FROM labels WHERE label = ?", (label,)
|
"SELECT id FROM labels WHERE label = ?", (label,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
return row["uuid"]
|
return row["id"]
|
||||||
|
|
||||||
def set_label(self, label: str, uuid: str) -> None:
|
def set_label(self, label: str, object_id: int) -> None:
|
||||||
"""
|
"""
|
||||||
Set a label to point to a UUID. Creates or updates the label.
|
Set a label to point to an ID. Creates or updates the label.
|
||||||
The UUID must exist in the objects table.
|
The ID must exist in the objects table.
|
||||||
"""
|
"""
|
||||||
self._con.execute(
|
self._con.execute(
|
||||||
"INSERT OR REPLACE INTO labels (label, uuid) VALUES (?, ?)",
|
"INSERT OR REPLACE INTO labels (label, id) VALUES (?, ?)",
|
||||||
(label, uuid),
|
(label, object_id),
|
||||||
)
|
)
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
|
|
||||||
|
|
@ -181,14 +177,14 @@ class TroveDB:
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
return cur.rowcount > 0
|
return cur.rowcount > 0
|
||||||
|
|
||||||
def list_labels(self) -> list[tuple[str, str]]:
|
def list_labels(self) -> list[tuple[str, int]]:
|
||||||
"""
|
"""
|
||||||
Return all labels as a list of (label, uuid) tuples.
|
Return all labels as a list of (label, id) tuples.
|
||||||
"""
|
"""
|
||||||
rows = self._con.execute(
|
rows = self._con.execute(
|
||||||
"SELECT label, uuid FROM labels ORDER BY label"
|
"SELECT label, id FROM labels ORDER BY label"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
return [(row["label"], row["uuid"]) for row in rows]
|
return [(row["label"], row["id"]) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
@ -202,22 +198,22 @@ def main():
|
||||||
subparsers.add_parser("create", help="Create a new database")
|
subparsers.add_parser("create", help="Create a new database")
|
||||||
|
|
||||||
# Get blob
|
# Get blob
|
||||||
get_parser = subparsers.add_parser("get", help="Get a blob object by UUID")
|
get_parser = subparsers.add_parser("get", help="Get a blob object by ID")
|
||||||
get_parser.add_argument("uuid", help="UUID of the blob to retrieve")
|
get_parser.add_argument("id", type=int, help="ID of the blob to retrieve")
|
||||||
|
|
||||||
# Write blob
|
# Write blob
|
||||||
write_parser = subparsers.add_parser("write", help="Write data to a blob")
|
write_parser = subparsers.add_parser("write", help="Write data to a blob")
|
||||||
write_parser.add_argument("data", help="Data to write (as string, will be encoded as UTF-8)")
|
write_parser.add_argument("data", help="Data to write (as string, will be encoded as UTF-8)")
|
||||||
write_parser.add_argument("--uuid", help="UUID of existing blob to update (optional)")
|
write_parser.add_argument("--id", type=int, help="ID of existing blob to update (optional)")
|
||||||
|
|
||||||
# Delete blob
|
# Delete blob
|
||||||
delete_parser = subparsers.add_parser("delete", help="Delete a blob by UUID")
|
delete_parser = subparsers.add_parser("delete", help="Delete a blob by ID")
|
||||||
delete_parser.add_argument("uuid", help="UUID of the blob to delete")
|
delete_parser.add_argument("id", type=int, help="ID of the blob to delete")
|
||||||
|
|
||||||
# Set label
|
# Set label
|
||||||
setlabel_parser = subparsers.add_parser("setlabel", help="Create or update a label to point to a UUID")
|
setlabel_parser = subparsers.add_parser("setlabel", help="Create or update a label to point to an ID")
|
||||||
setlabel_parser.add_argument("label", help="Label name")
|
setlabel_parser.add_argument("label", help="Label name")
|
||||||
setlabel_parser.add_argument("uuid", help="UUID to associate with the label")
|
setlabel_parser.add_argument("id", type=int, help="ID to associate with the label")
|
||||||
|
|
||||||
# Remove label
|
# Remove label
|
||||||
rmlabel_parser = subparsers.add_parser("rmlabel", help="Delete a label")
|
rmlabel_parser = subparsers.add_parser("rmlabel", help="Delete a label")
|
||||||
|
|
@ -231,59 +227,62 @@ def main():
|
||||||
try:
|
try:
|
||||||
match args.operation:
|
match args.operation:
|
||||||
case "create":
|
case "create":
|
||||||
db = TroveDB.init(args.database)
|
if Path(args.database).exists():
|
||||||
|
print(f"Database already exists: {args.database}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
db = Sqlite3Trove.open(args.database, create=True)
|
||||||
db.close()
|
db.close()
|
||||||
print(f"Database created: {args.database}")
|
print(f"Database created: {args.database}")
|
||||||
|
|
||||||
case "get":
|
case "get":
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
data = db.read_blob(args.uuid)
|
data = db.read_object(args.id)
|
||||||
db.close()
|
db.close()
|
||||||
if data is None:
|
if data is None:
|
||||||
print(f"Blob not found: {args.uuid}", file=sys.stderr)
|
print(f"Blob not found: {args.id}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
sys.stdout.buffer.write(data)
|
sys.stdout.buffer.write(data)
|
||||||
|
|
||||||
case "write":
|
case "write":
|
||||||
data_bytes = args.data.encode("utf-8")
|
data_bytes = args.data.encode("utf-8")
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
uuid = db.write_blob(data_bytes, args.uuid)
|
object_id = db.write_blob(data_bytes, args.id)
|
||||||
db.close()
|
db.close()
|
||||||
print(uuid)
|
print(object_id)
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
deleted = db.delete_blob(args.uuid)
|
deleted = db.delete_object(args.id)
|
||||||
db.close()
|
db.close()
|
||||||
if deleted:
|
if deleted:
|
||||||
print(f"Deleted blob: {args.uuid}")
|
print(f"Deleted blob: {args.id}")
|
||||||
else:
|
else:
|
||||||
print(f"Blob not found: {args.uuid}", file=sys.stderr)
|
print(f"Blob not found: {args.id}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
case "setlabel":
|
case "setlabel":
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
db.set_label(args.label, args.uuid)
|
db.set_label(args.label, args.id)
|
||||||
db.close()
|
db.close()
|
||||||
print(f"Label '{args.label}' set to UUID: {args.uuid}")
|
print(f"Label '{args.label}' set to ID: {args.id}")
|
||||||
|
|
||||||
case "rmlabel":
|
case "rmlabel":
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
deleted = db.delete_label(args.label)
|
deleted = db.delete_label(args.label)
|
||||||
db.close()
|
db.close()
|
||||||
if deleted:
|
if deleted:
|
||||||
print(f"Deleted label: {args.label}")
|
print(f"Deleted label: {args.label}")
|
||||||
else:
|
else:
|
||||||
print(f"Label not found: {args.label}", file=sys.stderr)
|
print(f"Label not found: {args.label}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
case "labels":
|
case "labels":
|
||||||
db = TroveDB.open(args.database)
|
db = Sqlite3Trove.open(args.database)
|
||||||
labels = db.list_labels()
|
labels = db.list_labels()
|
||||||
db.close()
|
db.close()
|
||||||
if labels:
|
if labels:
|
||||||
for label, uuid in labels:
|
for label, id in labels:
|
||||||
print(f"{label}: {uuid}")
|
print(f"{label}: {id}")
|
||||||
else:
|
else:
|
||||||
print("No labels found.")
|
print("No labels found.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ class Tree:
|
||||||
Initialize a Tree. If data is provided, deserialize from UTF-8 JSON bytes.
|
Initialize a Tree. If data is provided, deserialize from UTF-8 JSON bytes.
|
||||||
An empty Tree is created if data is None.
|
An empty Tree is created if data is None.
|
||||||
"""
|
"""
|
||||||
if data is None:
|
if not data:
|
||||||
self._entries: dict[str, str] = {}
|
self._entries: dict[str, int] = {}
|
||||||
else:
|
else:
|
||||||
self._entries = json.loads(data.decode("utf-8"))
|
self._entries = json.loads(data.decode("utf-8"))
|
||||||
|
|
||||||
|
|
@ -24,14 +24,18 @@ class Tree:
|
||||||
"""Serialize the tree to UTF-8 JSON bytes."""
|
"""Serialize the tree to UTF-8 JSON bytes."""
|
||||||
return json.dumps(self._entries).encode("utf-8")
|
return json.dumps(self._entries).encode("utf-8")
|
||||||
|
|
||||||
def set_entry(self, name: str, uuid: str) -> None:
|
def set_entry(self, name: str, object_id: int) -> None:
|
||||||
"""Add or update an entry mapping name -> uuid."""
|
"""Add or update an entry mapping name -> uuid."""
|
||||||
self._entries[name] = uuid
|
self._entries[name] = object_id
|
||||||
|
|
||||||
|
def get_entry(self, name: str) -> int:
|
||||||
|
"""Get the uuid associated with a name, or raise KeyError if not found."""
|
||||||
|
return self._entries[name]
|
||||||
|
|
||||||
def rm_entry(self, name: str) -> None:
|
def rm_entry(self, name: str) -> None:
|
||||||
"""Remove an entry by name. Raises KeyError if not found."""
|
"""Remove an entry by name. Raises KeyError if not found."""
|
||||||
del self._entries[name]
|
del self._entries[name]
|
||||||
|
|
||||||
def list(self) -> dict[str, str]:
|
def list(self) -> dict[str, int]:
|
||||||
"""Return a shallow copy of all entries as {name: uuid}."""
|
"""Return a shallow copy of all entries as {name: uuid}."""
|
||||||
return dict(self._entries)
|
return dict(self._entries)
|
||||||
|
|
|
||||||
75
trovedb/trove.py
Normal file
75
trovedb/trove.py
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
from typing import Protocol, runtime_checkable, Optional, Dict, List, Self
|
||||||
|
from uuid import UUID
|
||||||
|
from pathlib import PurePosixPath
|
||||||
|
|
||||||
|
NODE_ROOT_ID = 1
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Note(Protocol):
|
||||||
|
"""
|
||||||
|
Protocol for a Note item.
|
||||||
|
Represents access to an individual note's content and metadata.
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def object_id(self) -> int:
|
||||||
|
"""The unique identifier for this note."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_raw_metadata(self, key: str) -> Optional[bytes]:
|
||||||
|
"""Retrieve metadata value for the given key."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def set_raw_metadata(self, key: str, value: bytes) -> None:
|
||||||
|
"""Set metadata value for the given key."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Blob(Protocol):
|
||||||
|
def read(self) -> bytes:
|
||||||
|
"""Read the raw content of the note."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def write(self, data: bytes) -> None:
|
||||||
|
"""Write new content to the note."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Tree(Protocol):
|
||||||
|
def link(self, name: str, note: Note):
|
||||||
|
"""Link name to a given note."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def unlink(self, name: str):
|
||||||
|
"""Remove name from the tree."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def mkdir(self, name: str) -> Self:
|
||||||
|
"""Create a new Tree with the given name."""
|
||||||
|
...
|
||||||
|
|
||||||
|
class BlobNote(Note, Blob):
|
||||||
|
"""Blob Note"""
|
||||||
|
|
||||||
|
class TreeNote(Note, Tree):
|
||||||
|
"""Tree Note"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Trove(Protocol):
|
||||||
|
"""
|
||||||
|
Protocol for the Trove database API.
|
||||||
|
Provides high-level access to notes and trees.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_raw_note(self, note: int) -> Optional[Note]:
|
||||||
|
"""Retrieve a note by a UUID"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def create_blob(self, data: bytes | None = None) -> BlobNote:
|
||||||
|
"""Create a new blob node at the given path with content"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_root(self) -> TreeNote:
|
||||||
|
"""Get Tree Node at the given path"""
|
||||||
|
...
|
||||||
Loading…
Add table
Add a link
Reference in a new issue