Created
May 31, 2025 09:57
-
-
Save atwong/cfc099362c3c0b1026a8f23380621d4b to your computer and use it in GitHub Desktop.
Example classes that can un-nest python POPOs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
from collections import UserDict | |
from dataclasses import dataclass | |
from typing import Type, Any, Optional | |
import json | |
""" | |
Example how to simplify a heavily-nested POPO | |
using a set of declarative classes. This is motivated | |
by GraphQL responses which contain heavily-nested | |
objects | |
Each class defines a list of FieldSpec | |
FieldSpec | |
- path attribute specifies the location of the value using | |
dot notation for nested keys | |
- dest attribute provides an alternate key, otherwise the | |
path leaf is used | |
- klass attribute (optional) specifies another class to | |
further process an inner object | |
""" | |
@dataclass | |
class FieldSpec: | |
path: str | |
dest: Optional[str] = None | |
klass: Optional[Type[BaseGQLFlat]] = None | |
class BaseGQLFlat: | |
_bare = False | |
_dspec = [] | |
def __new__(self, src): | |
data = None if self._bare else {} | |
for cspec in self._dspec: | |
_src = src | |
for seg in cspec.path.split("."): | |
if seg not in _src: | |
break | |
_src = _src[seg] | |
else: | |
if isinstance(_src, list) and cspec.klass: | |
val = [ | |
cspec.klass(item) for item in _src | |
if cspec.klass(item) | |
] | |
else: | |
val = cspec.klass(_src) if cspec.klass else _src | |
if not self._bare: | |
if val is not None: | |
key = cspec.dest if cspec.dest else seg | |
data[key] = val | |
else: | |
data = val | |
return data | |
class Micro(BaseGQLFlat): | |
_bare = True | |
_dspec = [FieldSpec(path="b")] | |
class Nano(BaseGQLFlat): | |
_dspec = [ | |
FieldSpec(path="fix.me", dest="apple"), | |
FieldSpec(path="atl", klass=Micro), | |
] | |
class Newbie(BaseGQLFlat): | |
_dspec = [ | |
FieldSpec(path="inner.tube"), | |
FieldSpec(path="air.press.com", dest="apc"), | |
FieldSpec(path="inner.casp", dest="hun", klass=Nano), | |
FieldSpec(path="air.nothing.b"), | |
FieldSpec(path="air.aruba", klass=Nano), | |
] | |
if __name__ == "__main__": | |
gqlinput = { | |
"inner": { | |
"tube": 1234, | |
"ast": "abc", | |
"flex": ["a", "c", "e"], | |
"casp": { | |
"fix": { | |
"me": "is_hun", | |
"ignored": 231 | |
}, | |
"orange": "apple", | |
"atl": [ | |
{"a": 1, "b": 1}, | |
{"a": 1, "c": 2}, | |
{"a": 1, "b": 3}, | |
] | |
} | |
}, | |
"air": { | |
"press": { | |
"com": {"a": "zzz", "b": "zyx"}, | |
"bbbb": 322, | |
}, | |
"nothing": {"z": 1, "u": 55, "b": 66}, | |
"aruba": { | |
"fix": { | |
"x": "xxxx" | |
}, | |
"atl": {"k": "jjj"} | |
}, | |
} | |
} | |
goutput = Newbie(gqlinput) | |
print(goutput) | |
print(f"{json.dumps(goutput, indent=2)}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment