X-Git-Url: https://plomlompom.com/repos//%22https:/validator.w3.org/check?a=blobdiff_plain;f=plomtask%2Fhttp.py;h=0626b4c1e5aab5bd4bceb0d6594cbaa9da448ae8;hb=7d5347d7bbbbfaf0cbec191ef2dc1ff9cda41ac3;hp=0f5e88e570ed7eb74ea3e17fde16da13822b488a;hpb=501b2ef5f6373807b7728e7b8539105aa9030809;p=plomtask diff --git a/plomtask/http.py b/plomtask/http.py index 0f5e88e..0626b4c 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -177,6 +177,38 @@ class TaskHandler(BaseHTTPRequestHandler): @staticmethod def _request_wrapper(http_method: str, not_found_msg: str ) -> Callable[..., Callable[[TaskHandler], None]]: + """Wrapper for do_GET… and do_POST… handlers, to init and clean up. + + Among other things, conditionally cleans all caches, but only on POST + requests, as only those are expected to change the states of objects + that may be cached, and certainly only those are expected to write any + changes to the database. We want to call them as early though as + possible here, either exactly after the specific request handler + returns successfully, or right after any exception is triggered – + otherwise, race conditions become plausible. + + Note that any POST attempt, even a failed one, may end in problematic + inconsistencies: + + - if the POST handler experiences an Exception, changes to objects + won't get written to the DB, but the changed objects may remain in + the cache and affect other objects despite their possibly illegal + state + + - even if an object was just saved to the DB, we cannot be sure its + current state is completely identical to what we'd get if loading it + fresh from the DB (e.g. currently Process.n_owners is only updated + when loaded anew via .from_table_row, nor is its state written to + the DB by .save; a questionable design choice, but proof that we + have no guarantee that objects' .save stores all their states we'd + prefer at their most up-to-date. + """ + + def clear_caches() -> None: + for cls in (Day, Todo, Condition, Process, ProcessStep): + assert hasattr(cls, 'empty_cache') + cls.empty_cache() + def decorator(f: Callable[..., str | None] ) -> Callable[[TaskHandler], None]: def wrapper(self: TaskHandler) -> None: @@ -193,6 +225,8 @@ class TaskHandler(BaseHTTPRequestHandler): if hasattr(self, handler_name): handler = getattr(self, handler_name) redir_target = f(self, handler) + if 'POST' != http_method: + clear_caches() if redir_target: self.send_response(302) self.send_header('Location', redir_target) @@ -201,9 +235,8 @@ class TaskHandler(BaseHTTPRequestHandler): msg = f'{not_found_msg}: {self._site}' raise NotFoundException(msg) except HandledException as error: - for cls in (Day, Todo, Condition, Process, ProcessStep): - assert hasattr(cls, 'empty_cache') - cls.empty_cache() + if 'POST' != http_method: + clear_caches() ctx = {'msg': error} self._send_page(ctx, 'msg', error.http_code) finally: @@ -400,23 +433,7 @@ class TaskHandler(BaseHTTPRequestHandler): todos = [t for t in todos_by_date_range if comment_pattern in t.comment and ((not process_id) or t.process.id_ == process_id)] - if sort_by == 'doneness': - todos.sort(key=lambda t: t.is_done) - elif sort_by == '-doneness': - todos.sort(key=lambda t: t.is_done, reverse=True) - elif sort_by == 'title': - todos.sort(key=lambda t: t.title_then) - elif sort_by == '-title': - todos.sort(key=lambda t: t.title_then, reverse=True) - elif sort_by == 'comment': - todos.sort(key=lambda t: t.comment) - elif sort_by == '-comment': - todos.sort(key=lambda t: t.comment, reverse=True) - elif sort_by == '-date': - todos.sort(key=lambda t: t.date, reverse=True) - else: - todos.sort(key=lambda t: t.date) - sort_by = 'title' + sort_by = Todo.sort_by(todos, sort_by) return {'start': start, 'end': end, 'process_id': process_id, 'comment_pattern': comment_pattern, 'todos': todos, 'all_processes': Process.all(self.conn), 'sort_by': sort_by} @@ -426,15 +443,7 @@ class TaskHandler(BaseHTTPRequestHandler): pattern = self._params.get_str('pattern') sort_by = self._params.get_str('sort_by') conditions = Condition.matching(self.conn, pattern) - if sort_by == 'is_active': - conditions.sort(key=lambda c: c.is_active) - elif sort_by == '-is_active': - conditions.sort(key=lambda c: c.is_active, reverse=True) - elif sort_by == '-title': - conditions.sort(key=lambda c: c.title.newest, reverse=True) - else: - conditions.sort(key=lambda c: c.title.newest) - sort_by = 'title' + sort_by = Condition.sort_by(conditions, sort_by) return {'conditions': conditions, 'sort_by': sort_by, 'pattern': pattern} @@ -501,23 +510,7 @@ class TaskHandler(BaseHTTPRequestHandler): pattern = self._params.get_str('pattern') sort_by = self._params.get_str('sort_by') processes = Process.matching(self.conn, pattern) - if sort_by == 'steps': - processes.sort(key=lambda p: len(p.explicit_steps)) - elif sort_by == '-steps': - processes.sort(key=lambda p: len(p.explicit_steps), reverse=True) - elif sort_by == 'owners': - processes.sort(key=lambda p: p.n_owners or 0) - elif sort_by == '-owners': - processes.sort(key=lambda p: p.n_owners or 0, reverse=True) - elif sort_by == 'effort': - processes.sort(key=lambda p: p.effort.newest) - elif sort_by == '-effort': - processes.sort(key=lambda p: p.effort.newest, reverse=True) - elif sort_by == '-title': - processes.sort(key=lambda p: p.title.newest, reverse=True) - else: - processes.sort(key=lambda p: p.title.newest) - sort_by = 'title' + sort_by = Process.sort_by(processes, sort_by) return {'processes': processes, 'sort_by': sort_by, 'pattern': pattern} # POST handlers