Skip to content

Commit 6b8ec14

Browse files
committed
feat: integrate jsonpath extension package
1 parent 2e9030f commit 6b8ec14

File tree

11 files changed

+1151
-15
lines changed

11 files changed

+1151
-15
lines changed

jsonpath_rw/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .jsonpath import *
22
from .parser import parse
3+
from .lexer import lexer
34

4-
__version__ = '1.3.0'
5+
__version__ = '1.4.0'

jsonpath_rw/ext/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from .parser import parse # noqa

jsonpath_rw/ext/arithmetic.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
import operator
15+
from .. import JSONPath, DatumInContext
16+
17+
18+
OPERATOR_MAP = {
19+
'+': operator.add,
20+
'-': operator.sub,
21+
'*': operator.mul,
22+
'/': operator.truediv,
23+
}
24+
25+
26+
class Operation(JSONPath):
27+
def __init__(self, left, op, right):
28+
self.left = left
29+
self.op = OPERATOR_MAP[op]
30+
self.right = right
31+
32+
def find(self, datum):
33+
result = []
34+
if (isinstance(self.left, JSONPath)
35+
and isinstance(self.right, JSONPath)):
36+
left = self.left.find(datum)
37+
right = self.right.find(datum)
38+
if left and right and len(left) == len(right):
39+
for l, r in zip(left, right):
40+
try:
41+
result.append(self.op(l.value, r.value))
42+
except TypeError:
43+
return []
44+
else:
45+
return []
46+
elif isinstance(self.left, JSONPath):
47+
left = self.left.find(datum)
48+
for l in left:
49+
try:
50+
result.append(self.op(l.value, self.right))
51+
except TypeError:
52+
return []
53+
elif isinstance(self.right, JSONPath):
54+
right = self.right.find(datum)
55+
for r in right:
56+
try:
57+
result.append(self.op(self.left, r.value))
58+
except TypeError:
59+
return []
60+
else:
61+
try:
62+
result.append(self.op(self.left, self.right))
63+
except TypeError:
64+
return []
65+
return [DatumInContext.wrap(r) for r in result]
66+
67+
def __repr__(self):
68+
return '%s(%r%s%r)' % (self.__class__.__name__, self.left, self.op,
69+
self.right)
70+
71+
def __str__(self):
72+
return '%s%s%s' % (self.left, self.op, self.right)

jsonpath_rw/ext/filter.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
import operator
15+
from six import moves
16+
17+
from .. import JSONPath, DatumInContext, Index
18+
19+
20+
OPERATOR_MAP = {
21+
'!=': operator.ne,
22+
'==': operator.eq,
23+
'=': operator.eq,
24+
'<=': operator.le,
25+
'<': operator.lt,
26+
'>=': operator.ge,
27+
'>': operator.gt,
28+
}
29+
30+
31+
class Filter(JSONPath):
32+
"""The JSONQuery filter"""
33+
34+
def __init__(self, expressions):
35+
self.expressions = expressions
36+
37+
def find(self, datum):
38+
if not self.expressions:
39+
return datum
40+
41+
datum = DatumInContext.wrap(datum)
42+
if not isinstance(datum.value, list):
43+
return []
44+
45+
return [DatumInContext(datum.value[i], path=Index(i), context=datum)
46+
for i in moves.range(0, len(datum.value))
47+
if (len(self.expressions) ==
48+
len(list(filter(lambda x: x.find(datum.value[i]),
49+
self.expressions))))]
50+
51+
def __repr__(self):
52+
return '%s(%r)' % (self.__class__.__name__, self.expressions)
53+
54+
def __str__(self):
55+
return '[?%s]' % self.expressions
56+
57+
58+
class Expression(JSONPath):
59+
"""The JSONQuery expression"""
60+
61+
def __init__(self, target, op, value):
62+
self.target = target
63+
self.op = op
64+
self.value = value
65+
66+
def find(self, datum):
67+
datum = self.target.find(DatumInContext.wrap(datum))
68+
69+
if not datum:
70+
return []
71+
if self.op is None:
72+
return datum
73+
74+
found = []
75+
for data in datum:
76+
value = data.value
77+
if isinstance(self.value, int):
78+
try:
79+
value = int(value)
80+
except ValueError:
81+
continue
82+
elif isinstance(self.value, bool):
83+
try:
84+
value = bool(value)
85+
except ValueError:
86+
continue
87+
88+
if OPERATOR_MAP[self.op](value, self.value):
89+
found.append(data)
90+
91+
return found
92+
93+
def __eq__(self, other):
94+
return (isinstance(other, Filter) and
95+
self.target == other.target and
96+
self.op == other.op and
97+
self.value == other.value)
98+
99+
def __repr__(self):
100+
if self.op is None:
101+
return '%s(%r)' % (self.__class__.__name__, self.target)
102+
else:
103+
return '%s(%r %s %r)' % (self.__class__.__name__,
104+
self.target, self.op, self.value)
105+
106+
def __str__(self):
107+
if self.op is None:
108+
return '%s' % self.target
109+
else:
110+
return '%s %s %s' % (self.target, self.op, self.value)

jsonpath_rw/ext/iterable.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
import functools
15+
from .. import This, DatumInContext, JSONPath
16+
17+
18+
class SortedThis(This):
19+
"""The JSONPath referring to the sorted version of the current object.
20+
21+
Concrete syntax is '`sorted`' or [\\field,/field].
22+
"""
23+
def __init__(self, expressions=None):
24+
self.expressions = expressions
25+
26+
def _compare(self, left, right):
27+
left = DatumInContext.wrap(left)
28+
right = DatumInContext.wrap(right)
29+
30+
for expr in self.expressions:
31+
field, reverse = expr
32+
l_datum = field.find(left)
33+
r_datum = field.find(right)
34+
if (not l_datum or not r_datum or
35+
len(l_datum) > 1 or len(r_datum) > 1 or
36+
l_datum[0].value == r_datum[0].value):
37+
# NOTE(sileht): should we do something if the expression
38+
# match multiple fields, for now ignore them
39+
continue
40+
elif l_datum[0].value < r_datum[0].value:
41+
return 1 if reverse else -1
42+
else:
43+
return -1 if reverse else 1
44+
return 0
45+
46+
def find(self, datum):
47+
"""Return sorted value of This if list or dict."""
48+
if isinstance(datum.value, dict) and self.expressions:
49+
return datum
50+
51+
if isinstance(datum.value, dict) or isinstance(datum.value, list):
52+
key = (functools.cmp_to_key(self._compare)
53+
if self.expressions else None)
54+
return [DatumInContext.wrap(
55+
[value for value in sorted(datum.value, key=key)])]
56+
return datum
57+
58+
def __eq__(self, other):
59+
return isinstance(other, Len)
60+
61+
def __repr__(self):
62+
return '%s(%r)' % (self.__class__.__name__, self.expressions)
63+
64+
def __str__(self):
65+
return '[?%s]' % self.expressions
66+
67+
68+
class Len(JSONPath):
69+
"""The JSONPath referring to the len of the current object.
70+
71+
Concrete syntax is '`len`'.
72+
"""
73+
74+
def find(self, datum):
75+
datum = DatumInContext.wrap(datum)
76+
try:
77+
value = len(datum.value)
78+
except TypeError:
79+
return []
80+
else:
81+
return [DatumInContext(value,
82+
context=None,
83+
path=Len())]
84+
85+
def __eq__(self, other):
86+
return isinstance(other, Len)
87+
88+
def __str__(self):
89+
return '`len`'
90+
91+
def __repr__(self):
92+
return 'Len()'

0 commit comments

Comments
 (0)