From 13ba20c2496b8163baee37d0afb411d84e31245c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Csaba=20Gy=C3=B6rgyi?= Date: Sun, 23 Jun 2024 21:01:51 +0200 Subject: [PATCH] Fix (almost) every type hint in tags.py - Use the _Element class instead of the Element factory for typing. - Convert tag IDs to UUID during XML parsing. - During XML parsing, the "parent" property contains a UUID in the new format. This allowed for some simplifications. - Other minor typing fixes. - Fixed a related test case. --- GTG/core/tags.py | 63 ++++++++++++++++++------------------ GTG/core/tasks.py | 2 +- tests/core/test_tag_store.py | 2 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/GTG/core/tags.py b/GTG/core/tags.py index b456fcc79..b2f4aa5e5 100644 --- a/GTG/core/tags.py +++ b/GTG/core/tags.py @@ -19,15 +19,15 @@ """Everything related to tags.""" -from gi.repository import GObject, Gtk, Gio, Gdk +from gi.repository import GObject, Gtk, Gio, Gdk # type: ignore[import-untyped] from uuid import uuid4, UUID import logging import random import re -from lxml.etree import Element, SubElement -from typing import Any, Dict, List, Set +from lxml.etree import Element, SubElement, _Element +from typing import Any, Dict, List, Set, Optional from GTG.core.base_store import BaseStore @@ -49,10 +49,10 @@ def __init__(self, id: UUID, name: str) -> None: self.id = id self._name = name - self._icon = None - self._color = None + self._icon: Optional[str] = None + self._color: Optional[str] = None self.actionable = True - self.children = [] + self.children: List[Tag] = [] self.parent = None self._task_count_open = 0 @@ -81,7 +81,7 @@ def __eq__(self, other) -> bool: @GObject.Property(type=int) - def children_count(self) -> str: + def children_count(self) -> int: """Read only property.""" return len(self.children) @@ -100,7 +100,7 @@ def set_name(self, value: str) -> None: @GObject.Property(type=str) - def icon(self) -> str: + def icon(self) -> Optional[str]: """Read only property.""" return self._icon @@ -113,7 +113,7 @@ def set_icon(self, value: str) -> None: @GObject.Property(type=str) - def color(self) -> str: + def color(self) -> Optional[str]: """Read only property.""" return self._color @@ -128,13 +128,13 @@ def set_color(self, value: str) -> None: @GObject.Property(type=bool, default=False) def has_color(self) -> bool: - return self._color and not self._icon + return (self._color is not None) and (self._icon is None) @GObject.Property(type=bool, default=False) def has_icon(self) -> bool: - return self._icon + return self._icon is not None @GObject.Property(type=int, default=0) @@ -172,7 +172,7 @@ def set_task_count_closed(self, value: int) -> None: def get_ancestors(self) -> List['Tag']: """Return all ancestors of this tag""" - ancestors = [] + ancestors: List[Tag] = [] here = self while here.parent: here = here.parent @@ -194,7 +194,7 @@ class TagStore(BaseStore[Tag]): def __init__(self) -> None: - self.used_colors: Set[Color] = set() + self.used_colors: Set[str] = set() self.lookup_names: Dict[str, Tag] = {} super().__init__() @@ -234,7 +234,7 @@ def find(self, name: str) -> Tag: return self.lookup_names[name] - def new(self, name: str, parent: UUID = None) -> Tag: + def new(self, name: str, parent: Optional[UUID] = None) -> Tag: """Create a new tag and add it to the store.""" name = name if not name.startswith('@') else name[1:] @@ -254,7 +254,7 @@ def new(self, name: str, parent: UUID = None) -> Tag: return tag - def from_xml(self, xml: Element) -> None: + def from_xml(self, xml: _Element) -> None: """Load searches from an LXML element.""" elements = list(xml.iter(self.XML_TAG)) @@ -279,7 +279,7 @@ def from_xml(self, xml: Element) -> None: green = int(rgb.green * 255) color = '#{:02x}{:02x}{:02x}'.format(red, green, blue) - tag = Tag(id=tid, name=name) + tag = Tag(id=UUID(tid), name=str(name)) tag.color = color tag.icon = icon tag.actionable = (nonactionable == 'False') @@ -290,24 +290,25 @@ def from_xml(self, xml: Element) -> None: for element in elements: - parent_name = element.get('parent') - - if parent_name: - tid = element.get('id') + child_id: UUID = UUID(element.get('id')) + hex_parent_id: Optional[str] = element.get('parent') + if hex_parent_id is None: + continue - try: - parent_id = self.find(parent_name).id - except KeyError: - parent_id = parent_name + try: + parent_id: UUID = UUID(hex_parent_id) + except ValueError: + log.debug('Malformed parent UUID: %s', tag, hex_parent_id) + continue - try: - self.parent(tid, parent_id) - log.debug('Added %s as child of %s', tag, parent_name) - except KeyError: - pass + try: + self.parent(child_id, parent_id) + log.debug('Added %s as child of %s', tag, hex_parent_id) + except KeyError: + log.debug('Failed to add %s as child of %s', tag, hex_parent_id) - def to_xml(self) -> Element: + def to_xml(self) -> _Element: """Save searches to an LXML element.""" root = Element('taglist') @@ -359,7 +360,7 @@ def rand_color() -> str: return color - def add(self, item: Any, parent_id: UUID = None) -> None: + def add(self, item: Tag, parent_id: Optional[UUID] = None) -> None: """Add a tag to the tagstore.""" super().add(item, parent_id) diff --git a/GTG/core/tasks.py b/GTG/core/tasks.py index 735f62f10..32e3afd97 100644 --- a/GTG/core/tasks.py +++ b/GTG/core/tasks.py @@ -824,7 +824,7 @@ def from_xml(self, xml: Element, tag_store: TagStore) -> None: if taglist is not None: for t in taglist.iter('tag'): try: - tag = tag_store.get(t.text) + tag = tag_store.get(UUID(t.text)) task.add_tag(tag) except KeyError: pass diff --git a/tests/core/test_tag_store.py b/tests/core/test_tag_store.py index 3e173a30d..ff8d151fd 100644 --- a/tests/core/test_tag_store.py +++ b/tests/core/test_tag_store.py @@ -57,7 +57,7 @@ def test_xml_load_tree(self): - + ''')