From 6b627b86d510d8ed06032d1f4a845e3dfc849833 Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Sun, 27 Feb 2022 00:47:35 +0100 Subject: [PATCH 1/7] Minor generic cleanup --- examples/resume.py | 4 ---- gkeepapi/__init__.py | 11 +++++------ gkeepapi/node.py | 3 --- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/resume.py b/examples/resume.py index c7f2a1f..4cd162e 100755 --- a/examples/resume.py +++ b/examples/resume.py @@ -1,12 +1,8 @@ #!/usr/bin/env python3 import sys -import os -import argparse -import yaml import keyring import getpass import logging -import time import gkeepapi USERNAME = "user@gmail.com" diff --git a/gkeepapi/__init__.py b/gkeepapi/__init__.py index 55a61bb..c1f2ea4 100644 --- a/gkeepapi/__init__.py +++ b/gkeepapi/__init__.py @@ -9,7 +9,6 @@ import re import time import random -import json from uuid import getnode as get_mac @@ -110,7 +109,7 @@ def getEmail(self): return self._email def setEmail(self, email): - """Gets the account email. + """Sets the account email. Args: email (str): The account email. @@ -185,7 +184,7 @@ def __init__(self, base_url, auth=None): def getAuth(self): """Get authentication details for this API. - Args: + Return: auth (APIAuth): The auth object """ return self._auth @@ -577,7 +576,7 @@ def list(self, master=True): params.update({ 'recurrenceOptions': { - 'collapseMode':'INSTANCES_ONLY', + 'collapseMode': 'INSTANCES_ONLY', 'recurrencesOnly': True, }, 'includeArchived': False, @@ -676,7 +675,7 @@ def _clear(self): root_node = _node.Root() self._nodes[_node.Root.ID] = root_node - def login(self, username, password, state=None, sync=True, device_id=None): + def login(self, email, password, state=None, sync=True, device_id=None): """Authenticate to Google with the provided credentials & sync. Args: @@ -691,7 +690,7 @@ def login(self, username, password, state=None, sync=True, device_id=None): if device_id is None: device_id = get_mac() - ret = auth.login(username, password, device_id) + ret = auth.login(email, password, device_id) if ret: self.load(auth, state, sync) diff --git a/gkeepapi/node.py b/gkeepapi/node.py index 57abde3..f4775c1 100644 --- a/gkeepapi/node.py +++ b/gkeepapi/node.py @@ -590,9 +590,6 @@ def remove(self, annotation): Args: annotation (gkeepapi.node.Annotation): An Annotation object. - - Returns: - gkeepapi.node.Annotation: The Annotation. """ if annotation.id in self._annotations: del self._annotations[annotation.id] From 910b7c6f0dc2e93678db044869f66637341d2bb5 Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Sun, 27 Feb 2022 10:37:04 +0100 Subject: [PATCH 2/7] Remove redundant u string prefix Unicode strings are the default behaviour in Python 3 --- docs/conf.py | 17 +++++++---------- gkeepapi/node.py | 4 ++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b9a5df7..9ff71ca 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ master_doc = 'index' # General information about the project. -project = u'gkeepapi' -copyright = u'2017, Kai' -author = u'Kai' +project = 'gkeepapi' +copyright = '2017, Kai' +author = 'Kai' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -146,8 +146,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'gkeepapi.tex', u'gkeepapi Documentation', - u'Kai', 'manual'), + (master_doc, 'gkeepapi.tex', 'gkeepapi Documentation', + 'Kai', 'manual'), ] @@ -156,7 +156,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'gkeepapi', u'gkeepapi Documentation', + (master_doc, 'gkeepapi', 'gkeepapi Documentation', [author], 1) ] @@ -167,10 +167,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'gkeepapi', u'gkeepapi Documentation', + (master_doc, 'gkeepapi', 'gkeepapi Documentation', author, 'gkeepapi', 'One line description of project.', 'Miscellaneous'), ] - - - diff --git a/gkeepapi/node.py b/gkeepapi/node.py index f4775c1..802d4ad 100644 --- a/gkeepapi/node.py +++ b/gkeepapi/node.py @@ -1582,9 +1582,9 @@ def checked(self, value): self.touch(True) def __str__(self): - return u'%s%s %s' % ( + return '%s%s %s' % ( ' ' if self.indented else '', - u'☑' if self.checked else u'☐', + '☑' if self.checked else '☐', self.text ) From d158fa5e66689ce054b9c616a8f15e5d7908fe41 Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Sun, 27 Feb 2022 11:00:03 +0100 Subject: [PATCH 3/7] Replace some str.format with fstrings --- gkeepapi/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gkeepapi/node.py b/gkeepapi/node.py index 802d4ad..25ed4d7 100644 --- a/gkeepapi/node.py +++ b/gkeepapi/node.py @@ -212,7 +212,7 @@ def load(self, raw): try: self._load(raw) except (KeyError, ValueError) as e: - raise exception.ParseException('Parse error in %s' % (type(self)), raw) from e + raise exception.ParseException(f'Parse error in {type(self)}', raw) from e def _load(self, raw): """Unserialize from raw representation. (Implementation logic) @@ -1814,7 +1814,7 @@ def from_json(cls, raw): except (KeyError, ValueError) as e: logger.warning('Unknown blob type: %s', _type) if DEBUG: # pragma: no cover - raise exception.ParseException('Parse error for %s' % (_type), raw) from e + raise exception.ParseException(f'Parse error for {_type}', raw) from e return None blob = bcls() blob.load(raw) @@ -1923,7 +1923,7 @@ def from_json(raw): except (KeyError, ValueError) as e: logger.warning('Unknown node type: %s', _type) if DEBUG: # pragma: no cover - raise exception.ParseException('Parse error for %s' % (_type), raw) from e + raise exception.ParseException(f'Parse error for {_type}', raw) from e return None node = ncls() node.load(raw) From 7f9d0f47a341def76a161e27d94bbfa6d1344668 Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Sat, 27 Aug 2022 19:06:24 +0200 Subject: [PATCH 4/7] Fix Keep::dump docs --- gkeepapi/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gkeepapi/__init__.py b/gkeepapi/__init__.py index c1f2ea4..e6b82f6 100644 --- a/gkeepapi/__init__.py +++ b/gkeepapi/__init__.py @@ -745,8 +745,8 @@ def load(self, auth, state=None, sync=True): def dump(self): """Serialize note data. - Args: - state (dict): Serialized state to load. + Returns: + dict: Serialized state. """ # Find all nodes manually, as the Keep object isn't aware of new # ListItems until they've been synced to the server. From ebd0f5c8559f631fd19615641971d4f1ce58c4de Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Sat, 27 Aug 2022 20:33:51 +0200 Subject: [PATCH 5/7] Add initial type hints Only user-facing methods have typing info for now --- gkeepapi/__init__.py | 55 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/gkeepapi/__init__.py b/gkeepapi/__init__.py index e6b82f6..017f4e4 100644 --- a/gkeepapi/__init__.py +++ b/gkeepapi/__init__.py @@ -9,6 +9,7 @@ import re import time import random +from typing import List, Optional, Tuple, Dict from uuid import getnode as get_mac @@ -653,7 +654,7 @@ class Keep(object): """ OAUTH_SCOPES = 'oauth2:https://www.googleapis.com/auth/memento https://www.googleapis.com/auth/reminders' - def __init__(self): + def __init__(self) -> None: self._keep_api = KeepAPI() self._reminders_api = RemindersAPI() self._media_api = MediaAPI() @@ -675,7 +676,7 @@ def _clear(self): root_node = _node.Root() self._nodes[_node.Root.ID] = root_node - def login(self, email, password, state=None, sync=True, device_id=None): + def login(self, email: str, password: str, state: Optional[Dict] = None, sync=True, device_id: Optional[int] = None): """Authenticate to Google with the provided credentials & sync. Args: @@ -690,13 +691,12 @@ def login(self, email, password, state=None, sync=True, device_id=None): if device_id is None: device_id = get_mac() - ret = auth.login(email, password, device_id) - if ret: - self.load(auth, state, sync) + auth.login(email, password, device_id) + self.load(auth, state, sync) - return ret + return True - def resume(self, email, master_token, state=None, sync=True, device_id=None): + def resume(self, email: str, master_token: str, state: Optional[Dict] = None, sync=True, device_id: Optional[int] = None): """Authenticate to Google with the provided master token & sync. Args: @@ -711,13 +711,12 @@ def resume(self, email, master_token, state=None, sync=True, device_id=None): if device_id is None: device_id = get_mac() - ret = auth.load(email, master_token, device_id) - if ret: - self.load(auth, state, sync) + auth.load(email, master_token, device_id) + self.load(auth, state, sync) - return ret + return True - def getMasterToken(self): + def getMasterToken(self) -> str: """Get master token for resuming. Returns: @@ -725,7 +724,7 @@ def getMasterToken(self): """ return self._keep_api.getAuth().getMasterToken() - def load(self, auth, state=None, sync=True): + def load(self, auth: APIAuth, state: Optional[Dict] = None, sync=True) -> None: """Authenticate to Google with a prepared authentication object & sync. Args: auth (APIAuth): Authentication object. @@ -742,7 +741,7 @@ def load(self, auth, state=None, sync=True): if sync: self.sync(True) - def dump(self): + def dump(self) -> Dict: """Serialize note data. Returns: @@ -761,7 +760,7 @@ def dump(self): 'nodes': [node.save(False) for node in nodes] } - def restore(self, state): + def restore(self, state: Dict) -> None: """Unserialize saved note data. Args: @@ -772,7 +771,7 @@ def restore(self, state): self._parseNodes(state['nodes']) self._keep_version = state['keep_version'] - def get(self, node_id): + def get(self, node_id: str) -> _node.TopLevelNode: """Get a note with the given ID. Args: @@ -785,7 +784,7 @@ def get(self, node_id): self._nodes[_node.Root.ID].get(node_id) or \ self._nodes[_node.Root.ID].get(self._sid_map.get(node_id)) - def add(self, node): + def add(self, node: _node.Node) -> None: """Register a top level node (and its children) for syncing up to the server. There's no need to call this for nodes created by :py:meth:`createNote` or :py:meth:`createList` as they are automatically added. @@ -794,7 +793,7 @@ def add(self, node): node (gkeepapi.node.Node): The node to sync. Raises: - Invalid: If the parent node is not found. + InvalidException: If the parent node is not found. """ if node.parent_id != _node.Root.ID: raise exception.InvalidException('Not a top level node') @@ -845,7 +844,7 @@ def find(self, query=None, func=None, labels=None, colors=None, pinned=None, arc (trashed is None or node.trashed == trashed) ) - def createNote(self, title=None, text=None): + def createNote(self, title: Optional[str] = None, text: Optional[str] = None) -> _node.Node: """Create a new managed note. Any changes to the note will be uploaded when :py:meth:`sync` is called. Args: @@ -863,7 +862,7 @@ def createNote(self, title=None, text=None): self.add(node) return node - def createList(self, title=None, items=None): + def createList(self, title: Optional[str] = None, items: Optional[List[Tuple[str, bool]]] = None) -> _node.List: """Create a new list and populate it. Any changes to the note will be uploaded when :py:meth:`sync` is called. Args: @@ -887,7 +886,7 @@ def createList(self, title=None, items=None): self.add(node) return node - def createLabel(self, name): + def createLabel(self, name: str) -> _node.Label: """Create a new label. Args: @@ -906,7 +905,7 @@ def createLabel(self, name): self._labels[node.id] = node # pylint: disable=protected-access return node - def findLabel(self, query, create=False): + def findLabel(self, query, create=False) -> Optional[_node.Label]: """Find a label with the given name. Args: @@ -930,7 +929,7 @@ def findLabel(self, query, create=False): return self.createLabel(name) if create and is_str else None - def getLabel(self, label_id): + def getLabel(self, label_id: str) -> Optional[_node.Label]: """Get an existing label. Args: @@ -941,7 +940,7 @@ def getLabel(self, label_id): """ return self._labels.get(label_id) - def deleteLabel(self, label_id): + def deleteLabel(self, label_id: str) -> None: """Deletes a label. Args: @@ -955,7 +954,7 @@ def deleteLabel(self, label_id): for node in self.all(): node.labels.remove(label) - def labels(self): + def labels(self) -> List[_node.Label]: """Get all labels. Returns: @@ -963,7 +962,7 @@ def labels(self): """ return self._labels.values() - def getMediaLink(self, blob): + def getMediaLink(self, blob: _node.Blob) -> str: """Get the canonical link to media. Args: @@ -974,7 +973,7 @@ def getMediaLink(self, blob): """ return self._media_api.get(blob) - def all(self): + def all(self) -> List[_node.TopLevelNode]: """Get all Notes. Returns: @@ -982,7 +981,7 @@ def all(self): """ return self._nodes[_node.Root.ID].children - def sync(self, resync=False): + def sync(self, resync=False) -> None: """Sync the local Keep tree with the server. If resyncing, local changes will be destroyed. Otherwise, local changes to notes, labels and reminders will be detected and synced up. Args: From 49e72a29bb2be3bf69c7aa615b54755fc5b4033a Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Tue, 27 Sep 2022 17:17:04 +0200 Subject: [PATCH 6/7] Remove Python 2 regular expression support, add typing --- gkeepapi/__init__.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/gkeepapi/__init__.py b/gkeepapi/__init__.py index 017f4e4..3b77943 100644 --- a/gkeepapi/__init__.py +++ b/gkeepapi/__init__.py @@ -9,7 +9,7 @@ import re import time import random -from typing import List, Optional, Tuple, Dict +from typing import Callable, Iterator, List, Optional, Tuple, Dict, Union from uuid import getnode as get_mac @@ -21,11 +21,6 @@ logger = logging.getLogger(__name__) -try: - Pattern = re._pattern_type # pylint: disable=protected-access -except AttributeError: - Pattern = re.Pattern # pylint: disable=no-member - class APIAuth(object): """Authentication token manager""" def __init__(self, scopes): @@ -801,7 +796,16 @@ def add(self, node: _node.Node) -> None: self._nodes[node.id] = node self._nodes[node.parent_id].append(node, False) - def find(self, query=None, func=None, labels=None, colors=None, pinned=None, archived=None, trashed=False): # pylint: disable=too-many-arguments + def find( + self, + query: Union[re.Pattern, str, None] = None, + func: Optional[Callable] = None, + labels: Optional[List[str]] = None, + colors: Optional[List[str]] = None, + pinned: Optional[bool] = None, + archived: Optional[bool] = None, + trashed: Optional[bool] = False + ) -> Iterator[_node.TopLevelNode]: # pylint: disable=too-many-arguments """Find Notes based on the specified criteria. Args: @@ -823,7 +827,7 @@ def find(self, query=None, func=None, labels=None, colors=None, pinned=None, arc # Process the query. (query is None or ( (isinstance(query, str) and (query in node.title or query in node.text)) or - (isinstance(query, Pattern) and ( + (isinstance(query, re.Pattern) and ( query.search(node.title) or query.search(node.text) )) )) and @@ -905,7 +909,7 @@ def createLabel(self, name: str) -> _node.Label: self._labels[node.id] = node # pylint: disable=protected-access return node - def findLabel(self, query, create=False) -> Optional[_node.Label]: + def findLabel(self, query: Union[re.Pattern, str], create=False) -> Optional[_node.Label]: """Find a label with the given name. Args: @@ -924,7 +928,7 @@ def findLabel(self, query, create=False) -> Optional[_node.Label]: for label in self._labels.values(): # Match the label against query, which may be a str or Pattern. if (is_str and query == label.name.lower()) or \ - (isinstance(query, Pattern) and query.search(label.name)): + (isinstance(query, re.Pattern) and query.search(label.name)): return label return self.createLabel(name) if create and is_str else None From 0ab21a404a0adc693f78f100ba7117fa18b1b25a Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Tue, 27 Sep 2022 17:38:37 +0200 Subject: [PATCH 7/7] Remove unnecessary typing info from docstrings Sphinx supports PEP 484 type annotations now --- gkeepapi/__init__.py | 82 ++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/gkeepapi/__init__.py b/gkeepapi/__init__.py index 3b77943..b120dcd 100644 --- a/gkeepapi/__init__.py +++ b/gkeepapi/__init__.py @@ -675,9 +675,9 @@ def login(self, email: str, password: str, state: Optional[Dict] = None, sync=Tr """Authenticate to Google with the provided credentials & sync. Args: - email (str): The account to use. - password (str): The account password. - state (dict): Serialized state to load. + email: The account to use. + password: The account password. + state: Serialized state to load. Raises: LoginException: If there was a problem logging in. @@ -695,9 +695,9 @@ def resume(self, email: str, master_token: str, state: Optional[Dict] = None, sy """Authenticate to Google with the provided master token & sync. Args: - email (str): The account to use. - master_token (str): The master token. - state (dict): Serialized state to load. + email: The account to use. + master_token: The master token. + state: Serialized state to load. Raises: LoginException: If there was a problem logging in. @@ -715,15 +715,15 @@ def getMasterToken(self) -> str: """Get master token for resuming. Returns: - str: The master token. + The master token. """ return self._keep_api.getAuth().getMasterToken() def load(self, auth: APIAuth, state: Optional[Dict] = None, sync=True) -> None: """Authenticate to Google with a prepared authentication object & sync. Args: - auth (APIAuth): Authentication object. - state (dict): Serialized state to load. + auth: Authentication object. + state: Serialized state to load. Raises: LoginException: If there was a problem logging in. @@ -740,7 +740,7 @@ def dump(self) -> Dict: """Serialize note data. Returns: - dict: Serialized state. + Serialized state. """ # Find all nodes manually, as the Keep object isn't aware of new # ListItems until they've been synced to the server. @@ -759,7 +759,7 @@ def restore(self, state: Dict) -> None: """Unserialize saved note data. Args: - state (dict): Serialized state to load. + state: Serialized state to load. """ self._clear() self._parseUserInfo({'labels': state['labels']}) @@ -770,10 +770,10 @@ def get(self, node_id: str) -> _node.TopLevelNode: """Get a note with the given ID. Args: - node_id (str): The note ID. + node_id: The note ID. Returns: - gkeepapi.node.TopLevelNode: The Note or None if not found. + The Note or None if not found. """ return \ self._nodes[_node.Root.ID].get(node_id) or \ @@ -785,7 +785,7 @@ def add(self, node: _node.Node) -> None: LoginException: If :py:meth:`login` has not been called. Args: - node (gkeepapi.node.Node): The node to sync. + node: The node to sync. Raises: InvalidException: If the parent node is not found. @@ -809,16 +809,16 @@ def find( """Find Notes based on the specified criteria. Args: - query (Union[_sre.SRE_Pattern, str, None]): A str or regular expression to match against the title and text. - func (Union[callable, None]): A filter function. - labels (Union[List[str], None]): A list of label ids or objects to match. An empty list matches notes with no labels. - colors (Union[List[str], None]): A list of colors to match. - pinned (Union[bool, None]): Whether to match pinned notes. - archived (Union[bool, None]): Whether to match archived notes. - trashed (Union[bool, None]): Whether to match trashed notes. + query: A str or regular expression to match against the title and text. + func: A filter function. + labels: A list of label ids or objects to match. An empty list matches notes with no labels. + colors: A list of colors to match. + pinned: Whether to match pinned notes. + archived: Whether to match archived notes. + trashed: Whether to match trashed notes. Return: - Generator[gkeepapi.node.TopLevelNode]: Results. + Search results. """ if labels is not None: labels = [i.id if isinstance(i, _node.Label) else i for i in labels] @@ -852,11 +852,11 @@ def createNote(self, title: Optional[str] = None, text: Optional[str] = None) -> """Create a new managed note. Any changes to the note will be uploaded when :py:meth:`sync` is called. Args: - title (str): The title of the note. - text (str): The text of the note. + title: The title of the note. + text: The text of the note. Returns: - gkeepapi.node.List: The new note. + The new note. """ node = _node.Note() if title is not None: @@ -870,11 +870,11 @@ def createList(self, title: Optional[str] = None, items: Optional[List[Tuple[str """Create a new list and populate it. Any changes to the note will be uploaded when :py:meth:`sync` is called. Args: - title (str): The title of the list. - items (List[(str, bool)]): A list of tuples. Each tuple represents the text and checked status of the listitem. + title: The title of the list. + items: A list of tuples. Each tuple represents the text and checked status of the listitem. Returns: - gkeepapi.node.List: The new list. + The new list. """ if items is None: items = [] @@ -894,10 +894,10 @@ def createLabel(self, name: str) -> _node.Label: """Create a new label. Args: - name (str): Label name. + name: Label name. Returns: - gkeepapi.node.Label: The new label. + The new label. Raises: LabelException: If the label exists. @@ -913,11 +913,11 @@ def findLabel(self, query: Union[re.Pattern, str], create=False) -> Optional[_no """Find a label with the given name. Args: - name (Union[_sre.SRE_Pattern, str]): A str or regular expression to match against the name. - create (bool): Whether to create the label if it doesn't exist (only if name is a str). + name: A str or regular expression to match against the name. + create: Whether to create the label if it doesn't exist (only if name is a str). Returns: - Union[gkeepapi.node.Label, None]: The label. + The label. """ is_str = isinstance(query, str) name = None @@ -937,10 +937,10 @@ def getLabel(self, label_id: str) -> Optional[_node.Label]: """Get an existing label. Args: - label_id (str): Label id. + label_id: Label id. Returns: - Union[gkeepapi.node.Label, None]: The label. + The label. """ return self._labels.get(label_id) @@ -948,7 +948,7 @@ def deleteLabel(self, label_id: str) -> None: """Deletes a label. Args: - label_id (str): Label id. + label_id: Label id. """ if label_id not in self._labels: return @@ -962,7 +962,7 @@ def labels(self) -> List[_node.Label]: """Get all labels. Returns: - List[gkeepapi.node.Label]: Labels + Labels """ return self._labels.values() @@ -970,10 +970,10 @@ def getMediaLink(self, blob: _node.Blob) -> str: """Get the canonical link to media. Args: - blob (gkeepapi.node.Blob): The media resource. + blob: The media resource. Returns: - str: A link to the media. + A link to the media. """ return self._media_api.get(blob) @@ -981,7 +981,7 @@ def all(self) -> List[_node.TopLevelNode]: """Get all Notes. Returns: - List[gkeepapi.node.TopLevelNode]: Notes + Notes """ return self._nodes[_node.Root.ID].children @@ -989,7 +989,7 @@ def sync(self, resync=False) -> None: """Sync the local Keep tree with the server. If resyncing, local changes will be destroyed. Otherwise, local changes to notes, labels and reminders will be detected and synced up. Args: - resync (bool): Whether to resync data. + resync: Whether to resync data. Raises: SyncException: If there is a consistency issue.