home · contact · privacy
Simplify code with namedtuples and dataclasses.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 22 Apr 2024 01:52:44 +0000 (03:52 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 22 Apr 2024 01:52:44 +0000 (03:52 +0200)
plomtask/http.py
plomtask/processes.py
plomtask/todos.py
templates/day.html
templates/process.html
tests/processes.py
tests/todos.py

index b5a6c167b98f36c5b4ffb6e0c3d0ea14869101a7..d8ca8dfdffd93de4ffd7cc3858826cfd35cf60f4 100644 (file)
@@ -1,5 +1,6 @@
 """Web server stuff."""
 from typing import Any
 """Web server stuff."""
 from typing import Any
+from collections import namedtuple
 from http.server import BaseHTTPRequestHandler
 from http.server import HTTPServer
 from urllib.parse import urlparse, parse_qs
 from http.server import BaseHTTPRequestHandler
 from http.server import HTTPServer
 from urllib.parse import urlparse, parse_qs
@@ -119,23 +120,22 @@ class TaskHandler(BaseHTTPRequestHandler):
     def do_GET_day(self) -> dict[str, object]:
         """Show single Day of ?date=."""
         date = self.params.get_str('date', todays_date())
     def do_GET_day(self) -> dict[str, object]:
         """Show single Day of ?date=."""
         date = self.params.get_str('date', todays_date())
-        conditions_listing = []
         top_todos = [t for t in Todo.by_date(self.conn, date) if not t.parents]
         seen_todos: set[int] = set()
         seen_conditions: set[int] = set()
         todo_trees = [t.get_step_tree(seen_todos, seen_conditions)
                       for t in top_todos]
         top_todos = [t for t in Todo.by_date(self.conn, date) if not t.parents]
         seen_todos: set[int] = set()
         seen_conditions: set[int] = set()
         todo_trees = [t.get_step_tree(seen_todos, seen_conditions)
                       for t in top_todos]
-        for condition in Condition.all(self.conn):
-            enablers = Todo.enablers_for_at(self.conn, condition, date)
-            disablers = Todo.disablers_for_at(self.conn, condition, date)
-            conditions_listing += [{
-                    'condition': condition,
-                    'enablers': enablers,
-                    'disablers': disablers}]
+        ConditionsNode = namedtuple('ConditionsNode',
+                                    ('condition', 'enablers', 'disablers'))
+        conditions_nodes: list[ConditionsNode] = []
+        for condi in Condition.all(self.conn):
+            enablers = Todo.enablers_for_at(self.conn, condi, date)
+            disablers = Todo.disablers_for_at(self.conn, condi, date)
+            conditions_nodes += [ConditionsNode(condi, enablers, disablers)]
         return {'day': Day.by_id(self.conn, date, create=True),
                 'todo_trees': todo_trees,
                 'processes': Process.all(self.conn),
         return {'day': Day.by_id(self.conn, date, create=True),
                 'todo_trees': todo_trees,
                 'processes': Process.all(self.conn),
-                'conditions_listing': conditions_listing}
+                'conditions_nodes': conditions_nodes}
 
     def do_GET_todo(self) -> dict[str, object]:
         """Show single Todo of ?id=."""
 
     def do_GET_todo(self) -> dict[str, object]:
         """Show single Todo of ?id=."""
index eb438958d4ac7748426116664032cae18f73a5dc..32eee4d350db5ed78f4501f7016688f5c174c7cb 100644 (file)
@@ -1,12 +1,23 @@
 """Collecting Processes and Process-related items."""
 from __future__ import annotations
 """Collecting Processes and Process-related items."""
 from __future__ import annotations
-from typing import Any, Set
+from dataclasses import dataclass
+from typing import Set
 from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.misc import VersionedAttribute
 from plomtask.conditions import Condition, ConditionsRelations
 from plomtask.exceptions import NotFoundException, BadFormatException
 
 
 from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.misc import VersionedAttribute
 from plomtask.conditions import Condition, ConditionsRelations
 from plomtask.exceptions import NotFoundException, BadFormatException
 
 
+@dataclass
+class ProcessStepsNode:
+    """Collects what's useful to know for ProcessSteps tree display."""
+    process: Process
+    parent_id: int | None
+    is_explicit: bool
+    steps: dict[int, ProcessStepsNode]
+    seen: bool
+
+
 class Process(BaseModel, ConditionsRelations):
     """Template for, and metadata for, Todos, and their arrangements."""
     table_name = 'processes'
 class Process(BaseModel, ConditionsRelations):
     """Template for, and metadata for, Todos, and their arrangements."""
     table_name = 'processes'
@@ -76,29 +87,30 @@ class Process(BaseModel, ConditionsRelations):
         return [self.__class__.by_id(db_conn, id_) for id_ in owner_ids]
 
     def get_steps(self, db_conn: DatabaseConnection, external_owner:
         return [self.__class__.by_id(db_conn, id_) for id_ in owner_ids]
 
     def get_steps(self, db_conn: DatabaseConnection, external_owner:
-                  Process | None = None) -> dict[int, dict[str, object]]:
+                  Process | None = None) -> dict[int, ProcessStepsNode]:
         """Return tree of depended-on explicit and implicit ProcessSteps."""
 
         """Return tree of depended-on explicit and implicit ProcessSteps."""
 
-        def make_node(step: ProcessStep) -> dict[str, object]:
+        def make_node(step: ProcessStep) -> ProcessStepsNode:
             is_explicit = False
             if external_owner is not None:
                 is_explicit = step.owner_id == external_owner.id_
             process = self.__class__.by_id(db_conn, step.step_process_id)
             step_steps = process.get_steps(db_conn, external_owner)
             is_explicit = False
             if external_owner is not None:
                 is_explicit = step.owner_id == external_owner.id_
             process = self.__class__.by_id(db_conn, step.step_process_id)
             step_steps = process.get_steps(db_conn, external_owner)
-            return {'process': process, 'parent_id': step.parent_step_id,
-                    'is_explicit': is_explicit, 'steps': step_steps}
+            return ProcessStepsNode(process, step.parent_step_id,
+                                    is_explicit, step_steps, False)
 
 
-        def walk_steps(node_id: int, node: dict[str, Any]) -> None:
+        def walk_steps(node_id: int, node: ProcessStepsNode) -> None:
             explicit_children = [s for s in self.explicit_steps
                                  if s.parent_step_id == node_id]
             for child in explicit_children:
             explicit_children = [s for s in self.explicit_steps
                                  if s.parent_step_id == node_id]
             for child in explicit_children:
-                node['steps'][child.id_] = make_node(child)
-            node['seen'] = node_id in seen_step_ids
+                assert isinstance(child.id_, int)
+                node.steps[child.id_] = make_node(child)
+            node.seen = node_id in seen_step_ids
             seen_step_ids.add(node_id)
             seen_step_ids.add(node_id)
-            for id_, step in node['steps'].items():
+            for id_, step in node.steps.items():
                 walk_steps(id_, step)
 
                 walk_steps(id_, step)
 
-        steps: dict[int, dict[str, object]] = {}
+        steps: dict[int, ProcessStepsNode] = {}
         seen_step_ids: Set[int] = set()
         if external_owner is None:
             external_owner = self
         seen_step_ids: Set[int] = set()
         if external_owner is None:
             external_owner = self
@@ -124,12 +136,14 @@ class Process(BaseModel, ConditionsRelations):
         just deleted under its feet), or if the parent step would not be
         owned by the current Process.
         """
         just deleted under its feet), or if the parent step would not be
         owned by the current Process.
         """
