home · contact · privacy
Refactor from_table_row methods of core DB models.
authorChristian Heller <c.heller@plomlompom.de>
Fri, 19 Apr 2024 00:20:33 +0000 (02:20 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 19 Apr 2024 00:20:33 +0000 (02:20 +0200)
plomtask/conditions.py
plomtask/days.py
plomtask/db.py
plomtask/http.py
plomtask/processes.py
plomtask/todos.py
templates/todo.html
tests/todos.py

index 27ab62cf303084f4ae62add99ac4f6d39616d4f0..9fab77fc118d81bb650116ec307150fc390532ba 100644 (file)
@@ -22,13 +22,16 @@ class Condition(BaseModel):
     def from_table_row(cls, db_conn: DatabaseConnection,
                        row: Row) -> Condition:
         """Build condition from row, including VersionedAttributes."""
-        condition = cls(row[0], row[1])
+        condition = super().from_table_row(db_conn, row)
+        assert isinstance(condition, Condition)
         for title_row in db_conn.exec('SELECT * FROM condition_titles '
                                       'WHERE parent_id = ?', (row[0],)):
-            condition.title.history[title_row[1]] = title_row[2]
+            condition.title.history[title_row[1]]\
+                    = title_row[2]  # pylint: disable=no-member
         for desc_row in db_conn.exec('SELECT * FROM condition_descriptions '
                                      'WHERE parent_id = ?', (row[0],)):
-            condition.description.history[desc_row[1]] = desc_row[2]
+            condition.description.history[desc_row[1]]\
+                    = desc_row[2]  # pylint: disable=no-member
         return condition
 
     @classmethod
index 553579e35ee694989433cab6cc0d01310847b180..a21b4ef5535d81597a4b04d2662f6969313c5e9f 100644 (file)
@@ -1,7 +1,6 @@
 """Collecting Day and date-related items."""
 from __future__ import annotations
 from datetime import datetime, timedelta
-from sqlite3 import Row
 from plomtask.exceptions import BadFormatException, NotFoundException
 from plomtask.db import DatabaseConnection, BaseModel
 
@@ -29,6 +28,7 @@ class Day(BaseModel):
     """Individual days defined by their dates."""
     table_name = 'days'
     to_save = ['comment']
+    id_type = str
 
     def __init__(self, date: str, comment: str = '') -> None:
         self.id_: str = valid_date(date)
@@ -41,13 +41,6 @@ class Day(BaseModel):
     def __lt__(self, other: Day) -> bool:
         return self.date < other.date
 
-    @classmethod
-    def from_table_row(cls, db_conn: DatabaseConnection, row: Row) -> Day:
-        """Make Day from database row, write to cache."""
-        day = cls(row[0], row[1])
-        db_conn.cached_days[day.date] = day
-        return day
-
     @classmethod
     def all(cls, db_conn: DatabaseConnection,
             date_range: tuple[str, str] = ('', ''),
@@ -82,7 +75,9 @@ class Day(BaseModel):
             assert isinstance(day, Day)
             return day
         for row in db_conn.exec('SELECT * FROM days WHERE date = ?', (date,)):
-            return cls.from_table_row(db_conn, row)
+            day = cls.from_table_row(db_conn, row)
+            assert isinstance(day, Day)
+            return day
         if not create:
             raise NotFoundException(f'Day not found for date: {date}')
         day = cls(date)
index dfb388b7ec6ed9317f19a788336025a96e4c745e..2cc1d64d54576a46a0b9c7edb513abaa28d4008e 100644 (file)
@@ -1,7 +1,7 @@
 """Database management."""
 from os.path import isfile
 from difflib import Differ
-from sqlite3 import connect as sql_connect, Cursor
+from sqlite3 import connect as sql_connect, Cursor, Row
 from typing import Any, Dict
 from plomtask.exceptions import HandledException
 
@@ -73,6 +73,16 @@ class BaseModel:
     table_name = ''
     to_save: list[str] = []
     id_: None | int | str
+    id_type: type[Any] = int
+
+    @classmethod
+    def from_table_row(cls, db_conn: DatabaseConnection, row: Row) -> Any:
+        """Make from DB row, write to DB cache."""
+        obj = cls(*row)
+        assert isinstance(obj.id_, cls.id_type)
+        cache = getattr(db_conn, f'cached_{cls.table_name}')
+        cache[obj.id_] = obj
+        return obj
 
     def set_int_id(self, id_: int | None) -> None:
         """Set id_ if >= 1 or None, else fail."""
index a14233ae9cb81e0d65c18bbaf37497e63ab311e2..55120fffe0de409ab74d3e9354cfd8a95e4506ad 100644 (file)
@@ -137,7 +137,7 @@ class TaskHandler(BaseHTTPRequestHandler):
         id_ = self.params.get_int_or_none('id')
         todo = Todo.by_id(self.conn, id_)
         return {'todo': todo,
-                'todo_candidates': Todo.by_date(self.conn, todo.day.date),
+                'todo_candidates': Todo.by_date(self.conn, todo.date),
                 'condition_candidates': Condition.all(self.conn)}
 
     def do_GET_conditions(self) -> dict[str, object]:
@@ -193,7 +193,7 @@ class TaskHandler(BaseHTTPRequestHandler):
         process_id = self.form_data.get_int_or_none('new_todo')
         if process_id is not None:
             process = Process.by_id(self.conn, process_id)
-            todo = Todo(None, process, False, day)
+            todo = Todo(None, process, False, day.date)
             todo.save(self.conn)
 
     def do_POST_todo(self) -> None:
index 45de9dbe8e879f3ec06aae5575305e980df8c0d4..2f8c2d537a168062cee953985242b732f1973a72 100644 (file)
@@ -1,6 +1,5 @@
 """Collecting Processes and Process-related items."""
 from __future__ import annotations
-from sqlite3 import Row
 from typing import Any, Set
 from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.misc import VersionedAttribute
@@ -24,14 +23,6 @@ class Process(BaseModel):
         self.fulfills: list[Condition] = []
         self.undoes: list[Condition] = []
 
-    @classmethod
-    def from_table_row(cls, db_conn: DatabaseConnection, row: Row) -> Process:
-        """Make Process from database row, with empty VersionedAttributes."""
-        process = cls(row[0])
-        assert isinstance(process.id_, int)
-        db_conn.cached_processes[process.id_] = process
-        return process
-
     @classmethod
     def all(cls, db_conn: DatabaseConnection) -> list[Process]:
         """Collect all Processes and their connected VersionedAttributes."""
@@ -236,15 +227,6 @@ class ProcessStep(BaseModel):
         self.step_process_id = step_process_id
         self.parent_step_id = parent_step_id
 
-    @classmethod
-    def from_table_row(cls, db_conn: DatabaseConnection,
-                       row: Row) -> ProcessStep:
-        """Make ProcessStep from database row, store in DB cache."""
-        step = cls(row[0], row[1], row[2], row[3])
-        assert isinstance(step.id_, int)
-        db_conn.cached_process_steps[step.id_] = step
-        return step
-
     @classmethod
     def by_id(cls, db_conn: DatabaseConnection, id_: int) -> ProcessStep:
         """Retrieve ProcessStep by id_, or throw NotFoundException."""
@@ -254,7 +236,8 @@ class ProcessStep(BaseModel):
             return step
         for row in db_conn.exec('SELECT * FROM process_steps '
                                 'WHERE step_id = ?', (id_,)):
-            return cls.from_table_row(db_conn, row)
+            step = cls.from_table_row(db_conn, row)
+            assert isinstance(step, ProcessStep)
         raise NotFoundException(f'found no ProcessStep of ID {id_}')
 
     def save(self, db_conn: DatabaseConnection) -> None:
index 5d46b30d93640e40ba39f6e0cd1ba4b114241396..cfac5b536e91514f02d938fc760818d3d7278129 100644 (file)
@@ -1,8 +1,6 @@
 """Actionables."""
 from __future__ import annotations
-from sqlite3 import Row
 from plomtask.db import DatabaseConnection, BaseModel
-from plomtask.days import Day
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
@@ -14,16 +12,15 @@ class Todo(BaseModel):
 
     # pylint: disable=too-many-instance-attributes
 
-    name = 'Todo'
     table_name = 'todos'
     to_save = ['process_id', 'is_done', 'date']
 
     def __init__(self, id_: int | None, process: Process,
-                 is_done: bool, day: Day) -> None:
+                 is_done: bool, date: str) -> None:
         self.set_int_id(id_)
         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] = []
