diff --git a/docs/source/conf.py b/docs/source/conf.py index df5ffd6..d76bece 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,7 +32,10 @@ release = '0.1' # ones. -extensions = ['sphinx.ext.napoleon'] +extensions = [ + 'sphinx.ext.napoleon', + 'sphinx_autodoc_typehints' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -54,3 +57,16 @@ html_theme = 'alabaster' # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] + + +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True diff --git a/docs/source/index.rst b/docs/source/index.rst index 4f90f84..379c56f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -8,7 +8,8 @@ Welcome to rmapi's documentation! .. toctree:: :maxdepth: 2 - :caption: Contents: + + rmapi .. automodule:: rmapi diff --git a/rmapi/api.py b/rmapi/api.py index c3d72ce..a1531cf 100644 --- a/rmapi/api.py +++ b/rmapi/api.py @@ -297,12 +297,9 @@ class Client(object): This needs to be done in 3 steps: - 1. Create an upload request for a new CollectionType meta object. - - 2. Upload a zipfile with a *.content file containing - an empty object. - - 3. Update the meta object with the new name. + #. Create an upload request for a new CollectionType meta object. + #. Upload a zipfile with a *.content file containing an empty object. + #. Update the meta object with the new name. Args: folder: A folder instance. diff --git a/rmapi/collections.py b/rmapi/collections.py index bb55db8..78c7624 100644 --- a/rmapi/collections.py +++ b/rmapi/collections.py @@ -1,13 +1,17 @@ from .document import Document from .folder import Folder -from typing import NoReturn, TypeVar +from typing import NoReturn, TypeVar, List +from .exceptions import FolderNotFound DocOrFolder = TypeVar('DocumentOrFolder', Document, Folder) class Collection(object): - """ - A collection of meta items + """A collection of meta items + + This is basicly the content of the Remarkable Cloud. + Attributes: + items: A list containing the items. """ items = [] @@ -16,18 +20,36 @@ class Collection(object): self.items.append(i) def add(self, docdict: dict) -> NoReturn: + """Add an item to the collection. + It wraps it in the correct class based on the Type parameter of the + dict. + + Args: + docdict: A dict representing a document or folder. + """ + if docdict.get("Type", None) == "DocumentType": - return self.addDocument(docdict) + return self.add_document(docdict) elif docdict.get("Type", None) == "CollectionType": - return self.addFolder(docdict) + return self.add_folder(docdict) else: raise TypeError("Unsupported type: {_type}" .format(_type=docdict.get("Type", None))) - def addDocument(self, docdict: dict) -> NoReturn: + def add_document(self, docdict: dict) -> NoReturn: + """Add a document to the collection + + Args: + docdict: A dict respresenting a document. + """ self.items.append(Document(**docdict)) - def addFolder(self, dirdict: dict) -> NoReturn: + def add_folder(self, dirdict: dict) -> NoReturn: + """Add a document to the collection + + Args: + dirdict: A dict respresenting a folder. + """ self.items.append(Folder(**dirdict)) def parent(self, docorfolder: DocOrFolder) -> Folder: diff --git a/rmapi/config.py b/rmapi/config.py index 7b04b87..80524a3 100644 --- a/rmapi/config.py +++ b/rmapi/config.py @@ -4,8 +4,7 @@ from yaml import dump as yml_dump def load() -> dict: - """ - Load the .rmapi config file + """Load the .rmapi config file """ config_file_path = Path.joinpath(Path.home(), ".rmapi") @@ -18,8 +17,11 @@ def load() -> dict: def dump(config: dict) -> True: - """ - Dump config to the .rmapi config file + """Dump config to the .rmapi config file + + Args: + config: A dict containing data to dump to the .rmapi + config file. """ config_file_path = Path.joinpath(Path.home(), ".rmapi") diff --git a/rmapi/document.py b/rmapi/document.py index 0ce92fc..deaf356 100644 --- a/rmapi/document.py +++ b/rmapi/document.py @@ -3,13 +3,36 @@ from zipfile import ZipFile, ZIP_DEFLATED import shutil from uuid import uuid4 import json -from typing import NoReturn +from typing import NoReturn, TypeVar from requests import Response +BytesOrString = TypeVar("BytesOrString", BytesIO, str) + class Document(object): """ Document represents a real object expected in most - calls by the remarkable API""" + calls by the remarkable API + + This contains the metadata from a document. + + Attributes: + ID: Id of the meta object. + Version: The version of this object. + Success: If the last API Call was a succes. + BlobURLGet: The url to get the data blob from. Can be empty. + BlobURLGetExpires: The expiration date of the Get url. + BlobURLPut: The url to upload the data blob to. Can be empty. + BlobURLPutExpires: The expiration date of the Put url. + ModifiedClient: When the last change was by the client. + Type: Currently there are only 2 known types: DocumentType & + CollectionType. + VissibleName: The human name of the object. + CurrentPage: The current selected page of the object. + Bookmarked: If the object is bookmarked. + Parent: If empty, this object is is the root folder. This can be an ID + of a CollectionType. + + """ ID = "" Version = 0 @@ -31,7 +54,15 @@ class Document(object): for k in kkeys: setattr(self, k, kwargs.get(k, getattr(self, k))) - def to_dict(self): + def to_dict(self) -> dict: + """Return a dict representation of this object. + + Used for API Calls. + + Returns + a dict of the current object. + """ + return { "ID": self.ID, "Version": self.Version, @@ -50,20 +81,23 @@ class Document(object): } def __str__(self): + """String representation of this object""" return f"" def __repr__(self): + """String representation of this object""" return self.__str__() class ZipDocument(object): """ Here is the content of an archive retried on the tablet as example: - 384327f5-133e-49c8-82ff-30aa19f3cfa40.content - 384327f5-133e-49c8-82ff-30aa19f3cfa40-metadata.json - 384327f5-133e-49c8-82ff-30aa19f3cfa40.rm - 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata - 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg + + * 384327f5-133e-49c8-82ff-30aa19f3cfa40.content + * 384327f5-133e-49c8-82ff-30aa19f3cfa40-metadata.json + * 384326f5-133e-49c8-82ff-30aa19f3cfa40.rm + * 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata + * 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg As the .zip file from remarkable is simply a normal .zip file containing specific file formats, this package is a helper to @@ -76,6 +110,16 @@ class ZipDocument(object): You can find some help about the format at the following URL: https://remarkablewiki.com/tech/filesystem + + Attributes: + content: Sane defaults for the .content file in the zip. + metadata: parameters describing this blob. + pagedata: the content of the .pagedata file. + zipfile: The raw zipfile in memory. + pdf: the raw pdf file if there is one. + epub: the raw epub file if there is one. + rm: A list of :class:rmapi.document.RmPage in this zip. + """ content = { "ExtraMetadata": { @@ -114,6 +158,7 @@ class ZipDocument(object): "M33": 1, } } + metadata = { "deleted": False, "lastModified": "1568368808000", @@ -136,6 +181,13 @@ class ZipDocument(object): ID = None def __init__(self, ID=None, doc=None, file=None): + """Create a new instance of a ZipDocument + + Args: + ID: Can be left empty to generate one + doc: a raw pdf, epub or rm (.lines) file. + file: a zipfile to convert from + """ if not ID: ID = str(uuid4()) self.ID = ID @@ -160,15 +212,22 @@ class ZipDocument(object): if file: self.load(file) - def __str__(self): + def __str__(self) -> str: + """string representation of this class""" return f"" - def __repr__(self): + def __repr__(self) -> str: + """string representation of this class""" return self.__str__() - def dump(self, file): - """ - Dump the contents of ZipDocument back to a zip file + def dump(self, file: str) -> NoReturn: + """Dump the contents of ZipDocument back to a zip file. + + This builds a zipfile to upload back to the Remarkable Cloud. + + Args: + file: Where to save the zipfile + """ with ZipFile(f"{file}.zip", "w", ZIP_DEFLATED) as zf: @@ -198,10 +257,16 @@ class ZipDocument(object): zf.writestr(f"{self.ID}.thumbnails/{page.order}.jpg", page.thumbnail.read()) - def load(self, file) -> NoReturn: - """ - Fill in the defaults from the given ZIP + def load(self, file: BytesOrString) -> NoReturn: + """Load a zipfile into this class. + + Extracts the zipfile and reads in the contents. + + Args: + file: A string of a file location or a BytesIO instance of a raw + zipfile """ + self.zipfile = BytesIO() self.zipfile.seek(0) if isinstance(file, str): @@ -265,7 +330,11 @@ class ZipDocument(object): class RmPage(object): - """A Remarkable Page""" + """A Remarkable Page + + Contains the metadata, the page itself & thumbnail. + + """ def __init__(self, page, metadata=None, order=0, thumbnail=None, ID=None): self.page = page if metadata: @@ -281,24 +350,42 @@ class RmPage(object): else: self.ID = str(uuid4()) - def __str__(self): + def __str__(self) -> str: + """String representation of this object""" return f"" - def __repr__(self): + def __repr__(self) -> str: + """String representation of this object""" return self.__str__() def from_zip(ID: str, file: str) -> ZipDocument: + """Return A ZipDocument from a zipfile. + + Create a ZipDocument instance from a zipfile. + + Args: + ID: The object ID this zipfile represents. + file: the filename of the zipfile. + Returns: + An instance of the supplied zipfile. """ - Return A ZipDocument from a zipfile. - """ + return ZipDocument(ID, file=file) def from_request_stream(ID: str, stream: Response) -> ZipDocument: + """Return a ZipDocument from a request stream containing a zipfile. + + This is used with the BlobGETUrl from a :class:`rmapi.document.Document`. + + Args: + ID: The object ID this zipfile represents. + stream: a stream containing the zipfile. + Returns: + the object of the downloaded zipfile. """ - Return a ZipDocument from a request stream containing a zipfile. - """ + tmp = BytesIO() for chunk in stream.iter_content(chunk_size=8192): tmp.write(chunk)