+
         def walk_steps(node: ProcessStep) -> None:
             if node.step_process_id == self.id_:
                 raise BadFormatException('bad step selection causes recursion')
             step_process = self.by_id(db_conn, node.step_process_id)
             for step in step_process.explicit_steps:
                 walk_steps(step)
         def walk_steps(node: ProcessStep) -> None:
             if node.step_process_id == self.id_:
                 raise BadFormatException('bad step selection causes recursion')
             step_process = self.by_id(db_conn, node.step_process_id)
             for step in step_process.explicit_steps:
                 walk_steps(step)
+
         if parent_step_id is not None:
             try:
                 parent_step = ProcessStep.by_id(db_conn, parent_step_id)
         if parent_step_id is not None:
             try:
                 parent_step = ProcessStep.by_id(db_conn, parent_step_id)
index cf4c33048ba779fb2145e21a4f325f55b6bfe024..ff6cdcb1e96144743f828ca5fea14072988be10a 100644 (file)
@@ -1,6 +1,6 @@
 """Actionables."""
 from __future__ import annotations
 """Actionables."""
 from __future__ import annotations
-from collections import namedtuple
+from dataclasses import dataclass
 from typing import Any
 from sqlite3 import Row
 from plomtask.db import DatabaseConnection, BaseModel
 from typing import Any
 from sqlite3 import Row
 from plomtask.db import DatabaseConnection, BaseModel
@@ -10,8 +10,13 @@ from plomtask.exceptions import (NotFoundException, BadFormatException,
                                  HandledException)
 
 
                                  HandledException)
 
 
-TodoStepsNode = namedtuple('TodoStepsNode',
-                           ('item', 'is_todo', 'children', 'seen'))
+@dataclass
+class TodoStepsNode:
+    """Collects what's useful to know for Todo/Condition tree display."""
+    item: Todo | Condition
+    is_todo: bool
+    children: list[TodoStepsNode]
+    seen: bool
 
 
 class Todo(BaseModel, ConditionsRelations):
 
 
 class Todo(BaseModel, ConditionsRelations):
@@ -171,11 +176,13 @@ class Todo(BaseModel, ConditionsRelations):
 
     def add_child(self, child: Todo) -> None:
         """Add child to self.children, avoid recursion, update parenthoods."""
 
     def add_child(self, child: Todo) -> None:
         """Add child to self.children, avoid recursion, update parenthoods."""
+
         def walk_steps(node: Todo) -> None:
             if node.id_ == self.id_:
                 raise BadFormatException('bad child choice causes recursion')
             for child in node.children:
                 walk_steps(child)
         def walk_steps(node: Todo) -> None:
             if node.id_ == self.id_:
                 raise BadFormatException('bad child choice causes recursion')
             for child in node.children:
                 walk_steps(child)
+
         if self.id_ is None:
             raise HandledException('Can only add children to saved Todos.')
         if child.id_ is None:
         if self.id_ is None:
             raise HandledException('Can only add children to saved Todos.')
         if child.id_ is None:
index a089037a829092c7e1a29cda45775dd170e1f35e..8887f9a6609c05c119773d2d7ba68b131622856d 100644 (file)
@@ -39,13 +39,13 @@ add todo: <input name="new_todo" list="processes" autocomplete="off" />
 </datalist>
 </form>
 <h4>conditions</h4>
 </datalist>
 </form>
 <h4>conditions</h4>
-{% for node in conditions_listing %}
-<li>[{% if node['condition'].is_active %}x{% else %} {% endif %}] <a href="condition?id={{node['condition'].id_}}">{{node['condition'].title.newest|e}}</a>
+{% for node in conditions_nodes %}
+<li>[{% if node.condition.is_active %}x{% else %} {% endif %}] <a href="condition?id={{node.condition.id_}}">{{node.condition.title.newest|e}}</a>
 <ul>
 <ul>
-{% for enabler in node['enablers'] %}
+{% for enabler in node.enablers %}
 <li>&lt; {{enabler.process.title.newest|e}}</li>
 {% endfor %}
 <li>&lt; {{enabler.process.title.newest|e}}</li>
 {% endfor %}
-{% for disabler in node['disablers'] %}
+{% for disabler in node.disablers %}
 <li>! {{disabler.process.title.newest|e}}</li>
 {% endfor %}
 </ul>
 <li>! {{disabler.process.title.newest|e}}</li>
 {% endfor %}
 </ul>
index 85651d75795d8f7fa946601f445bf61c9c584db2..f8f7baf1eefff07564c49d124c4d7b771acb587d 100644 (file)
@@ -86,7 +86,7 @@ add enables: <input name="enables" list="condition_candidates" autocomplete="off
 add disables: <input name="disables" list="condition_candidates" autocomplete="off" />
 <h4>steps</h4>
 <table>
 add disables: <input name="disables" list="condition_candidates" autocomplete="off" />
 <h4>steps</h4>
 <table>
-{% for step_id, step_node in steps.items() %}
+{% for step_node in steps %}
 {{ step_with_steps(step_id, step_node, 0) }}
 {% endfor %}
 </table>
 {{ step_with_steps(step_id, step_node, 0) }}
 {% endfor %}
 </table>
index 084cfe0c6acac566f391500bf092261c1b82d811..9413822291a87439aafda0cd39459c93b3905661 100644 (file)
@@ -1,8 +1,7 @@
 """Test Processes module."""
 from unittest import TestCase
 """Test Processes module."""
 from unittest import TestCase
-from typing import Any
 from tests.utils import TestCaseWithDB, TestCaseWithServer
 from tests.utils import TestCaseWithDB, TestCaseWithServer
-from plomtask.processes import Process, ProcessStep
+from plomtask.processes import Process, ProcessStep, ProcessStepsNode
 from plomtask.conditions import Condition
 from plomtask.exceptions import NotFoundException, HandledException
 
 from plomtask.conditions import Condition
 from plomtask.exceptions import NotFoundException, HandledException
 
@@ -58,46 +57,30 @@ class TestsWithDB(TestCaseWithDB):
         assert isinstance(self.proc3.id_, int)
         steps_proc1: list[tuple[int | None, int, int | None]] = []
         add_step(self.proc1, steps_proc1, (None, self.proc2.id_, None), 1)
         assert isinstance(self.proc3.id_, int)
         steps_proc1: list[tuple[int | None, int, int | None]] = []
         add_step(self.proc1, steps_proc1, (None, self.proc2.id_, None), 1)
-        p_1_dict: dict[int, dict[str, Any]] = {1: {
-            'process': self.proc2, 'parent_id': None,
-            'is_explicit': True, 'steps': {}, 'seen': False
-        }}
+        p_1_dict: dict[int, ProcessStepsNode] = {}
+        p_1_dict[1] = ProcessStepsNode(self.proc2, None, True, {}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, None), 2)
         step_2 = self.proc1.explicit_steps[-1]
         assert isinstance(step_2.id_, int)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, None), 2)
         step_2 = self.proc1.explicit_steps[-1]
         assert isinstance(step_2.id_, int)
-        p_1_dict[2] = {
-            'process': self.proc3, 'parent_id': None,
-            'is_explicit': True, 'steps': {}, 'seen': False
-        }
+        p_1_dict[2] = ProcessStepsNode(self.proc3, None, True, {}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         steps_proc2: list[tuple[int | None, int, int | None]] = []
         add_step(self.proc2, steps_proc2, (None, self.proc3.id_, None), 3)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         steps_proc2: list[tuple[int | None, int, int | None]] = []
         add_step(self.proc2, steps_proc2, (None, self.proc3.id_, None), 3)
-        p_1_dict[1]['steps'] = {3: {
-            'process': self.proc3, 'parent_id': None,
-            'is_explicit': False, 'steps': {}, 'seen': False
-        }}
+        p_1_dict[1].steps[3] = ProcessStepsNode(self.proc3, None,
+                                                False, {}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc2.id_, step_2.id_),
                  4)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc2.id_, step_2.id_),
                  4)
-        p_1_dict[2]['steps'][4] = {
-            'process': self.proc2, 'parent_id': step_2.id_, 'seen': False,
-            'is_explicit': True, 'steps': {3: {
-                'process': self.proc3, 'parent_id': None,
-                'is_explicit': False, 'steps': {}, 'seen': True
-                }}}
+        step_3 = ProcessStepsNode(self.proc3, None, False, {}, True)
+        p_1_dict[2].steps[4] = ProcessStepsNode(self.proc2, step_2.id_, True,
+                                                {3: step_3}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 999), 5)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 999), 5)
-        p_1_dict[5] = {
-            'process': self.proc3, 'parent_id': None,
-            'is_explicit': True, 'steps': {}, 'seen': False
-        }
+        p_1_dict[5] = ProcessStepsNode(self.proc3, None, True, {}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 3), 6)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
         add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 3), 6)
