Source code for platypush.common.notes

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from hashlib import md5, sha256
from typing import Any, Dict, List, Optional, Set
from uuid import UUID

from platypush.message import JSONAble
from platypush.schemas.notes import NoteCollectionSchema, NoteItemSchema


class Serializable(JSONAble, ABC):
    """
    Base class for serializable objects.
    """

    @abstractmethod
    def to_dict(self) -> dict:
        """
        Convert the object to a dictionary representation.
        """

    def to_json(self) -> dict:
        return self.to_dict()


@dataclass
class Storable(Serializable, ABC):
    """
    Base class for note objects that can be represented as databases entries.
    """

    id: Any
    plugin: str

    @property
    def _db_id(self) -> UUID:
        """
        Generate a deterministic UUID based on the note's plugin and ID.
        """
        key = f'{self.plugin}:{self.id}'
        digest = md5(key.encode()).digest()
        return UUID(int=int.from_bytes(digest, 'little'))


[docs] @dataclass class NoteSource(Serializable): """ Represents a source for a note, such as a URL or file path. """ name: Optional[str] = None url: Optional[str] = None app: Optional[str] = None
[docs] def to_dict(self) -> dict: return self.__dict__
[docs] @dataclass class Note(Storable): """ Represents a note with a title and content. """ title: str description: Optional[str] = None content: Optional[str] = None parent: Optional['NoteCollection'] = None tags: Set[str] = field(default_factory=set) created_at: Optional[datetime] = None updated_at: Optional[datetime] = None digest: Optional[str] = field(default=None) latitude: Optional[float] = None longitude: Optional[float] = None altitude: Optional[float] = None author: Optional[str] = None source: Optional[NoteSource] = None _path: Optional[str] = None def __post_init__(self): """ Post-initialization to update the digest if content is provided. """ self.digest = self._update_digest() @property def path(self) -> str: # If the path is already set, return it if self._path: return self._path # Recursively build the path by expanding the parent collections path = [] parent = self.parent while parent: path.append(parent.title) parent = parent.parent return '/'.join(reversed(path)) + f'/{self.title}.md' @path.setter def path(self, value: str): """ Set the path for the note. """ self._path = value def _update_digest(self) -> Optional[str]: if self.content and not self.digest: self.digest = sha256(self.content.encode('utf-8')).hexdigest() return self.digest
[docs] def to_dict(self) -> dict: return NoteItemSchema().dump( # type: ignore { **{ field: getattr(self, field) for field in self.__dataclass_fields__ if not field.startswith('_') and field != 'parent' }, 'path': self.path, 'parent': ( { 'id': self.parent.id if self.parent else None, 'title': self.parent.title if self.parent else None, } if self.parent else None ), }, )
[docs] @dataclass class NoteCollection(Storable): """ Represents a collection of notes. """ title: str description: Optional[str] = None parent: Optional['NoteCollection'] = None created_at: Optional[datetime] = None updated_at: Optional[datetime] = None _notes: Dict[Any, Note] = field(default_factory=dict) _collections: Dict[Any, 'NoteCollection'] = field(default_factory=dict) @property def notes(self) -> List[Note]: return list(self._notes.values()) @property def collections(self) -> List['NoteCollection']: return list(self._collections.values())
[docs] def to_dict(self) -> dict: return NoteCollectionSchema().dump( # type: ignore { **{ field: getattr(self, field) for field in self.__dataclass_fields__ if not field.startswith('_') and field != 'parent' }, 'parent': ( { 'id': self.parent.id, 'title': self.parent.title, } if self.parent else None ), 'collections': [ collection.to_dict() for collection in self.collections ], 'notes': [ { field: getattr(note, field) for field in [*note.__dataclass_fields__, 'digest'] if field not in ['parent', 'content'] } for note in self.notes ], } )