Code cleanup & Refactoring

This commit is contained in:
Stijn Van Campenhout
2019-12-26 15:16:30 +01:00
parent 3f0e0e36a2
commit 506b34d40a
8 changed files with 91 additions and 98 deletions

View File

@@ -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,7 +25,7 @@ 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
@@ -34,7 +34,7 @@ and use the code you see on the webpage
# 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -27,4 +27,3 @@ class ApiError(Exception):
def __init__(self, msg, response=None):
self.response = response
super(ApiError, self).__init__(msg)

View File

@@ -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,7 +62,7 @@ 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 data structure to submit to the API.
"""
@@ -76,6 +77,3 @@ class Folder(Meta):
def __repr__(self):
return self.__str__()

View File

@@ -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,

View File

@@ -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',