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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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