Repository: kiwiz/gkeepapi

Issue:
Migrated Assistant Notes raise KeyError: 'title' on resume
Please make sure you've done the following before submitting your issue:

- [x] Check that you're running the newest version of the library.
- [x] If you're providing a stack trace, make sure it doesn't contain your password.
- [x] If you're getting a KeyError or ParseException, please follow the instructions [here](https://gkeepapi.readthedocs.io/en/latest/#reporting-errors) to dump the raw data.

Additionally, please provide the following information:

- Operating system: Windows 10 22H2
- Python version :  3.11.0
---
<details><summary>Raw data from print(e.raw)</summary>
<pre>
{
   "webLink":{
      "kind":"notes#webLink",
      "url":"https://support.google.com/keep/?p=migrated_from_assistant",
      "provenanceUrl":"https://support.google.com/keep/?p=migrated_from_assistant",
      "hasThumbnailUrl":true
   }
}
</pre>
</details>

Calling code can be found at
https://github.com/PrOF-kk/rainmeter-gkeep/blob/115a1f443372cdb4208eafe532508db6b9461543/Skins/Keep/Keep.py#L40


Discussion:

Patch:
diff --git a/src/gkeepapi/node.py b/src/gkeepapi/node.py
index eb52b05..79dbec1 100644
--- a/src/gkeepapi/node.py
+++ b/src/gkeepapi/node.py
@@ -311,15 +311,19 @@ class WebLink(Annotation):
     def __init__(self) -> None:
         """Construct a weblink"""
         super().__init__()
-        self._title = ""
+        self._title = None
         self._url = ""
         self._image_url = None
         self._provenance_url = ""
-        self._description = ""
+        self._description = None
 
     def _load(self, raw: dict) -> None:
         super()._load(raw)
-        self._title = raw["webLink"]["title"]
+        self._title = (
+            raw["webLink"]["title"]
+            if "title" in raw["webLink"]
+            else self.title
+        )
         self._url = raw["webLink"]["url"]
         self._image_url = (
             raw["webLink"]["imageUrl"]
@@ -327,7 +331,11 @@ class WebLink(Annotation):
             else self.image_url
         )
         self._provenance_url = raw["webLink"]["provenanceUrl"]
-        self._description = raw["webLink"]["description"]
+        self._description = (
+            raw["webLink"]["description"]
+            if "description" in raw["webLink"]
+            else self.description
+        )
 
     def save(self, clean: bool = True) -> dict:
         """Save the weblink"""
@@ -342,11 +350,11 @@ class WebLink(Annotation):
         return ret
 
     @property
-    def title(self) -> str:
+    def title(self) -> str | None:
         """Get the link title.
 
         Returns:
-            The link title.
+            The link title or None.
         """
         return self._title
 
@@ -398,11 +406,11 @@ class WebLink(Annotation):
         self._dirty = True
 
     @property
-    def description(self) -> str:
+    def description(self) -> str | None:
         """Get the link description.
 
         Returns:
-            The link description.
+            The link description or None.
         """
         return self._description
 


Test patch:
diff --git a/test/test_nodes.py b/test/test_nodes.py
index f0edbd7..74d91a4 100644
--- a/test/test_nodes.py
+++ b/test/test_nodes.py
@@ -52,6 +52,13 @@ class AnnotationTests(unittest.TestCase):
         PROVENANCEURL = 'https://provenance.url'
         DESCRIPTION = 'Description'
 
+        clean_node(n)
+        self.assertEqual(None, n.title)
+        self.assertEqual('', n.url)
+        self.assertEqual(None, n.image_url)
+        self.assertEqual('', n.provenance_url)
+        self.assertEqual(None, n.description)
+
         clean_node(n)
         n.title = TITLE
         self.assertTrue(n.dirty)