From f92de64d072009c8c4bf96b9eeb9fa245045662b Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Thu, 30 May 2024 08:12:33 +0200 Subject: [PATCH] Use same date ranging code for Day and Todo filtering. --- plomtask/dating.py | 24 ++++++++++++++++++++++++ plomtask/days.py | 36 +++--------------------------------- plomtask/db.py | 24 ++++++++++++++++++++++++ plomtask/http.py | 11 +++++------ plomtask/todos.py | 15 ++++++++++----- tests/days.py | 3 ++- tests/todos.py | 3 ++- 7 files changed, 70 insertions(+), 46 deletions(-) create mode 100644 plomtask/dating.py diff --git a/plomtask/dating.py b/plomtask/dating.py new file mode 100644 index 0000000..e143fb6 --- /dev/null +++ b/plomtask/dating.py @@ -0,0 +1,24 @@ +"""Various utilities for handling dates.""" +from datetime import datetime +from plomtask.exceptions import BadFormatException + +DATE_FORMAT = '%Y-%m-%d' +MIN_RANGE_DATE = '2024-01-01' +MAX_RANGE_DATE = '2030-12-31' + + +def valid_date(date_str: str) -> str: + """Validate date against DATE_FORMAT or 'today', return in DATE_FORMAT.""" + if date_str == 'today': + date_str = todays_date() + try: + dt = datetime.strptime(date_str, DATE_FORMAT) + except (ValueError, TypeError) as e: + msg = f'Given date of wrong format: {date_str}' + raise BadFormatException(msg) from e + return dt.strftime(DATE_FORMAT) + + +def todays_date() -> str: + """Return current date in DATE_FORMAT.""" + return datetime.now().strftime(DATE_FORMAT) diff --git a/plomtask/days.py b/plomtask/days.py index e3d56d7..d7083b4 100644 --- a/plomtask/days.py +++ b/plomtask/days.py @@ -1,30 +1,9 @@ """Collecting Day and date-related items.""" from __future__ import annotations from datetime import datetime, timedelta -from plomtask.exceptions import BadFormatException from plomtask.db import DatabaseConnection, BaseModel from plomtask.todos import Todo - -DATE_FORMAT = '%Y-%m-%d' -MIN_RANGE_DATE = '2024-01-01' -MAX_RANGE_DATE = '2030-12-31' - - -def valid_date(date_str: str) -> str: - """Validate date against DATE_FORMAT or 'today', return in DATE_FORMAT.""" - if date_str == 'today': - date_str = todays_date() - try: - dt = datetime.strptime(date_str, DATE_FORMAT) - except (ValueError, TypeError) as e: - msg = f'Given date of wrong format: {date_str}' - raise BadFormatException(msg) from e - return dt.strftime(DATE_FORMAT) - - -def todays_date() -> str: - """Return current date in DATE_FORMAT.""" - return datetime.now().strftime(DATE_FORMAT) +from plomtask.dating import (DATE_FORMAT, valid_date) class Day(BaseModel[str]): @@ -48,20 +27,11 @@ class Day(BaseModel[str]): fill_gaps: bool = False) -> list[Day]: """Return list of Days in database within (open) date_range interval. - If no range values provided, defaults them to MIN_RANGE_DATE and - MAX_RANGE_DATE. Also knows to properly interpret 'today' as value. - On fill_gaps=True, will instantiate (without saving) Days of all dates within the date range that don't exist yet. """ - min_date = '2024-01-01' - max_date = '2030-12-31' - start_date = valid_date(date_range[0] if date_range[0] else min_date) - end_date = valid_date(date_range[1] if date_range[1] else max_date) - days = [] - sql = 'SELECT id FROM days WHERE id >= ? AND id <= ?' - for row in db_conn.exec(sql, (start_date, end_date)): - days += [cls.by_id(db_conn, row[0])] + ret = cls.by_date_range_with_limits(db_conn, date_range, 'id') + days, start_date, end_date = ret days.sort() if fill_gaps: if start_date not in [d.date for d in days]: diff --git a/plomtask/db.py b/plomtask/db.py index 548381e..4396b44 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -6,6 +6,7 @@ from difflib import Differ from sqlite3 import connect as sql_connect, Cursor, Row from typing import Any, Self, TypeVar, Generic from plomtask.exceptions import HandledException, NotFoundException +from plomtask.dating import (MIN_RANGE_DATE, MAX_RANGE_DATE, valid_date) EXPECTED_DB_VERSION = 4 MIGRATIONS_DIR = 'migrations' @@ -354,6 +355,29 @@ class BaseModel(Generic[BaseModelId]): items[item.id_] = item return list(items.values()) + @classmethod + def by_date_range_with_limits(cls: type[BaseModelInstance], + db_conn: DatabaseConnection, + date_range: tuple[str, str] = ('', ''), + date_col: str = 'day' + ) -> tuple[list[BaseModelInstance], str, + str]: + """Return list of Days in database within (open) date_range interval. + + If no range values provided, defaults them to MIN_RANGE_DATE and + MAX_RANGE_DATE. Also knows to properly interpret 'today' as value. + """ + min_date = MIN_RANGE_DATE + max_date = MAX_RANGE_DATE + start_date = valid_date(date_range[0] if date_range[0] else min_date) + end_date = valid_date(date_range[1] if date_range[1] else max_date) + items = [] + sql = f'SELECT id FROM {cls.table_name} ' + sql += f'WHERE {date_col} >= ? AND {date_col} <= ?' + for row in db_conn.exec(sql, (start_date, end_date)): + items += [cls.by_id(db_conn, row[0])] + return items, start_date, end_date + @classmethod def matching(cls: type[BaseModelInstance], db_conn: DatabaseConnection, pattern: str) -> list[BaseModelInstance]: diff --git a/plomtask/http.py b/plomtask/http.py index b81083b..a2e8fa6 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -5,7 +5,8 @@ from http.server import HTTPServer from urllib.parse import urlparse, parse_qs from os.path import split as path_split from jinja2 import Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader -from plomtask.days import Day, todays_date +from plomtask.dating import todays_date +from plomtask.days import Day from plomtask.exceptions import HandledException, BadFormatException, \ NotFoundException from plomtask.db import DatabaseConnection, DatabaseFile @@ -160,11 +161,9 @@ class TaskHandler(BaseHTTPRequestHandler): process_id = self.params.get_int_or_none('process_id') comment_pattern = self.params.get_str('comment_pattern') todos = [] - for t in Todo.matching(self.conn, comment_pattern): - # pylint: disable=too-many-boolean-expressions - if (start and t.date < start)\ - or (end and t.date > end)\ - or (process_id and t.process.id_ != process_id): + for t in Todo.by_date_range(self.conn, (start, end)): + if (process_id and t.process.id_ != process_id)\ + or (comment_pattern not in t.comment): continue todos += [t] if sort_by == 'doneness': diff --git a/plomtask/todos.py b/plomtask/todos.py index ffef677..46a353d 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -9,6 +9,7 @@ from plomtask.versioned_attributes import VersionedAttribute from plomtask.conditions import Condition, ConditionsRelations from plomtask.exceptions import (NotFoundException, BadFormatException, HandledException) +from plomtask.dating import valid_date @dataclass @@ -46,7 +47,7 @@ class Todo(BaseModel[int], ConditionsRelations): raise NotFoundException('Process of Todo without ID (not saved?)') self.process = process self._is_done = is_done - self.date = date + self.date = valid_date(date) self.comment = comment self.effort = effort self.children: list[Todo] = [] @@ -59,6 +60,13 @@ class Todo(BaseModel[int], ConditionsRelations): self.enables = self.process.enables[:] self.disables = self.process.disables[:] + @classmethod + def by_date_range(cls, db_conn: DatabaseConnection, + date_range: tuple[str, str] = ('', '')) -> list[Todo]: + """Collect Todos of Days within date_range.""" + todos, _, _ = cls.by_date_range_with_limits(db_conn, date_range) + return todos + @classmethod def create_with_children(cls, db_conn: DatabaseConnection, date: str, process_ids: list[int]) -> list[Todo]: @@ -117,10 +125,7 @@ class Todo(BaseModel[int], ConditionsRelations): @classmethod def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]: """Collect all Todos for Day of date.""" - todos = [] - for id_ in db_conn.column_where('todos', 'id', 'day', date): - todos += [cls.by_id(db_conn, id_)] - return todos + return cls.by_date_range(db_conn, (date, date)) @property def is_doable(self) -> bool: diff --git a/tests/days.py b/tests/days.py index 0556164..c1e1343 100644 --- a/tests/days.py +++ b/tests/days.py @@ -2,7 +2,8 @@ from unittest import TestCase from datetime import datetime from tests.utils import TestCaseWithDB, TestCaseWithServer -from plomtask.days import Day, todays_date +from plomtask.dating import todays_date +from plomtask.days import Day from plomtask.exceptions import BadFormatException diff --git a/tests/todos.py b/tests/todos.py index 4ba5a1c..86986b6 100644 --- a/tests/todos.py +++ b/tests/todos.py @@ -63,7 +63,8 @@ class TestsWithDB(TestCaseWithDB): t2.save(self.db_conn) 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'), []) + with self.assertRaises(BadFormatException): + self.assertEqual(Todo.by_date(self.db_conn, 'foo'), []) def test_Todo_on_conditions(self) -> None: """Test effect of Todos on Conditions.""" -- 2.30.2