@@ -34,17 +31,6 @@ class Todo(BaseModel):
             self.fulfills = process.fulfills[:]
             self.undoes = process.undoes[:]
 
-    @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 isinstance(todo.id_, int)
-        db_conn.cached_todos[todo.id_] = todo
-        return todo
-
     @classmethod
     def by_id(cls, db_conn: DatabaseConnection, id_: int | None) -> Todo:
         """Get Todo of .id_=id_ and children (from DB cache if possible)."""
@@ -54,6 +40,11 @@ class Todo(BaseModel):
             todo = None
             for row in db_conn.exec('SELECT * FROM todos WHERE id = ?',
                                     (id_,)):
+                row = list(row)
+                if row[1] == 0:
+                    raise NotFoundException('calling Todo of '
+                                            'unsaved Process')
+                row[1] = Process.by_id(db_conn, row[1])
                 todo = cls.from_table_row(db_conn, row)
                 break
             if todo is None:
@@ -92,7 +83,7 @@ class Todo(BaseModel):
         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:
+            if todo.date == date:
                 enablers += [todo]
         return enablers
 
@@ -104,7 +95,7 @@ class Todo(BaseModel):
         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:
+            if todo.date == date:
                 disablers += [todo]
         return disablers
 
@@ -124,11 +115,6 @@ class Todo(BaseModel):
         """Return ID of tasked Process."""
         return self.process.id_
 
