Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive Delete-If-Empty? #273

Open
mahmoud opened this issue Nov 6, 2023 · 6 comments
Open

Recursive Delete-If-Empty? #273

mahmoud opened this issue Nov 6, 2023 · 6 comments

Comments

@mahmoud
Copy link
Owner

mahmoud commented Nov 6, 2023

Chatting with @hynek tonight, seems like there's a market for a Delete/delete flag to remove the value at a path if it's empty, and its parent if it's empty, and so on.

Kind of like rm -r (or rmdir, in that it fails/stops if the container isn't empty).

(Tangentially related, remap can do this for a whole object, but it's not very convenient for recursively deleting a specific path.)

@kurtbrose
Copy link
Collaborator

The easiest way to do that would be as a custom Mode -- maybe Trim?

~ 15 LOC I can whip something up real quick

@kurtbrose
Copy link
Collaborator

Hmm... I guess delete is an in-place mutation rather than returning a copy.

I think this would be easier to do as a follow-on step rather than mixing it into the guts of delete.

val = {"a": {"b": 1} }
glom(val, (Delete("a.b"), Trim()))
# val is now {}

@kurtbrose
Copy link
Collaborator

Let me see if this reasoning makes sense:

Recursive deleting "along a path" is probably not so much what is intended as recursive deletion of a subtree. That is, you just need to get the glom target to the right place and then let it go.

The interesting bits:

  • mutation -- if we want this to be an in-place mutation, like delete, then it will need to use the same mechanism as delete to go "one level up" and mutate the parent
  • is_empty -- probably needs a parameter to determine if something is empty, this could either be a callback, or an operator; I'm kinda leaning towards callback default to bool

@kurtbrose
Copy link
Collaborator

A switch would almost work -- except this requires post-evaluation not pre-evaluation. Switch({bool: T}, default=SKIP) or maybe And(bool, T, default=SKIP). Then wrap it with a Ref. The hard part is it is going to go parent-to-child, whereas for this operation you want to evaluate child-to-parent. Same with ** in a path.

I wonder if there's something general there. Kind of like T is the target going in, is there some expression or mechanism when the return depends on the subspec evaluation.

It's trivial with a custom spec -- the return value is passing through glomit().

If there was such a thing as post-evaluation **, then trim might look like this: ('**', Or(bool, Delete()))

@kurtbrose
Copy link
Collaborator

Traverse() was going to be the more general **, maybe this is a test use-case?

Of course, plain python recursion would have better performance than bouncing each item through glom.

@mahmoud
Copy link
Owner Author

mahmoud commented Nov 7, 2023

For this case, a copy was fine, and remap did the job. Agree that is_empty should be a param. But bool alone doesn't capture the "empty container" common case; it'll notably remove 0/0.0 which are often important values.

I think a recursive path trim makes sense as a separate step/spec. Even if it's not "context-free" for the whole tree like remap, i suspect it's still useful, as I've occasionally had APIs explode at empty values on specific paths only.

(additional context: this was a pre-step for db persistence, to minimize the size of a record)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants