from typing import List, Protocol, Tuple, Union class StrArgsToStr(Protocol): def __call__(self, *children: str) -> str: ... ClassArg = str|List[str]|Tuple[str, ...]|None class ComplexProxy(Protocol): def __repr__(self) -> str: ... def __getitem__(self, key:Tuple[Union['ComplexProxy',str], ...]) -> str: ... def MakeAttrs(classes:ClassArg, id:str|None, attributes: dict[str, str | None]) -> str: if classes: attributes['class'] = " ".join(classes) if isinstance(classes, (list, tuple)) else classes if id: attributes['id'] = id attrList: List[str] = [] for key, value in attributes.items(): if value: attrList.append(f'{key}="{value}"') return " ".join(attrList) class NodeLeaf(): name:str = "" attrs:str = "" def __init__(self): pass def __repr__(self) -> str: return f'<{self.name} {self.attrs}/>' def __getitem__(self, key:ComplexProxy) -> str: ... class NodeBranch(dict): name:str = "" attrs:str = "" def __init__(self): pass def __repr__(self) -> str: return f'<{self.name} {self.attrs}>' def __getitem__(self, key:Tuple[Union['ComplexProxy',str], ...]) -> str: return f'<{self.name} {self.attrs}>{"".join(key) if isinstance(key, tuple) else key}' class A(NodeBranch): name = "a" def __init__(self, classes:ClassArg = None, id:str|None = None, href:str|None = None, target:str|None = None): self.attrs = MakeAttrs(classes, id, {"href":href, "target":target}) class IMG(NodeLeaf): name = "img" def __init__(self, classes:ClassArg = None, id:str|None = None, src:str|None = None): self.attrs = MakeAttrs(classes, id, {"src":src}) a = A() print( A(href="outer")[ IMG(src="image.jpg"), IMG(src="image.jpg"), ] )