mahmoud / glom

☄️ Python's nested data operator (and CLI), for all your declarative restructuring needs. Got data? Glom it! ☄️
https://glom.readthedocs.io
Other
1.89k stars 61 forks source link

coalesce-along-list #192

Open kurtbrose opened 4 years ago

kurtbrose commented 4 years ago
context = [{}, {}, {'user': {'client': 123}}, {}]

client = glom(context,
     Coalesce(
        ([Coalesce('user.client', default=SKIP)], T[0]),
        default=None))

basically, you want to get the first element in a list which has a valid value here's how the above would look with that available:

client = glom(context, IterCoalesce('user.client', default=None))

maybe this should be Iter().first(coalesce=True)? I'm not sure the best place to fit it into the API

kurtbrose commented 4 years ago

this is close, but doesn't quite work right:

Iter().first('user.client', default=None)
>>> t3 = [{'b': 1}, {'a': 2}]
>>> glom(t3, Iter('a').first())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Kurt\workspace\glom\glom\core.py", line 2085, in glom
    raise err
glom.core.PathAccessError: error raised while processing, details below.
 Target-spec trace (most recent last):
 - Target: [{'b': 1}, {'a': 2}]
 + Spec: (Iter('a'), First(T))
 |\ Spec: Iter('a')
 ||\ Target: {'b': 1}
 ||X Spec: 'a'
 |X\ Target: <generator object Iter._iterate at 0x0000024741931848>
 ||| Spec: First(T)
 ||| Spec: Spec(Call(<class 'functools.partial'>, args=(<bound method Spec....
 ||| Spec: Call(<class 'functools.partial'>, args=(<bound method Spec.glom ...
 ||X Spec: S
 |\ Spec: Iter('a')
 ||\ Target: {'b': 1}
 ||X Spec: 'a'
 ||\ Target: <generator object Iter._iterate at 0x0000024741931848>
 ||| Spec: First(T)
 ||| Spec: Spec(Call(<class 'functools.partial'>, args=(<bound method Spec....
 ||| Spec: Call(<class 'functools.partial'>, args=(<bound method Spec.glom ...
 ||| Spec: S
kurtbrose commented 4 years ago

maybe this could be addressed by '*' paths -- Coalesce('*.user.client', default=None) instead of [Coalesce('user.client', default=SKIP)]

mahmoud commented 4 years ago

Hey what's the bottom of that .first() stack? Iter(default=None).first() was my first thought. Like, return a value if StopIteration without any results, but I need to think it out more.

kurtbrose commented 4 years ago

right? :-) I think Iter() does some weird things inside its eval

kurtbrose commented 4 years ago

image

kurtbrose commented 4 years ago

the actual use case was cleaning up some sloppy input that might be a dict, or a list of dicts looking for user.client

context = [{}, {}, {'user': {'client': 123}}, {}]

client = glom(context,
     Coalesce(
        'user.client',
        ([Coalesce('user.client', default=SKIP)], T[0]),
        default=None))

so, * support would actually be great

glom(context, Coalesce('user.client', '*.user.client'))