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

assign with coalesce [question] #226

Open radugrosu opened 2 years ago

radugrosu commented 2 years ago

Somewhat surprisingly, glom.assign(obj, glom.Coalesce(*specs), value) doesn't work. Is there an easier way to get the result, e.g. something like:

def glom_assign(config: Config, spec: Union[str, glom.Coalesce], value: Any) -> None:
    specs = [spec] if isinstance(spec, str) else spec.subspecs
    for spec in specs:
        try:
            glom.glom(config, spec)
        except glom.PathAccessError:
            continue
        glom.assign(config, spec, value)

Thanks.

mahmoud commented 2 years ago

Hey Radu! Interesting question. So the goal is to assign the value to config at the first spec that actually exists (according to the Coalesce call)? Is the desired use case focused on overwrites only? I notice in your function that glom_assign({}, Coalesce('key'), 'val') would result in no change to the input, is that desired behavior?

If so, I can't think of a way to do this as a single spec using assign/Assign as implemented, as it only works with paths/T objects. With a bit more detail, maybe we can come up with an enhancement PR :)

radugrosu commented 2 years ago

Hi Mahmoud, thanks for replying. Indeed, I only needed the overwrite use case so far, but the functionality could be extended to work something like:

def glom_assign(config: Config, spec: Union[str, glom.Coalesce], value: Any, strict: bool = True) -> None:
    specs = [spec] if isinstance(spec, str) else spec.subspecs
    for spec in specs:
        try:
            glom.glom(config, spec)
        except glom.PathAccessError:
            if not strict:
                try:
                    parent, leaf = spec.rsplit(".", maxsplit=1)
                except ValueError:
                    continue
                try:
                    glom.glom(config, parent)
                except glom.PathAccessError:
                    continue
                glom.assign(config, spec, value)
            continue
        glom.assign(config, spec, value)
kurtbrose commented 2 years ago

Hi, I'm not sure your exact use case, but the first thing that comes to mind for me:

glom.assign(obj, glom.Coalesce(*specs), value)
# instead of assign(coalesce), coalesce(assign)
glom.glom(obj, Coalesce(*[Assign(T, spec, value) for spec in specs]))
# if the only desired behavior is "try these in order, return the first one that doesn't fail", then Or can also work
glom.glom(obj, Or(*[Assign(T, spec, value) for spec in specs]))
radugrosu commented 2 years ago

Hi Kurt, your suggestions are close to what I want, but both bail out after the first success, and they are too readily successful (even if the path doesn't actually exist - which would correspond to what I described as non-strict behaviour in my implementation above). Anyway, thanks for the idea, it's pretty cool, I'll keep that in mind.

kurtbrose commented 2 years ago

Oh, just re-reading this -- you want to ensure that EACH branch gets exercised, not stop executing on the first success?

strict = And(*[Assign(T, spec, value) for spec in specs])

if you want to try to execute each, but ignore failures:

best_effort =  And(*[Or(Assign(T, spec, value), Val(None)) for spec in specs])