diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index ae4eaf1..a718e99 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -1,4 +1,4 @@ -quickstart +quick start ========== If you previously used the go package `rmapi`_ ,the keys for authorization @@ -12,7 +12,7 @@ If not, you'll need to register the client as a new device on `my remarkable`_. .. _rmapi: https://github.com/juruen/rmapi -Registering the API CLient +Registering the API Client ~~~~~~~~~~~~~~~~~~~~~~~~~~ Registering the device is easy. Go to `my remarkable`_ to register a new device @@ -25,16 +25,16 @@ and use the code you see on the webpage from rmapy.api import Client rmapy = Client() - # Shoud return False + # Should return False rmapy.is_authenticated() # This registers the client as a new device. The received device token is # stored in the users directory in the file ~/.rmapi, the same as with the # go rmapi client. rmapy.register_device("fkgzzklrs") - # It's always a good idea to refresh the user token everytime you start + # It's always a good idea to refresh the user token every time you start # a new session. rmapy.refresh_token() - # Shoud return True + # Should return True rmapy.is_authenticated() Working with items diff --git a/rmapy/api.py b/rmapy/api.py index 8e22bc8..dcb901f 100644 --- a/rmapy/api.py +++ b/rmapy/api.py @@ -26,12 +26,8 @@ DocumentOrFolder = Union[Document, Folder] class Client(object): """API Client for Remarkable Cloud - This allows you to authenticate & communiticate with the Remarkable Cloud + This allows you to authenticate & communicate with the Remarkable Cloud and does all the heavy lifting for you. - - Attributes: - token_set: the authentication tokens - """ token_set = { @@ -48,7 +44,7 @@ class Client(object): def request(self, method: str, path: str, data=None, - body=None, headers={}, + body=None, headers=None, params=None, stream=False) -> requests.Response: """Creates a request against the Remarkable Cloud API @@ -62,12 +58,14 @@ class Client(object): body: the body to request with. This will be converted to json. headers: a dict of additional headers to add to the request. params: Query params to append to the request. - steam: Should the response be a stream? + stream: Should the response be a stream? Returns: A Response instance containing most likely the response from the server. """ + if headers is None: + headers = {} if not path.startswith("http"): if not path.startswith('/'): path = '/' + path @@ -184,13 +182,13 @@ class Client(object): return collection - def get_doc(self, ID: str) -> Optional[DocumentOrFolder]: + def get_doc(self, _id: str) -> Optional[DocumentOrFolder]: """Get a meta item by ID Fetch a meta item from the Remarkable Cloud by ID. Args: - ID: The id of the meta item. + _id: The id of the meta item. Returns: A Document or Folder instance of the requested ID. @@ -198,10 +196,10 @@ class Client(object): DocumentNotFound: When a document cannot be found. """ - log.debug(f"GETTING DOC {ID}") + log.debug(f"GETTING DOC {_id}") response = self.request("GET", "/document-storage/json/2/docs", params={ - "doc": ID, + "doc": _id, "withBlob": True }) log.debug(response.url) @@ -214,7 +212,7 @@ class Client(object): elif data_response[0]["Type"] == "DocumentType": return Document(**data_response[0]) else: - raise DocumentNotFound(f"Cound not find document {ID}") + raise DocumentNotFound(f"Could not find document {_id}") return None def download(self, document: Document) -> ZipDocument: @@ -250,7 +248,7 @@ class Client(object): Args: doc: A Document or folder to delete. Raises: - ApiError: an error occured while uploading the document. + ApiError: an error occurred while uploading the document. """ response = self.request("PUT", "/document-storage/json/2/delete", @@ -259,28 +257,29 @@ class Client(object): "Version": doc.Version }]) - return self.check_reponse(response) + return self.check_response(response) - def upload(self, zipDoc: ZipDocument, to: Folder = Folder(ID="")): + def upload(self, zip_doc: ZipDocument, to: Folder = Folder(ID="")): """Upload a document to the cloud. Add a new document to the Remarkable Cloud. Args: - zipDoc: A ZipDocument instance containing the data of a Document. + zip_doc: A ZipDocument instance containing the data of a Document. + to: the parent of the document. (Default root) Raises: - ApiError: an error occured while uploading the document. + ApiError: an error occurred while uploading the document. """ - BlobURLPut = self._upload_request(zipDoc) - zipDoc.dump(zipDoc.zipfile) - response = self.request("PUT", BlobURLPut, data=zipDoc.zipfile.read()) + blob_url_put = self._upload_request(zip_doc) + zip_doc.dump(zip_doc.zipfile) + response = self.request("PUT", blob_url_put, data=zip_doc.zipfile.read()) # Reset seek - zipDoc.zipfile.seek(0) + zip_doc.zipfile.seek(0) if response.ok: - doc = Document(**zipDoc.metadata) - doc.ID = zipDoc.ID + doc = Document(**zip_doc.metadata) + doc.ID = zip_doc.ID doc.Parent = to.ID return self.update_metadata(doc) else: @@ -304,7 +303,7 @@ class Client(object): "/document-storage/json/2/upload/update-status", body=[req]) - return self.check_reponse(res) + return self.check_response(res) def get_current_version(self, docorfolder: DocumentOrFolder) -> int: """Get the latest version info from a Document or Folder @@ -318,7 +317,7 @@ class Client(object): the version information. Raises: DocumentNotFound: cannot find the requested Document or Folder. - ApiError: An error occured while processing the request. + ApiError: An error occurred while processing the request. """ try: @@ -329,8 +328,8 @@ class Client(object): return 0 return int(d.Version) - def _upload_request(self, zdoc: ZipDocument) -> dict: - zipFile, req = zdoc.create_request() + def _upload_request(self, zip_doc: ZipDocument) -> str: + zip_file, req = zip_doc.create_request() res = self.request("PUT", "/document-storage/json/2/upload/request", body=[req]) if not res.ok: @@ -362,7 +361,7 @@ class Client(object): True if the folder is created. """ - zipFolder, req = folder.create_request() + zip_folder, req = folder.create_request() res = self.request("PUT", "/document-storage/json/2/upload/request", body=[req]) if not res.ok: @@ -373,7 +372,7 @@ class Client(object): if len(response) > 0: dest = response[0].get("BlobURLPut", None) if dest: - res = self.request("PUT", dest, data=zipFolder.read()) + res = self.request("PUT", dest, data=zip_folder.read()) else: raise ApiError( "Cannot create a folder. because BlobURLPut is not set", @@ -382,7 +381,8 @@ class Client(object): self.update_metadata(folder) return True - def check_reponse(self, response: requests.Response): + @staticmethod + def check_response(response: requests.Response): """Check the response from an API Call Does some sanity checking on the Response @@ -416,4 +416,3 @@ class Client(object): raise ApiError( f"Got An invalid HTTP Response: {response.status_code}", response=response) - return True diff --git a/rmapy/collections.py b/rmapy/collections.py index 19cc93c..dd7f768 100644 --- a/rmapy/collections.py +++ b/rmapy/collections.py @@ -9,7 +9,7 @@ DocumentOrFolder = Union[Document, Folder] class Collection(object): """A collection of meta items - This is basicly the content of the Remarkable Cloud. + This is basically the content of the Remarkable Cloud. Attributes: items: A list containing the items. @@ -21,59 +21,59 @@ class Collection(object): for i in items: self.items.append(i) - def add(self, docdict: dict) -> None: + def add(self, doc_dict: dict) -> None: """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. + doc_dict: A dict representing a document or folder. """ - if docdict.get("Type", None) == "DocumentType": - self.add_document(docdict) - elif docdict.get("Type", None) == "CollectionType": - self.add_folder(docdict) + if doc_dict.get("Type", None) == "DocumentType": + self.add_document(doc_dict) + elif doc_dict.get("Type", None) == "CollectionType": + self.add_folder(doc_dict) else: raise TypeError("Unsupported type: {_type}" - .format(_type=docdict.get("Type", None))) + .format(_type=doc_dict.get("Type", None))) - def add_document(self, docdict: dict) -> None: + def add_document(self, doc_dict: dict) -> None: """Add a document to the collection Args: - docdict: A dict respresenting a document. + doc_dict: A dict representing a document. """ - self.items.append(Document(**docdict)) + self.items.append(Document(**doc_dict)) - def add_folder(self, dirdict: dict) -> None: + def add_folder(self, dir_dict: dict) -> None: """Add a document to the collection Args: - dirdict: A dict respresenting a folder. + dir_dict: A dict representing a folder. """ - self.items.append(Folder(**dirdict)) + self.items.append(Folder(**dir_dict)) - def parent(self, docorfolder: DocumentOrFolder) -> Folder: + def parent(self, doc_or_folder: DocumentOrFolder) -> Folder: """Returns the paren of a Document or Folder Args: - docorfolder: A document or folder to get the parent from + doc_or_folder: A document or folder to get the parent from Returns: The parent folder. """ - results = [i for i in self.items if i.ID == docorfolder.ID] + results = [i for i in self.items if i.ID == doc_or_folder.ID] if len(results) > 0 and isinstance(results[0], Folder): return results[0] else: raise FolderNotFound("Could not found the parent of the document.") def children(self, folder: Folder = None) -> List[DocumentOrFolder]: - """Get all the childern from a folder + """Get all the children from a folder Args: folder: A folder where to get the children from. If None, this will diff --git a/rmapy/document.py b/rmapy/document.py index 737dc19..ec43310 100644 --- a/rmapy/document.py +++ b/rmapy/document.py @@ -4,12 +4,11 @@ from zipfile import ZipFile, ZIP_DEFLATED import shutil from uuid import uuid4 import json -from typing import NoReturn, TypeVar, List, Tuple +from typing import TypeVar, List, Tuple from requests import Response from .meta import Meta BytesOrString = TypeVar("BytesOrString", BytesIO, str) -BytesOrNone = TypeVar("BytesOrNone", BytesIO, None) class RmPage(object): @@ -18,7 +17,7 @@ class RmPage(object): Contains the metadata, the page itself & thumbnail. """ - def __init__(self, page, metadata=None, order=0, thumbnail=None, ID=None): + def __init__(self, page, metadata=None, order=0, thumbnail=None, _id=None): self.page = page if metadata: self.metadata = metadata @@ -28,8 +27,8 @@ class RmPage(object): self.order = order if thumbnail: self.thumbnail = thumbnail - if ID: - self.ID = ID + if _id: + self.ID = _id else: self.ID = str(uuid4()) @@ -51,7 +50,7 @@ class Document(Meta): Attributes: ID: Id of the meta object. Version: The version of this object. - Success: If the last API Call was a succes. + Success: If the last API Call was a success. 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. @@ -179,17 +178,17 @@ class ZipDocument(object): rm: List[RmPage] = [] ID = None - def __init__(self, ID=None, doc=None, file=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 + _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 + if not _id: + _id = str(uuid4()) + self.ID = _id if doc: ext = doc[-4:] if ext.endswith("pdf"): @@ -207,7 +206,7 @@ class ZipDocument(object): elif ext.endswith("rm"): self.content["fileType"] = "notebook" with open(doc, 'rb') as fb: - self.rm.append(RmPage(page=BytesIO(doc.read()))) + self.rm.append(RmPage(page=BytesIO(fb.read()))) name = os.path.splitext(os.path.basename(doc))[0] self.metadata["VissibleName"] = name @@ -316,10 +315,8 @@ class ZipDocument(object): pages = [x for x in zf.namelist() if x.startswith(f"{self.ID}/") and x.endswith('.rm')] for p in pages: - pagenumber = int(p.replace(f"{self.ID}/", "") + page_number = int(p.replace(f"{self.ID}/", "") .replace(".rm", "")) - page = BytesIO() - thumbnail = BytesIO() with zf.open(p, 'r') as rm: page = BytesIO(rm.read()) page.seek(0) @@ -331,34 +328,34 @@ class ZipDocument(object): thumbnail = BytesIO(tn.read()) thumbnail.seek(0) - self.rm.append(RmPage(page, metadata, pagenumber, thumbnail, + self.rm.append(RmPage(page, metadata, page_number, thumbnail, self.ID)) self.zipfile.seek(0) -def from_zip(ID: str, file: str) -> ZipDocument: +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. + _id: The object ID this zipfile represents. file: the filename of the zipfile. Returns: An instance of the supplied zipfile. """ - return ZipDocument(ID, file=file) + return ZipDocument(_id, file=file) -def from_request_stream(ID: str, stream: Response) -> ZipDocument: +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:`rmapy.document.Document`. Args: - ID: The object ID this zipfile represents. + _id: The object ID this zipfile represents. stream: a stream containing the zipfile. Returns: the object of the downloaded zipfile. @@ -367,6 +364,6 @@ def from_request_stream(ID: str, stream: Response) -> ZipDocument: tmp = BytesIO() for chunk in stream.iter_content(chunk_size=8192): tmp.write(chunk) - zd = ZipDocument(ID=ID) + zd = ZipDocument(_id=_id) zd.load(tmp) return zd diff --git a/rmapy/exceptions.py b/rmapy/exceptions.py index d884bb9..7b4c2f7 100644 --- a/rmapy/exceptions.py +++ b/rmapy/exceptions.py @@ -27,4 +27,3 @@ class ApiError(Exception): def __init__(self, msg, response=None): self.response = response super(ApiError, self).__init__(msg) - diff --git a/rmapy/folder.py b/rmapy/folder.py index e23eeed..c5681ca 100644 --- a/rmapy/folder.py +++ b/rmapy/folder.py @@ -6,20 +6,21 @@ from zipfile import ZipFile, ZIP_DEFLATED from .const import RFC3339Nano from typing import Tuple, Optional + class ZipFolder(object): """A dummy zipfile to create a folder This is needed to create a folder on the Remarkable Cloud """ - def __init__(self, ID: str): + def __init__(self, _id: str): """Creates a zipfile in memory Args: - ID: the ID to create a zipFolder for + _id: the ID to create a zipFolder for """ super(ZipFolder, self).__init__() - self.ID = ID + self.ID = _id self.file = BytesIO() self.Version = 1 with ZipFile(self.file, 'w', ZIP_DEFLATED) as zf: @@ -37,7 +38,7 @@ class Folder(Meta): Args: name: An optional name for this folder. In the end, a name is - really needed, but can be ommitted to set a later time. + really needed, but can be omitted to set a later time. """ super(Folder, self).__init__(**kwargs) @@ -48,9 +49,9 @@ class Folder(Meta): self.ID = str(uuid4()) def create_request(self) -> Tuple[BytesIO, dict]: - """Prepares the nessesary parameters to create this folder. + """Prepares the necessary parameters to create this folder. - This creates a ZipFolder & the nessesary json body to + This creates a ZipFolder & the necessary json body to create an upload request. """ @@ -61,9 +62,9 @@ class Folder(Meta): } def update_request(self) -> dict: - """Perpares the nessesary parameters to update a folder. + """Prepares the necessary parameters to update a folder. - This sets some parameters in the datastructure to submit to the API. + This sets some parameters in the data structure to submit to the API. """ data = self.to_dict() @@ -76,6 +77,3 @@ class Folder(Meta): def __repr__(self): return self.__str__() - - - diff --git a/rmapy/meta.py b/rmapy/meta.py index 60e4680..2e7a0b5 100644 --- a/rmapy/meta.py +++ b/rmapy/meta.py @@ -7,7 +7,7 @@ class Meta(object): Attributes: ID: Id of the meta object. Version: The version of this object. - Success: If the last API Call was a succes. + Success: If the last API Call was a success. 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. @@ -26,7 +26,7 @@ class Meta(object): ID = "" Version = 0 Message = "" - Succes = True + Success = True BlobURLGet = "" BlobURLGetExpires = "" BlobURLPut = "" @@ -39,8 +39,8 @@ class Meta(object): Parent = "" def __init__(self, **kwargs): - kkeys = self.to_dict().keys() - for k in kkeys: + k_keys = self.to_dict().keys() + for k in k_keys: setattr(self, k, kwargs.get(k, getattr(self, k))) def to_dict(self) -> dict: @@ -56,7 +56,7 @@ class Meta(object): "ID": self.ID, "Version": self.Version, "Message": self.Message, - "Succes": self.Succes, + "Succes": self.Success, "BlobURLGet": self.BlobURLGet, "BlobURLGetExpires": self.BlobURLGetExpires, "BlobURLPut": self.BlobURLPut, diff --git a/setup.py b/setup.py index 4414815..6672d83 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( # package, this name will be registered for you. It will determine how # users can install this project, e.g.: # - # $ pip install sampleproject + # $ pip install sampleprojectk # # And where it will live on PyPI: https://pypi.org/project/sampleproject/ # @@ -37,7 +37,7 @@ setup( # For a discussion on single-sourcing the version across setup.py and the # project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.2.1', # Required + version='0.2.2', # Required # This is a one-line description or tagline of what your project does. This # corresponds to the "Summary" metadata field: @@ -88,8 +88,8 @@ setup( # 3 - Alpha # 4 - Beta # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - #'Development Status :: 4 - Beta', + #'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', #'Development Status :: 5 - Production/Stable' #'Development Status :: 6 - Mature' # Indicate who your project is intended for @@ -180,7 +180,7 @@ setup( # # For example, the following would provide a command called `sample` which # executes the function `main` from this package when invoked: - # TODO + # TODO Create a command line tool # entry_points={ # Optional # 'console_scripts': [ # 'sample=sample:main',