@@ -17,22 +17,68 @@ def keygetter(
17
17
obj : Mapping [str , Any ],
18
18
path : str ,
19
19
) -> Union [None , Any , str , list [str ], Mapping [str , str ]]:
20
- """obj, "foods__breakfast", obj['foods']['breakfast']
20
+ """Fetch values in objects and keys, deeply.
21
21
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 ")
25
25
{'breakfast': 'cereal'}
26
26
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'
27
69
"""
28
70
try :
29
71
sub_fields = path .split ("__" )
30
72
dct = obj
31
73
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 )
33
78
return dct
34
- except Exception :
79
+ except Exception as e :
35
80
traceback .print_stack ()
81
+ print (f"Above error was { e } " )
36
82
return None
37
83
38
84
@@ -41,10 +87,24 @@ def parse_lookup(obj: Mapping[str, Any], path: str, lookup: str) -> Optional[Any
41
87
42
88
If comparator not used or value not found, return None.
43
89
44
- mykey__endswith("mykey") -> "mykey" else None
45
-
46
90
>>> parse_lookup({ "food": "red apple" }, "food__istartswith", "__istartswith")
47
91
'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'
48
108
"""
49
109
try :
50
110
if isinstance (path , str ) and isinstance (lookup , str ) and path .endswith (lookup ):
@@ -258,6 +318,89 @@ class QueryList(list[T]):
258
318
'Elmhurst'
259
319
>>> query.filter(foods__fruit__in="orange")[0]['city']
260
320
'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'
261
404
"""
262
405
263
406
data : Sequence [T ]
0 commit comments