From: Christian Heller Date: Tue, 16 Jul 2024 22:48:44 +0000 (+0200) Subject: Minor code style/comment/type hinting improvements. X-Git-Url: https://plomlompom.com/repos/template?a=commitdiff_plain;h=316de65e9f513af88a04d6ef3aafe975503c33ee;p=plomtask Minor code style/comment/type hinting improvements. --- diff --git a/plomtask/db.py b/plomtask/db.py index 27dc117..67a7fc7 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -281,10 +281,11 @@ class BaseModel(Generic[BaseModelId]): return list(cls.versioned_defaults.keys()) @property - def as_dict_and_refs(self) -> tuple[dict[str, object], list[Any]]: + def as_dict_and_refs(self) -> tuple[dict[str, object], + list[BaseModel[int] | BaseModel[str]]]: """Return self as json.dumps-ready dict, list of referenced objects.""" d: dict[str, object] = {'id': self.id_} - refs: list[Any] = [] + refs: list[BaseModel[int] | BaseModel[str]] = [] for to_save in self.to_save_simples: d[to_save] = getattr(self, to_save) if len(self.to_save_versioned()) > 0: diff --git a/plomtask/http.py b/plomtask/http.py index 3164662..2877068 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -133,12 +133,14 @@ class TaskHandler(BaseHTTPRequestHandler): _form_data: InputsParser _params: InputsParser - def _send_page(self, - ctx: dict[str, Any], - tmpl_name: str, - code: int = 200 - ) -> None: - """Send ctx as proper HTTP response.""" + def _send_page( + self, ctx: dict[str, Any], tmpl_name: str, code: int = 200 + ) -> None: + """HTTP-send ctx as HTML or JSON, as defined by .server.render_mode. + + The differentiation by .server.render_mode serves to allow easily + comparable JSON responses for automatic testing. + """ body: str headers: list[tuple[str, str]] = [] if 'html' == self.server.render_mode: @@ -154,37 +156,42 @@ class TaskHandler(BaseHTTPRequestHandler): self.wfile.write(bytes(body, 'utf-8')) def _ctx_to_json(self, ctx: dict[str, object]) -> str: - """Render ctx into JSON string.""" + """Render ctx into JSON string. + + Flattens any objects that json.dumps might not want to serialize, and + turns occurrences of BaseModel objects into listings of their .id_, to + be resolved to a full dict inside a top-level '_library' dictionary, + to avoid endless and circular nesting. + """ - def flatten(node: object, library: dict[str, dict[str | int, object]] - ) -> Any: + def flatten(node: object) -> object: - def update_library_with(item: Any, - library: dict[str, dict[str | int, object]] - ) -> None: + def update_library_with( + item: BaseModel[int] | BaseModel[str]) -> None: cls_name = item.__class__.__name__ if cls_name not in library: library[cls_name] = {} if item.id_ not in library[cls_name]: d, refs = item.as_dict_and_refs - library[cls_name][item.id_] = d + id_key = '?' if item.id_ is None else item.id_ + library[cls_name][id_key] = d for ref in refs: - update_library_with(ref, library) + update_library_with(ref) if isinstance(node, BaseModel): - update_library_with(node, library) + update_library_with(node) return node.id_ if isinstance(node, DictableNode): d, refs = node.as_dict_and_refs for ref in refs: - update_library_with(ref, library) + update_library_with(ref) return d if isinstance(node, (list, tuple)): - return [flatten(item, library) for item in node] + return [flatten(item) for item in node] if isinstance(node, dict): d = {} for k, v in node.items(): - d[k] = flatten(v, library) + d[k] = flatten(v) return d if isinstance(node, HandledException): return str(node) @@ -192,7 +199,7 @@ class TaskHandler(BaseHTTPRequestHandler): library: dict[str, dict[str | int, object]] = {} for k, v in ctx.items(): - ctx[k] = flatten(v, library) + ctx[k] = flatten(v) ctx['_library'] = library return json_dumps(ctx) diff --git a/tests/todos.py b/tests/todos.py index 01297d4..0851bb5 100644 --- a/tests/todos.py +++ b/tests/todos.py @@ -322,11 +322,8 @@ class TestsWithServer(TestCaseWithServer): expected['_library']['Todo']['2']['parents'] = [1] expected['_library']['Todo']['1']['children'] = [2] expected['steps_todo_to_process'] = [{ - 'children': [], - 'fillable': False, - 'node_id': 1, - 'process': None, - 'todo': 2}] + 'children': [], 'fillable': False, + 'node_id': 1, 'process': None, 'todo': 2}] self.check_post({'adopt': 2}, '/todo?id=1') self.check_json_get('/todo?id=1', expected) # # test todo1 cannot be set done with todo2 not done yet