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.
- omlish/__about__.py +2 -2
- omlish/collections/hasheq.py +0 -10
- omlish/fnpairs.py +1 -10
- omlish/formats/json/__init__.py +5 -0
- omlish/formats/json/literals.py +179 -0
- omlish/formats/json/render.py +2 -3
- omlish/formats/json/stream/render.py +1 -1
- omlish/formats/json/types.py +6 -0
- omlish/lang/classes/abstract.py +37 -14
- omlish/lang/imports.py +1 -1
- omlish/lang/maybes.py +10 -12
- omlish/lite/journald.py +163 -0
- omlish/lite/logs.py +6 -2
- omlish/logs/_abc.py +53 -0
- omlish/logs/handlers.py +1 -1
- omlish/specs/jmespath/ast.py +199 -60
- omlish/specs/jmespath/cli.py +43 -29
- omlish/specs/jmespath/functions.py +397 -274
- omlish/specs/jmespath/lexer.py +2 -2
- omlish/specs/jmespath/parser.py +169 -133
- omlish/specs/jmespath/scope.py +15 -11
- omlish/specs/jmespath/visitor.py +211 -137
- omlish/testing/pytest/plugins/pydevd.py +6 -6
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/RECORD +29 -26
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/top_level.txt +0 -0
@@ -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
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
'
|
22
|
-
'
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
'
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
'
|
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
|
-
|
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 =
|
111
|
+
func.signature = params
|
41
112
|
return func
|
42
113
|
return _record_signature
|
43
114
|
|
44
115
|
|
45
|
-
class
|
46
|
-
def
|
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
|
-
|
52
|
-
|
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
|
-
|
61
|
-
if
|
62
|
-
function_table[name[6:]] =
|
63
|
-
|
64
|
-
|
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
|
-
|
145
|
+
cls._function_table = function_table
|
71
146
|
|
72
|
-
|
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.
|
151
|
+
spec = self._function_table[function_name]
|
77
152
|
except KeyError:
|
78
|
-
raise
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
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
|
167
|
+
param for param in sig if param and param.get('optional') and param['optional']
|
96
168
|
])
|
97
|
-
has_variadic =
|
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(
|
101
|
-
raise
|
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
|
180
|
+
raise ArityError(len(sig), len(args), function_name)
|
181
|
+
|
109
182
|
elif len(args) != required_arguments_count:
|
110
|
-
raise
|
183
|
+
raise ArityError(len(sig), len(args), function_name)
|
111
184
|
|
112
|
-
|
185
|
+
self._type_check(args, sig, function_name)
|
113
186
|
|
114
|
-
def _type_check(self, actual,
|
115
|
-
for i in range(min(len(
|
116
|
-
allowed_types = self._get_allowed_types_from_signature(
|
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
|
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,
|
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
|
-
|
128
|
-
if
|
129
|
-
raise
|
130
|
-
function_name,
|
131
|
-
|
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
|
136
|
-
if
|
137
|
-
self.
|
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
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
233
|
+
allowed_element_types: list = []
|
148
234
|
|
235
|
+
t: str
|
149
236
|
for t in types:
|
150
|
-
|
151
|
-
|
152
|
-
|
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[
|
241
|
+
allowed_types.extend(REVERSE_TYPES_MAP[ta.cast(JmespathType, t)])
|
158
242
|
|
159
|
-
return allowed_types,
|
243
|
+
return FunctionsClass._AllowedPytypes(allowed_types, allowed_element_types)
|
160
244
|
|
161
|
-
def
|
162
|
-
|
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
|
-
|
254
|
+
ets = allowed_element_types[0]
|
165
255
|
for element in current:
|
166
|
-
|
167
|
-
if
|
168
|
-
raise
|
169
|
-
|
170
|
-
elif len(
|
171
|
-
# Dynamic type validation.
|
172
|
-
first =
|
173
|
-
for
|
174
|
-
if first in
|
175
|
-
allowed =
|
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
|
268
|
+
raise JmespathTypeError(function_name, current[0], first, types)
|
179
269
|
|
180
270
|
for element in current:
|
181
|
-
|
182
|
-
if
|
183
|
-
raise
|
271
|
+
pytype = pytype_of(element)
|
272
|
+
if pytype not in allowed:
|
273
|
+
raise JmespathTypeError(function_name, element, pytype, types)
|
184
274
|
|
185
|
-
|
186
|
-
def _func_abs(self, arg):
|
187
|
-
return abs(arg)
|
275
|
+
#
|
188
276
|
|
189
|
-
|
190
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
321
|
-
|
322
|
-
|
454
|
+
class StringFunctions(FunctionsClass):
|
455
|
+
@signature({'types': ['string']})
|
456
|
+
def _func_lower(self, arg):
|
457
|
+
return arg.lower()
|
323
458
|
|
324
|
-
@signature({'types': ['
|
325
|
-
def
|
326
|
-
|
327
|
-
return list(arg.keys())
|
459
|
+
@signature({'types': ['string']})
|
460
|
+
def _func_upper(self, arg):
|
461
|
+
return arg.upper()
|
328
462
|
|
329
|
-
@signature({'types': ['
|
330
|
-
def
|
331
|
-
return
|
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
|
-
|
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
|
-
|
377
|
-
|
378
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
465
|
-
|
466
|
-
|
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
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
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({'
|
491
|
-
def
|
492
|
-
|
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
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
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(
|
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
|
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
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
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
|