@@ -17,22 +17,68 @@ def keygetter(
1717 obj : Mapping [str , Any ],
1818 path : str ,
1919) -> Union [None , Any , str , list [str ], Mapping [str , str ]]:
20- """obj, "foods__breakfast", obj['foods']['breakfast']
20+ """Fetch values in objects and keys, deeply.
2121
22- >>> keygetter({ "foods": { "breakfast": "cereal" } }, "foods__breakfast")
23- 'cereal'
24- >>> keygetter({ "foods ": { "breakfast": "cereal" } }, "foods ")
22+ **With dictionaries**:
23+
24+ >>> keygetter({ "menu ": { "breakfast": "cereal" } }, "menu ")
2525 {'breakfast': 'cereal'}
2626
27+ >>> keygetter({ "menu": { "breakfast": "cereal" } }, "menu__breakfast")
28+ 'cereal'
29+
30+ **With objects**:
31+
32+ >>> from typing import Optional
33+ >>> from dataclasses import dataclass, field
34+
35+ >>> @dataclass()
36+ ... class Menu:
37+ ... fruit: list[str] = field(default_factory=list)
38+ ... breakfast: Optional[str] = None
39+
40+
41+ >>> @dataclass()
42+ ... class Restaurant:
43+ ... place: str
44+ ... city: str
45+ ... state: str
46+ ... menu: Menu = field(default_factory=Menu)
47+
48+
49+ >>> restaurant = Restaurant(
50+ ... place="Largo",
51+ ... city="Tampa",
52+ ... state="Florida",
53+ ... menu=Menu(
54+ ... fruit=["banana", "orange"], breakfast="cereal"
55+ ... )
56+ ... )
57+
58+ >>> restaurant
59+ Restaurant(place='Largo',
60+ city='Tampa',
61+ state='Florida',
62+ menu=Menu(fruit=['banana', 'orange'], breakfast='cereal'))
63+
64+ >>> keygetter(restaurant, "menu")
65+ Menu(fruit=['banana', 'orange'], breakfast='cereal')
66+
67+ >>> keygetter(restaurant, "menu__breakfast")
68+ 'cereal'
2769 """
2870 try :
2971 sub_fields = path .split ("__" )
3072 dct = obj
3173 for sub_field in sub_fields :
32- dct = dct [sub_field ]
74+ if isinstance (dct , dict ):
75+ dct = dct [sub_field ]
76+ elif hasattr (dct , sub_field ):
77+ dct = getattr (dct , sub_field )
3378 return dct
34- except Exception :
79+ except Exception as e :
3580 traceback .print_stack ()
81+ print (f"Above error was { e } " )
3682 return None
3783
3884
@@ -41,10 +87,24 @@ def parse_lookup(obj: Mapping[str, Any], path: str, lookup: str) -> Optional[Any
4187
4288 If comparator not used or value not found, return None.
4389
44- mykey__endswith("mykey") -> "mykey" else None
45-
4690 >>> parse_lookup({ "food": "red apple" }, "food__istartswith", "__istartswith")
4791 'red apple'
92+
93+ It can also look up objects:
94+
95+ >>> from dataclasses import dataclass
96+
97+ >>> @dataclass()
98+ ... class Inventory:
99+ ... food: str
100+
101+ >>> item = Inventory(food="red apple")
102+
103+ >>> item
104+ Inventory(food='red apple')
105+
106+ >>> parse_lookup(item, "food__istartswith", "__istartswith")
107+ 'red apple'
48108 """
49109 try :
50110 if isinstance (path , str ) and isinstance (lookup , str ) and path .endswith (lookup ):
@@ -258,6 +318,89 @@ class QueryList(list[T]):
258318 'Elmhurst'
259319 >>> query.filter(foods__fruit__in="orange")[0]['city']
260320 'Tampa'
321+
322+ Examples of object lookups:
323+
324+ >>> from typing import Any
325+ >>> from dataclasses import dataclass, field
326+
327+ >>> @dataclass()
328+ ... class Restaurant:
329+ ... place: str
330+ ... city: str
331+ ... state: str
332+ ... foods: dict[str, Any]
333+
334+ >>> restaurant = Restaurant(
335+ ... place="Largo",
336+ ... city="Tampa",
337+ ... state="Florida",
338+ ... foods={
339+ ... "fruit": ["banana", "orange"], "breakfast": "cereal"
340+ ... }
341+ ... )
342+
343+ >>> restaurant
344+ Restaurant(place='Largo',
345+ city='Tampa',
346+ state='Florida',
347+ foods={'fruit': ['banana', 'orange'], 'breakfast': 'cereal'})
348+
349+ >>> query = QueryList([restaurant])
350+
351+ >>> query.filter(foods__fruit__in="banana")
352+ [Restaurant(place='Largo',
353+ city='Tampa',
354+ state='Florida',
355+ foods={'fruit': ['banana', 'orange'], 'breakfast': 'cereal'})]
356+
357+ >>> query.filter(foods__fruit__in="banana")[0].city
358+ 'Tampa'
359+
360+ Example of deeper object lookups:
361+
362+ >>> from typing import Optional
363+ >>> from dataclasses import dataclass, field
364+
365+ >>> @dataclass()
366+ ... class Menu:
367+ ... fruit: list[str] = field(default_factory=list)
368+ ... breakfast: Optional[str] = None
369+
370+
371+ >>> @dataclass()
372+ ... class Restaurant:
373+ ... place: str
374+ ... city: str
375+ ... state: str
376+ ... menu: Menu = field(default_factory=Menu)
377+
378+
379+ >>> restaurant = Restaurant(
380+ ... place="Largo",
381+ ... city="Tampa",
382+ ... state="Florida",
383+ ... menu=Menu(
384+ ... fruit=["banana", "orange"], breakfast="cereal"
385+ ... )
386+ ... )
387+
388+ >>> restaurant
389+ Restaurant(place='Largo',
390+ city='Tampa',
391+ state='Florida',
392+ menu=Menu(fruit=['banana', 'orange'], breakfast='cereal'))
393+
394+ >>> query = QueryList([restaurant])
395+
396+ >>> query.filter(menu__fruit__in="banana")
397+ [Restaurant(place='Largo',
398+ city='Tampa',
399+ state='Florida',
400+ menu=Menu(fruit=['banana', 'orange'], breakfast='cereal'))]
401+
402+ >>> query.filter(menu__fruit__in="banana")[0].city
403+ 'Tampa'
261404 """
262405
263406 data : Sequence [T ]
0 commit comments