home · contact · privacy
Clean up enablers/disablers code and naming conventions.
[plomtask] / plomtask / todos.py
index ce83faddff55278c0efe44f604f6a5fddfaa5851..ebe35ac56bb78af42a10644c9209b34778527c0e 100644 (file)
@@ -1,74 +1,70 @@
 """Actionables."""
 from __future__ import annotations
 """Actionables."""
 from __future__ import annotations
+from typing import Any
 from sqlite3 import Row
 from sqlite3 import Row
-from plomtask.db import DatabaseConnection
-from plomtask.days import Day
+from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
                                  HandledException)
 
 
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
                                  HandledException)
 
 
-class Todo:
+class Todo(BaseModel):
     """Individual actionable."""
 
     # pylint: disable=too-many-instance-attributes
 
     """Individual actionable."""
 
     # pylint: disable=too-many-instance-attributes
 
+    table_name = 'todos'
+    to_save = ['process_id', 'is_done', 'date']
+
     def __init__(self, id_: int | None, process: Process,
     def __init__(self, id_: int | None, process: Process,
-                 is_done: bool, day: Day) -> None:
-        self.id_ = id_
+                 is_done: bool, date: str) -> None:
+        self.set_int_id(id_)
         self.process = process
         self._is_done = is_done
         self.process = process
         self._is_done = is_done
-        self.day = day
+        self.date = date
         self.children: list[Todo] = []
         self.parents: list[Todo] = []
         self.conditions: list[Condition] = []
         self.children: list[Todo] = []
         self.parents: list[Todo] = []
         self.conditions: list[Condition] = []
-        self.fulfills: list[Condition] = []
-        self.undoes: list[Condition] = []
+        self.enables: list[Condition] = []
+        self.disables: list[Condition] = []
         if not self.id_:
             self.conditions = process.conditions[:]
         if not self.id_:
             self.conditions = process.conditions[:]
-            self.fulfills = process.fulfills[:]
-            self.undoes = process.undoes[:]
+            self.enables = process.enables[:]
+            self.disables = process.disables[:]
 
     @classmethod
 
     @classmethod
-    def from_table_row(cls, db_conn: DatabaseConnection, row: Row) -> Todo:
-        """Make Todo from database row, write to DB cache."""
-        todo = cls(id_=row[0],
-                   process=Process.by_id(db_conn, row[1]),
-                   is_done=bool(row[2]),
-                   day=Day.by_date(db_conn, row[3]))
-        assert todo.id_ is not None
-        db_conn.cached_todos[todo.id_] = todo
+    def from_table_row(cls, db_conn: DatabaseConnection,
+                       row: Row | list[Any]) -> Todo:
+        """Make from DB row, write to DB cache."""
+        if row[1] == 0:
+            raise NotFoundException('calling Todo of '
+                                    'unsaved Process')
+        row_as_list = list(row)
+        row_as_list[1] = Process.by_id(db_conn, row[1])
+        todo = super().from_table_row(db_conn, row_as_list)
+        assert isinstance(todo, Todo)
         return todo
 
     @classmethod
         return todo
 
     @classmethod
-    def by_id(cls, db_conn: DatabaseConnection, id_: int | None) -> Todo:
+    def by_id(cls, db_conn: DatabaseConnection, id_: int) -> Todo:
         """Get Todo of .id_=id_ and children (from DB cache if possible)."""
         """Get Todo of .id_=id_ and children (from DB cache if possible)."""
-        if id_ in db_conn.cached_todos.keys():
-            todo = db_conn.cached_todos[id_]
-        else:
-            todo = None
-            for row in db_conn.exec('SELECT * FROM todos WHERE id = ?',
-                                    (id_,)):
-                todo = cls.from_table_row(db_conn, row)
-                break
-            if todo is None:
-                raise NotFoundException(f'Todo of ID not found: {id_}')
-            for row in db_conn.exec('SELECT child FROM todo_children '
-                                    'WHERE parent = ?', (id_,)):
-                todo.children += [cls.by_id(db_conn, row[0])]
-            for row in db_conn.exec('SELECT parent FROM todo_children '
-                                    'WHERE child = ?', (id_,)):
-                todo.parents += [cls.by_id(db_conn, row[0])]
-            for row in db_conn.exec('SELECT condition FROM todo_conditions '
-                                    'WHERE todo = ?', (id_,)):
-                todo.conditions += [Condition.by_id(db_conn, row[0])]
-            for row in db_conn.exec('SELECT condition FROM todo_fulfills '
-                                    'WHERE todo = ?', (id_,)):
-                todo.fulfills += [Condition.by_id(db_conn, row[0])]
-            for row in db_conn.exec('SELECT condition FROM todo_undoes '
-                                    'WHERE todo = ?', (id_,)):
-                todo.undoes += [Condition.by_id(db_conn, row[0])]
+        todo, from_cache = super()._by_id(db_conn, id_)
+        if todo is None:
+            raise NotFoundException(f'Todo of ID not found: {id_}')
+        if not from_cache:
+            for t_id in db_conn.column_where('todo_children', 'child',
+                                             'parent', id_):
+                todo.children += [cls.by_id(db_conn, t_id)]
+            for t_id in db_conn.column_where('todo_children', 'parent',
+                                             'child', id_):
+                todo.parents += [cls.by_id(db_conn, t_id)]
+            for name in ('conditions', 'enables', 'disables'):
+                table = f'todo_{name}'
+                for cond_id in db_conn.column_where(table, 'condition',
+                                                    'todo', todo.id_):
+                    target = getattr(todo, name)
+                    target += [Condition.by_id(db_conn, cond_id)]
         assert isinstance(todo, Todo)
         return todo
 
         assert isinstance(todo, Todo)
         return todo
 
@@ -76,33 +72,34 @@ class Todo:
     def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]:
         """Collect all Todos for Day of date."""
         todos = []
     def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]:
         """Collect all Todos for Day of date."""
         todos = []
-        for row in db_conn.exec('SELECT id FROM todos WHERE day = ?', (date,)):
-            todos += [cls.by_id(db_conn, row[0])]
+        for id_ in db_conn.column_where('todos', 'id', 'day', date):
+            todos += [cls.by_id(db_conn, id_)]
         return todos
 
         return todos
 
+    @staticmethod
+    def _x_ablers_for_at(db_conn: DatabaseConnection, name: str,
+                         cond: Condition, date: str) -> list[Todo]:
+        """Collect all Todos of day that [name] condition."""
+        assert isinstance(cond.id_, int)
+        x_ablers = []
+        table = f'todo_{name}'
+        for id_ in db_conn.column_where(table, 'todo', 'condition', cond.id_):
+            todo = Todo.by_id(db_conn, id_)
+            if todo.date == date:
+                x_ablers += [todo]
+        return x_ablers
+
     @classmethod
     @classmethod
-    def enablers_for_at(cls, db_conn: DatabaseConnection, condition: Condition,
-                        date: str) -> list[Todo]:
+    def enablers_for_at(cls, db_conn: DatabaseConnection,
+                        condition: Condition, date: str) -> list[Todo]:
         """Collect all Todos of day that enable condition."""
         """Collect all Todos of day that enable condition."""
-        enablers = []
-        for row in db_conn.exec('SELECT todo FROM todo_fulfills '
-                                'WHERE condition = ?', (condition.id_,)):
-            todo = cls.by_id(db_conn, row[0])
-            if todo.day.date == date:
-                enablers += [todo]
-        return enablers
+        return cls._x_ablers_for_at(db_conn, 'enables', condition, date)
 
     @classmethod
     def disablers_for_at(cls, db_conn: DatabaseConnection,
                          condition: Condition, date: str) -> list[Todo]:
         """Collect all Todos of day that disable condition."""
 
     @classmethod
     def disablers_for_at(cls, db_conn: DatabaseConnection,
                          condition: Condition, date: str) -> list[Todo]:
         """Collect all Todos of day that disable condition."""
-        disablers = []
-        for row in db_conn.exec('SELECT todo FROM todo_undoes '
-                                'WHERE condition = ?', (condition.id_,)):
-            todo = cls.by_id(db_conn, row[0])
-            if todo.day.date == date:
-                disablers += [todo]
-        return disablers
+        return cls._x_ablers_for_at(db_conn, 'disables', condition, date)
 
     @property
     def is_doable(self) -> bool:
 
     @property
     def is_doable(self) -> bool:
@@ -115,6 +112,11 @@ class Todo:
                 return False
         return True
 
                 return False
         return True
 
+    @property
+    def process_id(self) -> int | str | None:
+        """Return ID of tasked Process."""
+        return self.process.id_
+
     @property
     def is_done(self) -> bool:
         """Wrapper around self._is_done so we can control its setter."""
     @property
     def is_done(self) -> bool:
         """Wrapper around self._is_done so we can control its setter."""
@@ -127,19 +129,20 @@ class Todo:
         if self._is_done != value:
             self._is_done = value
             if value is True:
         if self._is_done != value:
             self._is_done = value
             if value is True:
-                for condition in self.fulfills:
+                for condition in self.enables:
                     condition.is_active = True
                     condition.is_active = True
-                for condition in self.undoes:
+                for condition in self.disables:
                     condition.is_active = False
 
                     condition.is_active = False
 
-    def set_undoes(self, db_conn: DatabaseConnection, ids: list[int]) -> None:
-        """Set self.undoes to Conditions identified by ids."""
-        self.set_conditions(db_conn, ids, 'undoes')
-
-    def set_fulfills(self, db_conn: DatabaseConnection,
+    def set_disables(self, db_conn: DatabaseConnection,
                      ids: list[int]) -> None:
                      ids: list[int]) -> None:
-        """Set self.fulfills to Conditions identified by ids."""
-        self.set_conditions(db_conn, ids, 'fulfills')
+        """Set self.disables to Conditions identified by ids."""
+        self.set_conditions(db_conn, ids, 'disables')
+
+    def set_enables(self, db_conn: DatabaseConnection,
+                    ids: list[int]) -> None:
+        """Set self.enables to Conditions identified by ids."""
+        self.set_conditions(db_conn, ids, 'enables')
 
     def set_conditions(self, db_conn: DatabaseConnection, ids: list[int],
                        target: str = 'conditions') -> None:
 
     def set_conditions(self, db_conn: DatabaseConnection, ids: list[int],
                        target: str = 'conditions') -> None:
@@ -171,35 +174,14 @@ class Todo:
         """Write self and children to DB and its cache."""
         if self.process.id_ is None:
             raise NotFoundException('Process of Todo without ID (not saved?)')
         """Write self and children to DB and its cache."""
         if self.process.id_ is None:
             raise NotFoundException('Process of Todo without ID (not saved?)')
-        cursor = db_conn.exec('REPLACE INTO todos VALUES (?,?,?,?)',
-                              (self.id_, self.process.id_,
-                               self.is_done, self.day.date))
-        self.id_ = cursor.lastrowid
-        assert self.id_ is not None
+        self.save_core(db_conn)
+        assert isinstance(self.id_, int)
         db_conn.cached_todos[self.id_] = self
         db_conn.cached_todos[self.id_] = self
-        db_conn.exec('DELETE FROM todo_children WHERE parent = ?',
-                     (self.id_,))
-        for child in self.children:
-            db_conn.exec('INSERT INTO todo_children VALUES (?, ?)',
-                         (self.id_, child.id_))
-        db_conn.exec('DELETE FROM todo_fulfills WHERE todo = ?', (self.id_,))
-        for condition in self.fulfills:
-            if condition.id_ is None:
-                raise NotFoundException('Fulfilled Condition of Todo '
-                                        'without ID (not saved?)')
-            db_conn.exec('INSERT INTO todo_fulfills VALUES (?, ?)',
-                         (self.id_, condition.id_))
-        db_conn.exec('DELETE FROM todo_undoes WHERE todo = ?', (self.id_,))
-        for condition in self.undoes:
-            if condition.id_ is None:
-                raise NotFoundException('Undone Condition of Todo '
-                                        'without ID (not saved?)')
-            db_conn.exec('INSERT INTO todo_undoes VALUES (?, ?)',
-                         (self.id_, condition.id_))
-        db_conn.exec('DELETE FROM todo_conditions WHERE todo = ?', (self.id_,))
-        for condition in self.conditions:
-            if condition.id_ is None:
-                raise NotFoundException('Condition of Todo '
-                                        'without ID (not saved?)')
-            db_conn.exec('INSERT INTO todo_conditions VALUES (?, ?)',
-                         (self.id_, condition.id_))
+        db_conn.rewrite_relations('todo_children', 'parent', self.id_,
+                                  [[c.id_] for c in self.children])
+        db_conn.rewrite_relations('todo_conditions', 'todo', self.id_,
+                                  [[c.id_] for c in self.conditions])
+        db_conn.rewrite_relations('todo_enables', 'todo', self.id_,
+                                  [[c.id_] for c in self.enables])
+        db_conn.rewrite_relations('todo_disables', 'todo', self.id_,
+                                  [[c.id_] for c in self.disables])