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:
_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:
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)
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)