""" Contains SqlAlchemy models and their helper methods """
import datetime
import hashlib
import typing as T
from typing import Dict
from dateutil import parser as date_parser
from rmapy import document as rmapy_document
from sqlalchemy import (TIMESTAMP, Boolean, Column, ForeignKey, Integer,
String, Text)
from sqlalchemy.orm import declarative_base
Base = declarative_base()
C = T.TypeVar('C', bound='ModelMixIn') # pylint: disable=C0103
class ModelMixIn():
""" MixIn for common functionality used for SqlAlchemy models. """
@classmethod
def from_dict(cls: T.Type[C], data: Dict[str, T.Any]) -> C:
""" Construct model from a dictionary.
data should have keys that match model names exactly.
"""
columns = [column.name for column in cls.__table__.columns] # type: ignore
constructor_args = {}
for key, value in data.items():
if key in columns:
constructor_args[key] = value
return cls(**constructor_args) # type: ignore
def to_dict(self) -> Dict[str, T.Any]:
""" Return a dictionary that represents the model. """
return {c.name: getattr(self, c.name) for c in self.__table__.columns} # type: ignore
def __repr__(self) -> str:
return f"{self.__class__.__name__}({str(self.to_dict())})"
[docs]class Document(Base, ModelMixIn):
""" Represents the current state of a document originating from reMarkable cloud """
__tablename__ = "document"
id: str = Column(String(256), nullable=False, primary_key=True)
""" Primary key for document. This is a UUID generated by the reMarkable tablet. """
version: int = Column(Integer, nullable=False)
""" The version of the document according to the reMarkable cloud. """
modified_client: datetime.datetime = Column(TIMESTAMP, nullable=False)
""" The unix timestamp for the last time this document was modified. """
type: str = Column(String(256), nullable=False)
""" The type of the document. """
name: str = Column(Text, nullable=False)
""" The name of the document as visible on the reMarkable tablet. """
current_page: int = Column(Integer, nullable=False)
""" The current page the document is opened on. """
bookmarked: bool = Column(Boolean, nullable=False)
""" Indicate if the document is bookmarked. """
parent: str = Column(String(256), nullable=False)
""" The parent of the document. This is usually a folder ID. """
[docs] def equal(self, other: 'Document') -> bool:
""" Check for equality with other documents. """
return self.id == other.id
[docs] @classmethod
def from_cloud_document(cls, cloud_document: rmapy_document.Document) -> 'Document':
""" Create a Document model from a reMarkable cloud document. """
cloud_document_metadata = cloud_document.to_dict()
cloud_document_mapping = {
"ID": "id",
"Version": "version",
"Message": "message",
"Success": "success",
"ModifiedClient": "modified_client",
"Type": "type",
"VissibleName": "name",
"CurrentPage": "current_page",
"Bookmarked": "bookmarked",
"Parent": "parent"
}
cleaned_metadata = {}
for key, value in cloud_document_metadata.items():
if key not in cloud_document_mapping:
continue
new_key = cloud_document_mapping[key]
if key == "ModifiedClient":
cleaned_metadata[new_key] = date_parser.parse(value).replace(tzinfo=None)
else:
cleaned_metadata[new_key] = value
return cls.from_dict(cleaned_metadata)
[docs]class Highlight(Base, ModelMixIn):
""" Represents a single highlight for a Document. """
__tablename__ = "highlight"
hash: str = Column(String(256), primary_key=True, nullable=False)
""" Primary for a highlight. This is a hash of the document_id and text. """
document_id: str = Column(String(256), ForeignKey('document.id'), nullable=False)
""" The document that the highlight is associated with """
text: str = Column(Text)
""" The text of the highlight """
page_number: int = Column(Integer)
""" The page number on which the highlight is located. """
extracted_at: datetime.datetime = Column(TIMESTAMP) # type: ignore
""" A unix timestamp of when the highlight was extracted. """
extraction_method: str = Column(String(256), nullable=True)
""" What method was used to perform the highlight extraction. """
[docs] @classmethod
def create_highlight(cls, doc_id: str, text: str, page_number: int, extraction_method: str) -> 'Highlight':
""" Create a Highlight.
This should be used in place of the default constructor as it properly constructs the highlight hash.
:param doc_id: The id of the document this highlight is associated with.
:param text: The text of the highlight.
:param page_number: The page number where the highlight is located.
:param extract_method: The extraction method.
:return: An instance of highlight for these values.
"""
return Highlight(
document_id=doc_id,
text=text,
hash=hashlib.sha224((text + doc_id).encode()).hexdigest(),
page_number=page_number,
extraction_method=extraction_method,
extracted_at=datetime.datetime.now()
)
[docs] def equal(self, other: 'Highlight') -> bool:
""" Check for equality with other Highlights. """
return (self.hash == other.hash and
self.text == other.text and
self.document_id == other.document_id and
self.page_number == other.page_number)