Skip to content

Commit 5f7e5ae

Browse files
author
Laurent PINSON
committed
all tests passed
1 parent a00b218 commit 5f7e5ae

File tree

5 files changed

+105
-46
lines changed

5 files changed

+105
-46
lines changed

jsonpath_ng/jsonpath.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ def _find_base(self, datum, create):
625625
for index in self.indices:
626626
# invalid indices do not crash, return [] instead
627627
if datum.value and len(datum.value) > index:
628-
rv += [DatumInContext(datum.value[index], path=self, context=datum)]
628+
rv += [DatumInContext(datum.value[index], path=Index(index), context=datum)]
629629
return rv
630630

631631
def update(self, data, val):
@@ -646,6 +646,7 @@ def _update_base(self, data, val, create):
646646
if not isinstance(val, list):
647647
val = [val]
648648
# allows somelist[5,1,2] = [some_value, another_value, third_value]
649+
# skip the indices that are too high but the value will be applied to the next index
649650
for index in self.indices:
650651
if len(data) > index:
651652
data[index] = val.pop(0)
@@ -661,7 +662,7 @@ def __eq__(self, other):
661662
return isinstance(other, Index) and sorted(self.indices) == sorted(other.indices)
662663

663664
def __str__(self):
664-
return '[%i]' % self.indices
665+
return str(list(self.indices))
665666

666667
def __repr__(self):
667668
return '%s(indices=%r)' % (self.__class__.__name__, self.indices)
@@ -718,7 +719,7 @@ def find(self, datum):
718719
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in xrange(0, len(datum.value))]
719720
else:
720721
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]]
721-
722+
722723
def update(self, data, val):
723724
for datum in self.find(data):
724725
datum.path.update(data, val)

tests/test_create.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
insert_val=42,
3232
target={'foo': [{}, 42]}),
3333
34+
Params(string='$.foo[1,3]',
35+
initial_data={},
36+
insert_val=[42, 51],
37+
target={'foo': [{}, 42, {}, 51]}),
38+
3439
Params(string='$.foo[0].bar',
3540
initial_data={},
3641
insert_val=42,
@@ -41,6 +46,12 @@
4146
insert_val=42,
4247
target={'foo': [{}, {'bar': 42}]}),
4348
49+
# Note that each field will received the full <insert_val>
50+
Params(string='$.foo[1,3].bar',
51+
initial_data={},
52+
insert_val=[42, 51],
53+
target={'foo': [{}, {'bar': [42, 51]}, {}, {'bar': [42, 51]}]}),
54+
4455
Params(string='$.foo[0][0]',
4556
initial_data={},
4657
insert_val=42,
@@ -51,6 +62,22 @@
5162
insert_val=42,
5263
target={'foo': [{}, [{}, 42]]}),
5364
65+
# But here Note that each index will received one value of <insert_val>
66+
Params(string='$.foo[1,3][1]',
67+
initial_data={},
68+
insert_val=[42, 51],
69+
target={'foo': [{}, [{}, 42], {}, [{}, 51]]}),
70+
71+
Params(string='$.foo[1,3][1]',
72+
initial_data={},
73+
insert_val=[[42, 51], [42, 51]],
74+
target={'foo': [{}, [{}, [42, 51]], {}, [{}, [42, 51]]]}),
75+
76+
Params(string='$.foo[1,3][0,2]',
77+
initial_data={},
78+
insert_val=[42, 51, 42, 51],
79+
target={'foo': [{}, [42, {}, 51], {}, [42, {}, 51]]}),
80+
5481
Params(string='foo[0]',
5582
initial_data={},
5683
insert_val=42,
@@ -61,6 +88,11 @@
6188
insert_val=42,
6289
target={'foo': [{}, 42]}),
6390
91+
Params(string='foo[1,3]',
92+
initial_data={},
93+
insert_val=[42, 51],
94+
target={'foo': [{}, 42, {}, 51]}),
95+
6496
Params(string='foo',
6597
initial_data={},
6698
insert_val=42,
@@ -77,6 +109,11 @@
77109
insert_val=42,
78110
target=[{}, 42]),
79111
112+
Params(string='[1,3]',
113+
initial_data=[],
114+
insert_val=[42, 51],
115+
target=[{}, 42, {}, 51]),
116+
80117
# Converts initial data to a list if necessary
81118
Params(string='[0]',
82119
initial_data={},
@@ -88,6 +125,11 @@
88125
insert_val=42,
89126
target=[{}, 42]),
90127
128+
Params(string='[1,3]',
129+
initial_data={},
130+
insert_val=[42, 51],
131+
target=[{}, 42, {}, 51]),
132+
91133
Params(string='foo[?bar="baz"].qux',
92134
initial_data={'foo': [
93135
{'bar': 'baz'},
@@ -127,6 +169,14 @@ def test_update_or_create(string, initial_data, insert_val, target):
127169
target={'foo': 42}),
128170
# raises TypeError
129171
172+
# more indices than values to insert
173+
Params(string='$.foo[1,2,3]',
174+
initial_data={},
175+
insert_val=[42, 51],
176+
target={'foo': [{}, 42, 51, {}]}),
177+
# raises IndexError
178+
179+
130180
])
131181
@pytest.mark.xfail
132182
def test_unsupported_classes(string, initial_data, insert_val, target):

tests/test_examples.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
Child(Descendants(Root(), Fields('book')), Slice(start=-1))),
3333
3434
# The first two books
35-
# ("$..book[0,1]", # Not implemented
36-
# Child(Descendants(Root(), Fields('book')), Slice(end=2))),
35+
("$..book[0,1]",
36+
Child(Descendants(Root(), Fields('book')), Index(0,1))),
37+
3738
("$..book[:2]",
3839
Child(Descendants(Root(), Fields('book')), Slice(end=2))),
3940

tests/test_jsonpath.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ def setup_class(cls):
8686
#
8787
# Check that the data value returned is good
8888
#
89-
def check_cases(self, test_cases):
89+
def check_cases(self, test_cases, auto_id_field=None):
9090
# Note that just manually building an AST would avoid this dep and isolate the tests, but that would suck a bit
9191
# Also, we coerce iterables, etc, into the desired target type
9292

93+
# This is a global parameter, it should be updated with each call of this function automatically
94+
# Not manually outside this function, or someone might forget to switch it back to None
95+
jsonpath.auto_id_field = auto_id_field
9396
for string, data, target in test_cases:
9497
print('parse("%s").find(%s) =?= %s' % (string, data, target))
9598
result = parse(string).find(data)
@@ -101,25 +104,23 @@ def check_cases(self, test_cases):
101104
assert result.value == target
102105

103106
def test_fields_value(self):
104-
jsonpath.auto_id_field = None
105107
self.check_cases([ ('foo', {'foo': 'baz'}, ['baz']),
106108
('foo,baz', {'foo': 1, 'baz': 2}, [1, 2]),
107109
('@foo', {'@foo': 1}, [1]),
108-
('*', {'foo': 1, 'baz': 2}, set([1, 2])) ])
110+
('*', {'foo': 1, 'baz': 2}, set([1, 2]))
111+
])
109112

110-
jsonpath.auto_id_field = 'id'
111-
self.check_cases([ ('*', {'foo': 1, 'baz': 2}, set([1, 2, '`this`'])) ])
113+
self.check_cases([ ('*', {'foo': 1, 'baz': 2}, set([1, 2, '`this`']))
114+
], 'id')
112115

113116
def test_root_value(self):
114-
jsonpath.auto_id_field = None
115117
self.check_cases([
116118
('$', {'foo': 'baz'}, [{'foo':'baz'}]),
117119
('foo.$', {'foo': 'baz'}, [{'foo':'baz'}]),
118120
('foo.$.foo', {'foo': 'baz'}, ['baz']),
119121
])
120122

121123
def test_this_value(self):
122-
jsonpath.auto_id_field = None
123124
self.check_cases([
124125
('`this`', {'foo': 'baz'}, [{'foo':'baz'}]),
125126
('foo.`this`', {'foo': 'baz'}, ['baz']),
@@ -131,14 +132,16 @@ def test_index_value(self):
131132
('[0]', [42], [42]),
132133
('[5]', [42], []),
133134
('[2]', [34, 65, 29, 59], [29]),
135+
('[0,2,5]', [34, 65, 29, 59, 17, 3], [34, 29, 3]),
134136
('[0]', None, [])
135137
])
136138

137139
def test_slice_value(self):
138140
self.check_cases([('[*]', [1, 2, 3], [1, 2, 3]),
139141
('[*]', xrange(1, 4), [1, 2, 3]),
140142
('[1:]', [1, 2, 3, 4], [2, 3, 4]),
141-
('[:2]', [1, 2, 3, 4], [1, 2])])
143+
('[:2]', [1, 2, 3, 4], [1, 2])
144+
])
142145

