Coverage for jsonfind/jsonfind.py : 72%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2>>> obj = {"a":"b","c":{"d":"e"}}
3>>> tgt = obj["c"]
4>>> JsonFind.to_jsonpointer(JsonFind.find_eq(obj, tgt))
5'/c'
6>>> JsonFind.to_jsonpath(JsonFind.find_eq(obj, tgt))
7'c'
8"""
9import re
10import fnmatch
11from logging import getLogger
12import jsonpointer
13import jsonpath
14try:
15 import pyjq
16except (ModuleNotFoundError, ImportError):
17 pyjq = None
18try:
19 import jsonselect
20except (ModuleNotFoundError, ImportError):
21 jsonselect = None
23log = getLogger(__name__)
26def EQ(a, b):
27 """
28 >>> EQ(1, 2)
29 False
30 >>> EQ(1, 1)
31 True
32 >>> EQ("abc", "abc")
33 True
34 >>> EQ("abc", "def")
35 False
36 >>> EQ({"a":"b"}, {"a":"b"})
37 True
38 >>> EQ({"a":"b"}, {"a":"c"})
39 False
40 """
41 return a == b
44def IS(a, b):
45 """
46 >>> IS(1, 2)
47 False
48 >>> IS(1, 1)
49 True
50 >>> IS("abc", "abc")
51 True
52 >>> IS("abc", "def")
53 False
54 >>> IS({"a":"b"}, {"a":"b"})
55 False
56 >>> IS({"a":"b"}, {"a":"c"})
57 False
58 """
59 return a is b
62def IN1(a, b):
63 """
64 >>> IN1(1, [1,2,3])
65 True
66 >>> IN1([1,2,3], 1)
67 False
68 >>> IN1(1, [2,3])
69 False
70 >>> IN1("abc", "hello abc world")
71 True
72 >>> IN1("xyz", "hello abc world")
73 False
74 """
75 try:
76 return a in b
77 except TypeError:
78 return False
81def IN2(a, b):
82 """
83 >>> IN2(1, [1,2,3])
84 False
85 >>> IN2([1,2,3], 1)
86 True
87 >>> IN2([2,3], 1)
88 False
89 >>> IN2("hello abc world", "abc")
90 True
91 >>> IN2("hello abc world", "xyz")
92 False
93 """
94 try:
95 return b in a
96 except TypeError:
97 return False
100def compare_regexp(a, b):
101 """
102 >>> compare_regexp("abcde", "bcd")
103 False
104 >>> compare_regexp("abcde", "a[bcd]{3}e")
105 True
106 >>> compare_regexp("a,bcde", "[abcde,]*")
107 True
108 >>> compare_regexp("abcde", re.compile("bcd[ef]"))
109 False
110 >>> compare_regexp({"b":"abcde"}, "bcd[ef]")
111 False
112 """
113 log.debug("compare(regexp) %s %s", a, b)
114 if isinstance(a, str):
115 if hasattr(b, "fullmatch"):
116 return b.fullmatch(a) is not None
117 elif isinstance(b, str):
118 return re.fullmatch(b, a) is not None
119 return EQ(a, b)
122def compare_regexp_substr(a, b):
123 """
124 >>> compare_regexp_substr("abcde", "bcd")
125 True
126 >>> compare_regexp_substr("abcde", "bcdf")
127 False
128 >>> compare_regexp_substr("abcde", "bcd[ef]")
129 True
130 >>> compare_regexp_substr("abcde", re.compile("bcd[ef]"))
131 True
132 >>> compare_regexp_substr({"b":"abcde"}, "bcd[ef]")
133 False
134 """
135 if isinstance(a, str):
136 if hasattr(b, "search"):
137 return b.search(a) is not None
138 elif isinstance(b, str):
139 return re.search(b, a) is not None
140 return EQ(a, b)
143def compare_fnmatch(a, b):
144 """
145 >>> compare_fnmatch("world", "worl?")
146 True
147 >>> compare_fnmatch("world", "word*")
148 False
149 >>> compare_fnmatch("world", "wor*d")
150 True
151 >>> compare_fnmatch("world", "?or??")
152 True
153 >>> compare_fnmatch("world", "?and?")
154 False
155 >>> compare_fnmatch(1, 2)
156 False
157 """
158 if isinstance(a, str) and isinstance(b, str):
159 return fnmatch.fnmatch(a, b)
160 return EQ(a, b)
163def compare_range(a, b):
164 """
165 >>> compare_range(1, "-10")
166 True
167 >>> compare_range(1, "10-")
168 False
169 >>> compare_range(20, "-10")
170 False
171 >>> compare_range(1, "10-20")
172 False
173 >>> compare_range(1.0, "0-1.0")
174 True
175 >>> compare_range(100, "-")
176 True
177 >>> compare_range("b", "a-z")
178 True
179 >>> compare_range("b", "b")
180 True
181 >>> compare_range("b", "a")
182 False
183 """
184 if "-" not in b:
185 return a == type(a)(b)
186 bmin, bmax = b.split("-", 1)
187 if bmin not in (None, "") and type(a)(bmin) > a:
188 return False
189 if bmax not in (None, "") and type(a)(bmax) < a:
190 return False
191 return True
194def compare_eval(a, b):
195 """
196 >>> compare_eval(1, "x%2==1")
197 True
198 >>> compare_eval(1, "0<x/3 and x/3 <=1.0")
199 True
200 >>> compare_eval("xyz", "len(x)==3")
201 True
202 >>> compare_eval("xyz", 'hash(x)!=hash("xyz")')
203 False
204 >>> compare_eval("xyz", 'x[2]=="z"')
205 True
206 >>> compare_eval("xyzxyz", 'x[:int(len(x)/2)]==x[int(len(x)/2):]')
207 True
208 >>> compare_eval("xyzxyz123123", 'x[:int(len(x)/2)]==x[int(len(x)/2):]')
209 False
210 """
211 return eval(b, {}, {"x": a})
214def compare_subset(a, b, key_fn=EQ, val_fn=EQ):
215 """
216 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"b"})
217 True
218 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"c"})
219 False
220 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":"d"})
221 False
222 >>> compare_subset({"a":"b", "c":{"d":"e"}}, {})
223 True
224 >>> compare_subset([1,2,3], [1,3,5,2,4])
225 False
226 >>> compare_subset([1,2,3], [1,3])
227 True
228 """
229 if isinstance(b, (list, tuple)) and isinstance(a, (list, tuple)):
230 for ib in b:
231 if not any(compare_subset(ia, ib, key_fn, val_fn) for ia in a):
232 return False
233 return True
234 elif isinstance(b, dict) and isinstance(a, dict):
235 for kb, vb in b.items():
236 flag = False
237 for ka, va in filter(lambda f: key_fn(f[0], kb), a.items()):
238 if compare_subset(va, vb, key_fn, val_fn):
239 flag = True
240 break
241 if not flag:
242 return False
243 return True
244 return val_fn(a, b)
247def compare_superset(a, b, key_fn=EQ, val_fn=EQ):
248 """
249 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":{"d":"e", "f":"g"}})
250 True
251 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b"})
252 False
253 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {"a":"b", "c":"d"})
254 False
255 >>> compare_superset({"a":"b", "c":{"d":"e"}}, {})
256 False
257 >>> compare_superset({"a":"b"}, {"a":"b"})
258 True
259 >>> compare_superset({"a":"b"}, {"a":"b", "c":"d"})
260 True
261 >>> compare_superset([1,2,3], [1,3,5,2,4])
262 True
263 >>> compare_superset([1,2,3], [1,3])
264 False
265 """
266 if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
267 for ia in a:
268 if not any(compare_superset(ia, ib, key_fn, val_fn) for ib in b):
269 return False
270 return True
271 elif isinstance(a, dict) and isinstance(b, dict):
272 for ka, va in a.items():
273 flag = False
274 for kb, vb in filter(lambda f: key_fn(ka, f[0]), b.items()):
275 if compare_superset(va, vb, key_fn, val_fn):
276 flag = True
277 break
278 if not flag:
279 return False
280 return True
281 return val_fn(a, b)
284def compare_set(a, b, key_fn=EQ, val_fn=EQ):
285 """
286 >>> compare_set({"a":"b", "c":"d"}, {"a":"b", "c":"d"})
287 True
288 >>> compare_set({"a":"b", "c":"d"}, {"a":"b"})
289 False
290 >>> compare_set({"a":"b"}, {"a":"b", "c":"d"})
291 False
292 """
293 return compare_subset(a, b, key_fn, val_fn) and compare_superset(a, b, key_fn, val_fn)
296class JsonFind:
298 @classmethod
299 def get_children(cls, obj):
300 if isinstance(obj, dict):
301 return obj.items()
302 elif isinstance(obj, (tuple, list)):
303 return enumerate(obj)
304 return []
306 @classmethod
307 def get_children_attr(cls, obj):
308 """
309 >>> class A: hello="world"
310 >>> list(filter(lambda f: not f[0].startswith("_"), JsonFind.get_children_attr(A)))
311 [('hello', 'world')]
312 """
313 return obj.__dict__.items()
315 @classmethod
316 def issubset(cls, obj, target):
317 if isinstance(target, dict) and isinstance(obj, dict):
318 if obj.items() >= target.items():
319 return True
320 elif isinstance(target, (list, tuple)) and isinstance(obj, (list, tuple)):
321 if all(x in obj for x in target):
322 return True
323 return False
325 @classmethod
326 def filter_subset(cls, obj, target):
327 if cls.issubset(obj, target):
328 yield []
329 return
330 for k, v in cls.get_children(obj):
331 for chld in cls.filter_subset(v, target):
332 yield [k, *chld]
333 return
335 @classmethod
336 def filter_eq(cls, obj, target):
337 if obj == target:
338 yield []
339 return
340 for k, v in cls.get_children(obj):
341 for chld in cls.filter_eq(v, target):
342 yield [k, *chld]
343 return
345 @classmethod
346 def filter_is(cls, obj, target):
347 if obj is target:
348 yield []
349 return
350 for k, v in cls.get_children(obj):
351 for chld in cls.filter_is(v, target):
352 yield [k, *chld]
353 return
355 @classmethod
356 def filter_compare(cls, obj, target, key_fn=IS, val_fn=IS):
357 if compare_set(obj, target, key_fn, val_fn):
358 log.debug("found %s %s", obj, target)
359 yield []
360 return
361 log.debug("not-found %s %s", obj, target)
362 for k, v in cls.get_children(obj):
363 for chld in cls.filter_compare(v, target, key_fn, val_fn):
364 yield [k, *chld]
365 return
367 @classmethod
368 def filter_compare_subset(cls, obj, target, key_fn=IS, val_fn=IS):
369 if compare_subset(obj, target, key_fn, val_fn):
370 log.debug("found %s %s", obj, target)
371 yield []
372 return
373 log.debug("not-found %s %s", obj, target)
374 for k, v in cls.get_children(obj):
375 for chld in cls.filter_compare_subset(v, target, key_fn, val_fn):
376 yield [k, *chld]
377 return
379 @classmethod
380 def filter_compare_superset(cls, obj, target, key_fn=IS, val_fn=IS):
381 if compare_superset(obj, target, key_fn, val_fn):
382 log.debug("found %s %s", obj, target)
383 yield []
384 return
385 log.debug("not-found %s %s", obj, target)
386 for k, v in cls.get_children(obj):
387 for chld in cls.filter_compare_superset(v, target, key_fn, val_fn):
388 yield [k, *chld]
389 return
391 @classmethod
392 def filter_attr_eq(cls, obj, target):
393 if obj == target:
394 yield []
395 return
396 for k, v in cls.get_children_attr(obj):
397 for chld in cls.filter_attr_eq(v, target):
398 yield [k, *chld]
399 return
401 @classmethod
402 def filter_attr_is(cls, obj, target):
403 if obj is target:
404 yield []
405 return
406 for k, v in cls.get_children_attr(obj):
407 for chld in cls.filter_attr_is(v, target):
408 yield [k, *chld]
409 return
411 @classmethod
412 def filter_key(cls, obj, target, prev=[]):
413 if prev[-len(target):] == target:
414 yield []
415 return
416 for k, v in cls.get_children(obj):
417 for chld in cls.filter_key(v, target, [*prev, k]):
418 yield [k, *chld]
419 return
421 @classmethod
422 def find_eq(cls, obj, target):
423 return next(cls.filter_eq(obj, target), None)
425 @classmethod
426 def find_is(cls, obj, target):
427 return next(cls.filter_is(obj, target), None)
429 @classmethod
430 def find_attr_eq(cls, obj, target):
431 return next(cls.filter_attr_eq(obj, target), None)
433 @classmethod
434 def find_attr_is(cls, obj, target):
435 return next(cls.filter_attr_is(obj, target), None)
437 @classmethod
438 def find_subset(cls, obj, target):
439 return next(cls.filter_subset(obj, target), None)
441 @classmethod
442 def find_superset(cls, obj, target):
443 return next(cls.filter_superset(obj, target), None)
445 @classmethod
446 def find_key(cls, obj, target, prev=[]):
447 return next(cls.filter_key(obj, target, prev), None)
449 @classmethod
450 def to_jsonpath(cls, val):
451 res = ""
452 for i in val:
453 if isinstance(i, str):
454 res += "." + i
455 elif isinstance(i, int):
456 res += "[{}]".format(i)
457 else:
458 raise Exception("invalid type: {} ({})".format(i, val))
459 return res.lstrip(".")
461 @classmethod
462 def escape_jsonptr(cls, s):
463 if not isinstance(s, str):
464 return str(s)
465 s = s.replace("~", "~0")
466 return s.replace("/", "~1")
468 @classmethod
469 def to_jsonpointer(cls, val):
470 return "/" + "/".join([cls.escape_jsonptr(x) for x in val])
472 @classmethod
473 def format_to(cls, mode, val):
474 fn = getattr(cls, "to_{}".format(mode))
475 return fn(val)
477 @classmethod
478 def find_by(cls, mode, obj, path):
479 if mode == "jsonpointer":
480 return jsonpointer.resolve_pointer(obj, path)
481 elif mode == "jsonpath":
482 return jsonpath.jsonpath(obj, path)
483 elif pyjq is not None and mode == "jq":
484 return pyjq.all(path, obj)
485 elif jsonselect is not None and mode == "jsonselect":
486 return list(jsonselect.match(path, obj))
487 return None
490format_list = [x.split("_", 1)[-1]
491 for x in filter(lambda f: f.startswith("to_"), dir(JsonFind))]
492find_format_list = [*format_list]
493if pyjq is not None and "jq" not in find_format_list:
494 find_format_list.append("jq")
495if jsonselect is not None and "jsonselect" not in find_format_list:
496 find_format_list.append("jsonselect")