-        p_1_dict[6] = {
-            'process': self.proc3, 'parent_id': None,
-            'is_explicit': True, 'steps': {}, 'seen': False
-        }
+        p_1_dict[6] = ProcessStepsNode(self.proc3, None, True, {}, False)
         self.assertEqual(self.proc1.get_steps(self.db_conn, None),
                          p_1_dict)
         self.assertEqual(self.proc1.used_as_step_by(self.db_conn),
         self.assertEqual(self.proc1.get_steps(self.db_conn, None),
                          p_1_dict)
         self.assertEqual(self.proc1.used_as_step_by(self.db_conn),
index 4ab34abe2d2c92f3121688ee9ccea7bf30944332..6e8842570baa4a2f1eab2293c3ab664efca2ac72 100644 (file)
@@ -164,61 +164,50 @@ class TestsWithDB(TestCaseWithDB):
         todo_1.save(self.db_conn)
         assert isinstance(todo_1.id_, int)
         # test minimum
         todo_1.save(self.db_conn)
         assert isinstance(todo_1.id_, int)
         # test minimum
-        tree_expected = TodoStepsNode(todo_1, True, [], False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_0 = TodoStepsNode(todo_1, True, [], False)
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test non_emtpy seen_todo does something
         # test non_emtpy seen_todo does something
-        tree_expected = TodoStepsNode(todo_1, True, [], True)
-        self.assertEqual(todo_1.get_step_tree({todo_1.id_}, set()),
-                         tree_expected)
+        node_0.seen = True
+        self.assertEqual(todo_1.get_step_tree({todo_1.id_}, set()), node_0)
         # test child shows up
         todo_2 = Todo(None, self.proc, False, self.date1)
         todo_2.save(self.db_conn)
         assert isinstance(todo_2.id_, int)
         todo_1.add_child(todo_2)
         # test child shows up
         todo_2 = Todo(None, self.proc, False, self.date1)
         todo_2.save(self.db_conn)
         assert isinstance(todo_2.id_, int)
         todo_1.add_child(todo_2)
-        node_todo_2 = TodoStepsNode(todo_2, True, [], False)
-        tree_expected = TodoStepsNode(todo_1, True, [node_todo_2], False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_2 = TodoStepsNode(todo_2, True, [], False)
+        node_0.children = [node_2]
+        node_0.seen = False
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test child shows up with child
         todo_3 = Todo(None, self.proc, False, self.date1)
         todo_3.save(self.db_conn)
         assert isinstance(todo_3.id_, int)
         todo_2.add_child(todo_3)
         # test child shows up with child
         todo_3 = Todo(None, self.proc, False, self.date1)
         todo_3.save(self.db_conn)
         assert isinstance(todo_3.id_, int)
         todo_2.add_child(todo_3)
-        node_todo_3 = TodoStepsNode(todo_3, True, [], False)
-        node_todo_2 = TodoStepsNode(todo_2, True, [node_todo_3], False)
-        tree_expected = TodoStepsNode(todo_1, True, [node_todo_2], False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_3 = TodoStepsNode(todo_3, True, [], False)
+        node_2.children = [node_3]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test same todo can be child-ed multiple times at different locations
         todo_1.add_child(todo_3)
         # test same todo can be child-ed multiple times at different locations
         todo_1.add_child(todo_3)
-        node_todo_4 = TodoStepsNode(todo_3, True, [], True)
-        tree_expected = TodoStepsNode(todo_1, True,
-                                      [node_todo_2, node_todo_4], False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_4 = TodoStepsNode(todo_3, True, [], True)
+        node_0.children += [node_4]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test condition shows up
         todo_1.set_conditions(self.db_conn, [self.cond1.id_])
         # test condition shows up
         todo_1.set_conditions(self.db_conn, [self.cond1.id_])
-        node_cond_1 = TodoStepsNode(self.cond1, False, [], False)
-        tree_expected = TodoStepsNode(todo_1, True,
-                                      [node_todo_2, node_todo_4, node_cond_1],
-                                      False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_5 = TodoStepsNode(self.cond1, False, [], False)
+        node_0.children += [node_5]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test second condition shows up
         todo_2.set_conditions(self.db_conn, [self.cond2.id_])
         # test second condition shows up
         todo_2.set_conditions(self.db_conn, [self.cond2.id_])
-        node_cond_2 = TodoStepsNode(self.cond2, False, [], False)
-        node_todo_2 = TodoStepsNode(todo_2, True,
-                                    [node_todo_3, node_cond_2], False)
-        tree_expected = TodoStepsNode(todo_1, True,
-                                      [node_todo_2, node_todo_4, node_cond_1],
-                                      False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_6 = TodoStepsNode(self.cond2, False, [], False)
+        node_2.children += [node_6]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test second condition is not hidden if fulfilled by non-sibling
         todo_1.set_enables(self.db_conn, [self.cond2.id_])
         # test second condition is not hidden if fulfilled by non-sibling
         todo_1.set_enables(self.db_conn, [self.cond2.id_])
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
         # test second condition is hidden if fulfilled by sibling
         todo_3.set_enables(self.db_conn, [self.cond2.id_])
         # test second condition is hidden if fulfilled by sibling
         todo_3.set_enables(self.db_conn, [self.cond2.id_])
-        node_todo_2 = TodoStepsNode(todo_2, True, [node_todo_3], False)
-        tree_expected = TodoStepsNode(todo_1, True,
-                                      [node_todo_2, node_todo_4, node_cond_1],
-                                      False)
-        self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+        node_2.children.remove(node_6)
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
 
     def test_Todo_singularity(self) -> None:
         """Test pointers made for single object keep pointing to it."""
 
     def test_Todo_singularity(self) -> None:
         """Test pointers made for single object keep pointing to it."""