Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9453ec2

Browse files
committed
Add auto ids
1 parent 74e5c1f commit 9453ec2

2 files changed

Lines changed: 94 additions & 13 deletions

File tree

jsonpath_rw/jsonpath.py

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
logger = logging.getLogger(__name__)
66

7+
# Turn on/off the automatic creation of id attributes
8+
# ... could be a kwarg pervasively but uses are rare and simple today
9+
auto_id_field = None
10+
711
class JSONPath(object):
812
"""
913
The base class for JSONPath abstract syntax; those
@@ -23,6 +27,17 @@ def update(self, data, val):
2327
"Returns `data` with the specified path replaced by `val`"
2428
raise NotImplementedError()
2529

30+
def child(self, child):
31+
"""
32+
Equivalent to Child(self, next) but with some canonicalization
33+
"""
34+
if isinstance(self, This):
35+
return child
36+
elif isinstance(child, This):
37+
return self
38+
else:
39+
return Child(self, child)
40+
2641
class DatumAtPath(object):
2742
"""
2843
Represents a single datum along with the path followed to locate it.
@@ -48,7 +63,38 @@ def __str__(self):
4863
return str(self.value)
4964

5065
def in_context(self, context_path):
51-
return DatumAtPath(self.value, path=self.path if isinstance(context_path, This) else Child(context_path, self.path))
66+
return DatumAtPath(self.value, path=context_path.child(self.path))
67+
68+
class AutoIdDatum(DatumAtPath):
69+
"""
70+
This behaves like a DatumAtPath, but the value is
71+
always the path leading up to it (not including the "id").
72+
73+
For example, it will make "foo.bar.id" return a datum
74+
that behaves like DatumAtPath(value="foo.bar", path="foo.bar.id").
75+
76+
This is disabled by default; it can be turned on by
77+
settings the `auto_id_field` global to a value other
78+
than `None`.
79+
"""
80+
81+
def __init__(self, auto_id):
82+
self.auto_id = auto_id
83+
84+
@property
85+
def value(self):
86+
return str(self.auto_id)
87+
88+
@property
89+
def path(self):
90+
return self.auto_id.child(Fields(auto_id_field))
91+
92+
def __str__(self):
93+
return str(self.path)
94+
95+
def in_context(self, context_path):
96+
return AutoIdDatum(context_path.child(self.auto_id))
97+
5298

5399
class Root(JSONPath):
54100
"""
@@ -233,11 +279,15 @@ class Fields(JSONPath):
233279
def __init__(self, *fields):
234280
self.fields = fields
235281

236-
def safe_get(self, val, field):
282+
def get_datum(self, val, field):
237283
try:
238-
return val.get(field)
239-
except AttributeError:
240-
return None
284+
field_value = val[field] # Do NOT use `val.get(field)` since that confuses None as a value and None due to `get`
285+
return DatumAtPath(value=field_value, path=Fields(field))
286+
except (TypeError, AttributeError, KeyError): # This may not be all the interesting exceptions
287+
if field == auto_id_field:
288+
return AutoIdDatum(auto_id=This())
289+
else:
290+
return None
241291

242292
def find(self, data):
243293
if '*' in self.fields:
@@ -246,9 +296,9 @@ def find(self, data):
246296
except AttributeError:
247297
return []
248298
else:
249-
result = [DatumAtPath(val, path=Fields(field))
250-
for field, val in [(field, self.safe_get(data, field)) for field in self.fields]
251-
if val is not None]
299+
result = [datum
300+
for datum in [self.get_datum(data, field) for field in self.fields]
301+
if datum is not None]
252302

253303
return result
254304

tests/test_jsonpath.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import unittest
22

3+
from jsonpath_rw import jsonpath # For setting the global auto_id_field flag
4+
35
from jsonpath_rw.parser import parse
46
from jsonpath_rw.jsonpath import *
57

@@ -23,27 +25,27 @@ def check_cases(self, test_cases):
2325
else:
2426
assert result.value == target
2527

26-
def test_fields(self):
28+
def test_fields_value(self):
2729
self.check_cases([ ('foo', {'foo': 'baz'}, ['baz']),
2830
('foo,baz', {'foo': 1, 'baz': 2}, [1, 2]),
2931
('*', {'foo': 1, 'baz': 2}, [1, 2]) ])
3032

31-
def test_index(self):
33+
def test_index_value(self):
3234
self.check_cases([('[0]', [42], [42]),
3335
('[2]', [34, 65, 29, 59], [29])])
3436

35-
def test_slice(self):
37+
def test_slice_value(self):
3638
self.check_cases([('[*]', [1, 2, 3], [1, 2, 3]),
3739
('[*]', xrange(1, 4), [1, 2, 3]),
3840
('[1:]', [1, 2, 3, 4], [2, 3, 4]),
3941
('[:2]', [1, 2, 3, 4], [1, 2])])
4042

41-
def test_child(self):
43+
def test_child_value(self):
4244
self.check_cases([('foo.baz', {'foo': {'baz': 3}}, [3]),
4345
('foo.baz', {'foo': {'baz': [3]}}, [[3]]),
4446
('foo.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, [5])])
4547

46-
def test_descendants(self):
48+
def test_descendants_value(self):
4749
self.check_cases([('foo..baz', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, [1, 2] )])
4850

4951
#
@@ -83,3 +85,32 @@ def test_child_paths(self):
8385
def test_descendants_paths(self):
8486
self.check_paths([('foo..baz', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, ['foo.baz', 'foo.bing.baz'] )])
8587

88+
89+
#
90+
# Check the "auto_id_field" feature
91+
#
92+
def test_fields_auto_id(self):
93+
jsonpath.auto_id_field = "id"
94+
self.check_cases([ ('foo.id', {'foo': 'baz'}, ['foo']),
95+
('foo,baz.id', {'foo': 1, 'baz': 2}, ['foo', 'baz']),
96+
('*.id', {'foo':{'id': 1}, 'baz': 2}, [1, 'baz']) ])
97+
98+
def test_index_auto_id(self):
99+
jsonpath.auto_id_field = "id"
100+
self.check_cases([('[0].id', [42], ['[0]']),
101+
('[2].id', [34, 65, 29, 59], ['[2]'])])
102+
103+
def test_slice_auto_id(self):
104+
jsonpath.auto_id_field = "id"
105+
self.check_cases([ ('[*].id', [1, 2, 3], ['[0]', '[1]', '[2]']),
106+
('[1:].id', [1, 2, 3, 4], ['[1]', '[2]', '[3]']) ])
107+
108+
def test_child_auto_id(self):
109+
jsonpath.auto_id_field = "id"
110+
self.check_cases([('foo.baz.id', {'foo': {'baz': 3}}, ['foo.baz']),
111+
('foo.baz.id', {'foo': {'baz': [3]}}, ['foo.baz']),
112+
('foo.baz.bizzle.id', {'foo': {'baz': {'bizzle': 5}}}, ['foo.baz.bizzle'])])
113+
114+
def test_descendants_auto_id(self):
115+
jsonpath.auto_id_field = "id"
116+
self.check_cases([('foo..baz.id', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, ['foo.baz', 'foo.bing.baz'] )])

0 commit comments

Comments
 (0)