143146
# Funky slice hacks
144147
self.check_cases([
@@ -151,7 +154,8 @@ def test_slice_value(self):
151154
def test_child_value(self):
152155
self.check_cases([('foo.baz', {'foo': {'baz': 3}}, [3]),
153156
('foo.baz', {'foo': {'baz': [3]}}, [[3]]),
154-
('foo.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, [5])])
157+
('foo.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, [5])
158+
])
155159

156160
def test_descendants_value(self):
157161
self.check_cases([
@@ -161,28 +165,28 @@ def test_descendants_value(self):
161165

162166
def test_parent_value(self):
163167
self.check_cases([('foo.baz.`parent`', {'foo': {'baz': 3}}, [{'baz': 3}]),
164-
('foo.`parent`.foo.baz.`parent`.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, [5])])
168+
('foo.`parent`.foo.baz.`parent`.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, [5])
169+
])
165170

166171
def test_hyphen_key(self):
167172
self.check_cases([('foo.bar-baz', {'foo': {'bar-baz': 3}}, [3]),
168-
('foo.[bar-baz,blah-blah]', {'foo': {'bar-baz': 3, 'blah-blah':5}},
169-
[3,5])])
173+
('foo.[bar-baz,blah-blah]', {'foo': {'bar-baz': 3, 'blah-blah':5}}, [3,5])
174+
])
170175
self.assertRaises(JsonPathLexerError, self.check_cases,
171176
[('foo.-baz', {'foo': {'-baz': 8}}, [8])])
172177

173178

174-
175-
176179
#
177180
# Check that the paths for the data are correct.
178181
# FIXME: merge these tests with the above, since the inputs are the same anyhow
179182
#
180-
def check_paths(self, test_cases):
183+
def check_paths(self, test_cases, auto_id_field=None):
181184
# Note that just manually building an AST would avoid this dep and isolate the tests, but that would suck a bit
182185
# Also, we coerce iterables, etc, into the desired target type
183186

187+
jsonpath.auto_id_field = auto_id_field
184188
for string, data, target in test_cases:
185-
print('parse("%s").find(%s).paths =?= %s' % (string, data, target))
189+
print('parse("%s").find(%s).path =?= %s' % (string, data, target))
186190
result = parse(string).find(data)
187191
if isinstance(target, list):
188192
assert [str(r.full_path) for r in result] == target
@@ -192,24 +196,22 @@ def check_paths(self, test_cases):
192196
assert str(result.path) == target
193197

194198
def test_fields_paths(self):
195-
jsonpath.auto_id_field = None
196199
self.check_paths([ ('foo', {'foo': 'baz'}, ['foo']),
197200
('foo,baz', {'foo': 1, 'baz': 2}, ['foo', 'baz']),
198-
('*', {'foo': 1, 'baz': 2}, set(['foo', 'baz'])) ])
201+
('*', {'foo': 1, 'baz': 2}, set(['foo', 'baz']))
202+
], None)
199203

200-
jsonpath.auto_id_field = 'id'
201-
self.check_paths([ ('*', {'foo': 1, 'baz': 2}, set(['foo', 'baz', 'id'])) ])
204+
self.check_paths([ ('*', {'foo': 1, 'baz': 2}, set(['foo', 'baz', 'id']))
205+
], 'id')
202206

203207
def test_root_paths(self):
204-
jsonpath.auto_id_field = None
205208
self.check_paths([
206209
('$', {'foo': 'baz'}, ['$']),
207210
('foo.$', {'foo': 'baz'}, ['$']),
208211
('foo.$.foo', {'foo': 'baz'}, ['foo']),
209212
])
210213

211214
def test_this_paths(self):
212-
jsonpath.auto_id_field = None
213215
self.check_paths([
214216
('`this`', {'foo': 'baz'}, ['`this`']),
215217
('foo.`this`', {'foo': 'baz'}, ['foo']),
@@ -218,70 +220,72 @@ def test_this_paths(self):
218220

219221
def test_index_paths(self):
220222
self.check_paths([('[0]', [42], ['[0]']),
221-
('[2]', [34, 65, 29, 59], ['[2]'])])
223+
('[2]', [34, 65, 29, 59], ['[2]']),
224+
('[1,2,3]', [34, 65, 29, 59], ['[1]', '[2]', '[3]']),
225+
])
222226

223227
def test_slice_paths(self):
224228
self.check_paths([ ('[*]', [1, 2, 3], ['[0]', '[1]', '[2]']),
225-
('[1:]', [1, 2, 3, 4], ['[1]', '[2]', '[3]']) ])
229+
('[1:]', [1, 2, 3, 4], ['[1]', '[2]', '[3]'])
230+
])
226231

227232
def test_child_paths(self):
228233
self.check_paths([('foo.baz', {'foo': {'baz': 3}}, ['foo.baz']),
229234
('foo.baz', {'foo': {'baz': [3]}}, ['foo.baz']),
230-
('foo.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, ['foo.baz.bizzle'])])
235+
('foo.baz.bizzle', {'foo': {'baz': {'bizzle': 5}}}, ['foo.baz.bizzle'])
236+
])
231237

232238
def test_descendants_paths(self):
233-
self.check_paths([('foo..baz', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, ['foo.baz', 'foo.bing.baz'] )])
239+
self.check_paths([('foo..baz', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, ['foo.baz', 'foo.bing.baz'] )
240+
])
234241

235242

236243
#
237244
# Check the "auto_id_field" feature
238245
#
239246
def test_fields_auto_id(self):
240-
jsonpath.auto_id_field = "id"
241247
self.check_cases([ ('foo.id', {'foo': 'baz'}, ['foo']),
242248
('foo.id', {'foo': {'id': 'baz'}}, ['baz']),
243249
('foo,baz.id', {'foo': 1, 'baz': 2}, ['foo', 'baz']),
244250
('*.id',
245251
{'foo':{'id': 1},
246252
'baz': 2},
247-
set(['1', 'baz'])) ])
253+
set(['1', 'baz']))
254+
], 'id')
248255

