1313class JSONPath (object ):
1414 """
1515 The base class for JSONPath abstract syntax; those
16- methods stubbed here are the interface to supported
16+ methods stubbed here are the interface to supported
1717 JSONPath semantics.
1818 """
1919
@@ -56,8 +56,8 @@ class DatumInContext(object):
5656 """
5757 Represents a datum along a path from a context.
5858
59- Essentially a zipper but with a structure represented by JsonPath,
60- and where the context is more of a parent pointer than a proper
59+ Essentially a zipper but with a structure represented by JsonPath,
60+ and where the context is more of a parent pointer than a proper
6161 representation of the context.
6262
6363 For quick-and-dirty work, this proxies any non-special attributes
@@ -118,17 +118,17 @@ class AutoIdForDatum(DatumInContext):
118118 """
119119 This behaves like a DatumInContext, but the value is
120120 always the path leading up to it, not including the "id",
121- and with any "id" fields along the way replacing the prior
121+ and with any "id" fields along the way replacing the prior
122122 segment of the path
123123
124124 For example, it will make "foo.bar.id" return a datum
125125 that behaves like DatumInContext(value="foo.bar", path="foo.bar.id").
126126
127127 This is disabled by default; it can be turned on by
128128 settings the `auto_id_field` global to a value other
129- than `None`.
129+ than `None`.
130130 """
131-
131+
132132 def __init__ (self , datum , id_field = None ):
133133 """
134134 Invariant is that datum.path is the path from context to datum. The auto id
@@ -215,7 +215,7 @@ class Child(JSONPath):
215215 JSONPath that first matches the left, then the right.
216216 Concrete syntax is <left> '.' <right>
217217 """
218-
218+
219219 def __init__ (self , left , right ):
220220 self .left = left
221221 self .right = right
@@ -225,7 +225,7 @@ def find(self, datum):
225225 Extra special case: auto ids do not have children,
226226 so cut it off right now rather than auto id the auto id
227227 """
228-
228+
229229 return [submatch
230230 for subdata in self .left .find (datum )
231231 if not isinstance (subdata , AutoIdForDatum )
@@ -264,7 +264,7 @@ def __str__(self):
264264
265265 def __repr__ (self ):
266266 return 'Parent()'
267-
267+
268268
269269class Where (JSONPath ):
270270 """
@@ -275,7 +275,7 @@ class Where(JSONPath):
275275 WARNING: Subject to change. May want to have "contains"
276276 or some other better word for it.
277277 """
278-
278+
279279 def __init__ (self , left , right ):
280280 self .left = left
281281 self .right = right
@@ -299,7 +299,7 @@ class Descendants(JSONPath):
299299 JSONPath that matches first the left expression then any descendant
300300 of it which matches the right expression.
301301 """
302-
302+
303303 def __init__ (self , left , right ):
304304 self .left = left
305305 self .right = right
@@ -308,7 +308,7 @@ def find(self, datum):
308308 # <left> .. <right> ==> <left> . (<right> | *..<right> | [*]..<right>)
309309 #
310310 # With with a wonky caveat that since Slice() has funky coercions
311- # we cannot just delegate to that equivalence or we'll hit an
311+ # we cannot just delegate to that equivalence or we'll hit an
312312 # infinite loop. So right here we implement the coercion-free version.
313313
314314 # Get all left matches into a list
@@ -334,12 +334,12 @@ def match_recursively(datum):
334334 recursive_matches = []
335335
336336 return right_matches + list (recursive_matches )
337-
337+
338338 # TODO: repeatable iterator instead of list?
339339 return [submatch
340340 for left_match in left_matches
341341 for submatch in match_recursively (left_match )]
342-
342+
343343 def is_singular ():
344344 return False
345345
@@ -425,7 +425,7 @@ class Fields(JSONPath):
425425 WARNING: If '*' is any of the field names, then they will
426426 all be returned.
427427 """
428-
428+
429429 def __init__ (self , * fields ):
430430 self .fields = fields
431431
@@ -451,14 +451,18 @@ def reified_fields(self, datum):
451451
452452 def find (self , datum ):
453453 datum = DatumInContext .wrap (datum )
454-
454+
455455 return [field_datum
456456 for field_datum in [self .get_field_datum (datum , field ) for field in self .reified_fields (datum )]
457457 if field_datum is not None ]
458458
459459 def update (self , data , val ):
460460 for field in self .reified_fields (DatumInContext .wrap (data )):
461461 if field in data :
462+ if hasattr (val , '__call__' ):
463+ val (data [field ], data , field )
464+ else :
465+ data [field ] = val
462466 data [field ] = val
463467 return data
464468
@@ -475,7 +479,7 @@ def __eq__(self, other):
475479class Index (JSONPath ):
476480 """
477481 JSONPath that matches indices of the current datum, or none if not large enough.
478- Concrete syntax is brackets.
482+ Concrete syntax is brackets.
479483
480484 WARNING: If the datum is None or not long enough, it will not crash but will not match anything.
481485 NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]`
@@ -486,14 +490,16 @@ def __init__(self, index):
486490
487491 def find (self , datum ):
488492 datum = DatumInContext .wrap (datum )
489-
493+
490494 if datum .value and len (datum .value ) > self .index :
491495 return [DatumInContext (datum .value [self .index ], path = self , context = datum )]
492496 else :
493497 return []
494498
495499 def update (self , data , val ):
496- if len (data ) > self .index :
500+ if hasattr (val , '__call__' ):
501+ val .__call__ (data [self .index ], data , self .index )
502+ elif len (data ) > self .index :
497503 data [self .index ] = val
498504 return data
499505
@@ -505,15 +511,15 @@ def __str__(self):
505511
506512class Slice (JSONPath ):
507513 """
508- JSONPath matching a slice of an array.
514+ JSONPath matching a slice of an array.
509515
510516 Because of a mismatch between JSON and XML when schema-unaware,
511517 this always returns an iterable; if the incoming data
512518 was not a list, then it returns a one element list _containing_ that
513519 data.
514520
515521 Consider these two docs, and their schema-unaware translation to JSON:
516-
522+
517523 <a><b>hello</b></a> ==> {"a": {"b": "hello"}}
518524 <a><b>hello</b><b>goodbye</b></a> ==> {"a": {"b": ["hello", "goodbye"]}}
519525
@@ -531,10 +537,10 @@ def __init__(self, start=None, end=None, step=None):
531537 self .start = start
532538 self .end = end
533539 self .step = step
534-
540+
535541 def find (self , datum ):
536542 datum = DatumInContext .wrap (datum )
537-
543+
538544 # Here's the hack. If it is a dictionary or some kind of constant,
539545 # put it in a single-element list
540546 if (isinstance (datum .value , dict ) or isinstance (datum .value , six .integer_types ) or isinstance (datum .value , six .string_types )):
@@ -556,7 +562,7 @@ def __str__(self):
556562 if self .start == None and self .end == None and self .step == None :
557563 return '[*]'
558564 else :
559- return '[%s%s%s]' % (self .start or '' ,
565+ return '[%s%s%s]' % (self .start or '' ,
560566 ':%d' % self .end if self .end else '' ,
561567 ':%d' % self .step if self .step else '' )
562568
0 commit comments