From: Christian Heller Date: Fri, 19 Apr 2024 00:20:33 +0000 (+0200) Subject: Refactor from_table_row methods of core DB models. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/%7B%7Bdeck_id%7D%7D/%7B%7Bprefix%7D%7D?a=commitdiff_plain;h=5195f3f36960b76d1b6530ef1822d0806db221d8;p=plomtask Refactor from_table_row methods of core DB models. --- diff --git a/plomtask/conditions.py b/plomtask/conditions.py index 27ab62c..9fab77f 100644 --- a/plomtask/conditions.py +++ b/plomtask/conditions.py @@ -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 diff --git a/plomtask/days.py b/plomtask/days.py index 553579e..a21b4ef 100644 --- a/plomtask/days.py +++ b/plomtask/days.py @@ -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) diff --git a/plomtask/db.py b/plomtask/db.py index dfb388b..2cc1d64 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -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.""" diff --git a/plomtask/http.py b/plomtask/http.py index a14233a..55120ff 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -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: diff --git a/plomtask/processes.py b/plomtask/processes.py index 45de9db..2f8c2d5 100644 --- a/plomtask/processes.py +++ b/plomtask/processes.py @@ -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: diff --git a/plomtask/todos.py b/plomtask/todos.py index 5d46b30..cfac5b5 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -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.""" diff --git a/templates/todo.html b/templates/todo.html index 9be13a8..1fa2922 100644 --- a/templates/todo.html +++ b/templates/todo.html @@ -5,7 +5,7 @@

id: {{todo.id_}}
-day: {{todo.day.date}}
+day: {{todo.date}}
process: {{todo.process.title.newest|e}}
done:

diff --git a/tests/todos.py b/tests/todos.py index 6bc5a05..a90f466 100644 --- a/tests/todos.py +++ b/tests/todos.py @@ -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)