Last active
August 18, 2019 02:20
-
-
Save jwilson8767/30643ae3ff8f8cbdef1cfd3281bc53d2 to your computer and use it in GitHub Desktop.
Python wildcard apply / glob apply
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# MIT LICENSED | |
# Author: jwilson8767 | |
import collections | |
def glob_apply(obj, paths, fn: callable, **kwargs) -> None: | |
""" | |
Iterates a deeply nested structure using dot-notation wildcard paths. | |
:param obj: The List/Sequence or Dict/Mapping to iterate | |
:param paths: A single dot-notation path like "some_obj.*.label" or a list of paths like ["some_obj.*.label", "some_obj.*.items.*.label"] | |
:param fn: The function to apply, should return the new value or mutated object. | |
""" | |
full_path = None | |
def _map_seq_enumerate(obj): | |
if isinstance(obj, collections.Sequence) and not isinstance(obj, str): | |
return enumerate(obj) | |
if isinstance(obj, collections.Mapping): | |
return obj.items() | |
return [] # return empty | |
def _glob_apply(obj, path): | |
if obj is None: | |
return | |
try: | |
if len(path) == 1: | |
if path[0] == '*': | |
for i, _ in _map_seq_enumerate(obj): | |
obj[i] = fn(obj[i], **kwargs) | |
elif path[0] in obj: | |
obj[path[0]] = fn(obj[path[0]], **kwargs) | |
else: | |
return | |
else: | |
if path[0] == '*': | |
for i, _ in _map_seq_enumerate(obj): | |
_glob_apply(obj[i], path[1:]) | |
else: | |
if path[0] in obj: | |
_glob_apply(obj[path[0]], path[1:]) | |
except TypeError as ex: | |
if str(ex) == 'list indices must be integers or slices, not str': | |
raise ValueError('Path "{}" could not be recursed because a list object was encountered where a dict was expected. Try adding ".*" before ".{}" '.format(full_path, path[0])) | |
else: | |
raise ex | |
if isinstance(paths, str): | |
paths = [paths] | |
for _path in paths: | |
full_path = _path | |
_glob_apply(obj, full_path.split('.')) | |
# Example usage: | |
def wrap_parens(obj): | |
return '('+obj+')' | |
my_massively_nested_object = { | |
'foo': { | |
'items': [ | |
{'label': 'bar'}, | |
{'label': 'barr'}, | |
# ... | |
]}, | |
'foo2': { | |
'items': [ | |
{'label': '2bar'}, | |
{'label': '2barr'}, | |
# ... | |
]} | |
} | |
glob_apply(my_massively_nested_object, '*.items.*.label', wrap_parens); # obj, path, func | |
""" | |
{ | |
'foo': { | |
'items': [ | |
{'label': '(bar)'}, | |
{'label': '(barr)'}, | |
# ... | |
]} | |
'foo2': { | |
'items': [ | |
{'label': '(2bar)'}, | |
{'label': '(2barr)'}, | |
# ... | |
]} | |
}; | |
""" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment