forked from dpath-maintainers/dpath-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpath.py
More file actions
284 lines (241 loc) · 8.89 KB
/
path.py
File metadata and controls
284 lines (241 loc) · 8.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
from dpath import PY3
import dpath.exceptions
import dpath.options
import re
import fnmatch
import shlex
import sys
import traceback
from collections import MutableSequence, MutableMapping
def path_types(obj, path):
"""
Given a list of path name elements, return anew list of [name, type] path components, given the reference object.
"""
result = []
#for elem in path[:-1]:
cur = obj
for elem in path[:-1]:
if ((issubclass(cur.__class__, MutableMapping) and elem in cur)):
result.append([elem, cur[elem].__class__])
cur = cur[elem]
elif (issubclass(cur.__class__, MutableSequence) and int(elem) < len(cur)):
elem = int(elem)
result.append([elem, cur[elem].__class__])
cur = cur[elem]
else:
result.append([elem, dict])
try:
try:
result.append([path[-1], cur[path[-1]].__class__])
except TypeError:
result.append([path[-1], cur[int(path[-1])].__class__])
except (KeyError, IndexError):
result.append([path[-1], path[-1].__class__])
return result
def paths_only(path):
"""
Return a list containing only the pathnames of the given path list, not the types.
"""
l = []
for p in path:
l.append(p[0])
return l
def validate(path, regex=None):
"""
Validate that all the keys in the given list of path components are valid, given that they do not contain the separator, and match any optional regex given.
"""
validated = []
for elem in path:
key = elem[0]
strkey = str(key)
if (regex and (not regex.findall(strkey))):
raise dpath.exceptions.InvalidKeyName("{} at {} does not match the expression {}"
"".format(strkey,
validated,
regex.pattern))
validated.append(strkey)
def paths(obj, dirs=True, leaves=True, path=[], skip=False):
"""Yield all paths of the object.
Arguments:
obj -- An object to get paths from.
Keyword Arguments:
dirs -- Yield intermediate paths.
leaves -- Yield the paths with leaf objects.
path -- A list of keys representing the path.
skip -- Skip special keys beginning with '+'.
"""
if isinstance(obj, MutableMapping):
# Python 3 support
if PY3:
iteritems = obj.items()
string_class = str
else: # Default to PY2
iteritems = obj.iteritems()
string_class = basestring
for (k, v) in iteritems:
if issubclass(k.__class__, (string_class)):
if (not k) and (not dpath.options.ALLOW_EMPTY_STRING_KEYS):
raise dpath.exceptions.InvalidKeyName("Empty string keys not allowed without "
"dpath.options.ALLOW_EMPTY_STRING_KEYS=True")
elif (skip and k[0] == '+'):
continue
newpath = path + [[k, v.__class__]]
validate(newpath)
if dirs:
yield newpath
for child in paths(v, dirs, leaves, newpath, skip):
yield child
elif isinstance(obj, MutableSequence):
for (i, v) in enumerate(obj):
newpath = path + [[i, v.__class__]]
if dirs:
yield newpath
for child in paths(obj[i], dirs, leaves, newpath, skip):
yield child
elif leaves:
yield path + [[obj, obj.__class__]]
elif not dirs:
yield path
def match(path, glob):
"""Match the path with the glob.
Arguments:
path -- A list of keys representing the path.
glob -- A list of globs to match against the path.
"""
path_len = len(path)
glob_len = len(glob)
ss = -1
ss_glob = glob
if '**' in glob:
ss = glob.index('**')
if '**' in glob[ss + 1:]:
raise dpath.exceptions.InvalidGlob("Invalid glob. Only one '**' is permitted per glob.")
if path_len >= glob_len:
# Just right or more stars.
more_stars = ['*'] * (path_len - glob_len + 1)
ss_glob = glob[:ss] + more_stars + glob[ss + 1:]
elif path_len == glob_len - 1:
# Need one less star.
ss_glob = glob[:ss] + glob[ss + 1:]
if path_len == len(ss_glob):
# Python 3 support
if PY3:
return all(map(fnmatch.fnmatch, list(map(str, paths_only(path))), list(map(str, ss_glob))))
else: # Default to Python 2
return all(map(fnmatch.fnmatch, map(str, paths_only(path)), map(str, ss_glob)))
return False
def is_glob(string):
return any([c in string for c in '*?[]!'])
def set(obj, path, value, create_missing=True, afilter=None):
"""Set the value of the given path in the object. Path
must be a list of specific path elements, not a glob.
You can use dpath.util.set for globs, but the paths must
slready exist.
If create_missing is True (the default behavior), then any
missing path components in the dictionary are made silently.
Otherwise, if False, an exception is thrown if path
components are missing.
"""
cur = obj
traversed = []
def _presence_test_dict(obj, elem):
return (elem[0] in obj)
def _create_missing_dict(obj, elem):
obj[elem[0]] = elem[1]()
def _presence_test_list(obj, elem):
return (int(str(elem[0])) < len(obj))
def _create_missing_list(obj, elem):
idx = int(str(elem[0]))
while (len(obj)-1) < idx:
obj.append(None)
def _accessor_dict(obj, elem):
return obj[elem[0]]
def _accessor_list(obj, elem):
return obj[int(str(elem[0]))]
def _assigner_dict(obj, elem, value):
obj[elem[0]] = value
def _assigner_list(obj, elem, value):
obj[int(str(elem[0]))] = value
elem = None
for elem in path:
elem_value = elem[0]
elem_type = elem[1]
tester = None
creator = None
accessor = None
assigner = None
if issubclass(obj.__class__, (MutableMapping)):
tester = _presence_test_dict
creator = _create_missing_dict
accessor = _accessor_dict
assigner = _assigner_dict
elif issubclass(obj.__class__, MutableSequence):
if not str(elem_value).isdigit():
raise TypeError("Can only create integer indexes in lists, "
"not {}, in {}".format(type(obj),
traversed
)
)
tester = _presence_test_list
creator = _create_missing_list
accessor = _accessor_list
assigner = _assigner_list
else:
raise TypeError("Unable to path into elements of type {} "
"at {}".format(obj, traversed))
if (not tester(obj, elem)) and (create_missing):
creator(obj, elem)
elif (not tester(obj, elem)):
raise dpath.exceptions.PathNotFound(
"{} does not exist in {}".format(
elem,
traversed
)
)
traversed.append(elem_value)
if len(traversed) < len(path):
obj = accessor(obj, elem)
if elem is None:
return
if (afilter and afilter(accessor(obj, elem))) or (not afilter):
assigner(obj, elem, value)
def get(obj, path, view=False, afilter=None):
"""Get the value of the given path.
Arguments:
obj -- Object to look in.
path -- A list of keys representing the path.
Keyword Arguments:
view -- Return a view of the object.
"""
index = 0
path_count = len(path) - 1
target = obj
head = type(target)()
tail = head
up = None
for pair in path:
key = pair[0]
target = target[key]
if view:
if isinstance(tail, MutableMapping):
if issubclass(pair[1], (MutableSequence, MutableMapping)) and index != path_count:
tail[key] = pair[1]()
else:
tail[key] = target
up = tail
tail = tail[key]
elif issubclass(tail.__class__, MutableSequence):
if issubclass(pair[1], (MutableSequence, MutableMapping)) and index != path_count:
tail.append(pair[1]())
else:
tail.append(target)
up = tail
tail = tail[-1]
if not issubclass(target.__class__, (MutableSequence, MutableMapping)):
if (afilter and (not afilter(target))):
raise dpath.exceptions.FilteredValue
index += 1
if view:
return head
else:
return target