-    @property
-    def date(self) -> str:
-        """Return date of used Day."""
-        return self.day.date
-
     @property
     def is_done(self) -> bool:
         """Wrapper around self._is_done so we can control its setter."""
index 9be13a80c9be3f9446a91f31222994bbc4642d57..1fa2922c7c7912b67b1d77a42db6d816b9534ccc 100644 (file)
@@ -5,7 +5,7 @@
 <form action="todo?id={{todo.id_}}" method="POST">
 <p>
 id: {{todo.id_}}<br />
-day: <a href="day?date={{todo.day.date}}">{{todo.day.date}}</a><br />
+day: <a href="day?date={{todo.date}}">{{todo.date}}</a><br />
 process: <a href="process?id={{todo.process.id_}}">{{todo.process.title.newest|e}}</a><br />
 done: <input type="checkbox" name="done" {% if todo.is_done %}checked {% endif %} {% if not todo.is_doable %}disabled {% endif %}/><br /> 
 </p>
index 6bc5a05fbe465c57d2cdef1d69735d4c0c00cb2e..a90f466cb99a201d353ea5434d5ea9c336dae0da 100644 (file)
@@ -1,7 +1,6 @@
 """Test Todos module."""
 from tests.utils import TestCaseWithDB, TestCaseWithServer
 from plomtask.todos import Todo
-from plomtask.days import Day
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
@@ -13,10 +12,8 @@ class TestsWithDB(TestCaseWithDB):
 
     def setUp(self) -> None:
         super().setUp()
-        self.day1 = Day('2024-01-01')
-        self.day1.save(self.db_conn)
-        self.day2 = Day('2024-01-02')
-        self.day2.save(self.db_conn)
+        self.date1 = '2024-01-01'
+        self.date2 = '2024-01-02'
         self.proc = Process(None)
         self.proc.save(self.db_conn)
         self.cond1 = Condition(None)
@@ -27,7 +24,7 @@ class TestsWithDB(TestCaseWithDB):
     def test_Todo_by_id(self) -> None:
         """Test creation and findability of Todos."""
         process_unsaved = Process(None)
-        todo = Todo(None, process_unsaved, False, self.day1)
+        todo = Todo(None, process_unsaved, False, self.date1)
         with self.assertRaises(NotFoundException):
             todo.save(self.db_conn)
         process_unsaved.save(self.db_conn)
@@ -40,32 +37,32 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_by_date(self) -> None:
         """Test findability of Todos by date."""
-        t1 = Todo(None, self.proc, False, self.day1)
+        t1 = Todo(None, self.proc, False, self.date1)
         t1.save(self.db_conn)
-        t2 = Todo(None, self.proc, False, self.day1)
+        t2 = Todo(None, self.proc, False, self.date1)
         t2.save(self.db_conn)
-        self.assertEqual(Todo.by_date(self.db_conn, self.day1.date), [t1, t2])
-        self.assertEqual(Todo.by_date(self.db_conn, self.day2.date), [])
+        self.assertEqual(Todo.by_date(self.db_conn, self.date1), [t1, t2])
+        self.assertEqual(Todo.by_date(self.db_conn, self.date2), [])
         self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
 
     def test_Todo_from_process(self) -> None:
         """Test spawning of Todo attributes from Process."""
-        assert self.cond1.id_ is not None
-        assert self.cond2.id_ is not None
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
         self.proc.set_conditions(self.db_conn, [self.cond1.id_])
-        todo = Todo(None, self.proc, False, self.day1)
+        todo = Todo(None, self.proc, False, self.date1)
         self.assertEqual(todo.conditions, [self.cond1])
         todo.set_conditions(self.db_conn, [self.cond2.id_])
         self.assertEqual(todo.conditions, [self.cond2])
         self.assertEqual(self.proc.conditions, [self.cond1])
         self.proc.set_fulfills(self.db_conn, [self.cond1.id_])
-        todo = Todo(None, self.proc, False, self.day1)
+        todo = Todo(None, self.proc, False, self.date1)
         self.assertEqual(todo.fulfills, [self.cond1])
         todo.set_fulfills(self.db_conn, [self.cond2.id_])
         self.assertEqual(todo.fulfills, [self.cond2])
         self.assertEqual(self.proc.fulfills, [self.cond1])
         self.proc.set_undoes(self.db_conn, [self.cond1.id_])
