Skip to content

Instantly share code, notes, and snippets.

@jwilson8767
Last active August 18, 2019 02:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jwilson8767/30643ae3ff8f8cbdef1cfd3281bc53d2 to your computer and use it in GitHub Desktop.
Save jwilson8767/30643ae3ff8f8cbdef1cfd3281bc53d2 to your computer and use it in GitHub Desktop.
Python wildcard apply / glob apply
# 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