Source code for dachs.serialization

# -*- coding: utf-8 -*-
# serialization.py

"""
Utility function to serialize data and meta classes for export, to a HDF5 file, for example.
"""

__author__ = "Ingo Breßler"
__contact__ = "dev@ingobressler.net"
__license__ = "GPLv3+"
__date__ = "2023/02/07"
__status__ = "beta"

import xml.etree.ElementTree
from pathlib import Path, PurePosixPath

import graphviz


[docs] def type2str(obj): return ".".join((type(obj).__module__, type(obj).__name__))
[docs] def dumpKV(obj: object, path: PurePosixPath = None, lvl: int = 0, dbg: bool = False): """Serializes the given hierarchical DACHS structure as key-value pairs (a dict). :param objlst: A hierarchical instance for traversal. :param path: Optional, a word to prepend to all generated keys, a top-level name. It is replaced by the *ID* attribute if available. :param lvl: The current level of invocation, tracks the recursion depth for debugging. :param dbg: Enable debug output. """ indent = "".join([" " for _ in range(lvl)]) if dbg: from pprint import pformat print(indent, f"{obj=}") if not path: # may be undefined for the top-level object path = getattr(obj, "ID", None) path = PurePosixPath(path) if path else PurePosixPath("") if dbg: print(indent, f"path from id: '{path}', {type(obj)}") # find any children to traverse children = [(storeKey, getattr(obj, storeKey)) for storeKey in getattr(obj, "_storeKeys", [])] if not len(children): if type(obj) in (list, tuple): children = list(enumerate(obj)) if type(obj) in (dict,): children = list(obj.items()) # translate children path names from type names to their stored ID children = [(getattr(child, "ID", num), child) for num, child in children] if dbg: print(indent, f"{children=}") if not len(children): # no children found # omit this object if empty? TODO return {path: obj} # first store some meta info for later graphKV stage extended info pathlst = {path / "type": type2str(obj)} for name, child in children: if dbg: print(indent, f"{lvl}>", name) subpath = path / str(name) items = dumpKV(child, subpath, lvl + 1, dbg=dbg) if dbg: indentCount = (lvl + 1) * 2 + 1 print(indent, f"{lvl}<{{" + pformat(items, indent=indentCount, width=120)[1:].lstrip()) pathlst.update(items) return pathlst
[docs] def graphKV(paths): docsPath = Path("dist/docs/reference/autosummary") graphName = list(paths.keys())[0].parts[0] # pprint.pprint(paths, width=120) def nodeFromParts(parts, i, paths): node = PurePosixPath(*parts[: i + 1]) try: return node, paths[node / "type"] except KeyError: return node, type2str(paths[node]) nodes = dict( nodeFromParts(path.parts, i, paths) for path in paths for i in range(len(path.parts)) if path.parts[-1] != "type" # filter type info, evaluated for node type info ) edges = { (PurePosixPath(*path.parts[: i + 1]), PurePosixPath(*path.parts[: i + 2])) for path in paths for i in range(len(path.parts) - 1) if path.parts[-1] != "type" # filter type info, evaluated for node type info } graph = graphviz.Digraph(graphName, format="svg", graph_attr=dict(rankdir="LR")) def sanitize(name): return str(name).replace(":", ".") for nodepath, nodetype in nodes.items(): lbl = f"{nodepath.name}({nodetype.split('.')[-1]})" url, color = "", "" if nodetype.startswith("dachs."): url = docsPath / (nodetype + ".html") color = "blue" graph.node(sanitize(nodepath), label=lbl, URL=str(url), fontcolor=color) for tail, head in edges: graph.edge(sanitize(tail), sanitize(head)) outfn = graph.render(graph.name, cleanup=True) fixSVG(outfn)
[docs] def fixSVG(fn): """Apply some modifications to SVG generated by Graphviz. Convert to PNG with:: convert -background transparent AutoMOF5.svg AutoMOF5.png """ fn = Path(fn) xml.etree.ElementTree.register_namespace("", "http://www.w3.org/2000/svg") tree = xml.etree.ElementTree.parse(fn) # remove background color from first polygon element elem = [elem for elem in tree.iter() if elem.tag.split("}")[-1] == "polygon"][0] elem.set("fill", "none") tree.write(fn)