2026-03-26 21:00:35 -05:00
|
|
|
from typing import Protocol, runtime_checkable, Optional, Dict, List, Self, NamedTuple, Iterable, TypedDict, Iterator
|
2026-03-16 01:14:07 -05:00
|
|
|
from uuid import UUID
|
2026-03-22 23:57:05 -05:00
|
|
|
import datetime as dt
|
2026-03-26 21:00:35 -05:00
|
|
|
import errno
|
2026-03-21 12:08:54 -05:00
|
|
|
|
2026-03-21 22:25:32 -05:00
|
|
|
type ObjectId = int | str | UUID
|
2026-03-21 12:08:54 -05:00
|
|
|
|
2026-03-21 22:25:32 -05:00
|
|
|
class TroveError(Exception):
|
|
|
|
|
"""Base class for all Trove errors."""
|
2026-03-16 01:14:07 -05:00
|
|
|
|
2026-03-26 21:00:35 -05:00
|
|
|
class ErrorWithErrno(TroveError):
|
|
|
|
|
"""Raised when an error occurs with an errno."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, error: int, *args):
|
|
|
|
|
super().__init__(*args)
|
|
|
|
|
self.errno = error
|
|
|
|
|
|
|
|
|
|
class ErrorExists(ErrorWithErrno):
|
|
|
|
|
"""Raised when a note already exists."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
|
|
|
|
super().__init__(errno.EEXIST, *args)
|
|
|
|
|
|
|
|
|
|
class ErrorNotFound(ErrorWithErrno):
|
|
|
|
|
"""Raised when a note is not found."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
|
|
|
|
super().__init__(errno.ENOENT, *args)
|
|
|
|
|
|
|
|
|
|
class ErrorNotEmpty(ErrorWithErrno):
|
|
|
|
|
"""Raised when a directory is not empty."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
|
|
|
|
super().__init__(errno.ENOTEMPTY, *args)
|
|
|
|
|
|
2026-03-28 13:25:25 -05:00
|
|
|
class ErrorBadType(TypeError):
|
|
|
|
|
"""Raised when an invalid type is encountered."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-26 21:00:35 -05:00
|
|
|
|
2026-03-19 22:43:11 -05:00
|
|
|
class BadNoteType(TypeError):
|
|
|
|
|
"""Raised when an invalid note type is encountered."""
|
|
|
|
|
|
2026-03-21 22:25:32 -05:00
|
|
|
class TreeExists(RuntimeError):
|
2026-03-21 12:08:54 -05:00
|
|
|
"""Raised when a label already exists."""
|
|
|
|
|
|
|
|
|
|
class NoteNotFound(KeyError):
|
|
|
|
|
"""Raised when a note is not found."""
|
2026-03-19 22:43:11 -05:00
|
|
|
|
2026-03-21 22:25:32 -05:00
|
|
|
class OpenArguments(TypedDict):
|
|
|
|
|
create: bool
|
|
|
|
|
|
2026-03-26 21:00:35 -05:00
|
|
|
class TreeEntry(NamedTuple):
|
|
|
|
|
name: str
|
|
|
|
|
object_id: ObjectId
|
|
|
|
|
|
|
|
|
|
DEFAULT_MIME = "application/octet-stream"
|
|
|
|
|
|
2026-03-16 01:14:07 -05:00
|
|
|
@runtime_checkable
|
|
|
|
|
class Note(Protocol):
|
|
|
|
|
"""
|
|
|
|
|
Protocol for a Note item.
|
|
|
|
|
Represents access to an individual note's content and metadata.
|
|
|
|
|
"""
|
2026-03-22 23:57:05 -05:00
|
|
|
|
2026-03-16 01:14:07 -05:00
|
|
|
@property
|
2026-03-21 12:08:54 -05:00
|
|
|
def object_id(self) -> ObjectId:
|
2026-03-16 01:14:07 -05:00
|
|
|
"""The unique identifier for this note."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-22 23:57:05 -05:00
|
|
|
@property
|
|
|
|
|
def mime(self) -> str:
|
|
|
|
|
"""The MIME type of the note's content."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def readonly(self) -> bool:
|
|
|
|
|
"""Whether the note is read-only."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def mtime(self) -> dt.datetime:
|
|
|
|
|
"""The last modification time of the note."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-16 01:14:07 -05:00
|
|
|
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."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-26 00:36:01 -05:00
|
|
|
def read_content(self) -> bytes:
|
2026-03-16 01:14:07 -05:00
|
|
|
"""Read the raw content of the note."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-26 00:36:01 -05:00
|
|
|
def write_content(self, data:bytes) -> None:
|
|
|
|
|
"""Write the raw content of the note."""
|
2026-03-16 01:14:07 -05:00
|
|
|
...
|
|
|
|
|
|
2026-03-26 21:00:35 -05:00
|
|
|
def children(self) -> Iterator[TreeEntry]:
|
|
|
|
|
"""Get all children of this note."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def child(self, name: str) -> 'Note':
|
|
|
|
|
"""Retrieve a child note by name."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def new_child(self, name: str, mime: str, content: bytes | None, executable: bool, hidden: bool) -> 'Note':
|
|
|
|
|
"""Create a new child note."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def rm_child(self, name: str, recurse: bool):
|
|
|
|
|
"""Remove a child note."""
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def has_children(self) -> bool:
|
|
|
|
|
"""Check if note has children."""
|
|
|
|
|
return next(self.children(), None) is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def new_child(note: Note, name: str, mime: str = DEFAULT_MIME, content: bytes | None = None, executable: bool = False, hidden: bool = False) -> Note:
|
|
|
|
|
return note.new_child(name=name, mime=mime, content=content, executable=executable, hidden=hidden)
|
2026-03-21 12:08:54 -05:00
|
|
|
|
|
|
|
|
|
2026-03-16 01:14:07 -05:00
|
|
|
@runtime_checkable
|
|
|
|
|
class Trove(Protocol):
|
|
|
|
|
"""
|
|
|
|
|
Protocol for the Trove database API.
|
|
|
|
|
Provides high-level access to notes and trees.
|
|
|
|
|
"""
|
|
|
|
|
|
2026-03-21 12:08:54 -05:00
|
|
|
def get_raw_note(self, note: ObjectId) -> Note:
|
|
|
|
|
"""Retrieve a note by a object id"""
|
2026-03-16 01:14:07 -05:00
|
|
|
...
|
|
|
|
|
|
2026-03-28 13:25:25 -05:00
|
|
|
def move(self, src_parent: Note, src_name: str, dst_parent: Note, dst_name: str, overwrite: bool):
|
|
|
|
|
"""Move a child note to a new location."""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-26 00:36:01 -05:00
|
|
|
def create_blob(self, data: bytes | None = None) -> Note:
|
2026-03-16 01:14:07 -05:00
|
|
|
"""Create a new blob node at the given path with content"""
|
|
|
|
|
...
|
|
|
|
|
|
2026-03-28 23:13:16 -05:00
|
|
|
def get_root(self) -> Note:
|
2026-03-16 01:14:07 -05:00
|
|
|
"""Get Tree Node at the given path"""
|
|
|
|
|
...
|