-        todo = Todo(None, self.proc, False, self.day1)
+        todo = Todo(None, self.proc, False, self.date1)
         self.assertEqual(todo.undoes, [self.cond1])
         todo.set_undoes(self.db_conn, [self.cond2.id_])
         self.assertEqual(todo.undoes, [self.cond2])
@@ -73,9 +70,9 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_on_conditions(self) -> None:
         """Test effect of Todos on Conditions."""
-        assert self.cond1.id_ is not None
-        assert self.cond2.id_ is not None
-        todo = Todo(None, self.proc, False, self.day1)
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        todo = Todo(None, self.proc, False, self.date1)
         todo.save(self.db_conn)
         todo.set_fulfills(self.db_conn, [self.cond1.id_])
         todo.set_undoes(self.db_conn, [self.cond2.id_])
@@ -88,44 +85,42 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_enablers_disablers(self) -> None:
         """Test Todo.enablers_for_at/disablers_for_at."""
-        assert self.cond1.id_ is not None
-        assert self.cond2.id_ is not None
-        todo1 = Todo(None, self.proc, False, self.day1)
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        todo1 = Todo(None, self.proc, False, self.date1)
         todo1.save(self.db_conn)
         todo1.set_fulfills(self.db_conn, [self.cond1.id_])
         todo1.set_undoes(self.db_conn, [self.cond2.id_])
         todo1.save(self.db_conn)
-        todo2 = Todo(None, self.proc, False, self.day1)
+        todo2 = Todo(None, self.proc, False, self.date1)
         todo2.save(self.db_conn)
         todo2.set_fulfills(self.db_conn, [self.cond2.id_])
         todo2.save(self.db_conn)
-        todo3 = Todo(None, self.proc, False, self.day2)
+        todo3 = Todo(None, self.proc, False, self.date2)
         todo3.save(self.db_conn)
         todo3.set_fulfills(self.db_conn, [self.cond2.id_])
         todo3.save(self.db_conn)
-        date1 = self.day1.date
-        date2 = self.day2.date
-        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, date1)
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, self.date1)
         self.assertEqual(enablers, [todo1])
-        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, date2)
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, self.date2)
         self.assertEqual(enablers, [])
-        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, date1)
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, self.date1)
         self.assertEqual(disablers, [])
-        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, date2)
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, self.date2)
         self.assertEqual(disablers, [])
-        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, date1)
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, self.date1)
         self.assertEqual(enablers, [todo2])
-        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, date2)
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, self.date2)
         self.assertEqual(enablers, [todo3])
-        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, date1)
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, self.date1)
         self.assertEqual(disablers, [todo1])
-        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, date2)
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, self.date2)
         self.assertEqual(disablers, [])
 
     def test_Todo_children(self) -> None:
         """Test Todo.children relations."""
-        todo_1 = Todo(None, self.proc, False, self.day1)
-        todo_2 = Todo(None, self.proc, False, self.day1)
+        todo_1 = Todo(None, self.proc, False, self.date1)
+        todo_2 = Todo(None, self.proc, False, self.date1)
         with self.assertRaises(HandledException):
             todo_1.add_child(todo_2)
         todo_1.save(self.db_conn)
@@ -134,6 +129,7 @@ class TestsWithDB(TestCaseWithDB):
         todo_2.save(self.db_conn)
         todo_1.add_child(todo_2)
         todo_1.save(self.db_conn)
+        assert isinstance(todo_1.id_, int)
         todo_retrieved = Todo.by_id(self.db_conn, todo_1.id_)
         self.assertEqual(todo_retrieved.children, [todo_2])
         with self.assertRaises(BadFormatException):
@@ -141,10 +137,10 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_conditioning(self) -> None:
         """Test Todo.doability conditions."""
-        assert self.cond1.id_ is not None
-        todo_1 = Todo(None, self.proc, False, self.day1)
+        assert isinstance(self.cond1.id_, int)
+        todo_1 = Todo(None, self.proc, False, self.date1)
         todo_1.save(self.db_conn)
-        todo_2 = Todo(None, self.proc, False, self.day1)
+        todo_2 = Todo(None, self.proc, False, self.date1)
         todo_2.save(self.db_conn)
         todo_2.add_child(todo_1)
         with self.assertRaises(BadFormatException):
@@ -160,12 +156,12 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_singularity(self) -> None:
         """Test pointers made for single object keep pointing to it."""
-        todo = Todo(None, self.proc, False, self.day1)
+        todo = Todo(None, self.proc, False, self.date1)
         todo.save(self.db_conn)
         retrieved_todo = Todo.by_id(self.db_conn, 1)
         todo.is_done = True
         self.assertEqual(retrieved_todo.is_done, True)
-        retrieved_todo = Todo.by_date(self.db_conn, self.day1.date)[0]
+        retrieved_todo = Todo.by_date(self.db_conn, self.date1)[0]
         retrieved_todo.is_done = False
         self.assertEqual(todo.is_done, False)