omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev118__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,55 +1,133 @@
1
+ import dataclasses as dc
1
2
  import inspect
2
3
  import json
3
4
  import math
4
5
  import re
6
+ import typing as ta
5
7
 
6
- from . import exceptions
7
-
8
-
9
- # python types -> jmespath types
10
- TYPES_MAP = {
11
- 'bool': 'boolean',
12
- 'list': 'array',
13
- 'dict': 'object',
14
- 'NoneType': 'null',
15
- 'unicode': 'string',
16
- 'str': 'string',
17
- 'float': 'number',
18
- 'int': 'number',
19
- 'long': 'number',
20
- 'OrderedDict': 'object',
21
- '_Projection': 'array',
22
- '_Expression': 'expref',
8
+ from .exceptions import ArityError
9
+ from .exceptions import JmespathError
10
+ from .exceptions import JmespathTypeError
11
+ from .exceptions import JmespathValueError
12
+ from .exceptions import UnknownFunctionError
13
+ from .exceptions import VariadicArityError
14
+
15
+
16
+ T = ta.TypeVar('T')
17
+
18
+
19
+ ##
20
+
21
+
22
+ JmespathType: ta.TypeAlias = ta.Literal[
23
+ 'boolean',
24
+ 'array',
25
+ 'object',
26
+ 'null',
27
+ 'string',
28
+ 'number',
29
+ 'expref',
30
+ ]
31
+
32
+
33
+ PyType = ta.NewType('PyType', str)
34
+
35
+
36
+ def pytype_of(o: ta.Any) -> PyType:
37
+ return PyType(type(o).__name__)
38
+
39
+
40
+ TYPES_MAP: ta.Mapping[PyType, JmespathType] = {
41
+ PyType('bool'): 'boolean',
42
+ PyType('list'): 'array',
43
+ PyType('dict'): 'object',
44
+ PyType('NoneType'): 'null',
45
+ PyType('unicode'): 'string',
46
+ PyType('str'): 'string',
47
+ PyType('float'): 'number',
48
+ PyType('int'): 'number',
49
+ PyType('long'): 'number',
50
+ PyType('OrderedDict'): 'object',
51
+ PyType('_Projection'): 'array',
52
+ PyType('_Expression'): 'expref',
23
53
  }
24
54
 
25
55
 
26
- # jmespath types -> python types
27
- REVERSE_TYPES_MAP = {
28
- 'boolean': ('bool',),
29
- 'array': ('list', '_Projection'),
30
- 'object': ('dict', 'OrderedDict'),
31
- 'null': ('NoneType',),
32
- 'string': ('unicode', 'str'),
33
- 'number': ('float', 'int', 'long'),
34
- 'expref': ('_Expression',),
56
+ REVERSE_TYPES_MAP: ta.Mapping[JmespathType, ta.Sequence[PyType]] = {
57
+ 'boolean': [
58
+ PyType('bool'),
59
+ ],
60
+ 'array': [
61
+ PyType('list'),
62
+ PyType('_Projection'),
63
+ ],
64
+ 'object': [
65
+ PyType('dict'),
66
+ PyType('OrderedDict'),
67
+ ],
68
+ 'null': [
69
+ PyType('NoneType'),
70
+ ],
71
+ 'string': [
72
+ PyType('unicode'),
73
+ PyType('str'),
74
+ ],
75
+ 'number': [
76
+ PyType('float'),
77
+ PyType('int'),
78
+ ],
79
+ 'expref': [
80
+ PyType('_Expression'),
81
+ ],
35
82
  }
36
83
 
37
84
 
38
- def signature(*arguments):
85
+ ArrayParameterType: ta.TypeAlias = ta.Literal[
86
+ 'array-string',
87
+ 'array-number',
88
+ ]
89
+
90
+ ParameterType: ta.TypeAlias = JmespathType | ArrayParameterType
91
+
92
+
93
+ class Parameter(ta.TypedDict):
94
+ type: ta.NotRequired[ParameterType]
95
+ types: ta.NotRequired[ta.Sequence[ParameterType]]
96
+ variadic: ta.NotRequired[bool]
97
+ optional: ta.NotRequired[bool]
98
+
99
+
100
+ Signature: ta.TypeAlias = ta.Sequence[Parameter]
101
+
102
+
103
+ @dc.dataclass(frozen=True)
104
+ class Function:
105
+ function: ta.Callable
106
+ signature: Signature
107
+
108
+
109
+ def signature(*params: Parameter):
39
110
  def _record_signature(func):
40
- func.signature = arguments
111
+ func.signature = params
41
112
  return func
42
113
  return _record_signature
43
114
 
44
115
 
45
- class FunctionRegistry(type):
46
- def __init__(cls, name, bases, attrs):
47
- cls._populate_function_table()
116
+ class Functions(ta.Protocol):
117
+ def call_function(self, function_name, resolved_args): ...
48
118
 
49
- super().__init__(name, bases, attrs)
50
119
 
51
- def _populate_function_table(cls):
52
- function_table = {}
120
+ class FunctionsClass:
121
+ _function_table: ta.ClassVar[ta.Mapping[str, Function]] = {} # noqa
122
+
123
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
124
+ super().__init_subclass__(**kwargs)
125
+
126
+ cls._populate_function_table()
127
+
128
+ @classmethod
129
+ def _populate_function_table(cls) -> None:
130
+ function_table: dict[str, Function] = {}
53
131
 
54
132
  # Any method with a @signature decorator that also starts with "_func_" is registered as a function.
55
133
  # _func_max_by -> max_by function.
@@ -57,147 +135,195 @@ class FunctionRegistry(type):
57
135
  if not name.startswith('_func_'):
58
136
  continue
59
137
 
60
- signature = getattr(method, 'signature', None)
61
- if signature is not None:
62
- function_table[name[6:]] = {
63
- 'function': method,
64
- 'signature': signature,
65
- }
66
-
67
- cls.FUNCTION_TABLE = function_table
68
-
138
+ sig = getattr(method, 'signature', None)
139
+ if sig is not None:
140
+ function_table[name[6:]] = Function(
141
+ method,
142
+ sig,
143
+ )
69
144
 
70
- class Functions(metaclass=FunctionRegistry):
145
+ cls._function_table = function_table
71
146
 
72
- FUNCTION_TABLE: dict = {} # noqa
147
+ #
73
148
 
74
- def call_function(self, function_name, resolved_args):
149
+ def call_function(self, function_name: str, resolved_args: ta.Sequence[ta.Any]) -> ta.Any:
75
150
  try:
76
- spec = self.FUNCTION_TABLE[function_name]
151
+ spec = self._function_table[function_name]
77
152
  except KeyError:
78
- raise exceptions.UnknownFunctionError(f'Unknown function: {function_name}()') # noqa
79
-
80
- function = spec['function']
81
- signature = spec['signature']
153
+ raise UnknownFunctionError(f'Unknown function: {function_name}()') # noqa
82
154
 
83
- self._validate_arguments(resolved_args, signature, function_name)
155
+ self._validate_arguments(resolved_args, spec.signature, function_name)
156
+ return spec.function.__get__(self, self.__class__)(*resolved_args) # noqa
84
157
 
85
- return function(self, *resolved_args)
86
-
87
- def _validate_arguments(self, args, signature, function_name):
88
- if len(signature) == 0:
89
- return self._type_check(args, signature, function_name)
158
+ def _validate_arguments(self, args: ta.Sequence[ta.Any], sig: Signature, function_name: str) -> None:
159
+ if len(sig) == 0:
160
+ self._type_check(args, sig, function_name)
161
+ return
90
162
 
91
163
  required_arguments_count = len([
92
- param for param in signature if param and (not param.get('optional') or not param['optional'])
164
+ param for param in sig if param and (not param.get('optional') or not param['optional'])
93
165
  ])
94
166
  optional_arguments_count = len([
95
- param for param in signature if param and param.get('optional') and param['optional']
167
+ param for param in sig if param and param.get('optional') and param['optional']
96
168
  ])
97
- has_variadic = signature[-1].get('variadic') if signature is not None else False
169
+ has_variadic = sig[-1].get('variadic') if sig is not None else False
98
170
 
99
171
  if has_variadic:
100
- if len(args) < len(signature):
101
- raise exceptions.VariadicArityError(len(signature), len(args), function_name)
172
+ if len(args) < len(sig):
173
+ raise VariadicArityError(len(sig), len(args), function_name)
102
174
 
103
175
  elif optional_arguments_count > 0:
104
176
  if (
105
177
  len(args) < required_arguments_count or
106
178
  len(args) > (required_arguments_count + optional_arguments_count)
107
179
  ):
108
- raise exceptions.ArityError(len(signature), len(args), function_name)
180
+ raise ArityError(len(sig), len(args), function_name)
181
+
109
182
  elif len(args) != required_arguments_count:
110
- raise exceptions.ArityError(len(signature), len(args), function_name)
183
+ raise ArityError(len(sig), len(args), function_name)
111
184
 
112
- return self._type_check(args, signature, function_name)
185
+ self._type_check(args, sig, function_name)
113
186
 
114
- def _type_check(self, actual, signature, function_name):
115
- for i in range(min(len(signature), len(actual))):
116
- allowed_types = self._get_allowed_types_from_signature(signature[i])
187
+ def _type_check(self, actual: ta.Sequence[ta.Any], sig: Signature, function_name: str) -> None:
188
+ for i in range(min(len(sig), len(actual))):
189
+ allowed_types = self._get_allowed_types_from_signature(sig[i])
117
190
  if allowed_types:
118
191
  self._type_check_single(actual[i], allowed_types, function_name)
119
192
 
120
- def _type_check_single(self, current, types, function_name):
193
+ def _get_allowed_types_from_signature(self, spec: Parameter) -> ta.Sequence[ParameterType]:
194
+ # signature supports monotype {'type': 'type-name'}## or multiple types {'types': ['type1-name', 'type2-name']}
195
+ return [
196
+ *([st] if (st := spec.get('type')) is not None else []),
197
+ *spec.get('types', []),
198
+ ]
199
+
200
+ def _type_check_single(self, current: ta.Any, types: ta.Sequence[ParameterType], function_name: str) -> None:
121
201
  # Type checking involves checking the top level type, and in the case of arrays, potentially checking the types
122
202
  # of each element.
123
- allowed_types, allowed_subtypes = self._get_allowed_pytypes(types)
203
+ allowed_types, allowed_element_types = self._get_allowed_pytypes(types)
124
204
 
125
205
  # We're not using isinstance() on purpose. The type model for jmespath does not map 1-1 with python types
126
206
  # (booleans are considered integers in python for example).
127
- actual_typename = type(current).__name__
128
- if actual_typename not in allowed_types:
129
- raise exceptions.JmespathTypeError(
130
- function_name, current,
131
- self._convert_to_jmespath_type(actual_typename), types)
207
+ pytype = pytype_of(current)
208
+ if pytype not in allowed_types:
209
+ raise JmespathTypeError(
210
+ function_name,
211
+ current,
212
+ self._convert_to_jmespath_type(pytype),
213
+ types,
214
+ )
132
215
 
133
216
  # If we're dealing with a list type, we can have additional restrictions on the type of the list elements (for
134
217
  # example a function can require a list of numbers or a list of strings). Arrays are the only types that can
135
- # have subtypes.
136
- if allowed_subtypes:
137
- self._subtype_check(current, allowed_subtypes, types, function_name)
218
+ # have element types.
219
+ if allowed_element_types:
220
+ self._element_type_check(
221
+ current,
222
+ allowed_element_types,
223
+ types,
224
+ function_name,
225
+ )
138
226
 
139
- def _get_allowed_types_from_signature(self, spec):
140
- # signature supports monotype {'type': 'type-name'}## or multiple types {'types': ['type1-name', 'type2-name']}
141
- if spec.get('type'):
142
- spec.update({'types': [spec.get('type')]})
143
- return spec.get('types')
227
+ class _AllowedPytypes(ta.NamedTuple):
228
+ types: ta.Sequence[PyType]
229
+ element_types: ta.Sequence[ta.Sequence[PyType]]
144
230
 
145
- def _get_allowed_pytypes(self, types):
231
+ def _get_allowed_pytypes(self, types: ta.Iterable[ParameterType]) -> _AllowedPytypes:
146
232
  allowed_types: list = []
147
- allowed_subtypes: list = []
233
+ allowed_element_types: list = []
148
234
 
235
+ t: str
149
236
  for t in types:
150
- type_ = t.split('-', 1)
151
- if len(type_) == 2:
152
- type_, subtype = type_
153
- allowed_subtypes.append(REVERSE_TYPES_MAP[subtype])
154
- else:
155
- type_ = type_[0]
237
+ if '-' in t:
238
+ t, et = t.split('-', 1)
239
+ allowed_element_types.append(REVERSE_TYPES_MAP[ta.cast(JmespathType, et)])
156
240
 
157
- allowed_types.extend(REVERSE_TYPES_MAP[type_])
241
+ allowed_types.extend(REVERSE_TYPES_MAP[ta.cast(JmespathType, t)])
158
242
 
159
- return allowed_types, allowed_subtypes
243
+ return FunctionsClass._AllowedPytypes(allowed_types, allowed_element_types)
160
244
 
161
- def _subtype_check(self, current, allowed_subtypes, types, function_name):
162
- if len(allowed_subtypes) == 1:
245
+ def _element_type_check(
246
+ self,
247
+ current: ta.Sequence[ta.Any],
248
+ allowed_element_types: ta.Sequence[ta.Sequence[PyType]],
249
+ types: ta.Sequence[ParameterType],
250
+ function_name: str,
251
+ ) -> None:
252
+ if len(allowed_element_types) == 1:
163
253
  # The easy case, we know up front what type we need to validate.
164
- allowed_subtypes = allowed_subtypes[0]
254
+ ets = allowed_element_types[0]
165
255
  for element in current:
166
- actual_typename = type(element).__name__
167
- if actual_typename not in allowed_subtypes:
168
- raise exceptions.JmespathTypeError(function_name, element, actual_typename, types)
169
-
170
- elif len(allowed_subtypes) > 1 and current:
171
- # Dynamic type validation. Based on the first type we see, we validate that the remaining types match.
172
- first = type(current[0]).__name__
173
- for subtypes in allowed_subtypes:
174
- if first in subtypes:
175
- allowed = subtypes
256
+ pytype = pytype_of(element)
257
+ if pytype not in ets:
258
+ raise JmespathTypeError(function_name, element, pytype, types)
259
+
260
+ elif len(allowed_element_types) > 1 and current:
261
+ # Dynamic type validation. Based on the first type we see, we validate that the remaining types match.
262
+ first = pytype_of(current[0])
263
+ for element_types in allowed_element_types:
264
+ if first in element_types:
265
+ allowed = element_types
176
266
  break
177
267
  else:
178
- raise exceptions.JmespathTypeError(function_name, current[0], first, types)
268
+ raise JmespathTypeError(function_name, current[0], first, types)
179
269
 
180
270
  for element in current:
181
- actual_typename = type(element).__name__
182
- if actual_typename not in allowed:
183
- raise exceptions.JmespathTypeError(function_name, element, actual_typename, types)
271
+ pytype = pytype_of(element)
272
+ if pytype not in allowed:
273
+ raise JmespathTypeError(function_name, element, pytype, types)
184
274
 
185
- @signature({'types': ['number']})
186
- def _func_abs(self, arg):
187
- return abs(arg)
275
+ #
188
276
 
189
- @signature({'types': ['string']})
190
- def _func_lower(self, arg):
191
- return arg.lower()
277
+ def _convert_to_jmespath_type(self, pytype: PyType) -> JmespathType | ta.Literal['unknown']:
278
+ return TYPES_MAP.get(pytype, 'unknown')
192
279
 
193
- @signature({'types': ['string']})
194
- def _func_upper(self, arg):
195
- return arg.upper()
280
+ def _ensure_integer(
281
+ self,
282
+ func_name: str,
283
+ param_name: str,
284
+ param_value: ta.Any,
285
+ ) -> None:
286
+ if param_value is not None:
287
+ if int(param_value) != param_value:
288
+ raise JmespathValueError(
289
+ func_name,
290
+ param_value,
291
+ 'integer',
292
+ )
196
293
 
197
- @signature({'types': ['array-number']})
198
- def _func_avg(self, arg):
199
- if arg:
200
- return sum(arg) / len(arg)
294
+ def _ensure_non_negative_integer(
295
+ self,
296
+ func_name: str,
297
+ param_name: str,
298
+ param_value: ta.Any,
299
+ ) -> None:
300
+ if param_value is not None:
301
+ if int(param_value) != param_value or int(param_value) < 0:
302
+ raise JmespathValueError(
303
+ func_name,
304
+ param_name,
305
+ 'non-negative integer',
306
+ )
307
+
308
+
309
+ ##
310
+
311
+
312
+ class TypeFunctions(FunctionsClass):
313
+ @signature({'types': []})
314
+ def _func_type(self, arg):
315
+ if isinstance(arg, str):
316
+ return 'string'
317
+ elif isinstance(arg, bool):
318
+ return 'boolean'
319
+ elif isinstance(arg, list):
320
+ return 'array'
321
+ elif isinstance(arg, dict):
322
+ return 'object'
323
+ elif isinstance(arg, (float, int)):
324
+ return 'number'
325
+ elif arg is None:
326
+ return 'null'
201
327
  else:
202
328
  return None
203
329
 
@@ -242,22 +368,20 @@ class Functions(metaclass=FunctionRegistry):
242
368
  except ValueError:
243
369
  return None
244
370
 
371
+
372
+ class ContainerFunctions(FunctionsClass):
245
373
  @signature({'types': ['array', 'string']}, {'types': [], 'variadic': True})
246
374
  def _func_contains(self, subject, *searches):
247
375
  return any(search in subject for search in searches)
248
376
 
377
+ @signature({'types': ['array', 'string']}, {'types': [], 'variadic': True})
378
+ def _func_in(self, subject, *searches):
379
+ return subject in searches
380
+
249
381
  @signature({'types': ['string', 'array', 'object']})
250
382
  def _func_length(self, arg):
251
383
  return len(arg)
252
384
 
253
- @signature({'types': ['string']}, {'types': ['string']})
254
- def _func_ends_with(self, search, suffix):
255
- return search.endswith(suffix)
256
-
257
- @signature({'types': ['string']}, {'types': ['string']})
258
- def _func_starts_with(self, search, suffix):
259
- return search.startswith(suffix)
260
-
261
385
  @signature({'types': ['array', 'string']})
262
386
  def _func_reverse(self, arg):
263
387
  if isinstance(arg, str):
@@ -265,6 +389,41 @@ class Functions(metaclass=FunctionRegistry):
265
389
  else:
266
390
  return list(reversed(arg))
267
391
 
392
+ @signature({'types': ['expref']}, {'types': ['array']})
393
+ def _func_map(self, expref, arg):
394
+ result = []
395
+ for element in arg:
396
+ result.append(expref.visit(expref.expression, element))
397
+ return result
398
+
399
+ @signature({'types': ['object'], 'variadic': True})
400
+ def _func_merge(self, *arguments):
401
+ merged = {}
402
+ for arg in arguments:
403
+ merged.update(arg)
404
+ return merged
405
+
406
+ @signature({'types': ['array-string', 'array-number']})
407
+ def _func_sort(self, arg):
408
+ return sorted(arg)
409
+
410
+ @signature({'types': ['array'], 'variadic': True})
411
+ def _func_zip(self, *arguments):
412
+ return [list(t) for t in zip(*arguments)]
413
+
414
+
415
+ class NumberFunctions(FunctionsClass):
416
+ @signature({'types': ['number']})
417
+ def _func_abs(self, arg):
418
+ return abs(arg)
419
+
420
+ @signature({'types': ['array-number']})
421
+ def _func_avg(self, arg):
422
+ if arg:
423
+ return sum(arg) / len(arg)
424
+ else:
425
+ return None
426
+
268
427
  @signature({'types': ['number']})
269
428
  def _func_ceil(self, arg):
270
429
  return math.ceil(arg)
@@ -273,17 +432,6 @@ class Functions(metaclass=FunctionRegistry):
273
432
  def _func_floor(self, arg):
274
433
  return math.floor(arg)
275
434
 
276
- @signature({'types': ['string']}, {'types': ['array-string']})
277
- def _func_join(self, separator, array):
278
- return separator.join(array)
279
-
280
- @signature({'types': ['expref']}, {'types': ['array']})
281
- def _func_map(self, expref, arg):
282
- result = []
283
- for element in arg:
284
- result.append(expref.visit(expref.expression, element))
285
- return result
286
-
287
435
  @signature({'types': ['array-number', 'array-string']})
288
436
  def _func_max(self, arg):
289
437
  if arg:
@@ -291,13 +439,6 @@ class Functions(metaclass=FunctionRegistry):
291
439
  else:
292
440
  return None
293
441
 
294
- @signature({'types': ['object'], 'variadic': True})
295
- def _func_merge(self, *arguments):
296
- merged = {}
297
- for arg in arguments:
298
- merged.update(arg)
299
- return merged
300
-
301
442
  @signature({'types': ['array-number', 'array-string']})
302
443
  def _func_min(self, arg):
303
444
  if arg:
@@ -305,30 +446,65 @@ class Functions(metaclass=FunctionRegistry):
305
446
  else:
306
447
  return None
307
448
 
308
- @signature({'types': ['array-string', 'array-number']})
309
- def _func_sort(self, arg):
310
- return sorted(arg)
311
-
312
449
  @signature({'types': ['array-number']})
313
450
  def _func_sum(self, arg):
314
451
  return sum(arg)
315
452
 
316
- @signature({'types': ['object']})
317
- def _func_items(self, arg):
318
- return [list(t) for t in arg.items()]
319
453
 
320
- @signature({'types': ['array']})
321
- def _func_from_items(self, items):
322
- return dict(items)
454
+ class StringFunctions(FunctionsClass):
455
+ @signature({'types': ['string']})
456
+ def _func_lower(self, arg):
457
+ return arg.lower()
323
458
 
324
- @signature({'types': ['object']})
325
- def _func_keys(self, arg):
326
- # To be consistent with .values() should we also return the indices of a list?
327
- return list(arg.keys())
459
+ @signature({'types': ['string']})
460
+ def _func_upper(self, arg):
461
+ return arg.upper()
328
462
 
329
- @signature({'types': ['object']})
330
- def _func_values(self, arg):
331
- return list(arg.values())
463
+ @signature({'types': ['string']}, {'types': ['string']})
464
+ def _func_ends_with(self, search, suffix):
465
+ return search.endswith(suffix)
466
+
467
+ @signature({'types': ['string']}, {'types': ['string']})
468
+ def _func_starts_with(self, search, suffix):
469
+ return search.startswith(suffix)
470
+
471
+ @signature({'type': 'string'}, {'type': 'string', 'optional': True})
472
+ def _func_trim(self, text, chars=None):
473
+ if chars is None or len(chars) == 0:
474
+ return text.strip()
475
+ return text.strip(chars)
476
+
477
+ @signature({'type': 'string'}, {'type': 'string', 'optional': True})
478
+ def _func_trim_left(self, text, chars=None):
479
+ if chars is None or len(chars) == 0:
480
+ return text.lstrip()
481
+ return text.lstrip(chars)
482
+
483
+ @signature({'type': 'string'}, {'type': 'string', 'optional': True})
484
+ def _func_trim_right(self, text, chars=None):
485
+ if chars is None or len(chars) == 0:
486
+ return text.rstrip()
487
+ return text.rstrip(chars)
488
+
489
+ @signature({'types': ['string']}, {'types': ['array-string']})
490
+ def _func_join(self, separator, array):
491
+ return separator.join(array)
492
+
493
+ #
494
+
495
+ def _find_impl(self, text, search, func, start, end):
496
+ if len(search) == 0:
497
+ return None
498
+ if end is None:
499
+ end = len(text)
500
+
501
+ pos = func(text[start:end], search)
502
+ if start < 0:
503
+ start = start + len(text)
504
+
505
+ # restrict resulting range to valid indices
506
+ start = min(max(start, 0), len(text))
507
+ return start + pos if pos != -1 else None
332
508
 
333
509
  @signature(
334
510
  {'type': 'string'},
@@ -363,19 +539,15 @@ class Functions(metaclass=FunctionRegistry):
363
539
  end,
364
540
  )
365
541
 
366
- def _find_impl(self, text, search, func, start, end):
367
- if len(search) == 0:
368
- return None
369
- if end is None:
370
- end = len(text)
371
-
372
- pos = func(text[start:end], search)
373
- if start < 0:
374
- start = start + len(text)
542
+ #
375
543
 
376
- # restrict resulting range to valid indices
377
- start = min(max(start, 0), len(text))
378
- return start + pos if pos != -1 else None
544
+ def _pad_impl(self, func, padding):
545
+ if len(padding) != 1:
546
+ raise JmespathError(
547
+ f'syntax-error: pad_right() expects $padding to have a single character, but received '
548
+ f'`{padding}` instead.',
549
+ )
550
+ return func()
379
551
 
380
552
  @signature(
381
553
  {'type': 'string'},
@@ -395,13 +567,7 @@ class Functions(metaclass=FunctionRegistry):
395
567
  self._ensure_non_negative_integer('pad_right', 'width', width)
396
568
  return self._pad_impl(lambda: text.ljust(width, padding), padding)
397
569
 
398
- def _pad_impl(self, func, padding):
399
- if len(padding) != 1:
400
- raise exceptions.JmespathError(
401
- f'syntax-error: pad_right() expects $padding to have a single character, but received '
402
- f'`{padding}` instead.',
403
- )
404
- return func()
570
+ #
405
571
 
406
572
  @signature(
407
573
  {'type': 'string'},
@@ -447,68 +613,45 @@ class Functions(metaclass=FunctionRegistry):
447
613
 
448
614
  return text.split(search)
449
615
 
450
- def _ensure_integer(
451
- self,
452
- func_name,
453
- param_name,
454
- param_value,
455
- ):
456
- if param_value is not None:
457
- if int(param_value) != param_value:
458
- raise exceptions.JmespathValueError(
459
- func_name,
460
- param_value,
461
- 'integer',
462
- )
616
+ #
463
617
 
464
- def _ensure_non_negative_integer(
465
- self,
466
- func_name,
467
- param_name,
468
- param_value,
469
- ):
470
- if param_value is not None:
471
- if int(param_value) != param_value or int(param_value) < 0:
472
- raise exceptions.JmespathValueError(
473
- func_name,
474
- param_name,
475
- 'non-negative integer',
476
- )
618
+ @signature({'types': ['string']}, {'types': ['string']})
619
+ def _func_match(self, string, pattern):
620
+ return re.match(pattern, string) is not None
477
621
 
478
- @signature({'type': 'string'}, {'type': 'string', 'optional': True})
479
- def _func_trim(self, text, chars=None):
480
- if chars is None or len(chars) == 0:
481
- return text.strip()
482
- return text.strip(chars)
483
622
 
484
- @signature({'type': 'string'}, {'type': 'string', 'optional': True})
485
- def _func_trim_left(self, text, chars=None):
486
- if chars is None or len(chars) == 0:
487
- return text.lstrip()
488
- return text.lstrip(chars)
623
+ class ObjectFunctions(FunctionsClass):
624
+ @signature({'types': ['object']})
625
+ def _func_items(self, arg):
626
+ return [list(t) for t in arg.items()]
489
627
 
490
- @signature({'type': 'string'}, {'type': 'string', 'optional': True})
491
- def _func_trim_right(self, text, chars=None):
492
- if chars is None or len(chars) == 0:
493
- return text.rstrip()
494
- return text.rstrip(chars)
628
+ @signature({'types': ['array']})
629
+ def _func_from_items(self, items):
630
+ return dict(items)
495
631
 
496
- @signature({'types': []})
497
- def _func_type(self, arg):
498
- if isinstance(arg, str):
499
- return 'string'
500
- elif isinstance(arg, bool):
501
- return 'boolean'
502
- elif isinstance(arg, list):
503
- return 'array'
504
- elif isinstance(arg, dict):
505
- return 'object'
506
- elif isinstance(arg, (float, int)):
507
- return 'number'
508
- elif arg is None:
509
- return 'null'
510
- else:
511
- return None
632
+ @signature({'types': ['object']})
633
+ def _func_keys(self, arg):
634
+ # To be consistent with .values() should we also return the indices of a list?
635
+ return list(arg.keys())
636
+
637
+ @signature({'types': ['object']})
638
+ def _func_values(self, arg):
639
+ return list(arg.values())
640
+
641
+
642
+ class KeyedFunctions(FunctionsClass):
643
+ def _create_key_func(self, expref, allowed_types, function_name):
644
+ def keyfunc(x):
645
+ result = expref.visit(expref.expression, x)
646
+ jmespath_type = self._convert_to_jmespath_type(pytype_of(result))
647
+ # allowed_types is in terms of jmespath types, not python types.
648
+ if jmespath_type not in allowed_types:
649
+ raise JmespathTypeError(
650
+ function_name, result, jmespath_type, allowed_types)
651
+
652
+ return result
653
+
654
+ return keyfunc
512
655
 
513
656
  @signature({'types': ['array']}, {'types': ['expref']})
514
657
  def _func_sort_by(self, array, expref):
@@ -519,9 +662,9 @@ class Functions(metaclass=FunctionRegistry):
519
662
  # We evaluate the first array element and verify that it's either a string of a number. We then create a key
520
663
  # function that validates that type, which requires that remaining array elements resolve to the same type as
521
664
  # the first element.
522
- required_type = self._convert_to_jmespath_type(type(expref.visit(expref.expression, array[0])).__name__)
665
+ required_type = self._convert_to_jmespath_type(pytype_of(expref.visit(expref.expression, array[0])))
523
666
  if required_type not in ['number', 'string']:
524
- raise exceptions.JmespathTypeError(
667
+ raise JmespathTypeError(
525
668
  'sort_by',
526
669
  array[0],
527
670
  required_type,
@@ -557,10 +700,6 @@ class Functions(metaclass=FunctionRegistry):
557
700
  else:
558
701
  return None
559
702
 
560
- @signature({'types': ['array'], 'variadic': True})
561
- def _func_zip(self, *arguments):
562
- return [list(t) for t in zip(*arguments)]
563
-
564
703
  @signature({'types': ['array']}, {'types': ['expref']})
565
704
  def _func_group_by(self, array, expref):
566
705
  keyfunc = self._create_key_func(expref, ['null', 'string'], 'group_by')
@@ -575,30 +714,14 @@ class Functions(metaclass=FunctionRegistry):
575
714
 
576
715
  return result
577
716
 
578
- def _create_key_func(self, expref, allowed_types, function_name):
579
- def keyfunc(x):
580
- result = expref.visit(expref.expression, x)
581
- actual_typename = type(result).__name__
582
-
583
- jmespath_type = self._convert_to_jmespath_type(actual_typename)
584
- # allowed_types is in terms of jmespath types, not python types.
585
- if jmespath_type not in allowed_types:
586
- raise exceptions.JmespathTypeError(
587
- function_name, result, jmespath_type, allowed_types)
588
-
589
- return result
590
-
591
- return keyfunc
592
717
 
593
- def _convert_to_jmespath_type(self, pyobject):
594
- return TYPES_MAP.get(pyobject, 'unknown')
595
-
596
- #
597
-
598
- @signature({'types': ['string']}, {'types': ['string']})
599
- def _func_match(self, string, pattern):
600
- return re.match(pattern, string) is not None
601
-
602
- @signature({'types': ['array', 'string']}, {'types': [], 'variadic': True})
603
- def _func_in(self, subject, *searches):
604
- return subject in searches
718
+ class DefaultFunctions(
719
+ KeyedFunctions,
720
+ ObjectFunctions,
721
+ StringFunctions,
722
+ NumberFunctions,
723
+ ContainerFunctions,
724
+ TypeFunctions,
725
+ FunctionsClass,
726
+ ):
727
+ pass