3
3
import inspect
4
4
from pydoc import resolve
5
5
6
+ from collections import OrderedDict
7
+
6
8
from jmespath import exceptions
7
- from jmespath .compat import string_type as STRING_TYPE
8
9
from jmespath .compat import get_methods
10
+ from jmespath .compat import iteritems
11
+ from jmespath .compat import map
12
+ from jmespath .compat import string_type as STRING_TYPE
9
13
10
14
11
15
# python types -> jmespath types
@@ -90,22 +94,28 @@ def call_function(self, function_name, resolved_args, *args, **kwargs):
90
94
return function (self , * resolved_args )
91
95
92
96
def _validate_arguments (self , args , signature , function_name ):
93
- if signature and signature [- 1 ].get ('variadic' ):
97
+ required_arguments_count = len ([param for param in signature if not param .get ('optional' ) or not param ['optional' ]])
98
+ optional_arguments_count = len ([param for param in signature if param .get ('optional' ) and param ['optional' ]])
99
+ has_variadic = signature [- 1 ].get ('variadic' ) if signature != None else False
100
+ if has_variadic :
94
101
if len (args ) < len (signature ):
95
102
raise exceptions .VariadictArityError (
96
103
len (signature ), len (args ), function_name )
97
- elif len (args ) != len (signature ):
104
+ elif optional_arguments_count > 0 :
105
+ if len (args ) < required_arguments_count or len (args ) > (required_arguments_count + optional_arguments_count ):
106
+ raise exceptions .ArityError (
107
+ len (signature ), len (args ), function_name )
108
+ elif len (args ) != required_arguments_count :
98
109
raise exceptions .ArityError (
99
110
len (signature ), len (args ), function_name )
100
111
return self ._type_check (args , signature , function_name )
101
112
102
113
def _type_check (self , actual , signature , function_name ):
103
- for i in range (len (signature )):
104
- allowed_types = signature [i ][ 'types' ]
114
+ for i in range (min ( len (signature ), len ( actual ) )):
115
+ allowed_types = self . _get_allowed_types_from_signature ( signature [i ])
105
116
if allowed_types :
106
117
self ._type_check_single (actual [i ], allowed_types ,
107
118
function_name )
108
-
109
119
def _type_check_single (self , current , types , function_name ):
110
120
# Type checking involves checking the top level type,
111
121
# and in the case of arrays, potentially checking the types
@@ -129,6 +139,13 @@ def _type_check_single(self, current, types, function_name):
129
139
self ._subtype_check (current , allowed_subtypes ,
130
140
types , function_name )
131
141
142
+ ## signature supports monotype {'type': 'type-name'}
143
+ ## or multiple types {'types': ['type1-name', 'type2-name']}
144
+ def _get_allowed_types_from_signature (self , spec ):
145
+ if spec .get ('type' ):
146
+ spec .update ({'types' : [spec .get ('type' )]})
147
+ return spec .get ('types' )
148
+
132
149
def _get_allowed_pytypes (self , types ):
133
150
allowed_types = []
134
151
allowed_subtypes = []
@@ -173,6 +190,14 @@ def _subtype_check(self, current, allowed_subtypes, types, function_name):
173
190
@signature ({'types' : ['number' ]})
174
191
def _func_abs (self , arg ):
175
192
return abs (arg )
193
+
194
+ @signature ({'types' : ['string' ]})
195
+ def _func_lower (self , arg ):
196
+ return arg .lower ()
197
+
198
+ @signature ({'types' : ['string' ]})
199
+ def _func_upper (self , arg ):
200
+ return arg .upper ()
176
201
177
202
@signature ({'types' : ['array-number' ]})
178
203
def _func_avg (self , arg ):
@@ -290,12 +315,172 @@ def _func_sort(self, arg):
290
315
def _func_sum (self , arg ):
291
316
return sum (arg )
292
317
318
+ @signature ({'types' : ['object' ]})
319
+ def _func_items (self , arg ):
320
+ return list (map (list , iteritems (arg )))
321
+
322
+ @signature ({'types' : ['array' ]})
323
+ def _func_from_items (self , items ):
324
+ return dict (items )
325
+
293
326
@signature ({"types" : ['object' ]})
294
327
def _func_keys (self , arg ):
295
328
# To be consistent with .values()
296
329
# should we also return the indices of a list?
297
330
return list (arg .keys ())
298
331
332
+ @signature (
333
+ {'type' : 'string' },
334
+ {'type' : 'string' },
335
+ {'type' : 'number' , 'optional' : True },
336
+ {'type' : 'number' , 'optional' : True })
337
+ def _func_find_first (self , text , search , start = 0 , end = None ):
338
+ self ._ensure_integer ('find_first' , 'start' , start )
339
+ self ._ensure_integer ('find_first' , 'end' , end )
340
+ return self ._find_impl (
341
+ text ,
342
+ search ,
343
+ lambda t , s : t .find (s ),
344
+ start ,
345
+ end
346
+ )
347
+
348
+ @signature (
349
+ {'type' : 'string' },
350
+ {'type' : 'string' },
351
+ {'type' : 'number' , 'optional' : True },
352
+ {'type' : 'number' , 'optional' : True })
353
+ def _func_find_last (self , text , search , start = 0 , end = None ):
354
+ self ._ensure_integer ('find_last' , 'start' , start )
355
+ self ._ensure_integer ('find_last' , 'end' , end )
356
+ return self ._find_impl (
357
+ text ,
358
+ search ,
359
+ lambda t , s : t .rfind (s ),
360
+ start ,
361
+ end
362
+ )
363
+
364
+ def _find_impl (self , text , search , func , start , end ):
365
+ if len (search ) == 0 :
366
+ return None
367
+ if end == None :
368
+ end = len (text )
369
+
370
+ pos = func (text [start :end ], search )
371
+ if start < 0 :
372
+ start = start + len (text )
373
+
374
+ # restrict resulting range to valid indices
375
+ start = min (max (start , 0 ), len (text ))
376
+ return start + pos if pos != - 1 else None
377
+
378
+ @signature (
379
+ {'type' : 'string' },
380
+ {'type' : 'number' },
381
+ {'type' : 'string' , 'optional' : True })
382
+ def _func_pad_left (self , text , width , padding = ' ' ):
383
+ self ._ensure_non_negative_integer ('pad_left' , 'width' , width )
384
+ return self ._pad_impl (lambda : text .rjust (width , padding ), padding )
385
+
386
+ @signature (
387
+ {'type' : 'string' },
388
+ {'type' : 'number' },
389
+ {'type' : 'string' , 'optional' : True })
390
+ def _func_pad_right (self , text , width , padding = ' ' ):
391
+ self ._ensure_non_negative_integer ('pad_right' , 'width' , width )
392
+ return self ._pad_impl (lambda : text .ljust (width , padding ), padding )
393
+
394
+ def _pad_impl (self , func , padding ):
395
+ if len (padding ) != 1 :
396
+ raise exceptions .JMESPathError (
397
+ 'syntax-error: pad_right() expects $padding to have a '
398
+ 'single character, but received `{}` instead.'
399
+ .format (padding ))
400
+ return func ()
401
+
402
+ @signature (
403
+ {'type' : 'string' },
404
+ {'type' : 'string' },
405
+ {'type' : 'string' },
406
+ {'type' : 'number' , 'optional' : True })
407
+ def _func_replace (self , text , search , replacement , count = None ):
408
+ self ._ensure_non_negative_integer (
409
+ 'replace' ,
410
+ 'count' ,
411
+ count )
412
+
413
+ if count != None :
414
+ return text .replace (search , replacement , int (count ))
415
+ return text .replace (search , replacement )
416
+
417
+ @signature (
418
+ {'type' : 'string' },
419
+ {'type' : 'string' },
420
+ {'type' : 'number' , 'optional' : True })
421
+ def _func_split (self , text , search , count = None ):
422
+ self ._ensure_non_negative_integer (
423
+ 'split' ,
424
+ 'count' ,
425
+ count )
426
+
427
+ if len (search ) == 0 :
428
+ chars = list (text )
429
+ if count == None :
430
+ return chars
431
+
432
+ head = [c for c in chars [:count ]]
433
+ tail = ['' .join (chars [count :])]
434
+ return head + tail
435
+
436
+ if count != None :
437
+ return text .split (search , count )
438
+ return text .split (search )
439
+
440
+ def _ensure_integer (
441
+ self ,
442
+ func_name ,
443
+ param_name ,
444
+ param_value ):
445
+
446
+ if param_value != None :
447
+ if int (param_value ) != param_value :
448
+ raise exceptions .JMESPathValueError (
449
+ func_name ,
450
+ param_value ,
451
+ "integer" )
452
+
453
+ def _ensure_non_negative_integer (
454
+ self ,
455
+ func_name ,
456
+ param_name ,
457
+ param_value ):
458
+
459
+ if param_value != None :
460
+ if int (param_value ) != param_value or int (param_value ) < 0 :
461
+ raise exceptions .JMESPathValueError (
462
+ func_name ,
463
+ param_name ,
464
+ "non-negative integer" )
465
+
466
+ @signature ({'type' : 'string' }, {'type' : 'string' , 'optional' : True })
467
+ def _func_trim (self , text , chars = None ):
468
+ if chars == None or len (chars ) == 0 :
469
+ return text .strip ()
470
+ return text .strip (chars )
471
+
472
+ @signature ({'type' : 'string' }, {'type' : 'string' , 'optional' : True })
473
+ def _func_trim_left (self , text , chars = None ):
474
+ if chars == None or len (chars ) == 0 :
475
+ return text .lstrip ()
476
+ return text .lstrip (chars )
477
+
478
+ @signature ({'type' : 'string' }, {'type' : 'string' , 'optional' : True })
479
+ def _func_trim_right (self , text , chars = None ):
480
+ if chars == None or len (chars ) == 0 :
481
+ return text .rstrip ()
482
+ return text .rstrip (chars )
483
+
299
484
@signature ({"types" : ['object' ]})
300
485
def _func_values (self , arg ):
301
486
return list (arg .values ())
@@ -361,6 +546,23 @@ def _func_let(self, scope, expref, *args, **kwargs):
361
546
kwargs .get ('scopes' ).pushScope (scope )
362
547
return expref .visit (expref .expression , expref .context , * args , ** kwargs )
363
548
549
+ @signature ({'types' : ['array' ], 'variadic' : True })
550
+ def _func_zip (self , * arguments ):
551
+ return list (map (list , zip (* arguments )))
552
+
553
+ @signature ({'types' : ['array' ]}, {'types' : ['expref' ]})
554
+ def _func_group_by (self , array , expref ):
555
+ keyfunc = self ._create_key_func (expref , ['null' , 'string' ], 'group_by' )
556
+ if array :
557
+ result = OrderedDict ()
558
+ keys = list (dict .fromkeys ([keyfunc (item ) for item in array if keyfunc (item ) != None ]))
559
+ for key in keys :
560
+ items = [ item for item in array if keyfunc (item ) == key ]
561
+ result .update ({key : items })
562
+ return result
563
+ else :
564
+ return None
565
+
364
566
def _create_key_func (self , expref , allowed_types , function_name ):
365
567
def keyfunc (x ):
366
568
result = expref .visit (expref .expression , x )
0 commit comments