249256
def test_root_auto_id(self):
250-
jsonpath.auto_id_field = 'id'
251257
self.check_cases([
252258
('$.id', {'foo': 'baz'}, ['$']), # This is a wonky case that is not that interesting
253259
('foo.$.id', {'foo': 'baz', 'id': 'bizzle'}, ['bizzle']),
254260
('foo.$.baz.id', {'foo': 4, 'baz': 3}, ['baz']),
255-
])
261+
], 'id')
256262

257263
def test_this_auto_id(self):
258-
jsonpath.auto_id_field = 'id'
259264
self.check_cases([
260265
('id', {'foo': 'baz'}, ['`this`']), # This is, again, a wonky case that is not that interesting
261266
('foo.`this`.id', {'foo': 'baz'}, ['foo']),
262267
('foo.`this`.baz.id', {'foo': {'baz': 3}}, ['foo.baz']),
263-
])
268+
], 'id')
264269

265270
def test_index_auto_id(self):
266-
jsonpath.auto_id_field = "id"
267271
self.check_cases([('[0].id', [42], ['[0]']),
268-
('[2].id', [34, 65, 29, 59], ['[2]'])])
272+
('[2].id', [34, 65, 29, 59], ['[2]'])
273+
], 'id')
269274

270275
def test_slice_auto_id(self):
271-
jsonpath.auto_id_field = "id"
272276
self.check_cases([ ('[*].id', [1, 2, 3], ['[0]', '[1]', '[2]']),
273-
('[1:].id', [1, 2, 3, 4], ['[1]', '[2]', '[3]']) ])
277+
('[1:].id', [1, 2, 3, 4], ['[1]', '[2]', '[3]'])
278+
], 'id')
274279

275280
def test_child_auto_id(self):
276-
jsonpath.auto_id_field = "id"
277281
self.check_cases([('foo.baz.id', {'foo': {'baz': 3}}, ['foo.baz']),
278282
('foo.baz.id', {'foo': {'baz': [3]}}, ['foo.baz']),
279283
('foo.baz.id', {'foo': {'id': 'bizzle', 'baz': 3}}, ['bizzle.baz']),
280284
('foo.baz.id', {'foo': {'baz': {'id': 'hi'}}}, ['foo.hi']),
281-
('foo.baz.bizzle.id', {'foo': {'baz': {'bizzle': 5}}}, ['foo.baz.bizzle'])])
285+
('foo.baz.bizzle.id', {'foo': {'baz': {'bizzle': 5}}}, ['foo.baz.bizzle'])
286+
], 'id')
282287

283288
def test_descendants_auto_id(self):
284-
jsonpath.auto_id_field = "id"
285289
self.check_cases([('foo..baz.id',
286290
{'foo': {
287291
'baz': 1,
@@ -290,9 +294,11 @@ def test_descendants_auto_id(self):
290294
}
291295
} },
292296
['foo.baz',
293-
'foo.bing.baz'] )])
297+
'foo.bing.baz'] )
298+
], 'id')
294299

295-
def check_update_cases(self, test_cases):
300+
def check_update_cases(self, test_cases, auto_id_field=None):
301+
jsonpath.auto_id_field = auto_id_field
296302
for original, expr_str, value, expected in test_cases:
297303
print('parse(%r).update(%r, %r) =?= %r'
298304
% (expr_str, original, value, expected))

tests/test_parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def test_atomic(self):
2424
('*', Fields('*')),
2525
('baz,bizzle', Fields('baz','bizzle')),
2626
('[1]', Index(1)),
27+
('[0,2,3]', Index(0,2,3)),
2728
('[1:]', Slice(start=1)),
2829
('[:]', Slice()),
2930
('[*]', Slice()),

0 commit comments

Comments
 (0)