from dataclasses import dataclass from typing import Any, Iterator, List, Tuple from my.core.compat import NoneType, assert_never # TODO Popper? not sure @dataclass class Helper: manager: 'Manager' item: Any # todo realistically, list or dict? could at least type as indexable or something path: Tuple[str, ...] def pop_if_primitive(self, *keys: str) -> None: """ The idea that primitive TODO """ item = self.item for k in keys: v = item[k] if isinstance(v, (str, bool, float, int, NoneType)): item.pop(k) # todo kinda unfortunate to get dict item twice.. but not sure if can avoid? def check(self, key: str, expected: Any) -> None: actual = self.item.pop(key) assert actual == expected, (key, actual, expected) def zoom(self, key: str) -> 'Helper': return self.manager.helper(item=self.item.pop(key), path=(*self.path, key)) def is_empty(x) -> bool: if isinstance(x, dict): return len(x) == 0 elif isinstance(x, list): return all(map(is_empty, x)) else: assert_never(x) # noqa: RET503 class Manager: def __init__(self) -> None: self.helpers: List[Helper] = [] def helper(self, item: Any, *, path: Tuple[str, ...] = ()) -> Helper: res = Helper(manager=self, item=item, path=path) self.helpers.append(res) return res def check(self) -> Iterator[Exception]: remaining = [] for h in self.helpers: # TODO recursively check it's primitive? if is_empty(h.item): continue remaining.append((h.path, h.item)) if len(remaining) == 0: return yield RuntimeError(f'Unparsed items remaining: {remaining}')